如何在Jenkins流水线中运行Git子模块顶层的Shell脚本?
问题场景
我有一个Git子模块,顶层目录下存在delete-branches.sh脚本,在本地终端执行以下命令可正常运行:
$ git submodule foreach 'cp ../delete-branches.sh . && ./delete-branches.sh true && rm -f ./delete-branches.sh'
但将其放到Jenkins流水线(DRY_RUN为作业参数)中,写法如下时出现报错:
stage('Delete branches') { sshagent(credentials: ['bitbucket-creds']) { sh(label: "Delete branches", script: "git submodule foreach \'cp ../delete-branches.sh . && ./delete-branches.sh ${env.DRY_RUN}\'", returnStatus: true) } }
报错信息:
git submodule foreach cp ../delete-branches.sh . && ./delete-branches.sh true Entering 'module1' ./delete-branches.sh: 6: [[: not found ./delete-branches.sh: 6: [[: not found fatal: option '--since' must come before non-option arguments ./delete-branches.sh: 6: [[: not found ./delete-branches.sh: 6: [[: not found ./delete-branches.sh: 6: [[: not found fatal: option '--since' must come before non-option arguments
脚本内容(已添加可执行权限):
#!/bin/sh DRY_RUN=$1 ECHO='echo ' for branch in $(git branch -a | sed 's/^\s*//' | sed 's/^remotes\///' | grep -v 'master$' | grep -v 'v2$'); do if ! ( [[ -f "$branch" ]] || [[ -d "$branch" ]] ) && [[ "$(git log $branch --since "1 year ago" | wc -l)" -eq 0 ]]; then if [[ "$DRY_RUN" = "false" ]]; then ECHO="" fi local_branch_name=$(echo "$branch" | sed 's/remotes\/origin\///') $ECHO git branch -d "${local_branch_name}" $ECHO git push origin --delete "${local_branch_name}" fi done
求Jenkins流水线中运行该脚本的正确语法。
错误原因分析
- Shell语法兼容性问题:脚本开头声明的是
#!/bin/sh,但使用了[[这种Bash专属语法。Jenkins的sh步骤默认调用系统sh,多数Linux发行版中sh指向Dash(而非Bash),Dash不支持[[语法,因此报错[[: not found。 - Git命令参数顺序错误:
git log $branch --since "1 year ago"中,--since属于选项参数,必须放在分支名之前,否则Git会将其识别为分支的一部分,触发option '--since' must come before non-option arguments错误。 - Jenkins脚本引号处理问题:原流水线中用
\'转义单引号,导致git submodule foreach的命令被拆分,无法作为整体执行;同时遗漏了本地终端命令中删除临时脚本的步骤。
解决方案
步骤1:修正脚本本身
- 将脚本开头的
#!/bin/sh改为#!/bin/bash,强制使用Bash执行以支持[[语法; - 调整
git log的参数顺序,将--since放在分支名之前:
#!/bin/bash DRY_RUN=$1 ECHO='echo ' for branch in $(git branch -a | sed 's/^\s*//' | sed 's/^remotes\///' | grep -v 'master$' | grep -v 'v2$'); do if ! ( [[ -f "$branch" ]] || [[ -d "$branch" ]] ) && [[ "$(git log --since "1 year ago" $branch | wc -l)" -eq 0 ]]; then if [[ "$DRY_RUN" = "false" ]]; then ECHO="" fi local_branch_name=$(echo "$branch" | sed 's/remotes\/origin\///') $ECHO git branch -d "${local_branch_name}" $ECHO git push origin --delete "${local_branch_name}" fi done
步骤2:修正Jenkins流水线脚本
使用三重引号包裹脚本内容,确保git submodule foreach的命令作为整体传递,同时保留删除临时脚本的步骤:
stage('Delete branches') { sshagent(credentials: ['bitbucket-creds']) { sh(label: "Delete branches", script: """ git submodule foreach 'cp ../delete-branches.sh . && ./delete-branches.sh ${env.DRY_RUN} && rm -f ./delete-branches.sh' """, returnStatus: true) } }
若担心变量插值的转义问题,也可通过Shell环境变量传递DRY_RUN参数:
stage('Delete branches') { sshagent(credentials: ['bitbucket-creds']) { sh(label: "Delete branches", script: 'git submodule foreach \'cp ../delete-branches.sh . && ./delete-branches.sh $DRY_RUN && rm -f ./delete-branches.sh\'', returnStatus: true, environment: ['DRY_RUN': env.DRY_RUN]) } }
内容的提问来源于stack exchange,提问作者Chris F
相关产品推荐
相关产品推荐

