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

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

在 bash 中,假设你有var=a.b.c.,那么:

$ IFS=. printf "%s\n" $var
a.b.c

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

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

当然,这非常方便,但是这个记录在哪里?快速阅读以下章节数组或者分词Bash 文档中没有给出任何指示。寻找IFS通过单页文档也没有提供有关此效果的任何提示。

我不确定什么时候可以可靠地做到:

IFS=x do something

并预计这IFS会影响字段分割。

答案1

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

如果some-command是一个外部命令VAR=VALUE some-command相当于env VAR=VALUE some-commandVAR在 的环境中导出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 语法定义的function NAME …,则执行此操作,而不是使用标准语法定义的函数NAME ()。在巴什,这仅在 bash 模式下完成,而不是在 POSIX 模式下完成(当使用 运行时POSIXLY_CORRECT=1)。在桀骜posix_builtins,如果未设置该选项则执行此操作;默认情况下未设置此选项,但可以通过emulate sh或打开emulate ksh

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

赋值发生在别名扩展之后,因此 ifsome-command别名,先展开它看看会发生什么。

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

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

总之,您可以IFS使用暂时的仅限字段拆分:

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

答案2

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

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

$ IFS=. printf "%s\n" $var
a.b.c

它的工作原理很简单,整个命令行都是之前解析过它被执行了。每个“单词”都会被 shell 处理一次。
作业,例如IFS=.,被延迟(第四步是最后一步):

4.- 每个变量赋值应扩展...

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

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c

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

评估

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

$ IFS=. eval printf "'%s\n'" \$var
a
b
c

该行被解析(第一次)并且“IFS=”。设置为环境如下:

$ printf '%s\n' $var

然后再次解析为:

$ printf '%s\n' a b c

并执行到此:

a
b
c

(abc)的值$var与使用中的 IFS 的值分开:.

环境

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

吉尔斯回答的第一部分对此进行了很好的解释。

还有一个额外的细节。

当执行该命令时:

$ IFS=. arr=($var)

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

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 

IFS 用于单个语句。

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

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

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 

答案3

您的问题关于

var=a.b.c
IFS=. printf "%s\n" $var

是一个极端情况。

这是因为macro expansion命令中发生了shell 变量IFS=.已设置。

换句话说:当$var展开时,先前的IFS值处于活动状态,然后IFS设置为'.'

相关内容