BitBucket Pipelines执行Shell脚本时跳过后续命令的问题
我来帮你分析这个问题,这种情况大概率是SSH通过管道接收脚本内容时,脚本中的某些命令(比如./gradlew build)会读取标准输入(stdin),导致后续的脚本内容被这些命令“吃掉”,从而中断了脚本的执行。
问题根源
当你用cat ./deploy.sh | ssh -T user@[myIp]这种方式执行时,SSH会把管道里的脚本内容作为远程shell的stdin。而./gradlew build这类命令在运行过程中可能会尝试读取stdin(比如Gradle的某些内部机制、潜在的交互场景),这就会把后续还没执行的脚本内容当作输入读走,导致脚本到这一步之后就没下文了。
解决方案
这里有几种可靠的解决方式,按推荐程度排序:
1. 先把脚本上传到服务器再执行(最稳妥)
彻底避免管道传递的问题,先通过SCP把脚本传到服务器,再远程执行:
step: name: Deploy dev trigger: manual script: - scp ./deploy.sh user@[myIp]:/tmp/deploy.sh - ssh user@[myIp] "chmod +x /tmp/deploy.sh && /tmp/deploy.sh dev && rm /tmp/deploy.sh"
这样脚本是在服务器本地完整执行的,完全不会有stdin被抢占的问题。
2. 使用SSH的-n参数禁止读取stdin
ssh -n会把SSH的stdin重定向到/dev/null,这样远程命令就不会读取管道里的脚本内容了:
step: name: Deploy dev trigger: manual script: - cat ./deploy.sh | ssh -n user@[myIp]
注意:如果你的脚本里有需要交互输入的步骤(比如输入密码),这个参数会导致问题,但你的脚本里没有交互逻辑,所以这个方法完全适用。
3. 将脚本作为字符串传递给SSH
把脚本内容直接作为SSH命令的一部分执行,避免依赖管道stdin:
step: name: Deploy dev trigger: manual script: - ssh user@[myIp] "$(cat ./deploy.sh)" dev
这种方式把脚本内容打包成远程shell的命令字符串,只要脚本里没有复杂的特殊字符(比如嵌套引号、反斜杠),用起来会很方便。
4. 修改deploy.sh,让读取stdin的命令关闭输入
在./gradlew build这一行后追加< /dev/null,强制它不从stdin读取内容:
echo "---------------------------------------- | BUILD API" cd ${basePath}/API ./gradlew build < /dev/null
这样Gradle就不会抢占脚本的stdin,后续命令就能正常执行了。这个方法适合不想修改Pipeline步骤的场景。
验证建议
你可以先在本地模拟BitBucket的执行方式测试:
cat ./deploy.sh | ssh -T user@[myIp]
如果本地这样执行也出现同样的问题,就能100%确认是stdin被抢占的问题,再用上面的方法解决即可。
内容的提问来源于stack exchange,提问作者Juliette

