如何在bash

时间:2017-01-23 18:40:32

标签: bash

我试图弄清楚IFS如何影响bash中的单词分裂。行为依赖于上下文,其方式似乎与单词分裂的直觉相匹配。

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

  

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

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

echo_n () {
  echo Num args: $#, Args: "$@"
}
( 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

到目前为止,事情似乎相当扭曲,但我可以绕过它们。当我们开始为图片添加for循环时,事情变得更加激烈。

(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)

所以这就是问题:控制执行IFS分词的上下文的推荐的惯用方法是什么?

1 个答案:

答案 0 :(得分: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的全局更改,但仅限于在命令替换完成后消失的全局范围。

  3. 与您的for循环示例相关,只要您想要迭代硬编码列表以外的其他内容(包括数组的扩展),您可能希望使用while循环使用read代替for循环,按Bash FAQ 001

    在此处使用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