【发布时间】:2019-08-18 09:51:50
【问题描述】:
问题详情
假设我们有一个目录,其中包含三个文件:file_1、file_2,以及非常不方便命名的file 3。如果我对filename expansion 的理解是正确的,bash 解释字符串的方式
echo *
是它看到(未引用的)*,并修改了字符串,使其现在读取
echo file_1 file_2 file 3
然后,由于不再需要执行扩展,bash 尝试评估字符串。在这种情况下,它运行命令echo,向它传递四个 参数:file、3、file_1 和file_2。在任何情况下,输出都是相同的:
$ echo *
> file 3 file_1 file_2
$ echo file 3 file_1 file_2
> file 3 file_1 file_2
但是,在其他情况下,情况似乎并非如此。比如
$ arr1=( * )
$ arr2=( file 3 file_1 file_2 )
$ echo ${#arr1}
> 3
$ echo ${#arr2}
> 4
然而,如果 shell 扩展按照in the bash documentation 描述的方式工作,那么它们应该是相同的。
类似的事情发生在for 循环中:
$ for f in *; do echo $f; done
> file 3
> file_1
> file_2
$ for f in file 3 file_1 file_2; do echo $f; done
> file
> 3
> file_1
> file_2
我错过了什么?在这些情况下不会发生通配吗?
用例
根据MIT's Hacker Tools 的建议,我正在整理一个 GitHub 存储库来集中我的点文件。我写的脚本有两种用法:
./install.sh DOTFILE [DOTFILE [DOTFILE ...]]
./install.sh -a
在第一种情况下,src/config 中的每个命名点文件都符号链接到我的主目录中的相应点文件;第二,-a 标志提示脚本运行,就好像我输入了每个点文件作为参数一样。
我想出的解决方案是使用两个数组之一在for 循环中运行ln -sih:$@ 和*。1 所以,只需分配FILES=( $@ )或FILES=( * ),然后运行for f in $FILES--除了,在我看来,* 应该在此分配中中断,如果其中有一个带有空格的文件名。显然bash 比我聪明,因为它没有,但我不明白为什么。
1:显然,您不希望脚本本身在循环中运行,但这很容易用
if [[ "$f" != "$0" ]] 子句排除。
【问题讨论】:
-
你的理解不正确:glob扩展根本不会产生string;它会产生一个单词列表。如果扩展步骤一直回到字符串表示,那么在 bash 中安全处理不受信任的数据几乎是不可能的,所以很幸运它没有。
-
还要注意,扩展以非常特定的瀑布顺序发生;这不是“是否保留任何扩展”的问题——如果您有一个使用
touch '$(rm -rf ~)'创建的文件,您不希望echo *运行该$(...)命令。 -
未来提示:不要使用
*扩展。使用find。并使用以 null 结尾的字符串和 bash 数组。IFS= readarray -d $'\0' arr < <(find . -mindepth 1 -maxdepth 1 -print0) -
@jgaeb 它可以防止文件名中包含换行符等字符。在 UNIX/Linux 中,文件名可以包含除空字符以外的任何字符。为了清楚起见,Kamil 的示例不是创建“空终止”字符串,而是从
find创建一个空分隔 输出流。我想您可能会称其为一个和六个中的六个,但关键是 null 是文件名之间的分隔符,而不仅仅是字符串结束指示符。两个空字符之间的所有内容(包括\n等)都是一个文件名。 -
请注意,
FILES=( $@ )与FILES=( $* )完全相同,两者都有问题。如果你想将你的参数列表扩展为一个数组,在每个元素上,它需要是"$@",并带有精确的引用。 (此外,全大写名称位于命名空间中,用于对 shell 和 OS 有意义的名称;最好为您自己的 shell 和环境变量名称使用小写字母 - 请参阅 POSIX 规范 @pubs.opengroup.org/onlinepubs/9699919799/basedefs/…,记住设置 shell变量将覆盖任何类似命名的环境变量)。