2017-01-23 102 views
8

我想知道IFS如何影响bash中的分词。行为是依赖于上下文的,这种方式看起来不符合分词的直觉。如何在bash中控制IFS分词

一般想法似乎很简单。从bash手册页引用:

shell将IFS的每个字符视为分隔符,并将其他扩展的结果拆分为这些字符上的单词。 ...请注意,如果不发生扩展,则不会执行分割。

这可以通过将IFS变量设置为','并使用逗号分隔参数列表调用shell函数来轻松验证。

echo_n() { 
    echo Num args: $#, Args: "[email protected]" 
} 
(IFS=',' 
    args=foo,bar,baz 
    echo_n $args 
) 

正如预期的那样,这导致在三个不同的参数echo_n

Num args: 3, Args: foo bar baz 

调用echo_n直接用逗号分隔的列表,因为没有扩展触发失败。

IFS=, echo_n foo,bar,baz 

结果

Num args: 1, Args: foo,bar,baz 

到这里事情似乎相当扭曲的,但我可以换我的头周围。当我们开始为图片添加循环时,事情会变得更加复杂。

(IFS=,; for i in foo,bar,baz ; do echo_n $i; done) 

导致

Num args: 3, Args: foo bar baz 

这违背的目的for循环。

现在,我可以通过强制某种形式的扩展触发的任何几个bash技巧强制IFS词分裂。例如:

(IFS=,; for i in ${NO_VAR:-foo,bar,baz} ; do echo_n $i; done) 

导致

Num args: 1, Args: foo 
Num args: 1, Args: bar 
Num args: 1, Args: baz 

(诀窍在于具有缺省值评估未定义的变量NO_VAR。)

另一个类似的特技,依靠命令替换:

(IFS=,; for i in $(echo foo,bar,baz) ; do echo_n $i; done) 

所以,这里的问题是:什么是推荐的,惯用的方式t o是否控制执行IFS分词的上下文?

回答

5

它实现为什么下失败是很重要的:

$ IFS=, echo_n foo,bar,baz 
Num args: 1, Args: foo,bar,baz 

命令预分配IFS只适用echo_n; foo,bar,baz不会在,上分割,因为在该命令行上发生任何分词(或缺少该分词)之前echo_n运行。

(IFS=,; for i in foo,bar,baz ; do echo_n $i; done) 

导致单次迭代,因为IFS仅用于拆分膨胀的结果(和由read,见下文),而不是文字串。 shell在第一次解析命令行时完成的单词拆分实际上是硬编码的,只能在空白处分割。


这不是完全清楚你要完成什么,但一个好的经验法则是,如果你在全球范围内设置的IFS值,你做错了什么(或者至少次优)。只有两种情况下我记得有用地改性IFS

  1. IFS=, read -r a b c分裂包含逗号成多个(在此为3)片的线。 IFS的更改在read的本地;它读取的任何字符串都是完整读取的,并且仅在内部分割read分割。

  2. foo=$(IFS=.; echo "${foo[*]}")将数组元素加入单个字符串并以.作为分隔符。请注意,这是对IFS的全局更改,但只能在全局范围内在命令替换完成后消失。

与您for循环的例子,任何时候你想要遍历,你可能想使用一个while循环与read,而不是比一个硬编码的列表以外的东西(包括阵列的扩展)根据Bash FAQ 001,循环为for

把你for循环这里,例如:

(IFS=,; for i in $(echo foo,bar,baz) ; do echo_n $i; done) 

我反而把它分割成一个数组,然后再与for迭代:

data="foo,bar,baz" 
IFS=, read -r -a items <<< "$data" 
for i in "${data[@]}"; do 
    echo_n "$i" 
done