有没有一种简单的方法来为一个glob设置nullglob?

27

在Bash中,如果您执行以下操作:

mkdir /tmp/empty
array=(/tmp/empty/*)

你会发现array现在有一个元素,即"/tmp/empty/*",而不是你想要的零个。幸运的是,可以通过使用shopt -s nullglob来避免这种情况。

但是,nullglob是全局的,如果编辑现有的shell脚本,可能会破坏某些东西(例如,有人检查了ls foo*的退出代码以检查是否有以"foo"开头的文件吗?)。因此,理想情况下,我希望仅将其打开到小范围中——最好是一个文件名扩展。你可以使用shopt -u nullglob再次关闭它。但是,前提是它之前已被禁用:

old_nullglob=$(shopt -p | grep 'nullglob$')
shopt -s nullglob
array=(/tmp/empty/*)
eval "$old_nullglob"
unset -v old_nullglob

让我想到一定有更好的方法。显而易见的“将其放入子shell”并不起作用,因为当然变量赋值会随着子shell而消失。除了等待Austin组导入ksh93语法之外,还有其他方法吗?

5个回答

18

完成后取消它:

shopt -u nullglob

并且正确地(即存储先前的状态):

shopt -u | grep -q nullglob && changed=true && shopt -s nullglob
... do whatever you want ...
[ $changed ] && shopt -u nullglob; unset changed

1
那确实避免了eval,而我没有意识到-s和-u有这种行为(help shopt没有提到)。我会为此点赞。但说实话,就需要记住正确的内容而言,它并不是真正简单的——在第一行中有很多要记住的东西。 - derobert
1
我从未说过这更简单 :-) 但它确实完成了你的问题所要求的,使用nullglob而不影响从同一会话开始的其他脚本。记住nullglob的值增加了复杂性,但这是您可以_保留_环境原样的唯一方法。 - estani
好的,这个问题是为了寻找更好或更容易的方法来做到这一点(因为问题中的第二个代码示例也成功地将设置恢复到其先前的状态)。显然,我在问题中没有表达清楚 - 请建议如何改进它。 - derobert
我认为你无法改进它。被调用的代码量是最小的。确实,你提到了一种“更简单”的方法,但是你的示例并没有做到你想要的,即不会将对nullglob的更改弄乱其他代码。如果这不是你期望的答案,很抱歉,也许其他人可以更好地回答它。不要绝望;-) - estani

12

使用 Bash 4 中的 mapfile,您可以像这样从子shell中加载数组:mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done)。完整示例:

$ shopt nullglob
nullglob        off
$ find
.
./bar baz
./qux quux
$ mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done)
$ shopt nullglob
nullglob        off
$ echo ${#array[@]}
2
$ echo ${array[0]}
bar baz
$ echo ${array[1]}
qux quux
$ rm *
$ mapfile array < <(shopt -s nullglob; for f in ./*; do echo "$f"; done)
$ echo ${#array[@]}
0
  • 使用./*而不是裸的*进行全局匹配,当使用echo打印文件名时一定要这样做
  • 无法处理文件名中的换行符 :( 正如derobert所指出的那样

如果你需要处理文件名中的换行符,你必须使用更加冗长的方式:

array=()
while read -r -d $'\0'; do
    array+=("$REPLY")
done < <(shopt -s nullglob; for f in ./*; do printf "$f\0"; done)

但是到了这个时候,遵循其他答案中的建议可能会更简单。


1
这很不错。如果文件名中有换行符,它将失败,但尽管允许这样做,这种情况非常罕见。只要文件名不受攻击者的控制。 - derobert
是的,在文件名中有换行符存在。通常,这只意味着“找到罪犯并惩治他/她”。除了那一次我见过文件名中有换行符的时候,罪犯是一位迷人的老太太... - mivk
这个解决方案会在原本没有换行符的文件名末尾插入新的换行符。如果使用 mapfile -t,它会在文件名末尾存在换行符时将其剥离。 - wheeler

8
这只是比你最初的建议稍微好了一点点:

local nullglob=$(shopt -p nullglob) ; shopt -s nullglob

...随意操作...

$nullglob ; unset nullglob

2
这可能接近你想要的内容;目前需要执行一个命令来扩展 glob。
$ ls
file1 file2
$ array=( $(shopt -s nullglob; ls foo*) )
$ ls foo*
ls: foo*: No such file or directory
$ echo ${array[*]}
file1 file2

我们不是在子shell中设置array,而是使用$()创建一个子shell,其输出被array捕获。


6
不适用于带有空格的文件名(并不意外):touch "file1" "file 2"; array=( $(shopt -s nullglob; ls foo*) ); set | grep ^array 会输出 array=([0]="file1" [1]="file" [2]="2") - derobert

0

5
注意,这是zsh的一个特性,在bash中无法找到,而原作者进行了指定。 - ssmith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接