我试图弄清楚IFS如何影响bash中的单词拆分。该行为是取决于上下文的,其方式似乎与分词的直觉不匹配。

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



例如,可以通过将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的命令前分配仅适用于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一起使用,而不是按照Bash FAQ 001进行for循环。

    以此处的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/

    10-13 05:50