我有数百个Google脚本项目,并有各种使用卡环工具( Node.js应用程序)管理项目的Bash脚本。许多脚本要求在对本地文件采取一些操作之前首先使用clasp pull在本地提取项目,因此我有一个脚本,它循环遍历本地clasp项目文件夹并在每个文件夹上运行clasp pull。循环按顺序遍历目录,因此如果要花3-4秒来拉一个项目,那么每100个项目运行它需要5-6分钟。
我的目标是能够并行地运行clasp pull命令,以便它们同时启动,并且能够知道哪些项目被成功地拉出,哪些项目没有被拉出来。
给定这样的目录结构:
├── project-1
│ ├── .clasp.json
│ ├── .claspignore
│ ├── _main.js
│ └── appsscript.json
├── project-2
│ ├── .clasp.json
│ ├── .claspignore
│ ├── _main.js
│ └── appsscript.json
├── project-3
│ ├── .clasp.json
│ ├── .claspignore
│ ├── _main.js
│ └── appsscript.json
└── pull_all.sh这个pull_all.sh Bash脚本:
#!/bin/bash
# use Node 14.17.5 to prevent "Error: Looks like you are offline." errors
# (see https://github.com/google/clasp/issues/872)
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"
nvm install 14.17.5
nvm use 14.17.5
find . -name '.clasp.json' |
while read file; do
(
cd "$(dirname "$file")"
project_dir_name="$(basename "$(pwd)")"
echo "Pulling project ($project_dir_name)"
clasp pull
) &
done当运行这个脚本时,它会输出每个目录的“拉项目”行,然后给出一个shell提示符,这意味着脚本已经完成执行。但是,在用户不做任何操作的情况下,3-4秒后,它将显示所有clasp pull命令的输出(显然是并行运行的,因为一些命令的输出是无序/重叠的),然后挂起,并没有给出新的shell提示。此时,我必须按ctrl+c来终止脚本。
完整的输出结果如下所示:
$ ./pull_all.sh
v14.17.5 is already installed.
Now using node v14.17.5 (npm v6.14.14)
Now using node v14.17.5 (npm v6.14.14)
Pulling project (project-3)
Pulling project (project-2)
Pulling project (project-1)
$
Cloned 2 files.
⠙ Pulling files…└─ appsscript.json
└─ _main.js
Cloned 2 files.
└─ _main.js
└─ appsscript.json
Cloned 2 files.
└─ _main.js要强制其中一个脚本失败,我可以将scriptId更改为任何.clasp.json文件中的无效脚本ID。在本例中,我确实看到了预期的输出:
Could not find script.
Did you provide the correct scriptId?
Are you logged in to the correct account with the script?..。但是它仍然和其他的输出混合在一起,并且还不清楚是哪个项目产生的。
我怎样才能做到:
clasp pull操作的成功或失败,并由项目的目录名( .clasp.json文件所在的位置)引用。clasp pull的输出,因此脚本只显示每个项目的成功或失败结果(由目录名引用)。注意:我已经将clasp pull作为示例命令,但是有效的解决方案将允许我在bash循环中作为后台进程运行任何clasp命令,包括但不限于clasp push、clasp deploy等。
发布于 2021-10-25 11:44:49
我建议以下解决方案:
#!/usr/bin/env bash
# use Node 14.17.5 to prevent "Error: Looks like you are offline." errors
# (see https://github.com/google/clasp/issues/872)
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"
nvm install 14.17.5
nvm use 14.17.5
# Check and process command line
if (( $# < 1 )); then
echo "Usage: $(basename "$0") ACTION [ARG]..."
exit 2
fi
action="$1"
args=("${@:2}")
# Define cleanup handler, create temporary log directory
trap '[[ -n "$(jobs -p)" ]] && kill -- -$$; [[ -n "${logdir}" ]] && rm -rf "${logdir}"' EXIT
logdir=$(mktemp -d)
# Start specified action for each project
declare -A pid_pro_map=() pid_log_map=()
readarray -t files < <(find . -name '.clasp.json' -printf "%P\n" | sort -V)
for file in "${files[@]}"; do
project=$(dirname "${file}")
logfile=$(mktemp -p "${logdir}")
( cd "${project}" && clasp "${action}" "${args[@]}" ) &>"${logfile}" &
pid=$!; pid_pro_map[${pid}]="${project}"; pid_log_map[${pid}]="${logfile}"
echo -e "Started action '\e[1m${action}\e[0m' for project '\e[1m${project}\e[0m' (pid ${pid})"
done
# Wait for background jobs to finish and report results
echo -e "\nWaiting for background jobs to finish...\n"
jobs_done=0; jobs_total=${#files[@]}
while true; do
wait -n -p pid; result=$?
[[ -z "${pid}" ]] && break
jobs_done=$((jobs_done + 1))
if (( ${result} == 0 )); then
echo -e "Action '\e[1m${action}\e[0m' for project '\e[1m${pid_pro_map[${pid}]}\e[0m' (pid ${pid}) (${jobs_done}/${jobs_total}): \e[1;32mSUCCESS\e[0m"
else
echo -e "Action '\e[1m${action}\e[0m' for project '\e[1m${pid_pro_map[${pid}]}\e[0m' (pid ${pid}) (${jobs_done}/${jobs_total}): \e[1;31mFAILURE\e[0m"
cat "${pid_log_map[${pid}]}"
fi
done特性:
clasp支持的任何操作(例如pull、push、deploy)clasp产生的输出被抑制(但在发生故障时被捕获以打印)clasp为在失败时进一步分析而产生的输出)<projects-done>/<projects-total>的形式)Requirements:
wait -p,Bash >= 4.3用于wait -n,Bash >= 4.0用于关联数组)find ... -printf "%P\n"的GNU (findutils的一部分);可能的解决办法: readarray -t文件<<(查找)。用于“${.clasp.json@}”中的文件;do project=$(大号"${ file #'./'}")示例输出:

作为对这句话的响应,这里有一个可能的调整来限制并发后台作业的生成量:
# Start specified action for each project
max_jobs=25; poll_delay="0.1s"
declare -A pid_pro_map=() pid_log_map=()
readarray -t files < <(find . -name '.clasp.json' -printf "%P\n" | sort -V)
for file in "${files[@]}"; do
if (( ${max_jobs} > 0 )); then
while jobs=$(jobs -r -p | wc -l) && (( ${jobs} >= ${max_jobs} )); do
sleep "${poll_delay}"
done
fi
project=$(dirname "${file}")
logfile=$(mktemp -p "${logdir}")
( cd "${project}" && clasp "${action}" "${args[@]}" ) &>"${logfile}" &
pid=$!; pid_pro_map[${pid}]="${project}"; pid_log_map[${pid}]="${logfile}"
echo -e "Started action '\e[1m${action}\e[0m' for project '\e[1m${project}\e[0m' (pid ${pid})"
done此外,这可用于将产生的背景进程减少一半:
( cd "${project}" && exec clasp "${action}" "${args[@]}" ) &>"${logfile}" &这将将子subshell的进程替换为clasp,它应该非常好,因为子subshell在执行cd之后就会失去它的有用性。
发布于 2021-10-23 14:26:54
您应该强制脚本在完成之前等待输出:
{
while IFS= read -d $'\0' -ru $find file; do
(
cd "$(dirname "$file")"
project_dir_name="$(basename "$(pwd)")"
echo "Pulling project ($project_dir_name)"
if clasp pull </dev/null 2>&1 ;then
printf '\nExeClaspResult: %s Success\n' "$project_dir_name"
else
printf '\nExeClaspResult: %s Failed\n' "$project_dir_name"
fi
) &
done {find}< <(find . -name '.clasp.json' -print0)
wait
} |
sed -une 's/^ExeClaspResult: //p'其中:
sed)如果你想做一些调试
{
while IFS= read -d $'\0' -ru $find file; do
(
cd "$(dirname "$file")"
project_dir_name="$(basename "$(pwd)")"
echo "Pulling project ($project_dir_name)"
if clasp pull </dev/null > >(
sed "s/^/OUT $project_dir_name: /") 2> >(
sed "s/^/ERR $project_dir_name: /"
);then
printf '\nExeClaspResult: %s Success\n' "$project_dir_name"
else
printf '\nExeClaspResult: %s Failed\n' "$project_dir_name"
fi
) &
done {find}< <(find . -name '.clasp.json' -print0)
wait
} |
sed -ue '
s/^ExeClaspResult: \(.* Failed\)$/\o33[31m** \1 **\o33[0m/;
s/^ExeClaspResult: \(.*\)$/\o33[32m** \1 **\o33[0m/;'将显示以OUT $project或ERR project为前缀的所有输出,如果成功则颜色为绿色或失败时为红色。
发布于 2021-10-26 16:11:45
我的方法是首先记录所有异步调用。这些事情很快就会发生。然后开始打印任务失败/成功日志。
这个脚本使用命名管道来实现这一点。异步命令发生在子which中,子which继承了成功/失败消息也可以打印的打开文件描述符。在将这些调用发送到终端之前,我们可以等待所有调用都被记录下来。
pull all输出是隐藏的。可以用另一个命令或shell函数替换它。
stdout和stderr可以按正常方式重定向(如pull-all 2>err-log或pull-all >/dev/null,仅用于查看错误)。
脚本等待拉命令完成,然后正常退出。
如果它被中断(ctrl+c),所有子进程都会被杀死。否则,它们将在脚本退出后继续运行。我不确定是否有更好的方法来解决这个问题。
解决方案:
#!/bin/bash
cleanup () {
exec 3>&-
exec 4>&-
rm .pull-all-log.fifo .pull-all-log-err.fifo
kill $(jobs -p)
kill -9 $(jobs -p) &>/dev/null
}
# use Node 14.17.5 to prevent "Error: Looks like you are offline." errors
# (see https://github.com/google/clasp/issues/872)
[[ -s "/usr/local/opt/nvm/nvm.sh" ]] && . "/usr/local/opt/nvm/nvm.sh"
nvm install 14.17.5
nvm use 14.17.5
# you can use /tmp or mktemp -u if you're worried about clobbering
rm -f .pull-all-log.fifo .pull-all-log-err.fifo
mkfifo .pull-all-log.fifo .pull-all-log-err.fifo
trap cleanup EXIT
exec 3<> .pull-all-log.fifo
exec 4<> .pull-all-log-err.fifo
for file in ./*/.clasp.json; do
[[ -d "$file" ]] && continue
parent=$(dirname "$file")
proj=${parent##*/}
echo "Pulling $proj..."
{
cd "$parent"
if clasp pull &>/dev/null; then
echo "Pulling $proj succeeded" >&3
else
echo "Pulling $proj FAILED" >&4
fi
} &
done
running_pids=$(jobs -p)
cat <&3 &
cat <&4 >&2 &
[[ "$running_pids" ]] && wait $running_pids示例输出:
Pulling project-1...
Pulling project-2...
Pulling project-3...
Pulling project-4...
Pulling project-5...
Pulling project-6...
Pulling project-7...
Pulling project-8...
Pulling project-9...
Pulling project-1 succeeded
Pulling project-9 succeeded
Pulling project-3 succeeded
Pulling project-4 FAILED
Pulling project-5 succeeded
Pulling project-2 succeeded
Pulling project-6 FAILED
Pulling project-7 FAILED
Pulling project-8 FAILED每次调用完成时都会打印FAILED或succeeded (即使在调用循环结束之前)。
示例目录名为project-1等。我重新创建了示例树来测试它。
https://stackoverflow.com/questions/69653686
复制相似问题