On 5/11/21 3:58 AM, Kurt von Laven wrote: > The following command correctly outputs nothing. > xargs --no-run-if-empty -I str echo str <<< "" > > The following command incorrectly outputs 1 empty line. > xargs --no-run-if-empty --delimiter="\n" -I str echo str <<< "" > > The following command incorrectly outputs 2 empty lines. > xargs --no-run-if-empty --null -I str echo str <<< ""
First of all, let's check what the shell syntax '<<<' produces for an empty string as argument: $ od -tx1z <<< "" 0000000 0a >.< 0000001 Aha, the shell apparently turns the <<<"" into nothing, but finally outputs a newline character. My interpretation of this is that the shell does this for POSIX compatibility: the receiving tool is supposed to work with text files ... which by definition have a terminating newline character. Also wc(1) tells us that there was 1 line but without a word: $ wc <<< "" 1 0 1 Now let's see what is happening in xargs(1) - using the 'strace' tool: $ strace -vfe read,write xargs --no-run-if-empty --null -I str echo str <<< "" >/dev/null read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`|\2\0\0\0\0\0"..., 832) = 832 read(0, "\n", 4096) = 1 read(0, "", 4096) = 0 strace: Process 31530 attached [pid 31529] read(3, "", 4) = 0 [pid 31530] read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`|\2\0\0\0\0\0"..., 832) = 832 [pid 31530] write(1, "\n", 1) = 1 [pid 31530] write(1, "\n", 1) = 1 [pid 31530] +++ exited with 0 +++ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1335, si_uid=1000, si_status=0, si_utime=0, si_stime=0} --- +++ exited with 0 +++ And here again with my comments in between: $ strace -vfe read,write xargs --no-run-if-empty --null -I str echo str <<< "" read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`|\2\0\0\0\0\0"..., 832) = 832 Ignore, that comes from reading a library. read(0, "\n", 4096) = 1 xargs reads a partial item - because it hasn't seen neither the delimiter '\0' nor EOF until then. Therefore it goes for another round ... read(0, "", 4096) = 0 Now, xargs has hit EOF. This means the first and only item is "\n". Okay, we have more than Zero items, so it performs the -exec action with the -I str replacement: strace: Process 31530 attached [pid 31529] read(3, "", 4) = 0 This read() is in the parent xargs process, and irrelevant for this case. It is for error handling - for those who care: https://git.sv.gnu.org/cgit/findutils.git/tree/xargs/xargs.c?id=11576f4e6a#n1376 [pid 31530] read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`|\2\0\0\0\0\0"..., 832) = 832 ignore again, the child process is reading one of the libraries. [pid 31530] write(1, "\n", 1) = 1 Here, 'echo' outputs the item it got from xargs(1): the newline character. [pid 31530] write(1, "\n", 1) = 1 Here, 'echo' appends the usual newline. [pid 31530] +++ exited with 0 +++ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31530, si_uid=1000, si_status=0, si_utime=0, si_stime=0} --- +++ exited with 0 +++ The child process and xargs terminate without error. With the -t, --verbose option, one can exactly see how echo is invoked (in shell-quotation style): $ xargs --no-run-if-empty --null -t -I str echo str <<< "" >/dev/null echo ''$'\n' Therefore, I don't see anything wrong with xargs, and I think it's more a matter of what a user expects from the shell's <<< operator: it is made for text input. At least in the manual page of bash(1) on my system, this is documented properly: Here Strings A variant of here documents, the format is: [n]<<<word The word undergoes [...], and quote removal. Pathname expansion and word splitting are not performed. The result is supplied as a single string, with a newline appended, to the command ____^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ on its standard input (or file descriptor n if n is specified). Have a nice day, Berny