Is quoting filenames enough security for running `xargs sudo rm -rf`?
Solution 1:
In Linux, any character is a valid filename constituting character except:
-
\0
(ASCII NUL): as used for string termination in C -
/
(forward slash): as used for path separation
So, your approach will definitely not work in many cases as you can imagine e.g. does it handle a newline (\n
) in filename? (Hint: No).
Few notes:
- Don't parse
ls
; use dedicated tools (there is at least one for most use cases) - When dealing with filenames, try to leverage the NUL separated output provided by almost all GNU tools that work with such data
- Take care when piping, make sure both programs can understand NUL separations
- Whenever you're invoking
xargs
, see if you can get away withfind ... -exec
; in most cases, you will be fine with justfind
alone
I think these will get you going for now. steeldriver already provided the NUL separated idea in the comment (printf -- '%s\0' /path/to/some/folder/* | head -zn -2 | xargs -0 rm
), use this as a starting point.
Solution 2:
xargs
does support some quoting: with single quotes, double quotes or backslash which allows it to accept arbitrary arguments¹, but with a syntax that is different from the Bourne-like shells' quoting syntax.
The GNU implementation of ls
as found on Ubuntu doesn't have any quoting mode that is compatible with the xargs
input format.
Its ls --quoting-style=shell-always
is compatible with ksh93, bash and zsh shells quoting syntax, but only when the output of ls
is interpreted by the shell in the same locale as ls
was when it output it. Also, some locales, like those using BIG5, BIG5-HKSCS, GBK or GB18030 should be avoided.
So with those shells, you can actually do:
typeset -a files
eval "files=($(ls --quoting-style=shell-always))"
xargs -r0a <(printf '%s\0' "${files[@]:0:3}") ...
But that has little advantage over:
files=(*(N)) # zsh
files=(~(N)*) # ksh93
shopt -s nullglob; files=(*) # bash
The only case where it becomes useful is when you want to use the -t
option of ls
to sort the files by mtime/atime/ctime or -S
/-V
. But even then, you might as well use zsh
's:
files=(*(Nom))
for instance to sort the files by mtime (use oL
for -S
, and n
for -V
).
To remove all but the two most recently modified regular files:
rm -f -- *(D.om[3,-1])
¹ there are still some length limitations (by execve()
and in some non-GNU xargs
implementations much lower arbitrary ones), and some non-GNU xargs
implementations will choke of input that contains sequences of bytes not forming valid characters.