Something I feel I ought to know for sure: if I ls <something>
, will rm <something>
remove exactly the same files that ls
displayed? Are there any circumstances where rm
could remove files that ls
did not show? (This is in the 18.04 bash)
Edit: thank you to everyone who answered. I think the full answer is a combination of all the answers, so I have accepted the most up-voted answer as "the answer".
Unexpected things I have learned along the way:
ls
is not as straightforward as you might think in its handling of its arguments- In a simple un-fiddled-with installation of Ubuntu, .bashrc aliases
ls
- Don't name your files beginning with a dash as they can look like command arguments, and naming one -r is asking for it!
Well, both
ls
andrm
operate on the arguments which are passed to them.These arguments can be a simple file, so
ls file.ext
andrm file.ext
operate on the same file and the outcome is clear (list the file / delete the file).If instead argument is a directory,
ls directory
lists the content of the directory whilerm directory
won't work as is (i.e.rm
without flags cannot remove directories, while if you dorm -r directory
, it recursively deletes all files underdirectory
and the directory itself).But keep in mind that command line arguments can be subjected to shell expansion, so it's not always guaranteed that the same arguments are passed to both commands if they contain wildcards, variables, output from other commands, etc.
As an extreme example think
ls $(rand).txt
andrm $(rand).txt
, the arguments are "the same" but the results are quite different!If you're thinking of something like
ls foo*.txt
vs.rm foo*.txt
, then yes, they will show and remove the same files. The shell expands the glob, and passes it to the command in question, and the commands work on the listed files. One listing them, one removing them.The obvious difference is that if any of those files happened to be a directory, then
ls
would list its contents, butrm
would fail to remove it. That's usually not a problem, sincerm
would remove less than what was shown byls
.The big issue here comes from running
ls *
orrm *
in a directory containing filenames starting with a dash. They would expand to the command lines of the two programs as if you wrote them out yourself, andls
would take-r
to mean "reverse sort order", whilerm
would take-r
to mean a recursive removal. The difference matters if you have subdirectories at least two levels deep. (ls *
will show the contents of the first level directories, butrm -r *
will everything past the first sublevel, too.)To avoid that, write permissive globs with a leading
./
to indicate the current directory, and/or put a--
to signal the end of option processing before the glob (i.e.rm ./*
orrm -- *
).With a glob like
*.txt
, that's actually not an issue since the dot is an invalid option character, and will cause an error (until someone expands the utilities to invent a meaning for it), but it's still safer to put the./
there anyway.Of course you could also get different results for the two commands if you changed the shell's globbing options, or created/moved/removed files in between the commands, but I doubt you meant any of those cases. (Dealing with new/moved files would be extremely messy to do safely.)
Leaving aside shell behavior,let's focus on only what
rm
andls
can deal with themselves. At least one case wherels
will show whatrm
can't remove involves directory permissions, and the other - special directories.
and..
.Folder permissions
rm
is an operation on a directory, because by removing a file, you're changing directory contents ( or in other words listing of directory entries,since directory is nothing more than a list of filenames and inodes). This means you need write permissions on a directory. Even if you are the owner of the file, without directory permissions you can't remove files. The reverse is also true:rm
can remove files that may be owned by others, if you are directory owner.So you may very well have read and execute permissions on a directory, which will allow you traverse the directory and view contents within just fine, for example
ls /bin/echo
, but you can'trm /bin/echo
unless you are the owner of/bin
or elevate your privileges withsudo
.And you'll see cases like this everywhere. Here's one such case: https://superuser.com/a/331124/418028
Special directories '.' and '..'
Another special case is
.
and..
directories. If you dols .
orls ..
, it will happily show you the contents, butrm
'ing them is not allowed:If you type
ls *
and thenrm *
, it's possible you'll remove more files thanls
showed - they might have been created in the tiny time interval between the end ofls
and start ofrm
.ls *
andrm *
are not responsible for expanding the glob - that's done by the shell before passing it to the command.This means that you can use any command with the expanded filelist - so I would use something that does as little as possible.
So a better way to do this (or at the least, another way) is to skip the middle-man.
echo *
will show you exactly what would be passed to yourrm
command.If you do only
ls
instead ofls -a
, yesrm
can remove hidden files you haven't seen withls
without-a
.Example :
According to :
ls dir_test
: will display only test2ls -A dir_test
: will display test2 + .testrm -r dir_test
: will remove all (.test + test2)I hope that will help you.
How about:
Basically wildcards expanding to stuff starting with
-
(or manually entered stuff starting with-
but that looks a bit more like cheating) may be interpreted differently byls
andrm
.There are edge cases where what
ls
shows is not whatrm
removes. A rather extreme, but fortunately benign one is if the argument you pass is a symbolic link to a directory:ls
will show you all the files in the symlinked directory, whilerm
will remove the symlink, leaving the original directory and its contents untouched:There are already many good answers, but I want to add some more deep insight.
Ask yourself the question: How many parameters are passed to
ls
, if you write...? Note that the
ls
command does not get the*
as parameter if there are any files that*
can be expanded to. Instead, the shell first performs globbing before invoking the command, so thels
command actually gets as many parameters as there are files matched by the globbing. To suppress globbing, quote the parameter.This is true for any command:
echo *
vsecho '*'
.There is a script, call it
countparams.sh
to test the effect. It tells you how many parameters it got passed and lists them.Make it executable and run
./countparams.sh *
. Learn from its output!The glob will expand the same way both times, if the directory contents are the same at those two different times.
If you really want to check what will be removed, use
rm -i *.txt
. It will prompt you separately for each file before (trying to) remove it.This is guaranteed to be safe against race conditions:
ls *.txt
/ a new file is created /rm *.txt
because you're prompted for every file by the same program that's doing the removal.
This is too cumbersome for normal use, and if you alias
rm
torm -i
, you'll find yourself using\rm
orrm -f
fairly often. But it is worth at least mentioning that there is a solution to the race condition. (It's even portable to non-GNU systems: POSIXrm(1)
specifies the-i
option.)Another option would be a bash array:
to_remove=(*.txt)
, then ask the user to confirm (perhaps after doingls -ld -- "${to_remove[@]}"
), thenrm -- "${to_remove[@]}"
. So glob expansion is only done once, and the list is passed verbatim torm
.Another practically-usable option is GNU
rm -I
(man page), which prompts if removing more than 4 items. (But doesn't show you the list, just the total.) I usealias rm='rm -I'
on my desktop.It's a nice safeguard against fat-fingering return with a half-typed pattern that matches too much. But using
ls
first is generally good in a directory you own, or on a single-user system, and when there aren't background processes that could asynchronously create new files there. To guard against fat-fingering, don't typerm -rf /foo/bar/baz
from left to right.rm -rf /
is special-cased, butrm -rf /usr
isn't! Leave out the-rf
part, or start withls
, and only add therm -rf
part after typing the path.