我什么时候可以使用临时 IFS 进行字段拆分?

mur*_*uru 19 bash

在 bash 中,假设您有var=a.b.c.,然后:

$ IFS=. printf "%s\n" $var
a.b.c
Run Code Online (Sandbox Code Playgroud)

但是,这样的用法IFS在创建数组时确实生效:

$ IFS=. arr=($var)
$ printf "%s\n" "${arr[@]}"
a
b
c
Run Code Online (Sandbox Code Playgroud)

当然,这非常方便,但这是在哪里记录的?快速阅读Bash 文档中有关数组或分的部分并没有给出任何指示。对于搜索IFS通过单页文档不提供有关这种效应的任何暗示无论是。

我不确定何时可以可靠地执行以下操作:

IFS=x do something
Run Code Online (Sandbox Code Playgroud)

并预计这IFS会影响场分裂。

Gil*_*il' 29

基本思想是VAR=VALUE some-commandset VARtoVALUE执行some-commandwhensome-command是一个外部命令,没有比这更花哨的了。如果您将这种直觉与有关 shell 工作原理的一些知识结合起来,在大多数情况下您应该会得出正确的答案。POSIX 参考是“Shell 命令语言”一章中的“简单命令”

如果some-command外部命令VAR=VALUE some-command则相当于env VAR=VALUE some-command. VAR在 的环境中导出some-command,并且它在 shell 中的值(或缺少值)不会改变。

如果some-command函数,则VAR=VALUE some-command等价于VAR=VALUE; some-command,即函数返回后赋值保持不变,变量不导出到环境中。其原因与 Bourne shell 的设计有关(以及随后的向后兼容性):它无法在函数执行期间保存和恢复变量值。不导出变量是有意义的,因为函数在 shell 本身中执行。但是,ksh(包括 ATT ksh93 和 pdksh/mksh)、bash 和 zsh 实现了更有用的行为,其中VAR仅在函数执行期间设置(它也被导出)。在ksh 中,如果函数是用 ksh 语法定义的,则会完成此操作function NAME …, 不是如果它是用标准语法定义的NAME ()。在bash 中,这仅在 bash 模式下完成,而不是在 POSIX 模式下(使用 运行时POSIXLY_CORRECT=1)。在zsh 中,如果posix_builtins未设置该选项,则会执行此操作;默认情况下未设置此选项,而是通过emulate sh或启用emulate ksh

如果some-command是内置函数,则行为取决于内置函数的类型。特殊内置函数的行为类似于函数。特殊的内置函数必须在 shell 内部实现,因为它们会影响状态 shell(例如,break影响控制流、cd影响当前目录、set影响位置参数和选项……)。其他内置函数只是为了性能和方便而内置的(主要是——例如 bash 功能printf -v只能由内置函数实现),它们的行为就像一个外部命令。

赋值发生在别名扩展之后,所以如果some-command别名,首先扩展它以找出会发生什么。

请注意,在所有情况下,赋值都是在解析命令行之后执行的,包括命令行本身的任何变量替换。所以var=a; var=b echo $var打印a, 因为$var在分配发生之前被评估。因此IFS=. printf "%s\n" $var使用旧IFS值拆分$var.

我已经介绍了所有类型的命令,但还有一种情况:当没有要执行的命令时,即如果该命令仅包含赋值(可能还有重定向)。在这种情况下,分配仍然存在VAR=VALUE OTHERVAR=OTHERVALUE相当于VAR=VALUE; OTHERVAR=OTHERVALUE。所以之后IFS=. arr=($var)IFS仍然设置为.。由于您可以$IFS在赋值中使用toarr并期望它已经具有新值,因此将 的新值IFS用于 的扩展是有意义的$var

总之,您只能IFS用于临时字段拆分:

  • 通过启动一个新的外壳或子外壳(例如,这third=$(IFS=.; set -f; set -- $var; echo "$3")是一种复杂的做法,third=${var#*.*.}除了当 的值var包含少于两个.字符时它们的行为不同);
  • 在 ksh 中,IFS=. some-functionwheresome-function用 ksh 语法定义function some-function …
  • 在 bash 和 zsh 中,IFS=. some-function只要它们在本机模式而不是兼容模式下运行。


小智 6

@Gilles 的回答真的很棒,他(详细)解释了一个复杂的问题。

但是,我相信为什么这个命令的答案:

$ IFS=. printf "%s\n" $var
a.b.c
Run Code Online (Sandbox Code Playgroud)

它的工作原理很简单,即在执行之前解析整个命令行。并且每个“单词”都由外壳处理一次。
分配,象IFS=.,被延迟(步骤4是最后一个):

4.- 每个变量分配应扩展...

直到执行命令之前,首先处理参数中的所有扩展以构建此可执行行:

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c
Run Code Online (Sandbox Code Playgroud)

的值$var用“旧”IFS 扩展到a.b.c命令printf被赋予参数"%s\n"和之前a.b.c

评估

可以通过以下方式引入一级延迟eval

$ IFS=. eval printf "'%s\n'" \$var
a
b
c
Run Code Online (Sandbox Code Playgroud)

该行被解析(第一次)和'IFS =。' 设置为环境如下:

$ printf '%s\n' $var
Run Code Online (Sandbox Code Playgroud)

然后再次解析为:

$ printf '%s\n' a b c
Run Code Online (Sandbox Code Playgroud)

并执行到这个:

a
b
c
Run Code Online (Sandbox Code Playgroud)

的值$var(ABC)是分裂与IFS的使用中的值:.

环境

复杂和棘手的部分是什么在环境中有效 !!!

这在 Gilles 回答的第一部分中得到了很好的解释。

有一个额外的细节。

执行此命令时:

$ IFS=. arr=($var)
Run Code Online (Sandbox Code Playgroud)

IFS的值保留在目前的环境中,是的:

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 
Run Code Online (Sandbox Code Playgroud)

IFS 用于单个语句。

但可以避免:为单个语句设置 IFS

$ IFS=. command eval arr\=\(\$var\)

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 
Run Code Online (Sandbox Code Playgroud)