bash shell脚本编程经典实例(第2版)
上QQ阅读APP看书,第一时间看更新

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 节