The POSIX specification does give you an example for that:
ls | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'
(with filenames being arbitrary sequences of bytes (other than /
and NULL) and sed
/xargs
expecting text, you'd also need to fix the locale to C (where all non-NUL bytes would make valid characters) to make that reliable (except for xargs
implementations that have a very low limit on the maximum length of an argument))
The -E ''
is needed for some xargs
implementations that without it, would understand a _
argument to signify the end of input (where echo a _ b | xargs
outputs a
only for instance).
With GNU xargs
, you can use:
ls | xargs -rd '\n' printf '<%s>\n'
(also adding -r
(also a GNU extension) for the command not be run if the input is empty).
GNU xargs
also has a -0
that has been copied by a few other implementations, so:
ls | tr '\n' '\0' | xargs -0 printf '<%s>\n'
is slightly more portable.
All of those assume the file names don't contain newline characters. If there may be filenames with newline characters, the output of ls
is simply not post-processable. If you get:
a
b
That can be either both a a
and b
files or one file called a<newline>b
, there's no way to tell.
GNU ls
has a --quoting-style=shell-always
which makes its output unambiguous and could be post-processable, but the quoting is not compatible with the quoting expected by xargs
. xargs
recognise "..."
, \x
and '...'
forms of quoting. But both "..."
and '...'
are strong quotes and can't contain newline characters (only \
can escape newline characters for xargs
), so that's not compatible with sh quoting where only '...'
are strong quotes (and can contain newline characters) but \<newline>
is a line-continuation (is removed) instead of an escaped newline.
You can use the shell to parse that output and then output it in a format expected by xargs
:
eval "files=($(ls --quoting-style=shell-always))"
[ "${#files[@]}" -eq 0 ] || printf '%s\0' "${files[@]}" |
xargs -0 printf '<%s>\n'
Or you can have the shell get the list of files and pass it NUL-delimited to xargs
. For instance:
with zsh
:
print -rNC1 -- *(N) | xargs -r0 printf '<%s>\n'
with ksh93
:
(set -- ~(N)*; (($# == 0)) || printf '%s\0' "$@") |
xargs -r0 printf '<%s>\n'
with fish
:
begin set -l files *; string join0 -- $files; end |
xargs -r0 printf '<%s>\n'
with bash
:
(
shopt -s nullglob
set -- *
(($# == 0)) || printf '%s\0' "$@"
) | xargs -r0 printf '<%s>\n'
2023 Edit. Since version 9.0 of GNU coreutils (September 2021), GNU ls
now has a --zero
option that can be used in conjunction with xargs -r0
:
ls --zero | xargs -r0 printf '<%s>\n'
ls
doesn't work here? That's normal behavior, and that's why you never parse the output of ls. Is that your question or is there something else? – terdon Aug 11 '17 at 12:20ls
is just to supply the names, one line per entry. This is not about parsing the output ofls
(because in this case the firstls
just gives the filenames, without any other modification except adding newline per entry). The secondls
is to demonstrate howxargs
feeds the names as arguments to the command. In this case,xargs
somehow fails to pass the second--
tols
. – Gerry Lufwansa Aug 11 '17 at 12:26ls -- * | xargs -0 -- ls -l --
. I'd expectls
be passed just one big argument (possibly starting with a-
but that wouldn't be a problem with--
) since the input doesn't contain NULs and the error would be about that file with a long name and some newline characters not being found. What system and version ofls
/xargs
is that? – Stéphane Chazelas Aug 11 '17 at 14:42ls: cannot access '-name-with-dash-prefix'$'\n''name with space'$'\n''name-with-backslash\'$'\n''name-with-double-quote"'$'\n''name-with-single-quote'\'''$'\n''safe-name'$'\n': No such file or directory
– Stéphane Chazelas Aug 11 '17 at 14:44ls -- * | strace -fe execve xargs -0 -- ls -l --
instead? – Stéphane Chazelas Aug 11 '17 at 14:54