6

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

<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诡计?

4

2 回答 2

7

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

$ 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是外壳的“内部字段分隔符”。它通常设置为空格、制表符和换行符。它用于变量扩展后的分词。设置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分配放在子外壳中。尽管不是必需的,但这样做的好处是,IFS一旦子外壳终止,它就会自动返回到其先前的值。如果子外壳不适合您的应用程序,这里是另一种方法:

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

使用 shell 活动字符匹配模式

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

$ 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|

因为?未转义,所以将其视为通配符。因为*被转义了,所以它只匹配一个文字*

于 2014-10-24T18:46:56.140 回答
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 )

“设置”显示:

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

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

eval "A=( $p )"

'set' 然后显示:

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

那是“eval 诡计”,但它显然并不比前面描述的 IFS 诡计更糟。此外,如果要匹配的文件的名称中包含通配元字符,则 IFS 诡计将无济于事。例如,如果您已经创建了文件,您会怎么做

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

并且您想匹配以 'a*b' 为基数的那些?如果设置为

p="a*b.?"

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

“设置”显示:

p='a\*b.?'

之后

eval "A=( $p )"

表明:

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

我认为使用“eval”是最后的手段,但在这种情况下,我想不出更好的选择,而且它非常安全。

于 2014-10-24T23:26:46.013 回答