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


19

用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会影响字段拆分。

Answers:


28

其基本思路是,VAR=VALUE some-commandVARVALUE了执行some-command的时候some-command是一个外部命令,它没有得到比这更花哨。如果您将这种直觉与壳的工作原理相结合,则在大多数情况下应该给出正确的答案。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中,如果使用ksh语法定义了函数,则可以完成此操作function NAME …,而不是使用标准语法定义的NAME ()。在bash中,只能在bash模式下完成此操作,而在POSIX模式下则不能(使用时运行POSIXLY_CORRECT=1)。在zsh中,如果posix_builtins未设置该选项,则完成此操作;此选项不是默认设置,而是由emulate sh或启用emulate ksh

如果some-command是内置的,则行为取决于内置的类型。特殊的内置函数的行为类似于函数。特殊的内置组件是必须在外壳程序内部实现的,因为它们会影响状态外壳程序(例如,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在分配中arr使用并期望它已经具有新值,因此将的新值IFS用于的扩展是有意义的$var

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

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

IFS设置为.“ Eek。在阅读了第一部分之后,这是有道理的,但是在我发布此问题之前,我不会期望如此。
muru

1
这是对另一个问题的答案
schily

几年前此答案中有一些其他解释。
Ti Strga

6

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

但是,我相信为什么使用此命令的答案是:

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

这样做是一个简单的想法,即在执行整个命令行之前先对其进行分析。并且每个“单词”都由外壳处理一次。像这样
分配IFS=.被延迟了(步骤4是最后一个):

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

的值$var(ABC)是分裂与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>  < 
> 

2

您有关的问题

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

是一个极端的情况。

这是因为macro expansionin命令设置shell变量之前发生IFS=.

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.