如何在Bash中控制IFS单词分割

8
我正在尝试弄清楚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个回答

5

重要的是要意识到以下内容为什么会失败:why

$ 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循环示例相关的是,任何时候,如果您想要迭代除硬编码列表(包括数组扩展)以外的其他内容,则可能需要使用带有readwhile循环而不是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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接