有趣的rm -rf

1
2
3
4
5
6
7
8
9
10
11
12
13
root@enabling-clam:~/testdir# echo "foolish rm" > "-rf" && ls -la
total 8
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 32 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..
drwxr-xr-x 1 root root 14 Jul 8 06:36 mydir1
-rw-r--r-- 1 root root 6 Jul 8 06:36 myfile1

root@enabling-clam:~/testdir# rm * && ls -la
total 4
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 6 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..

一段有趣的Bash命令记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
root@enabling-clam:~# mkdir testdir && cd testdir && ls -la
total 0
drwxr-xr-x 1 root root 0 Jul 8 06:35 .
drwx------ 1 root root 52 Jul 8 06:35 ..

root@enabling-clam:~/testdir# mkdir mydir1 && echo "Test1" > myfile1 && echo "Test2" > mydir1/myfile2 && ls -la
total 4
drwxr-xr-x 1 root root 26 Jul 8 06:36 .
drwx------ 1 root root 52 Jul 8 06:35 ..
drwxr-xr-x 1 root root 14 Jul 8 06:36 mydir1
-rw-r--r-- 1 root root 6 Jul 8 06:36 myfile1

root@enabling-clam:~/testdir# echo "foolish rm" > "-rf" && ls -la
total 8
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 32 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..
drwxr-xr-x 1 root root 14 Jul 8 06:36 mydir1
-rw-r--r-- 1 root root 6 Jul 8 06:36 myfile1

root@enabling-clam:~/testdir# rm * && ls -la
total 4
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 6 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..

root@enabling-clam:~/testdir# rm -rf && ls -la
total 4
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 6 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..

root@enabling-clam:~/testdir# rm "-rf" && ls -la
total 4
-rw-r--r-- 1 root root 11 Jul 8 06:37 -rf
drwxr-xr-x 1 root root 6 Jul 8 06:37 .
drwx------ 1 root root 52 Jul 8 06:35 ..

root@enabling-clam:~/testdir# rm "./-rf" && ls -la
total 0
drwxr-xr-x 1 root root 0 Jul 8 06:38 .
drwx------ 1 root root 52 Jul 8 06:35 ..
root@enabling-clam:~/testdir#

为什么?

这两个问题本质上都与 Bash 的 Shell展开 有关。

文件名展开

先看 rm * 一句命令,为什么这一行命令看起来像被解析成了 rm -rf mydir1 myfile1

原因很简单:文件名通配符 * 遵照 Bash 的文件名展开机制,在真正执行 rm 命令前就被处理完毕。

After word splitting, ... Bash scans each word for the characters *, ?, and [ ... then the word is regarded as a pattern, and replaced with a sorted list of filenames matching the pattern

也就是说,rm 命令接收到的,是经过 Bash 展开后的文件名列表,在上面的例子中就等价于:

1
rm -rf mydir1 myfile1

双引号处理

我们还注意到,rm "-rf" 一句命令并没能成功移除 -rf 文件。明明我们已经试着用引号告诉 rm 这是一个整体,为什么还会失败呢?

在 Bash 中,经过参数展开、文件名展开等处理的命令在传递给程序前,还会最终经过 引号移除

引号移除发生在所有展开操作的最后,所有未经引号或转移保护的斜杠、单双引号都会被移除。

这也就是说,对于上面的例子,双引号对 -rf 的保护仍然局限在 Shell 处理阶段,保护其中字符的字面值,阻止空格分词。

然而,稍后 rm 命令收到的仍然是 -rf 字符串,并在稍后将其解析为 -r-f 两个 option,最后还是没能将其作为要删除的文件名称进行处理。

解决方案与启示

想要正确删除 -rf 文件,有以下两种方法:

1
2
3
4
5
# Same as Example
rm ./-rf

# Another Example
rm -- -rf

上面两个例子都不需要给 -rf 加引号。

  • 对于第一种写法,其提供给 rm 的参数首字符为点号 . ,以避免被 rm 当作短横线参数 - 处理。
  • 第二种写法中,-- 通常约定表示“选项到此结束”,位于 -- 后面的参数,即使以 - 开头,也应该被视为操作数而非选项。

Shell 层安全 != 命令语义安全