I am trying to execute the following command

ls -d a* | xargs -i sudo rm -rf {}/*

The problem is that when I add the /* part the command does not work. I want to remove the directory contents (not the directory itself).


What about rm -r a*/*? This should solve your issue.

  • Good point, I had thought the ls -d a* was a placeholder for something more complex - but I shouldn't have assumed that. – RedGrittyBrick Dec 30 '10 at 15:27
  • 1
    actually it was a placeholder but this did the trick – Lombo Dec 30 '10 at 15:40

Commands like rm rely on the shell to expand wildcards, so you'd need to invoke a shell somewhere.


 ls -d a* | xargs -i sh -c sudo rm -rf {}/*
  • good to know! ty – Lombo Dec 30 '10 at 15:41

(Warning: I haven't tested any of these commands. There may be typos or errors. Try everything with echo or ls and no sudo first.)

General advice: xargs is rarely the simplest way to do something. Without -i (which is deprecated by the way, use the portable -I {} instead), xargs expects an input format that no standard or common tool produces. With -I, xargs does have some use, though if you're piping find into it, find -exec is simpler.

You need a shell to expand {}/*, and that shell must act after {} has been replaced by one actual path. A simple solution is to do the line-by-line processing in the shell. Note the use of three patterns to capture all the files in a directory as * omits dot files; it doesn't matter if some of the patterns don't match any file, as rm -f ignores any argument that doesn't reference an existing file.

… |
while read -r line; do
  sudo rm -rf -- "$line"/* "$line"/.[!.]* "$line"/..?*

Xargs is no use here since you have to process the files one by one anyway at some point, in order to get the shell to perform the globbing. As the general technique can be useful however, here's a way to feed file names back into a shell from xargs. The _ is the $0 argument to the shell (it's there in part not to have to bother to stick $0 back onto any subsequent argument, and to ensure that everything works even if $1 begins with a - and so would otherwise be treated as an option to the shell). There are two approaches here: either have the shell loop over its arguments, or tell xargs to pass a single argument to each shell invocation.

… | xargs -I {} sudo sh -c 'for x; do rm -rf -- "$x"/* "$x"/.[!.]* "$x"/..?*; done' _ {}
… | xargs -I {} -n 1 sudo sh -c 'rm -rf -- "$1"/* "$1"/.[!.]* "$1"/..?*' _ {}

I'm assuming that ls -d a* is a toy example and that you have a more complex command that produces one file name per line. Note that ls -d a* would not work there, as most ls implementations do not output nonprintable characters transparently. If the is a find command, use -exec, as in

find a* -exec sh -c 'rm -rf -- "$1"/* "$1"/.[!.]* "$1"/..?*' _ {} \; -prune

Another option is to use a shell loop instead of invoking xargs at all.


ls -d a* | while read dir; do sudo rm -rf "$dir"/*; done;

Mostly for the sake of example, you can also combine the two techniques by putting a shell construct in the middle of a pipeline:

ls -d a* | while read dir; do ls -d "$dir"/*; done | xargs -d \\n rm -rf

The -d option to xargs just specifies that the delimiter between arguments is a newline, which avoids potentially catastrophic problems with filenames which contain spaces (for example deadly / file)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.