在for循环中使用任意glob模式(带空格)

时间:2014-10-24 18:34:38

标签: bash glob variable-expansion

有没有办法可靠地使用存储在变量中的任意globbing模式?如果模式包含空格和元字符,我会遇到困难。这就是我的意思。如果我将一个模式存储在一个没有空格的变量中,那么事情似乎就可以了:

<prompt> touch aa.{1,2,3} "a b".{1,2,3}
<prompt> p="aa.?"
<prompt> for f in ${p} ; do echo "|$f|" ; done
|aa.1|
|aa.2|
|aa.3|
<prompt> declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done
|aa.1|
|aa.2|
|aa.3|

然而,只要我在模式中抛出一个空格,事情就变得站不住脚了:

<prompt> p="a b.?"
<prompt> for f in ${p} ; do echo "|$f|" ; done
|a|
|b.?|
<prompt> declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done
|a|
|b.?|
<prompt> for f in "${p}" ; do echo "|$f|" ; done
|a b.?|
<prompt> for f in $(printf "%q" "$p") ; do echo "|$f|" ; done
|a\|
|b.\?|

显然,如果事先知道模式,我可以手动逃避它:

<prompt> for f in a\ b.* ; do echo "|$f|" ; done
|a b.1|
|a b.2|
|a b.3|

问题是,我正在编写一个我不提前知道模式的脚本。有没有办法可靠地使bash将变量的内容视为通配模式,而不采用某种eval技巧?

2 个答案:

答案 0 :(得分:6)

您需要关闭分词功能。回顾一下,这不起作用:

$ p="a b.?"
$ for f in ${p} ; do echo "|$f|" ; done
|a|
|b.?|

然而,这样做:

$ ( IFS=; for f in ${p} ; do echo "|$f|" ; done )
|a b.1|
|a b.2|
|a b.3|

IFS是shell的“内部字段分隔符”。它通常设置为空格,制表符和换行符。它用于变量扩展后的单词拆分。将IFS设置为空会停止分词,从而允许glob工作。

数组示例

同样适用于数组示例:

$ declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done
|a|
|b.?|
$ ( IFS=; declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done )
|a b.1|
|a b.2|
|a b.3|

确保IFS恢复正常值

在上面的示例中,我将IFS赋值放在子shell中。虽然没有必要,但优点是IFS在子shell终止后自动返回其先前值。如果子套不适合您的应用,这是另一种方法:

$ oldIFS=$IFS; IFS=; for f in ${p} ; do echo "|$f|" ; done; IFS=$oldIFS
|a b.1|
|a b.2|
|a b.3|

匹配模式与shell-active字符

假设我们的名称中包含文字*的文件:

$ touch ab.{1,2,3} 'a*b'.{1,2,3}
$ ls
a*b.1  ab.1  a*b.2  ab.2  a*b.3  ab.3

并且,假设我们想要匹配那个明星。既然我们希望明星得到真正的对待,我们必须逃避它:

$ p='a\*b.?'
$ ( IFS=; for f in ${p} ; do echo "|$f|" ; done )
|a*b.1|
|a*b.2|
|a*b.3|

由于?未转义,因此将其视为通配符。由于*已转义,因此它仅匹配文字*

答案 1 :(得分:0)

中使用的模式
p="a b.?"

不正确,如果您直接使用它就很清楚了:

A=( a b.? )

正如问题中所述,正确的模式是

a\ b.?

所以正确的变量赋值是

p='a\ b.?'

模式来自哪里?他们可以在源头纠正吗?对于 例如,如果通过添加'。?'来创建模式。到基地,你可以 使用'printf'进行所需的引用:

base='a b'
printf -v p '%q.?' "$base"

'set'然后显示:

p='a\ b.?'

不幸的是,如果你尝试

,单词拆分仍会导致失败
A=( $p )

'set'显示:

A=([0]="a\\" [1]="b.?")

解决此问题的一种方法是使用'eval':

eval "A=( $p )"

'set'然后显示:

A=([0]="a b.1" [1]="a b.2" [2]="a b.3")

这是“eval trickery”,但它并不比IFS技巧更糟糕 先前描述过。此外,如果文件是IFS技巧将无济于事 匹配在他们的名字中有globbing元字符。你做了什么,为 例如,如果您已经使用

创建了文件
touch ab.{1,2,3} 'a*b'.{1,2,3}

你想匹配基数是'a * b'的那些?没有IFS的数量 如果有的话,欺骗将使得与变量p正确匹配成为可能 设置为

p="a*b.?"

base='a*b'
printf -v p '%q.?' "$base"

'set'显示:

p='a\*b.?'

之后

eval "A=( $p )"

它显示:

A=([0]="a*b.1" [1]="a*b.2" [2]="a*b.3")

我认为使用'eval'是最后的手段,但在这种情况下我无法思考 更好的选择,这是非常安全的。