bash - 如何在 bash 中控制 IFS 分词

标签 bash

我正在尝试弄清楚 IFS 如何影响 bash 中的分词。该行为依赖于上下文,其方式似乎与分词的直觉不符。

总体思路似乎很简单。引自 bash 手册页:

The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words on these characters. ... Note that if no expansion occurs, no splitting is performed.

这可以很容易地验证,例如通过将 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

(技巧在于使用默认值评估 undefined variable NO_VAR。)

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

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

那么问题来了:推荐的惯用方法是什么来控制执行 IFS 分词的上下文?

最佳答案

了解为什么以下失败很重要:

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

IFS 的前置命令赋值仅适用于inside echo_nfoo,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 而不是 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

关于bash - 如何在 bash 中控制 IFS 分词,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41813369/

相关文章:

linux - 对 bash 脚本进行故障排除以将每个单词的首字母大写

linux - 模式解码

windows - 环境 : bash\r: No such file or directory

linux - 使用python脚本查找和删除文件

regex - 如何编写搜索模式的 shell 脚本

linux - 使用变量目录查找(但没有 -name)抛出查找 : paths must precede expression: find

php - 嵌套GNU Parallel处理多个大文件,拆分每个文件数据作为队列处理

git - 如何在 commit-msg 钩子(Hook)中捕获 git commit 消息?

linux - linux终端中的随机文本

regex - "sed"命令删除与第一个单词上的精确字符串匹配的行