Hudson: "是的:标准输出:管道损坏"

21

我需要在Hudson中运行一个shell脚本。该脚本需要用户提供答案。为了给出自动答案,我执行了以下命令行:  

yes | ./MyScript.sh

在Ubuntu终端中这个命令很好用。但当我在Hudson工作时使用同样的命令,脚本会自动化并完成所有所需工作,但最后我会得到这两行错误:

yes: standard output: Broken pipe
yes: write error

这导致了我的Hudson工作失败。

我应该如何更改命令行,使其在Hudson中正常工作?

5个回答

31
但是你如何解释,为什么我在本地运行脚本时不会出现此错误,但是在从Hudson作业远程运行时会出现错误?
当您在终端中运行它(本地)时,yes被SIGPIPE信号杀死,当MyScript.sh已经退出时,它尝试写入管道时生成该信号。
无论谁在Hudson中运行命令(远程),都会捕获该信号(将其处理程序设置为SIG_IGN,您可以通过运行trap命令并在输出中搜索SIGPIPE来测试它),并且它不会为新的子进程恢复信号(例如,yes和运行MyScript.sh的任何内容,例如您的情况下的sh)。这导致写入错误(EPIPE)而不是信号。yes检测到写入错误并报告它。
您可以简单地忽略错误消息:
yes 2>/dev/null | ./MyScript.sh

您还可以针对运行管道的组件报告错误。该错误在子进程被fork后未将SIGPIPE恢复为默认处理程序中。这是在POSIX系统上在终端上运行时程序期望的结果。虽然我不知道是否有一种标准的方式来为基于Java的程序执行此操作。jvm可能会为每个写入错误引发异常,因此在SIGPIPE上不死亡对于Java程序来说不是问题。
像hudson进程这样的守护程序通常会忽略SIGPIPE信号。您不希望您的守护程序仅因您正在通信的进程死亡而死亡,并且您无论如何都会检查写入错误。
编写为在终端上运行的普通程序不会检查每个printf()的错误状态,但如果管道中的程序死亡,您希望它们能够退出,例如,如果您运行source | sink管道;通常情况下,如果退出,则希望进程尽快退出。
If SIGPIPE signal is disabled (as it looks like in Hudson's case) or if a program does not die on receiving it (yes program does not define any handlers for SIGPIPE so it should die on receiving the signal), EPIPE write error is returned. The goal is to find the right command or fix to get rid of the error, not to ignore it. The only way yes process stops if it is killed or encountered a write error. If SIGPIPE signal is set to be ignored (by the parent) and no other signal kills the process, then yes receives write error on ./MyScript.sh exit. There are no other options if you use yes program. Please keep the HTML tags and make the content more understandable. SIGPIPE信号和EPIPE错误传达了完全相同的信息——管道已断开连接。如果SIGPIPEyes进程启用,则不会看到错误。仅因为您看到它,没有发生任何新的事情。这意味着./MyScript.sh已退出(成功或不成功——无关紧要)。

你好,感谢您提供的解决方案。但我担心这个解决方案可能会将任何错误重定向到/deb/null(管道中断或其他)。因此,我更需要一个可以解决问题的命令。谢谢。 - Farah
1
我同意你刚刚所做的修改,+1。但是你能解释一下为什么我在本地运行脚本时不会出现错误,但是在从Hudson作业远程运行时却会出现错误吗? - Farah
@Farah:你上一个问题就是我的答案一开始就试图要解释的。 - jfs
谢谢,非常好的解释。你值得获得奖励。 - Farah
精彩的回答,见解深刻。 - igniteflow
显示剩余2条评论

3
我遇到了这个错误,我的问题不是它输出了“yes: standard output: Broken pipe”,而是它返回了一个错误代码。
因为我在运行脚本时使用了bash strict mode,包括-o pipefail,当yes“出错”时,会导致我的脚本出错。

如何避免错误

我避免这种情况的方法如下:
bash -c "yes || true" | my-script.sh

1
你是想使用yes程序管道到脚本中吗?还是将yes打印到脚本中?如果该过程通过Jenkins进行,请在shell命令的末尾添加“; true”。

2
你好,谢谢你的建议。但我不应该忽略异常。 - Farah

0

由于yes./MyScript.sh都可以在显式子shell中运行,因此可以将yes命令放入后台,将yespid发送到./MyScript.sh子shell中,然后在那里实现对EXIT的陷阱以手动终止yes命令。(对于管道命令序列的最后一个命令的子shell,应始终实现对EXIT的陷阱)。

# avoid hangup or "broken pipe" error message when parent process set SIGPIPE to be ignored
# sleep 0 or cat /dev/null: do nothing but with external command (for a shell builtin command see: help :)
(
trap "" PIPE
( (sleep 0; exec yes) & echo ${!}; wait ${!} ) | 
   ( 
     trap 'trap - EXIT; kill "$yespid"; exit 0' EXIT
     yespid="$(head -n 1)"
     head -n 10  # replacement for ./MyScript.sh
   )
echo ${PIPESTATUS[*]}
)

如果你想以退出代码为0的形式退出“yes”子shell,也可以这样做:
# avoid hangup or "broken pipe" error message when parent process set SIGPIPE to be ignored
# set exit code of yes subshell to 0
(
trap "" PIPE
   (
      trap 'trap - TERM; echo "kill from yes subshell ..." 1>&2; kill "${!}"; exit 0' TERM 
      subshell_pid="$(bash -c 'echo "$PPID"')"
      (sleep 0; exec yes) & echo "${subshell_pid}"; wait ${!} 
   ) | 
   ( 
      trap 'trap - EXIT; kill -s TERM "$subshell_pid"; exit' EXIT
      subshell_pid="$(head -n 1)"
      head -n 10  # replacement for ./MyScript.sh
   )
echo ${PIPESTATUS[*]}
)

特别是后面的脚本看起来过于复杂。你可以在第一个脚本中使用 wait ${!} || : 来使退出状态为0。最好使用 read -r yespid 而不是 yespid="$(head -n 1)"。当你在 sleep 0 之前有 yes 时,你能确定 pid 是首先写入管道的吗?这不是 yes 的问题,但在一般情况下,如果管道的左侧先退出,则应该隐藏可能的 kill 错误。 - jarno
在我对相关问题的回答中,我解决了我上面提到的问题。 - jarno

-2

由于命令yes在无限循环中运行,我认为这可能是解决方案:

yes | head -1 | ./MyScript.sh #only one "Y" would be output of the command yes

但是,我得到了相同的错误。

我们可以像@J.F. Sebastian建议的那样将错误重定向到/dev/null,或者通过以下方式强制执行正确的命令:

yes | head -1 | ./MyScript.sh || yes

但是,这些建议受到的赞赏较少。因此,我不得不创建自己的命名管道,如下所示:

mkfifo /tmp/my_fifo #to create the named pipe
exec 3<>/tmp/my_fifo #to make the named pipe in read and write mode by assigning it to a file descriptor
echo "Y" >/tmp/my_fifo #to write into the named pipe, "Y" is the default value of yes
./MyScript.sh </tmp/my_fifo #to read from the named pipe
rm /tmp/my_fifo #remove the named pipe

我期待更有价值、更详细的解决方案。

这里是一个关于 Linux 文件描述符的解释。

谢谢。


第一条命令应该产生相同的错误。如果你不理解它,那么我的答案失败了。如果./MyScript.sh失败,第二条命令会再次启动yes。为什么要这样做?(请记住,管道的退出代码是最后一个命令的退出代码(yes | cmd给出cmd的退出状态,无论yes的退出状态如何)。) - jfs
1
第三组命令(使用mkfifo)是一种奇怪的方式来编写 echo "Y" | ./MyScript.sh; true。它只产生一个 "Y"(因为 yes 的默认值是无限的,即尽可能多的小写字母 "y"),并且它隐藏了 ./MyScript.sh 的退出状态。如果 ./MyScript.sh 只需要一个 "Y",那么请使用 echo Y | ./MyScript.sh - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接