On 1/15/21 2:47 AM, Peng Yu wrote: > Hi, > > I want to suppress the message "xargs: ls: terminated by signal 13". > > $ find . -type f | xargs ls -altr | head > ... > xargs: ls: terminated by signal 13 > > I could use this. But I don't want to miss other error messages. Is > there an option of xargs to just suppress this message? Thanks. > > $ find . -type f | xargs ls -altr 2>/dev/null | head
Thanks for that question. The quick answer is: no, xargs(1) does not have an option to control suppressing the output of this error diagnostic. The longer answer is a bit more complex. First of all, the POSIX specification [1] requires this behavior: [1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html CONSEQUENCES OF ERRORS If [...] an invocation of the utility is terminated by a signal, [...], the xargs utility shall write a diagnostic message and exit without processing any remaining input. Before going deeper, let me also mention the expected behavior of the write(2) function [2]: [2] https://pubs.opengroup.org/onlinepubs/007904875/functions/write.html ERRORS The write() and [...] functions shall fail if: ... [EPIPE] An attempt is made to write to a pipe or FIFO that is not open for reading by any process, or that only has one end open. A SIGPIPE signal shall also be sent to the thread. Another good related reading is: https://bugs.gnu.org/34488 and https://bugs.gnu.org/11540 Your example does not show all aspects which I want to discuss, because depending on the file hierarchy in '.' the list of arguments might already fit into one invocation of 'ls'. Also other errors might interfere. Therefore, let's play the examples with the following: * Use 'seq inf' as data producer which writes numbers to the xargs pipe infinitely. * Use 'env printf "%s\n"' as the command to be executed by 'xargs -n5'; this gives us each input item on a separate line in the output. * Use 'head -n1' as the consumer which breaks the pipe after one line. Now let's discuss. In practice, these 3 things play together: SIGPIPE, EPIPE and exit code 255. a) SIGPIPE: With the default signal settings ("SIG_DFL"), a process will be terminated via the SIGPIPE signal when it attempts to write() to a pipe where the (last) reader has already disappeared. As the utility has been terminated by a signal, xargs(1) will diagnose this error and terminate itself. No further processing. $ seq inf | xargs -n5 printf "%s\n" | head -n1 1 xargs: printf: terminated by signal 13 b) EPIPE: When the signal SIGPIPE is ignored ("SIG_IGN"), the utility attempting to write() to a closed pipe will not receive the SIGPIPE signal, and instead the write() syscall will return with an error and errno=EPIPE. Usually, a well-written utility will know that further outputting does not make sense, and therefore will output an EPIPE error diagnostic, and then terminate itself with the exist status 1. Now, xargs(1) notices that the utility ended with an error, but as the exit code was not 255, it will continue to process the input and launch the utility as often as needed. Well, if the work of the utility is to filter its input and write() it to the same already closed output pipe, all those invocations will also fail like the first one. In our example, we'll see this an an endless loop until we press CTLR+C: $ seq inf | ( trap '' PIPE && xargs -n5 printf "%s\n" ) | head -n1 1 printf: write error: Broken pipe printf: write error: Broken pipe ... printf: write error: Broken pipe printf: write error: Broken pipe ^C c) exit code 255: xargs only terminates processing further input if the command being run terminated with an exist code of 255 (or was killed by a signal, as in a)). This means: in an environment where SIGPIPE is ignored, and where the xargs(1) processing needs to be stopped after an error, the COMMAND has to exit with status 255. $ seq inf \ | xargs -n5 sh -c ' trap "" PIPE \ && printf "%s\n" "$@" \ || exit 255' sh \ | head -n1 1 sh: line 0: printf: write error: Broken pipe xargs: sh: exited with status 255; aborting As can be seen, xargs(1) will still diagnose the exit status 255 (even if the command being run would swallow the EPIPE message "write error: Broken pipe"). What does all that mean? 1. With today's xargs(1), the processing will continue after a EPIPE error when SIGPIPE is ignored. Well, xargs(1) could ensure that SIGPIPE is always set to the default action in the child process after the fork(): signal (SIGPIPE, SIG_DFL); By that, case b) would be changed to terminate the processing. OTOH, the caller may have set SIGPIPE to SIG_IGN by intention. Furthermore, the COMMAND being called may write to several files or pipes, and only one - e.g. a logger - may be closed, and further invocation are desired/expected and would maybe succeed again. Therefore, adding the above signal() call hard-coded would be definitely wrong. 2. With today's xargs(1), it is not possible to suppress the SIGPIPE message (without discarding all the other possible errors to /dev/null as well). The attached is a first draft to add the option --sigpipe-nowarn; with this, the use can control whether xargs(1) diagnoses the SIGPIPE error or not. Please note that this option implicitly sets the SIGPIPE handler to SIG_DFL in the child process, although the invoking shell might have set it to SIG_IGN. I might have overseen something - or even I'm totally off. Comments welcome. Have a nice day, Berny
diff --git a/xargs/xargs.c b/xargs/xargs.c index 7e3db67a..3a96c0e3 100644 --- a/xargs/xargs.c +++ b/xargs/xargs.c @@ -167,9 +167,22 @@ static char input_delimiter = '\0'; */ static char* slot_var_name = NULL; +/* Boolean flag whether child processed terminated by SIGPIPE shall be diagnosed + * or not. Traditional behavior is to output an error diagnostic on stderr + * when a child process was terminated by SIGPIPE. + * If TRUE, the writing of that error diagnostic will be skipped. Use case: + * seq 100 | xargs --sigpipe-nowarn COMMAND | head -n3 + * Please note that this option implicitly sets the SIGPIPE handler to SIG_DFL + * in the child process, although the invoking shell might have set it to + * SIG_IGN. + * If FALSE, i.e., when --child-sigpipe-fatal is not given, + * then we get the Traditional behavior, sigpipe enabled. */ +static bool sigpipe_nowarn_option; + enum LongOptionIdentifier { - PROCESS_SLOT_VAR = CHAR_MAX+1 + PROCESS_SLOT_VAR = CHAR_MAX+1, + SIGPIPE_NOWARN_OPTION }; static struct option const longopts[] = @@ -190,6 +203,7 @@ static struct option const longopts[] = {"exit", no_argument, NULL, 'x'}, {"max-procs", required_argument, NULL, 'P'}, {"process-slot-var", required_argument, NULL, PROCESS_SLOT_VAR}, + {"sigpipe-nowarn", no_argument, NULL, SIGPIPE_NOWARN_OPTION}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {NULL, no_argument, NULL, 0} @@ -701,6 +715,10 @@ main (int argc, char **argv) } break; + case SIGPIPE_NOWARN_OPTION: + sigpipe_nowarn_option = true; + break; + default: usage (EXIT_FAILURE); } @@ -1227,6 +1245,20 @@ prep_child_for_exec (void) unsigned int slot = add_proc (0); set_slot_var (slot); + /* For the --sigpipe-no-warn option to work, ensure that the SIGPIPE signal + * handler is set to default for the child process. When the child process + * was terminated by a signal, the parent xargs(1) will end the input + * processing and terminate itself. + * Otherwise, if SIGPIPE is ignored, then write() errors on a closed pipe + * would return with errno set to EPIPE, the child process would (hopefully) + * diagnose the error, and exit with value 1. The parent xargs(1) would not + * be able to distinguish from any other error, and not end the processing. + */ + if (sigpipe_nowarn_option) + { + signal (SIGPIPE, SIG_DFL); + } + if (!keep_stdin || open_tty) { int fd; @@ -1582,8 +1614,16 @@ wait_for_proc (bool all, unsigned int minreap) 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)); + { + int sig = WTERMSIG (status); + if (sigpipe_nowarn_option && sig == SIGPIPE) + { + /* Exit without the regular error diagnostic. */ + exit (XARGS_EXIT_CLIENT_FATAL_SIG); + } + error (XARGS_EXIT_CLIENT_FATAL_SIG, 0, + _("%s: terminated by signal %d"), bc_state.cmd_argv[0], sig); + } if (WEXITSTATUS (status) != 0) child_error = XARGS_EXIT_CLIENT_EXIT_NONZERO; } @@ -1744,6 +1784,8 @@ usage (int status) " if this option is not given, COMMAND will be\n" " run at least once\n")); HTL (_(" -s, --max-chars=MAX-CHARS limit length of command line to MAX-CHARS\n")); + HTL (_(" --sigpipe-nowarn suppress diagnostic if COMMAND was terminated\n" + " by the SIGPIPE signal\n")); HTL (_(" --show-limits show limits on command-line length\n")); HTL (_(" -t, --verbose print commands before executing them\n")); HTL (_(" -x, --exit exit if the size (see -s) is exceeded\n"));