【问题标题】:Bash arbitrary glob pattern (with spaces) in for loop在 for 循环中重击任意 glob 模式(带空格)
【发布时间】:2014-10-24 18:34:38
【问题描述】:

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

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

【问题讨论】:

  • 我不相信你可以在不分词的情况下获得通配符。
  • 根本问题是分词发生在路径名扩展(也称为通配符)之前,并且无法更改扩展顺序(这让希望{1..$n} 工作的人非常懊恼) .就像 John1024 的回答一样,您可以禁用分词。
  • 如果您事先不知道模式,那么在使用 eval 时要非常小心。你永远不知道Little Bobby Tables 什么时候会出现。 :)

标签: bash glob variable-expansion


【解决方案1】:

您需要关闭分词。回顾一下,这是行不通的:

$ 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 赋值放在了一个子shell 中。尽管不是必需的,但这样做的好处是IFS 在子shell 终止后立即自动返回到其先前的值。如果子shell 不适合您的应用程序,这里有另一种方法:

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

因为? 没有转义,所以它被视为通配符。因为* 被转义了,所以它只匹配文字*

【讨论】:

  • 呸,我忘了尝试 IFS。 (我一直在其他脚本中使用它,只是没想到将它应用于这个问题)。谢谢@John1024。我遇到的一个问题是 IFS 在 for 循环期间被设置为愚蠢的东西。但我想我可以将 glob 扩展为一个数组,然后循环遍历该数组。我会尝试一些东西。
【解决方案2】:

中使用的模式

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 诡计”,但它显然并不比 IFS 诡计更糟糕 前面描述过。此外,如果文件是 IFS 欺骗将无济于事 匹配的名称中有通配符。你做什么,为了 例如,如果你有用

创建的文件
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”是最后的手段,但在这种情况下我想不出来 一个更好的选择,而且非常安全。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-23
    • 2014-09-24
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-12
    相关资源
    最近更新 更多