为单个语句设置 IFS

为单个语句设置 IFS

我知道可以为单个命令/内置的范围设置自定义 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,您可以省略commandif 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在新进程中执行并传入cmdand arginargv[]var=valueinenvp[]。这并不是真正的变量赋值,而是更多地将环境变量传递给被处决命令。在 Bourne 或 Korn shell 中,set -k您甚至可以编写它cmd var=value arg

现在,这不适用于非被处决。在 Bourne shell 中, in var=value some-builtin,var最终被设置,就像 with var=valuealone 一样。这意味着,例如,var=value echo foo(这是没有用的)的行为会根据是否echo是内置的而变化。

POSIX 和/或ksh改变了这一点,因为 Bourne 行为仅发生在一类称为特殊的内置函数eval是一个特殊的内置,read不是。对于非特殊内置命令,仅var=value builtin设置var内置命令的执行,这使其行为与运行外部命令时类似。

command命令可用于删除特别的那些的属性特殊的内置函数。 POSIX 忽略的是,对于eval.内置函数,这意味着 shell 必须实现一个变量堆栈(即使它没有指定localtypeset范围限制命令),因为你可以这样做:

a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a

甚至:

a=1 command eval myfunction

是一个使用或设置并可能调用 的myfunction函数。$acommand 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将 分词为数组元素。$strIFS

您可能会想使用子 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(aka set -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。

相关内容