GNU xargs – Why Is -r Ignored When Used with -0 in GNU xargs?

gnuxargs

The regular case with linefeeds, xargs calls printf and something is printed:

>>printf "foo\n" | xargs -r printf "->%s\n"
->foo
>>

With an empty input instead, -r makes xargs not call the command at all:

>>printf "\n" | xargs -r printf "->%s\n"
>>

Using nulls instead of line feeds, everything is the same if there is an input to xargs:

>>printf "foo\0" | xargs -r -0 printf "->%s\n"
->foo
>>

But if there is no input, something is printed anyway:

>>printf "\0" | xargs -r -0 printf "->%s\n"
->
>>

So, either

  • I'm missing something (what?)

  • There is some good reason to ignore -r when used with -0 (but which)?

  • There is a bug in my xargs (findutils 4.6.0.225-235f) but it's hard to decide:

    • I find the same behavior in two other instances (a 4.7.0-git on Ubuntu 16.04 and another 4.6.0 on Windows),
    • I can't believe this kind of thing could slip thru regression tests.
    • On the other hand this question implies that it could have worked in the past

So, what is the true expected behavior of xargs?

Best Answer

xargs -r cmd skips running cmd without arguments.

With -0, one argument is created for each NUL-delimited record on input

printf '\0' | xargs -r0 cmd

You're feeding one empty record, so cmd is called with one empty argument (and with printf '\0\0', that would be 2 empty records, etc).

With:

printf '' | xargs -r0 cmd

You're feeding no record, so cmd is not run.

With:

printf '' | xargs -0 cmd

You're still not feeding any record, but because -r is not provided, cmd is still called once without argument.

Without -0, xargs expects a very special input format, it's not a simple delimited list like with xargs -0 or xargs -d '\n'

To feed one empty argument, you need something like:

printf '""\n' | xargs -r cmd
printf "''\n" | xargs -r cmd

or for that matters:

printf '\n\n \t\t "" \n\n \t\n' | xargs -r cmd

printf -- '-%s\n' is not the best choice of command to test that as it gives the same output when passed no argument as when passed one empty argument.

A better one could be:

$ printf '\n\n \t\t "" \n\n \t\n' | xargs -r zsh -c 'PS4=Got; set -x; : "$@"' zsh
Got: ''
$ printf '\n\n \t\t \n\n \t\n' | xargs zsh -c 'PS4=Got; set -x; : "$@"' zsh
Got:
$ printf '\t\n\n' | xargs -rd '\n' zsh -c 'PS4=Got; set -x; : "$@"' zsh
Got: $'\t' ''

Or:

$ printf '\t\n\0\0' | xargs -0 zsh -c 'print -rl $#: " - ${(q+)^@}"' zsh
2:
 - $'\t\n'
 - ''