首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Bash脚本并行运行多个Google脚本clasp命令

使用Bash脚本并行运行多个Google脚本clasp命令
EN

Stack Overflow用户
提问于 2021-10-20 22:21:24
回答 4查看 602关注 0票数 4

我有数百个Google脚本项目,并有各种使用卡环工具( Node.js应用程序)管理项目的Bash脚本。许多脚本要求在对本地文件采取一些操作之前首先使用clasp pull在本地提取项目,因此我有一个脚本,它循环遍历本地clasp项目文件夹并在每个文件夹上运行clasp pull。循环按顺序遍历目录,因此如果要花3-4秒来拉一个项目,那么每100个项目运行它需要5-6分钟。

我的目标是能够并行地运行clasp pull命令,以便它们同时启动,并且能够知道哪些项目被成功地拉出,哪些项目没有被拉出来。

给定这样的目录结构:

代码语言:javascript
复制
├── 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脚本:

代码语言:javascript
复制
#!/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来终止脚本。

完整的输出结果如下所示:

代码语言:javascript
复制
$ ./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。在本例中,我确实看到了预期的输出:

代码语言:javascript
复制
Could not find script.
Did you provide the correct scriptId?
Are you logged in to the correct account with the script?

..。但是它仍然和其他的输出混合在一起,并且还不清楚是哪个项目产生的。

我怎样才能做到:

  1. 脚本不会导致在脚本执行期间出现新的shell提示符。
  2. 脚本输出一行,指示每个clasp pull操作的成功或失败,并由项目的目录名( .clasp.json文件所在的位置)引用。
  3. 奖励:禁止clasp pull的输出,因此脚本只显示每个项目的成功或失败结果(由目录名引用)。

注意:我已经将clasp pull作为示例命令,但是有效的解决方案将允许我在bash循环中作为后台进程运行任何clasp命令,包括但不限于clasp pushclasp deploy等。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2021-10-25 11:44:49

我建议以下解决方案:

代码语言:javascript
复制
#!/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支持的任何操作(例如pullpushdeploy)
  • 并行地对每个项目执行指定的操作。
  • clasp产生的输出被抑制(但在发生故障时被捕获以打印)
  • 等待后台任务完成,并在结果可用时立即报告结果。
  • 提供关于每个项目的成功/失败的信息(包括clasp为在失败时进一步分析而产生的输出)
  • 显示当前进度(以<projects-done>/<projects-total>的形式)
  • 增加可读性的彩色输出

Requirements:

  • Bash >= 5.1 (详细信息: Bash >= 5.1用于wait -p,Bash >= 4.3用于wait -n,Bash >= 4.0用于关联数组)
  • find ... -printf "%P\n"的GNU (findutils的一部分);可能的解决办法: readarray -t文件<<(查找)。用于“${.clasp.json@}”中的文件;do project=$(大号"${ file #'./'}")

示例输出:

作为对这句话的响应,这里有一个可能的调整来限制并发后台作业的生成量:

代码语言:javascript
复制
# 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

此外,这可用于将产生的背景进程减少一半:

代码语言:javascript
复制
( cd "${project}" && exec clasp "${action}" "${args[@]}" ) &>"${logfile}" &

这将将子subshell的进程替换为clasp,它应该非常好,因为子subshell在执行cd之后就会失去它的有用性。

票数 3
EN

Stack Overflow用户

发布于 2021-10-23 14:26:54

您应该强制脚本在完成之前等待输出:

代码语言:javascript
复制
{ 
    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'

其中:

  1. 无相互作用
  2. 所有的输出将被删除(由sed)
  3. 只会显示结果。

如果你想做一些调试

代码语言:javascript
复制
{ 
    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 $projectERR project为前缀的所有输出,如果成功则颜色为绿色或失败时为红色。

票数 1
EN

Stack Overflow用户

发布于 2021-10-26 16:11:45

我的方法是首先记录所有异步调用。这些事情很快就会发生。然后开始打印任务失败/成功日志。

这个脚本使用命名管道来实现这一点。异步命令发生在子which中,子which继承了成功/失败消息也可以打印的打开文件描述符。在将这些调用发送到终端之前,我们可以等待所有调用都被记录下来。

pull all输出是隐藏的。可以用另一个命令或shell函数替换它。

stdoutstderr可以按正常方式重定向(如pull-all 2>err-logpull-all >/dev/null,仅用于查看错误)。

脚本等待拉命令完成,然后正常退出。

如果它被中断(ctrl+c),所有子进程都会被杀死。否则,它们将在脚本退出后继续运行。我不确定是否有更好的方法来解决这个问题。

解决方案:

代码语言:javascript
复制
#!/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

示例输出:

代码语言:javascript
复制
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

每次调用完成时都会打印FAILEDsucceeded (即使在调用循环结束之前)。

示例目录名为project-1等。我重新创建了示例树来测试它。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69653686

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档