Are there any disadvantages of using rm $(ls) to delete files?

I was wondering if using rm $(ls) to delete files(or rm -r $(ls) to delete directories as well) was safe? Because in all the websites, people give other ways to do this even though this command seems much easier than other commands.


Solution 1:

No, it is not safe, and the commonly used alternative rm * isn't a lot safer.

There are many problems with rm $(ls). As others have already covered in their answers, the output of ls will be split at characters present in the internal field separator.

Best case scenario, it simply doesn't work. Worst case scenario, you intended to remove only files (but not directories) – or selectively remove some files with -i – but there's a file with the name c -rf in the current directory. Let's see what happens.

$ mkdir a
$ touch b
$ touch 'c -rf'
$ rm -i $(ls)
$ ls
c -rf

The command rm -i $(ls) was supposed to remove only files and ask before removing each one, but the command that was ultimately executed read

rm -i a b c -rf

so it did something else entirely.

Note that rm * is only marginally better. With the directory structure as before, it will behave as intended here, but if you have a file called -rf, you're still out of luck.

$ mkdir a
$ touch b
$ touch ./-rf
$ rm -i *
$ ls
-rf

There are several better alternatives. The easiest ones involve only rm and globbing.

  • The command

    rm -- *
    

    will work exactly as intended, where -- signals that everything after it should not be interpreted as an option.

    This has been part of the POSIX utility syntax guidelines for over two decades now. It's widespread, but you shouldn't expect it to be present everywhere.

  • The command

    rm ./*
    

    makes the glob expand differently and thus requires no support from the called utility.

    For my example from above, you can see the command that will ultimately be executed by prepending echo.

    $ echo rm ./*
    rm ./a ./b ./-rf
    

    The leading ./ prevents rm from accidentally treating any of the filenames like options.

Solution 2:

What this is intended to do?

  • ls lists files in current directory
  • $(ls) substitutes output of ls places that as argument for rm
  • Essentially rm $(ls) is intended to delete all files in current directory

What's wrong with this picture ?

ls cannot properly handle special characters in filename. Unix users generally advised to use different approaches. I've also showed that in a related question about counting filenames. For instance:

$ touch file$'\n'name                                                                                                    
$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ 

Also, as properly mentioned in Denis's answer, a filename with leading dashes, could be interpreted as argument to rm after substitution, which defeats the purpose of removing filename.

What works

You want to delete files in current directory. So use glob rm *:

$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ rm *
$ ls
$ 

You can use find command. This tool is frequently recommended for more than just current directory - it can recursively traverse entire directory tree, and operate on files via -exec . . .{} \;

$ touch "file name"                                
$ find . -maxdepth 1 -mindepth 1                                                                                         
./file name
$ find . -maxdepth 1 -mindepth 1 -exec rm {} \;                                                                          
$ ls
$ 

Python doesn't have issue with special characters in filenames, so we could employ that as well(note that this one is for files only, you will need to use os.rmdir() and os.path.isdir() if you want to operate on directories):

python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

In fact, the command above could be turned into function or alias in ~/.bashrc for brevity. For example,

rm_stuff()
{
    # Clears all files in the current working directory
    python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

}

Perl version of that would be

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'