![什么时候可以使用临时 IFS 进行字段拆分?](https://linux22.com/image/83981/%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E4%B8%B4%E6%97%B6%20IFS%20%E8%BF%9B%E8%A1%8C%E5%AD%97%E6%AE%B5%E6%8B%86%E5%88%86%EF%BC%9F.png)
答案1
基本思想是VAR=VALUE some-command
设置VAR
为VALUE
用于执行some-command
whensome-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 语法定义的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 中,with
IFS=. some-function
是some-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.c
printf
"%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
设置为'.'
。