Yes, with -0
, xargs
expects a NUL-delimited list on stdin. Here, you're feeding it the part of the name of the newest file after the last newline characters in it, followed by a newline character (that newline character is added by ls
and not part of file name).
There is no NUL in that input, so xargs
takes the whole input as one argument to pass to mv
which does contain that newline character, so it would only work correctly if the name of newest file did contain one and only one newline character and it was the last character in the file name.
Here, you'd need to make sure ls
outputs a NUL-delimited list instead of a newline-delimited one, but I'm not aware of any ls
implementation that can.
Or you'd need to revert to the default xargs
input format (without -0
) where arguments are delimited by blanks (the list of which depends on the xargs
implementation and possibly the locale) or newlines and where "..."
and '...'
and \
are used for escaping those and each other (in a different way from the same shell quoting operators). As some xargs
implementations try to interpret their input as text but filenames can contain any byte values (other than NUL and /
), you'll also need to do that processing in the C locale.
export LC_ALL=C
ls -td ./* | awk -v q="'" '
{gsub(/"/, "\"\\\"\"")} # escape "
NR == 1 {printf "\"%s", $0; next}
/\// {exit} # a / means the start of the second file
{printf "\"\\\n\"%s", $0} # escape newlines
END {if (NR) print "\""}' |
xargs -J % mv % newname
As you can see, using xargs
reliably is a total pain. Some xargs
implementations also have a very low limit on the size of argument or input lines. Note that -J
is a non-portable BSD extension.
Dealing with arbitrary file names using line-based utilities like ls
is also very hard.
Best would be to use zsh
which can sort the list of files by modification times by itself as @Kusalananda has shown:
mv -- *(.om[1]) newname
In bash
, you could also do:
IFS= read -rd / newest < <(ls -td ./*) && newest=${newest%.}
newest=${newest%?} # strip newline
[ -n "$newest" ] && mv "$newest" newname
(would also work in ksh93 or zsh). Like in the previous xargs
approach, we're using ./*
to be able to tell on which line the second file in the list starts.
ls
is discouraged. – AdminBee Jun 22 '20 at 15:22find
a better bet? – Scott Anderson Jun 22 '20 at 15:23