晚上好,
以下是我在脚本中使用的一段代码。从 SSH 会话启动工作正常,但是,当它通过 cron 运行时,它会在屏幕上显示损坏的管道错误。
我无法通过 SSH 重现它。
代码:
IP=$(sort --random-sort /root/ips.csv | head -n 1); nc -zv -w 2 $IP 443 2>&1 | grep succeeded >> outfile
屏幕错误:
sort: write failed: standard output; Broken pipe
sort: write error
有什么提示/指示吗?
谢谢你!
答案1
当head
处理第一行后完成时,它退出,关闭管道的另一端。sort
可能仍在尝试写入更多内容,并且写入关闭的管道或套接字会返回 EPIPE 错误。但它也会引发 SIGPIPE 信号,杀死进程,除非该信号被忽略或处理。忽略信号后,sort
会看到错误、抱怨并退出。如果不忽略该信号,sort
就会死掉。
我们可以使用trap
内置忽略来自 shell 的信号,出现错误:
$ trap "" PIPE
$ sort bigfile | head -1 > /dev/null
sort: write failed: standard output: Broken pipe
sort: write error
但可悲的是,我们不能用于trap
取消忽略信号并获得所需的行为,如POSIX 要求不允许在非交互式 shell(脚本)中执行此操作。它确实允许交互式 shell,但 Bashtrap
在这种情况下也不这样做。
去测试:
sh$ trap '' PIPE # ignore the signal
sh$ PS1='another$ ' bash # run another shell
another$ trap - PIPE # try to reset the signal
# it doesn't work
another$ sort bigfile |head -1 > /dev/null
sort: write failed: 'standard output': Broken pipe
sort: write error
相反,我们可以使用外部工具,例如 Perl 单行代码来运行脚本或命令,并且信号不被忽略(sort
此处静默退出):
another$ perl -e '$SIG{PIPE}="DEFAULT"; exec "@ARGV"' \
'sort bigfile |head -1' > /dev/null
another$
至于你的 cron 情况,原因可能是 systemd显然默认情况下会忽略 SIGPIPE,提到:
[SIGPIPE] 对于普通守护进程来说并不是很有用,当我们试图为守护进程提供一个良好、有用的执行环境时,我们将其关闭。当然,shell 之类的应该再次打开它。
当然,这也提到了在文档中(systemd.exec):
IgnoreSIGPIPE=
采用布尔参数。如果为 true,则导致 SIGPIPE 在执行的进程中被忽略。默认为 true,因为 SIGPIPE 通常仅在 shell 管道中有用。
在我的 Debian 系统上,/lib/systemd/system/cron.service
显式设置IgnoreSIGPIPE=false
,撤消 cron 的 systemd 默认值。您可能想检查一下这对您的情况是否有帮助。
答案2
因为--random-sort
无论如何它都是一个 GNU 扩展,所以您不妨使用shuf
来自同一 GNU 实用程序集合 (GNU coreutils
) 的 GNU:
ip=$(shuf -n 1 ips.tsv)
除了避免 SIGPIPE 问题之外,这也更有效,因为它不需要像 GNU 那样对整个文件进行洗牌sort --random-sort
(shuf
使用一些油藏取样算法需要大量投入才能做到这一点)。
答案3
作为一般解决方案,您可以使用这种 Bash 函数来运行管道,而不会出现管道损坏错误,并且不会隐藏其他可能的错误消息:
entire_pipe() {
( set -m; (
trap 'exit 0' INT
echo $BASHPID
$1
) & wait $! ) |
(
trap 'trap - EXIT; kill -s INT -- -$pid 2>/dev/null || :' EXIT
read -r pid
$2
)
}
为了使其更便携,您可以替换$BASHPID
为$(exec sh -c 'echo "$PPID"')
.
要在出现此问题时使用该函数,您需要替换
sort --random-sort /root/ips.csv | head -n 1
经过
entire_pipe 'sort --random-sort /root/ips.csv' 'head -n 1'
即使管道的左侧启动后代进程,这也应该起作用;他们都会被打断。