1.7 显示当前目录下的所有隐藏(点号)文件
1.7.1 问题
你只想查看目录下的隐藏(点号)文件,然后编辑其中一个已经忘记名字的文件,或者删除一些废弃文件。ls -a
可以显示出包括隐藏文件在内的所有文件,但这种输出往往包含了过多干扰项,而 ls -a .*
的结果也比你想象或需要的更多。
1.7.2 解决方案
使用 ls -d
配合筛选条件。例如:
ls -d .*
ls -d .b*
ls -d .[!.]*
ls -d .*/
因为每个正常的目录中都包含 .
和 ..
,所以就没必要再显示了。你可以用 ls -A
列出目录中除这两个文件外的其余所有文件。对于可以利用通配符(也就是模式)列出文件的命令,下面给出了能够将 .
和 ..
排除在外的通配符写法:
$ grep -l 'PATH' ~/.[!.]*
/home/jp/.bash_history
/home/jp/.bash_profile
$
1.7.3 讨论
鉴于 shell 处理文件通配符的方式,.*
的行为并不符合你的预期。文件名扩展或通配符匹配(globbing)1 的工作方式如下:任何包含字符 *
、?
或 [
的字符串均被视为模式,会被依字母排序的、匹配该模式的文件名列表所替换。*
可以匹配包括空串在内的任意字符串,?
可以匹配任意单个字符。[]
内出现的字符形成了一个字符列表或范围,可以匹配其中的任意字符。还有其他各种扩展模式匹配操作符,我们就不在此展开细说了(参见 A.15 节和 A.16 节)。因此,*.txt
表示以 .txt 结尾的所有文件,*txt
表示以 txt 结尾的所有文件(模式中没有点号)。f?o
可以匹配 foo 或 fao,但无法匹配 fooo。有鉴于此,你可能认为 .*
会匹配以点号开头的所有文件。
1这里特别说明一下 globbing 和 wildcard 的区别:globbing 是对 wildcard 进行扩展的过程。在贝尔实验室诞生的 Unix 中,有一个名为 glob(global 的简写)的独立程序(/etc/glob)。早期 Unix 版本(第 1~6 版,1969—1975 年)的命令解释器(也就是 shell)都要依赖于该程序扩展命令中未被引用的 wildcard,然后将扩展后的结果提供给命令执行。因此,本书正文将 globbing 译为“通配符匹配”,将 wildcard 译为“通配符”。另外,正文中的“扩展模式匹配”是指,如果使用内建命令 shopt
启用了 shell 选项 extglob
,那么 shell 就能够识别一些扩展模式匹配操作符,如 ?(pattern-list)
、*(pattern-list)
、+(pattern-list)
等。——译者注
问题在于 .*
还可以匹配目录 .
和 ..
(存在于每个目录中),它们会和其他以点号开头的文件名一同显示出来。如果 ls
的参数是目录名,那么除了目录名之外,它还会列出该目录中的内容。因此,当你使用 ls .*
时,得到的可不仅仅是当前目录下的点号文件,还包括当前目录(.
)下的所有文件和子目录、父目录(..
)下的所有文件和子目录、当前目录下以点号开头的子目录名及其内容。这种结果可以说非常杂乱,而且通常也用不着这么多输出。
你可以用同样的 ls
命令实验一下,看看加上 -d
选项和没有 -d
选项的区别,然后试试 echo .*
。echo
命令只会简单地显示出扩展 .*
后的结果,这些结果就是 ls
命令的参数。
接着还可以试试 echo .[!.]*
。.[!.]*
是文件名扩展模式,其中 []
指定了要匹配的字符列表,但是开头的 !
对该列表做了求反操作。因此我们要找的是一个点号,然后是除点号外的任意字符,接着是任意数量的任意字符。^
也可以实现相同的求反效果,但 !
是 POSIX 标准中规定的,可移植性更好。
ls
命令中的一种特殊情况也能帮上忙。如果指定了 -d
选项,同时文件名模式以斜线结尾,那么 ls
命令只显示匹配模式的目录,匹配的文件名一概不显示。例如:
$ ls -d .v*
.vim .viminfo .vimrc
$ ls -d .v*/
.vim
$
第一条命令显示出以 .v 开头的 3 个文件名,如果其中有目录,也不会列出其内容(因为使用了 -d
选项)。第二条命令在模式结尾加上了斜线(.v*/
),因此 ls
命令只显示匹配该模式的目录,在这种情况下,符合条件的只有目录 .vim。
如果你在命令
ls -d .v*/
的输出中看到了两条斜线:$ ls -d .v*/ .vim// $
这可能是因为你执行的是加入了
-F
选项的ls
别名。在命令名前面使用反斜线就可以避开所有的别名:$ \ls -d .v*/ .vim/ $
有些文件名很难匹配。.[!.]*
会漏掉名为 ..foo 的文件。你可以加上形如 .??*
的模式来匹配以点号开头且长度至少为 3 个字符的文件名,但是 ls -d .[!.]* .??*
会将同时匹配这两种模式的文件名显示两遍。但如果只使用 .??*
,又会漏掉像 .a 这样的文件:
$ touch ..foo .a .normal_dot_file normal_file
$ ls -a
. .. ..foo .a .normal_dot_file normal_file
$ ls -d .??*
..foo .normal_dot_file
$ ls -d .[!.]*
.a .normal_dot_file
$ ls -d .[!.]* .??* | sort -u
..foo
.a
.normal_dot_file
具体用哪一种取决于你的需求和环境,没有什么通用的解决方案。
如果
ls
命令损坏或不知何故无法使用,那么可以用echo *
作为一种应急替代。这种方法可行的原因是,shell 会将*
扩展为当前目录下的所有文件,其结果和ls
差不多。
1.7.4 参考
man ls
- `GNU Core Utilities FAQ 中的问题 18
- Unix FAQs 中的 2.11 节
- A.14 节
- A.15 节