如何让`xargs`忽略子进程的退出并继续处理后续任务

我有时会在夜间运行长时间的xargs作业,早上发现xargs在中途死掉真的很烦人,比如因为一个特殊情况下的分段错误,就像今晚发生的那样。
即使只有一个xargs子进程被杀死,它也不会再处理任何输入:
控制台1:
[09:35:48] % seq 40 | xargs -i --max-procs=4 bash -c 'sleep 10; date +"%H:%M:%S {}";'
xargs: bash: terminated by signal 15
09:35:58 3
09:35:58 4
09:35:58 2
<Exit with code 125>

控制台2:
[09:35:54] kill 5601

我能以某种方式防止xargs在子进程死亡后停止处理任何更多的输入,而是继续处理吗?


我在debian wheezy中使用的是xargs版本4.4.2,看起来即使我杀掉一个特定的sleep进程,一切似乎都运行正常。你使用的是哪个版本的xargs?也许他们已经在最新版本中修复了这个问题。 - Kannan Mohan
有点晚来参加派对,但是怎么样用 xargs ... bash -c '...;exit 0' 或者甚至 xargs ... bash -c '... || echo erk' - Samveen
请注意,parallel -j 1 是一种可能的黑客解决方案。 - user2267
5个回答

不行,你不能。从xargs sources at savannah.gnu.org上看到的:
if (WEXITSTATUS (status) == CHILD_EXIT_PLEASE_STOP_IMMEDIATELY)
  error (XARGS_EXIT_CLIENT_EXIT_255, 0,
         _("%s: exited with status 255; aborting"), bc_state.cmd_argv[0]);
if (WIFSTOPPED (status))
  error (XARGS_EXIT_CLIENT_FATAL_SIG, 0,
         _("%s: stopped by signal %d"), bc_state.cmd_argv[0], WSTOPSIG (status));
if (WIFSIGNALED (status))
  error (XARGS_EXIT_CLIENT_FATAL_SIG, 0,
         _("%s: terminated by signal %d"), bc_state.cmd_argv[0], WTERMSIG (status));
if (WEXITSTATUS (status) != 0)
  child_error = XARGS_EXIT_CLIENT_EXIT_NONZERO;

在检查周围或调用它的函数周围没有标志。它似乎与最大进程数有关,这是有道理的:如果将最大进程数设置得足够高,它就不会在达到限制之前进行检查,这可能永远不会发生。

对于您尝试做的事情,更好的解决方案可能是使用GNU Make

TARGETS=$(patsubst %,target-%,$(shell seq 1 40))

all: $(TARGETS)

target-%:
    sleep 10; date +"%H:%M:%S $*"

然后:

$ make -k -j4 

将会产生相同的效果,并且给您更好的控制。

看起来最明显的口头语之一只被其他提议所暗示。

也就是说,你可以使用以下内容:

bash -c '$PROG_WHICH_MAY_FAIL ; (true)'

为了"强制成功"。
注意,这与lornix的提议类似(只是没有那么明确)。
无论如何,由于这实际上忽略了实际的进程退出状态,我建议您考虑一种方式来保存子进程的状态以供事后分析。例如:
bash -c '$PROG_WHICH_MAY_FAIL || touch failed; (true)'

这里的true有点多余,所以最好改为:
bash -c '$PROG_WHICH_MAY_FAIL || touch failed'

既然我们可能想知道何时无法访问“失败”的文件。换句话说,我们不再忽视这个错误,而是做好记录并继续进行。

考虑到这个问题的递归性质,或许我们可以看出为什么xargs不容易忽略失败。因为这从来都不是一个好主意 - 你应该在你正在开发的过程中增强错误处理。然而,我相信这个观念更多地体现在“Unix哲学”本身。

最后,我想这也是詹姆斯·杨曼通过推荐trap所暗示的,它可能以类似的方式使用。也就是说,不要忽视问题...捕获它并处理它,否则你可能有一天会发现所有的子程序都没有成功执行 ;-)


使用trap
$ seq 40 | xargs -i --max-procs=4 bash -c \
 'trap "echo erk; exit 1" INT TERM;  sleep 10; date +"%H:%M:%S {}";' fnord
16:07:39 2
16:07:39 4
erk
16:07:39 1
^C
erk
erk
erk
erk

另外,你可以从shell切换到另一种语言,这种语言中也可以设置信号处理程序。
另外请注意,在执行之后,你应该指定$0应该取的值(在这里是fnord),以便不会被seq生成的第一个单词吞掉。

无论是time还是env都不适用于我(它们只传递给其子程序的返回值),因此我写了bliss

#!/bin/sh
"$@"
exit 0

然后执行 chmod u+x ~/bliss 还有类似的命令 find_or_similar | xargs ~/bliss fatally_dying_program.sh

在那里加入另一个命令来“吞噬”正在关闭的程序的信号。
我尝试了你的示例,最初按照你展示的方式来证明问题... 'killall sleep' 杀死了 sleep 进程,中断了 bash,并且 xargs 退出。
作为一个测试,我在 xargs 和 bash 之间插入了一个“运行另一个命令”的类型的命令... 在这种情况下是 '/usr/bin/time'。这一次(无意思),killall sleep 杀死了 sleep 进程,但是 xargs 继续执行。
你可以将 time 的输出导向 /dev/null,这样就能完全达到你想要的效果,而不需要对现有流程进行重大改写。
我想,如果我花点时间思考,我可能会想出另一个程序来完成同样的任务,而不会产生 '/usr/bin/time' 的 stderr 内容。甚至可以自己编写一个,只是一个“fork”(或 exec() 的衍生)。
记得使用 '/usr/bin/time',因为我不确定 bash 内置的 'time' 是否能够做到同样的“吞噬”信号的功能。

1一个很好的替代time的选择是env,因为它只是向运行的程序的环境中添加零个或多个可选变量。它不会产生任何输出,被调用程序的返回码将传递回调用env的地方。 - James Sneeringer
{轻笑} 我在写完这段时间后才想到这个。 "运行某事" 命令,首先想到的是时间。虽然很好用。恭喜你,谢谢。 - lornix