如何在bash中控制IFS分词

Ale*_*tta 8 bash

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

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

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

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

echo_n () {
  echo Num args: $#, Args: "$@"
}
( IFS=','
  args=foo,bar,baz
  echo_n $args
)
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这会产生echo_n的三个不同参数

Num args: 3, Args: foo bar baz
Run Code Online (Sandbox Code Playgroud)

直接使用逗号分隔列表调用echo_n失败,因为未触发扩展.

IFS=, echo_n foo,bar,baz
Run Code Online (Sandbox Code Playgroud)

结果是

Num args: 1, Args: foo,bar,baz
Run Code Online (Sandbox Code Playgroud)

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

(IFS=,; for i in foo,bar,baz ; do echo_n $i; done)
Run Code Online (Sandbox Code Playgroud)

结果是

Num args: 3, Args: foo bar baz
Run Code Online (Sandbox Code Playgroud)

这违背了for循环的目的.

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

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

结果是

Num args: 1, Args: foo
Num args: 1, Args: bar
Num args: 1, Args: baz
Run Code Online (Sandbox Code Playgroud)

(诀窍在于使用默认值评估未定义变量NO_VAR.)

另一个类似的技巧,依赖于命令替换:

(IFS=,; for i in $(echo foo,bar,baz) ; do echo_n $i; done)
Run Code Online (Sandbox Code Playgroud)

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

che*_*ner 5

认识到这一点很重要,为什么下面的失败:

$ IFS=, echo_n foo,bar,baz
Num args: 1, Args: foo,bar,baz
Run Code Online (Sandbox Code Playgroud)

预命令分配IFS仅适用于内部 echo_n ; foo,bar,baz不会拆分,,因为在此命令行(或缺少)上的任何分词都会 echo_n运行之前发生.

(IFS=,; for i in foo,bar,baz ; do echo_n $i; done)
Run Code Online (Sandbox Code Playgroud)

导致单次迭代,因为IFS它仅用于分割扩展的结果(以及通过read,见下文),而不是文字字符串.shell在第一次解析命令行时完成的分词实际上是硬编码的,只能在空格上分割.


目前还不完全清楚你想要完成什么,但一个好的经验法则是,如果你设定IFS全局的价值,你做错了(或者至少是次优的).只有两种情况我可以回想起有用的修改IFS:

  1. IFS=, read -r a b c将包含逗号的行拆分为多个(此处为3个).改变IFS是本地的read; 无论串读取读取保存完好,只有拆分内部通过read.

  2. foo=$(IFS=.; echo "${foo[*]}")将数组的元素连接到单个字符串中,并使用a .作为分隔符.请注意,这是全局更改IFS,但仅限于在命令替换完成后消失的全局范围.

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

把你的for循环放在这里,例如:

(IFS=,; for i in $(echo foo,bar,baz) ; do echo_n $i; done)
Run Code Online (Sandbox Code Playgroud)

我会先将它拆分成一个数组,然后迭代for:

data="foo,bar,baz"
IFS=, read -r -a items <<< "$data"
for i in "${data[@]}"; do
    echo_n "$i"
done
Run Code Online (Sandbox Code Playgroud)