对于临时的,按命令分配的变量,尤其是使用IFS
时,Bash似乎表现出不可预测的行为。
我经常将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
答案 0 :(得分:3)
$ 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
。
所以回到您的主要问题,基本上您已经在最后尝试使用subshell了,只是您实际上并不需要subshell:
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
答案 1 :(得分:2)
答案比其他答案要简单一些。
$ foo=bar
$ foo=qux echo $foo
bar
我们看到“条形”,因为外壳在设置$foo
之前扩展了foo=qux
Simple Command Expansion-这里有很多事情要做,所以请忍受...
执行简单命令时,shell从左到右执行以下扩展,分配和重定向。
- 解析器标记为变量赋值(在命令名称之前的变量)和重定向的单词将保存以供以后处理。
- 不是变量分配或重定向的单词将被扩展(请参见Shell Expansions)。如果扩展后还剩下任何单词,则将第一个单词作为命令的名称,其余的单词作为参数。
- 重定向如上所述进行(请参阅重定向)。
- 在分配给变量之前,每个变量分配中“ =”之后的文本都会进行波浪线扩展,参数扩展,命令替换,算术扩展和引号删除。
如果未产生命令名称,则变量分配会影响当前的Shell环境。 否则,变量将添加到已执行命令的环境中,并且不会影响当前的shell环境。如果任何一种分配尝试将值分配给只读变量,都会发生错误,并且命令以非零状态退出。
如果没有命令名称,将执行重定向,但不会影响当前的Shell环境。重定向错误导致命令以非零状态退出。
如果扩展后还剩下命令名称,执行将继续,如下所述。否则,命令退出。如果其中一个扩展包含命令替换,则命令的退出状态为上次执行的命令替换的退出状态。如果没有命令替换,则命令以零状态退出。
所以:
foo=qux
并将其保存以供以后使用$foo
并将其扩展为“ bar” foo=qux echo bar
一旦您真正了解了bash做事的顺序,那么许多谜团就会消失。
答案 2 :(得分:1)
简短的回答:更改IFS
的影响是复杂且难以理解的,最好避免使用,除非有一些定义明确的习惯用法(IFS=, read ...
是我认为可以的习惯用法之一)。 / p>
长答案:为了了解从IFS
的更改中看到的结果,您需要记住几件事:
使用IFS=something
作为命令的前缀会更改IFS
仅针对该命令的执行。特别是,它不会影响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}"
,并且由于外壳程序IFS
的定义以空格开头,因此将A
的元素与它们之间的空间。因此,这等效于运行IFS=, echo "alpha bravo"
。
另一方面,IFS=,; echo "${A[*]:1:2}"
更改了外壳程序IFS
的定义,因此它确实影响了外壳程序将元素放在一起的方式,因此等效于IFS=, echo "alpha,bravo"
。不幸的是,从那时起,它还会影响其他所有内容,因此您要么必须将其隔离到子外壳中,要么之后再将其设置为正常。
仅出于完整性考虑,以下是其他两个无效的版本:
$ IFS=,; echo "${A[@]:1:2}"
bravo charlie
在这种情况下,[@]
告诉外壳程序将数组的每个元素视为一个单独的参数,因此将其留给echo
进行合并,而忽略IFS
和总是使用空格。
$ IFS=,; echo "${A[@]:1:2}"
bravo charlie
那呢:
$ IFS=,; echo ${A[*]:1:2}
bravo charlie
在这种情况下,[*]
告诉外壳程序将所有元素与它们之间的IFS
的第一个字符混在一起,得到bravo,charlie
。但是它不是用双引号引起来的,因此shell会立即在“,”上将其重新拆分,再次将其重新拆分为单独的参数(然后echo
总是将它们与空格连接在一起)。
如果您想更改IFS
的shell定义而不必将其隔离到子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
,它只是强制外壳恢复其默认行为,这与以常规方式定义的IFS
差不多。
...但是,如果在较大的上下文中更改了IFS
,并且您不想弄乱它,则需要先保存然后重新设置。如果已正常更改,它将起作用:
saveIFS=$IFS
...
IFS=$saveIFS
...但是,如果有人认为使用unset IFS
是个好主意,则会将其定义为空白,从而产生奇怪的结果。因此,您可以使用此方法或unset
方法,但不能同时使用两者。如果您想针对unset
冲突使它变得更强大,则可以在bash中使用类似的方法:
saveIFS=${IFS:-$' \t\n'}
...或者出于便携性考虑,请省略$' '
并使用文字空格+ tab +换行符:
saveIFS=${IFS:-
} # Again, there's an invisible space and tab at the end of the first line
总而言之,对于那些粗心的人来说,充满了很多陷阱。我建议尽可能避免使用它。