我试图弄清楚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分词的上下文的推荐的惯用方法是什么?
答案 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
:
IFS=, read -r a b c
将包含逗号的行拆分为多个(此处为3个)。对IFS
的更改是read
的本地更改;它读取的任何字符串都是完整读取的,并且只在read
内部内部分开。
foo=$(IFS=.; echo "${foo[*]}")
将数组的元素连接成一个以.
作为分隔符的单个字符串。请注意,这是对IFS
的全局更改,但仅限于在命令替换完成后消失的全局范围。
与您的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