我知道可以为单个命令/内置的范围设置自定义 IFS 值。有没有办法为单个语句设置自定义 IFS 值?显然不是,因为根据以下内容,尝试执行此操作时全局 IFS 值会受到影响
#check environment IFS value, it is space-tab-newline
printf "%s" "$IFS" | od -bc
0000000 040 011 012
\t \n
0000003
#invoke built-in with custom IFS
IFS=$'\n' read -r -d '' -a arr <<< "$str"
#environment IFS value remains unchanged as seen below
printf "%s" "$IFS" | od -bc
0000000 040 011 012
\t \n
0000003
#now attempt to set IFS for a single statement
IFS=$'\n' a=($str)
#BUT environment IFS value is overwritten as seen below
printf "%s" "$IFS" | od -bc
0000000 012
\n
0000001
答案1
在某些 shell 中(包括bash
):
IFS=: command eval 'p=($PATH)'
(使用bash
,您可以省略command
if not in sh/POSIX emulation)。但请注意,当使用不带引号的变量时,通常还需要这样做set -f
,并且在大多数 shell 中没有本地作用域。
使用 zsh,您可以执行以下操作:
(){ local IFS=:; p=($=PATH); }
$=PATH
是强制分词,这在默认情况下不会完成(变量扩展时的通配符也不会完成,所以除非在 sh 模拟中,否则zsh
不需要)。set -f
但是,在 中zsh
,您宁愿使用$path
数组捆绑to $PATH
, 或 用任意分隔符分割:p=(${(s[:])PATH})
或p=("${(s[:]@)PATH}")
保留空元素。
(){...}
(或者function {...}
) 被称为匿名函数通常用于设置本地范围。对于其他支持函数局部作用域的 shell,您可以执行类似的操作:
e() { eval "$@"; }
e 'local IFS=:; p=($PATH)'
要在 POSIX shell 中实现变量和选项的本地作用域,您还可以使用以下位置提供的函数:https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh。然后您可以将其用作:
. /path/to/locvar.sh
var=3,2,2
call eval 'locvar IFS; locopt -f; IFS=,; set -- $var; a=$1 b=$2 c=$3'
(顺便说一句,$PATH
上面的分割方式是无效的,除了zsh
在其他 shell 中,IFS 是字段分隔符,而不是字段分隔符)。
IFS=$'\n' a=($str)
只是两项作业,一项接一项,就像a=1 b=2
.
注释说明var=value cmd
:
在:
var=value cmd arg
shell/path/to/cmd
在新进程中执行并传入cmd
and arg
inargv[]
和var=value
inenvp[]
。这并不是真正的变量赋值,而是更多地将环境变量传递给被处决命令。在 Bourne 或 Korn shell 中,set -k
您甚至可以编写它cmd var=value arg
。
现在,这不适用于非被处决。在 Bourne shell 中, in var=value some-builtin
,var
最终被设置,就像 with var=value
alone 一样。这意味着,例如,var=value echo foo
(这是没有用的)的行为会根据是否echo
是内置的而变化。
POSIX 和/或ksh
改变了这一点,因为 Bourne 行为仅发生在一类称为特殊的内置函数。eval
是一个特殊的内置,read
不是。对于非特殊内置命令,仅var=value builtin
设置var
内置命令的执行,这使其行为与运行外部命令时类似。
该command
命令可用于删除特别的那些的属性特殊的内置函数。 POSIX 忽略的是,对于eval
和.
内置函数,这意味着 shell 必须实现一个变量堆栈(即使它没有指定local
或typeset
范围限制命令),因为你可以这样做:
a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a
甚至:
a=1 command eval myfunction
是一个使用或设置并可能调用 的myfunction
函数。$a
command eval
这确实是一个忽视,因为ksh
(规范主要基于该规范)没有实现它(AT&Tksh
仍然zsh
没有实现),但现在,除了这两个之外,大多数 shell 都实现了它。不同 shell 的行为有所不同,例如:
a=0; a=1 command eval a=2; echo "$a"
尽管。在支持它的 shell 上使用local
是实现本地作用域的更可靠的方法。
答案2
标准保存和恢复取自 Kernighan 和 Pike 的“Unix 编程环境”:
#!/bin/sh
old_IFS=$IFS
IFS="something_new"
some_program_or_builtin
IFS=${old_IFS}
答案3
问题中的这个片段:
IFS=$'\n' a=($str)
被解释为从左到右计算的两个单独的全局变量赋值,相当于:
IFS=$'\n'; a=($str)
或者
IFS=$'\n'
a=($str)
这解释了为什么全局被修改,以及为什么使用新值IFS
将 分词为数组元素。$str
IFS
您可能会想使用子 shell 来限制修改的效果IFS
,如下所示:
str="value 0:value 1"
a=( old values )
( # Following code runs in a subshell
IFS=":"
a=($str)
printf 'Subshell IFS: %q\n' "${IFS}"
echo "Subshell: a[0]='${a[0]}' a[1]='${a[1]}'"
)
printf 'Parent IFS: %q\n' "${IFS}"
echo "Parent: a[0]='${a[0]}' a[1]='${a[1]}'"
但你很快就会注意到,修改a
也仅限于子shell:
Subshell IFS: :
Subshell: a[0]='value 0' a[1]='value 1'
Parent IFS: $' \t\n'
Parent: a[0]='old' a[1]='values'
接下来,您可能会想使用以下解决方案保存/恢复 IFS之前的这个答案通过 @msw 或尝试使用local IFS
函数内部按照建议通过@helpermethod。但很快,您就会发现自己遇到了各种各样的麻烦,特别是如果您是一名库作者,需要对调用脚本的不当行为保持鲁棒性:
- 如果
IFS
最初未设置怎么办? - 如果我们使用
set -u
(akaset -o nounset
) 运行怎么办? - 如果
IFS
通过 设为只读怎么办declare -r IFS
? - 如果我需要保存/恢复机制来处理递归和/或异步执行(例如
trap
处理程序)怎么办?
请不要保存/恢复 IFS。相反,坚持临时修改:
要将变量修改限制为单个命令、内置或函数调用,请使用
IFS="value" command
.要通过分割特定字符来读取多个变量(
:
如下例所示),请使用:IFS=":" read -r var1 var2 <<< "$str"
要读入数组,请使用(执行此操作而不是
array_var=( $str )
):IFS=":" read -r -a array_var <<< "$str"
将修改变量的效果限制在子 shell 中。
要输出以逗号分隔的数组元素:
(IFS=","; echo "${array[*]}")
要将其捕获到字符串中:
csv="$(IFS=","; echo "${array[*]}")"
答案4
对于该命令:
IFS=$'\n' a=($str)
还有一个替代解决方案:给第一个赋值 ( IFS=$'\n'
) 一个要执行的命令(函数):
$ split(){ a=( $str ); }
$ IFS=$'\n' split
这会将IFS放入调用split的环境中,但不会保留在当前环境中。
这也避免了总是有风险地使用 eval。