Reputation: 304
A few days ago, my lab's server suffered a serious meltdown when one of our interns accidentally copy-pasted this code on bash
trying to delete node.js.
$ rm -rfv /usr/{bin/node,lib / node,share / man /* / node.*};
They tried a brace expansion to list up directories to delete, but notice spaces between the directory separators (/
). This ended up deleting everything on our server because they applied sudo
.
I tried this command on my virtual machine and confirmed that it was pretty much equivalent to rm -rf /
.
I'm confused about the way bash
interpreted the statement. When I try to create a simple, nondestructive command that does similar things (spaces working as separators for expansion,) I don't seem to be able to pull that off. I tried the first command but with for
loop in bash:
$ for f in /usr/{bin/node,lib / node,share / man /* / node.*}; do echo $f; done
which listed some contents in node.js and the directories in /
. This should confirm that this is not a special feature in rm
.
But when I try something like this:
$ for f in {a,b c,d e}; echo $f
It results in a syntax error near echo
where I expected a to e, each letter in a single line.
I did some research, but I couldn't find anything that explains this behavior.
Can someone please tell me, in the first command, how did bash
interpret this command?
p.s. I found out that in zsh
the 'for
loop test' version doesn't work. Never tried the real 'rm
test' though. I'm scared.
Upvotes: 3
Views: 254
Reputation: 440536
You must \
-escape all spaces that are part of your brace expansion:
$ printf '%s\n' {a,b\ c}
a
b c
Brace expansions only work:
when they're neither neither single- nor double-quoted (you got that part right)
and when they're recognized as a single word (token) by the shell (that's where your attempt fell short) - hence the need for character-individual quoting of spaces with \
.
Without this, bash
breaks what you meant to be a brace expansion into multiple arguments, and brace expansion never happens - see below.
As for how bash
parses /usr/{bin/node,lib / node,share / man /* / node.*}
:
The following tells you the resulting arguments (with actual globbing omitted to better demonstrate what happens):
$1=/usr/{bin/node,lib
$2=/
$3=node,share
$4=/
$5=man
$6=/*
$7=/
$8=node.*}
As you can see:
The unescaped spaces caused the word-splitting to occur by spaces, breaking what you meant to be brace expressions into multiple arguments.
One of the resulting arguments /*
, unfortunately, matches all (non-hidden) items in the root directory, and therefore wreaks havoc when passed to sudo rm
.
Upvotes: 5