对于临时的每命令变量赋值,Bash似乎表现得不可预测,特别是与IFS
。
我经常将IFS
与read
命令一起分配给临时值。我想使用相同的机制来定制输出,但目前使用函数或子shell来包含变量赋值。
$ while IFS=, read -a A; do
> echo "${A[@]:1:2}" # control (undesirable)
> done <<< alpha,bravo,charlie
bravo charlie
$ while IFS=, read -a A; do
> IFS=, echo "${A[*]:1:2}" # desired solution (failure)
> done <<< alpha,bravo,charlie
bravo charlie
$ perlJoin(){ local IFS="$1"; shift; echo "$*"; }
$ while IFS=, read -a A; do
> perlJoin , "${A[@]:1:2}" # function with local variable (success)
> done <<< alpha,bravo,charlie
bravo,charlie
$ while IFS=, read -a A; do
> (IFS=,; echo "${A[*]:1:2}") # assignment within subshell (success)
> done <<< alpha,bravo,charlie
bravo,charlie
如果以下块中的第二个赋值不影响命令的环境,并且它不会生成错误,那么它的用途是什么?
$ foo=bar
$ foo=qux echo $foo
bar
$ foo=bar
$ foo=qux echo $foo
bar
这是一个常见的bash陷阱 - 而https://www.shellcheck.net/抓住了它:
foo=qux echo $foo
^-- SC2097: This assignment is only seen by the forked process.
^-- SC2098: This expansion will not see the mentioned assignment.
问题是第一个foo=bar
正在设置一个bash变量,而不是一个环境变量。然后,内联foo=qux
语法用于为echo
设置环境变量 - 但是echo
实际上从未查看该变量。相反,$foo
被识别为bash变量并被bar
取代。
回到你的主要问题,你基本上是在使用子shell进行最后的尝试 - 除了你实际上不需要子shell:
while IFS=, read -a A; do
IFS=,; echo "${A[*]:1:2}"
done <<< alpha,bravo,charlie
输出:
bravo,charlie
为了完整起见,这是一个最后一个示例,它读取多行并使用不同的输出分隔符来演示不同的IFS分配不会相互踩踏:
while IFS=, read -a A; do
IFS=:; echo "${A[*]:1:2}"
done < <(echo -e 'alpha,bravo,charlie\nfoo,bar,baz')
输出:
bravo:charlie
bar:baz
答案比其他答案更简单:
$ foo=bar
$ foo=qux echo $foo
bar
我们看到“bar”因为在设置$foo
之前shell扩展了foo=qux
Simple Command Expansion - 这里有很多东西可以通过,所以忍受我...
执行简单命令时,shell将从左到右执行以下扩展,分配和重定向。
- 解析器标记为变量赋值(命令名称前面的那些)和重定向的单词将保存以供以后处理。
- 不是变量赋值或重定向的单词被扩展(参见Shell Expansions)。如果在扩展后仍有任何单词,则第一个单词将被视为命令的名称,其余单词为参数。
- 如上所述执行重定向(请参阅重定向)。
- 每个变量赋值中'='之后的文本在分配给变量之前经历波浪扩展,参数扩展,命令替换,算术扩展和引用删除。
如果未生成命令名称,则变量赋值会影响当前的shell环境。否则,变量将添加到已执行命令的环境中,并且不会影响当前的shell环境。如果任何分配尝试将值分配给只读变量,则会发生错误,并且命令以非零状态退出。
如果没有命令名称结果,则执行重定向,但不影响当前的shell环境。重定向错误导致命令以非零状态退出。
如果在扩展后留下命令名称,则执行如下所述。否则,该命令退出。如果其中一个扩展包含命令替换,则该命令的退出状态是执行的最后一个命令替换的退出状态。如果没有命令替换,则命令以状态为零退出。
所以:
foo=qux
并保存以供日后使用$foo
并将其扩展为“bar”foo=qux echo bar
一旦你真正理解了bash做事的顺序,很多谜团就会消失。
简短的回答:改变IFS
的影响是复杂的,难以理解,最好避免除了一些明确定义的习语(IFS=, read ...
是我认为好的习语之一)。
答案很长:为了理解你从IFS
的变化中看到的结果,你需要记住几件事:
IFS=something
作为命令的前缀只会为该命令的执行更改IFS
。特别是,它不会影响shell如何解析传递给该命令的参数;这是由shell的IFS
值控制的,而不是用于命令执行的值。IFS
的值(例如read
),但是其他命令没有(例如echo
)。鉴于上述情况,IFS=, read -a A
做了你所期望的,它将输入分为“,”:
$ IFS=, read -a A <<<"alpha,bravo,charlie"
$ declare -p A
declare -a A='([0]="alpha" [1]="bravo" [2]="charlie")'
但echo
不重视;它总是在它传递的参数之间放置空格,因此使用IFS=something
作为它的前缀根本没有效果:
$ echo alpha bravo
alpha bravo
$ IFS=, echo alpha bravo
alpha bravo
所以当你使用IFS=, echo "${A[*]:1:2}"
时,它只相当于echo "${A[*]:1:2}"
,并且因为shell的IFS
定义以空格开头,所以它将A
的元素与它们之间的空格放在一起。所以它相当于运行IFS=, echo "alpha bravo"
。
另一方面,IFS=,; echo "${A[*]:1:2}"
改变了shell对IFS
的定义,所以它确实会影响shell如何将元素放在一起,因此它等同于IFS=, echo "alpha,bravo"
。不幸的是,从那时起它也会影响其他所有内容,因此您必须将其隔离到子shell或者之后将其恢复正常。
只是为了完整性,以下是其他几个不起作用的版本:
$ IFS=,; echo "${A[@]:1:2}"
bravo charlie
在这种情况下,[@]
告诉shell将数组的每个元素视为一个单独的参数,因此它留给echo
合并它们,它忽略IFS
并且总是使用空格。
$ IFS=,; echo "${A[@]:1:2}"
bravo charlie
那怎么样:
$ IFS=,; echo ${A[*]:1:2}
bravo charlie
在这种情况下,[*]
告诉shell将所有元素与它们之间的IFS
的第一个字符混合在一起,给出bravo,charlie
。但它不是双引号,所以shell会立即将它重新拆分为“,”,再次将其拆分为单独的参数(然后echo
将它们与空格一起加入)。
如果你想更改shell的IFS
定义而不必将它隔离到子shell,有一些选项可以更改它并在之后重新设置它。在bash中,您可以将其设置为正常,如下所示:
$ IFS=,
$ while read -a A; do # Note: IFS change not needed here; it's already changed
> echo "${A[*]:1:2}"
> done <<<alpha,bravo,charlie
bravo,charlie
$ IFS=$' \t\n'
但$'...'
语法并非在所有shell中都可用;如果你需要可移植性,最好使用文字字符:
IFS='
' # You can't see it, but there's a literal space and tab after the first '
有些人更喜欢使用unset IFS
,它只是强制shell的默认行为,这与正常方式定义的IFS
几乎相同。
...但是如果IFS
在某些更大的背景下被改变了,并且你不想搞砸它,你需要保存它然后再将它设置回来。如果它已经正常更改,这将工作:
saveIFS=$IFS
...
IFS=$saveIFS
...但是如果有人认为使用unset IFS
是一个好主意,这将把它定义为空白,给出奇怪的结果。所以你可以使用这种方法或unset
方法,但不能同时使用这两种方法。如果你想对unset
冲突做出强有力的反应,你可以在bash中使用这样的东西:
saveIFS=${IFS:-$' \t\n'}
...或者为了便携性,请不要使用$' '
并使用文字空格+制表符+换行符:
saveIFS=${IFS:-
} # Again, there's an invisible space and tab at the end of the first line
总而言之,对于那些粗心大意的人来说,这里充满了陷阱。我建议尽可能避免使用它。