我总是很犹豫,$IFS
因为它正在破坏全球市场。
但是,这通常使将字符串加载到bash数组中变得简洁明了,而且对于bash脚本而言,简洁性很难实现。
因此,我认为如果尝试将“开始”内容“保存” $IFS
到另一个变量,然后在用完$IFS
某些东西后立即将其还原,那总比没有好。
这可行吗?还是从本质上讲毫无意义,我应该直接将其设置IFS
为后续使用所需的任何内容?
$' \t\n'
如果您正在使用bash。unset $IFS
只是并不总是将其还原为您期望的默认值。
我总是很犹豫,$IFS
因为它正在破坏全球市场。
但是,这通常使将字符串加载到bash数组中变得简洁明了,而且对于bash脚本而言,简洁性很难实现。
因此,我认为如果尝试将“开始”内容“保存” $IFS
到另一个变量,然后在用完$IFS
某些东西后立即将其还原,那总比没有好。
这可行吗?还是从本质上讲毫无意义,我应该直接将其设置IFS
为后续使用所需的任何内容?
$' \t\n'
如果您正在使用bash。unset $IFS
只是并不总是将其还原为您期望的默认值。
Answers:
您可以根据需要保存并分配给IFS。这样做没有错。像数组分配示例一样,在临时的,迅速的修改之后,保存其值以进行恢复并不少见。
正如@llua在对您的问题的评论中提到的那样,只需取消设置IFS即可恢复默认行为,等同于分配space-tab-newline。
值得考虑的是,没有显式设置/未设置IFS可能比这样做更成问题。
从POSIX 2013版本2.5.3 Shell变量开始:
在调用外壳程序时,实现可能会忽略环境中IFS的值,或者环境中缺少IFS,在这种情况下,外壳程序在调用时应将IFS设置为<space> <tab> <newline> 。
兼容POSIX的被调用Shell可能会也可能不会从其环境继承IFS。由此可见:
"$*"
),但可以在从环境初始化IFS的外壳下运行的脚本,必须明确设置/取消设置IFS,以防御环境入侵。注意:重要的是要理解,在此讨论中,“调用”一词具有特定的含义。仅当使用其名称(包括#!/path/to/shell
shebang)显式调用shell时,才调用shell 。子外壳程序(例如可能由$(...)
或创建的子cmd1 || cmd2 &
外壳程序)不是被调用的外壳程序,其IFS(及其大多数执行环境)与父级外壳程序相同。被调用的外壳将其值设置$
为其pid,而子外壳则继承它。
这不仅仅是一个书呆子的问题。在这方面确实存在分歧。这是一个简短的脚本,它使用几种不同的外壳测试场景。它将修改后的IFS(设置为:
)导出到被调用的Shell,然后打印其默认IFS。
$ cat export-IFS.sh
export IFS=:
for sh in bash ksh93 mksh dash busybox:sh; do
printf '\n%s\n' "$sh"
$sh -c 'printf %s "$IFS"' | hexdump -C
done
IFS通常没有标记为要导出,但要注意,请注意bash,ksh93和mksh如何忽略其环境的IFS=:
,而破折号和busybox则尊重它。
$ sh export-IFS.sh
bash
00000000 20 09 0a | ..|
00000003
ksh93
00000000 20 09 0a | ..|
00000003
mksh
00000000 20 09 0a | ..|
00000003
dash
00000000 3a |:|
00000001
busybox:sh
00000000 3a |:|
00000001
一些版本信息:
bash: GNU bash, version 4.3.11(1)-release
ksh93: sh (AT&T Research) 93u+ 2012-08-01
mksh: KSH_VERSION='@(#)MIRBSD KSH R46 2013/05/02'
dash: 0.5.7
busybox: BusyBox v1.21.1
即使bash,ksh93和mksh不在环境中初始化IFS,它们也会重新导出其修改后的IFS。
如果出于某种原因需要通过环境可移植地传递IFS,则不能使用IFS本身进行传递;您需要将值分配给其他变量,然后将该变量标记为要导出。然后,孩子将需要将该值明确分配给他们的IFS。
IFS
,因此即使尝试“保留”其原始值通常也不是很有效。
read
,或对的双引号引用,则脚本的行为取决于IFS $*
。该列表只是我的头上,所以它可能并不全面(尤其是考虑到现代外壳的POSIX扩展时)。
通常,将条件恢复为默认值是一种好习惯。
但是,在这种情况下,不需要那么多。
为什么?:
$' \t\n'
。unset IFS
,它就好像被设置为default一样。另外,存储IFS值也有问题。
如果未设置原始IFS,则代码IFS="$OldIFS"
会将IFS设置为""
,而不是将其设置。
要实际保留IFS的值(即使未设置),请使用以下命令:
${IFS+"false"} && unset oldifs || oldifs="$IFS" # correctly store IFS.
IFS="error" ### change and use IFS as needed.
${oldifs+"false"} && unset IFS || IFS="$oldifs" # restore IFS.
bash
中unset IFS
无法取消设置IFS。
您应该犹豫是否破坏全球。不用担心,有可能编写干净的工作代码而无需修改实际的global IFS
,也无需进行繁琐且容易出错的保存/恢复操作。
您可以:
为单个调用设置IFS:
IFS=value command_or_function
要么
在子shell中设置IFS:
(IFS=value; statement)
$(IFS=value; statement)
要从数组获取逗号分隔的字符串:
str="$(IFS=, ; echo "${array[*]-}")"
注意:-
只能set -u
通过在未设置时提供默认值(在这种情况下该值为空字符串)来保护空数组
。
该IFS
修改仅适用于由$()
命令替换产生的子外壳内部。这是因为子外壳程序具有调用外壳程序变量的副本,因此可以读取其值,但是子外壳程序执行的任何修改仅影响子外壳程序的副本,而不会影响父外壳程序的变量。
您可能还在想:为什么不跳过子Shell而是这样做:
IFS=, str="${array[*]-}" # Don't do this!
此处没有命令调用,而是将此行解释为两个独立的后续变量分配,就好像是:
IFS=, # Oops, global IFS was modified
str="${array[*]-}"
最后,让我们解释一下为什么该变体不起作用:
# Notice missing ';' before echo
str="$(IFS=, echo "${array[*]-}")" # Don't do this!
echo
确实会在将IFS
变量设置为的情况下调用该命令,
,但echo
并不关心或使用该命令IFS
。扩展"${array[*]}"
为字符串的魔力是在echo
调用之前由(sub)shell自身完成的。
将整个文件(不包含NULL
字节)读入名为的单个变量中VAR
:
IFS= read -r -d '' VAR < "${filepath}"
注意: IFS=
与IFS=""
和相同IFS=''
,所有都将IFS设置为空字符串,这与unset IFS
:有很大不同:如果IFS
未设置,则内部使用的所有bash功能的行为与默认值IFS
完全相同。IFS
$' \t\n'
设置IFS
为空字符串可确保保留前导和尾随空格。
该-d ''
或-d ""
告诉读取只停止在其当前调用NULL
字节,而不是通常的换行符。
要拆分$PATH
沿其:
分隔符:
IFS=":" read -r -d '' -a paths <<< "$PATH"
这个例子纯粹是说明性的。在通常情况下,您将沿着分隔符进行拆分,各个字段可能包含该分隔符(该版本的转义版本)。考虑尝试读取.csv
文件的某行,该文件的各列本身可能包含逗号(以某种方式转义或引用)。以上代码段不适用于此类情况。
也就是说,您不太可能在中遇到此类包含:
路径$PATH
。虽然UNIX / Linux路径名允许包含a :
,但如果您尝试将bash添加到自己的路径中$PATH
并在其中存储可执行文件,bash似乎将无法处理这些路径,因为没有代码可以解析转义/引用的冒号:bash 4.4的源代码。
最后,请注意,此代码段将尾随换行符附加到结果数组的最后一个元素(如@StéphaneChazelas在现在删除的注释中所指出的那样),并且如果输入为空字符串,则输出将为单个元素数组,其中元素将由换行符($'\n'
)组成。
对于最简单的脚本old_IFS="${IFS}"; command; IFS="${old_IFS}"
,触及全局的基本方法IFS
将按预期工作。但是,一旦增加任何复杂性,它就很容易分解并引起细微的问题:
command
bash函数也修改了全局变量IFS
(直接或隐藏在它调用的另一个函数内部),并且在这样做时错误地使用相同的全局old_IFS
变量进行保存/恢复,则会出现错误。IFS
未设置的原始状态,则如果常用(错误)使用set -u
(aka set -o nounset
)shell选项,则幼稚的保存和恢复将不起作用,甚至会导致彻底失败。生效。help trap
)。如果该代码还修改了全局IFS
或假定其具有特定值,则可能会得到一些细微的错误。您可以设计一个更健壮的保存/恢复序列(例如在另一个答案中提出的序列,以避免某些或所有这些问题。但是,无论何时您需要临时自定义,都必须重复这段嘈杂的样板代码IFS
。降低代码的可读性和可维护性。
IFS
Shell函数库的作者尤其需要关注的是,他们需要确保其代码稳定运行,而不管其调用程序所IFS
施加的全局状态(,shell选项...)如何,并且也根本不打扰该状态(调用程序可能依赖于此)使其始终保持静态)。
在编写库代码时,您不能依赖于IFS
具有任何特定值(甚至不是默认值),也不能完全被设置。相反,您需要为IFS
行为取决于的任何代码段显式设置IFS
。
如果IFS
在此值所影响的代码的每一行中都将值显式设置为必要值(即使恰好是默认值),则使用此答案中描述的两种机制中的任何一种都适合于确定效果,那么代码都是独立于全局状态,避免完全破坏全局状态。这种方法的另一个好处是,使阅读该脚本的人非常明确,而该脚本IFS
恰好以最小的文本成本(甚至是最基本的保存/恢复)对这一命令/扩展至关重要。
IFS
无论如何,什么代码受到影响?幸运的是,没有那么多重要的场景IFS
(假设您总是引用自己的扩展):
"$*"
和"${array[*]}"
扩展read
针对多个变量(read VAR1 VAR2 VAR3
)或数组变量(read -a ARRAY_VAR_NAME
)的内置调用read
涉及到出现在中的前导/后跟空白字符或非空白字符时针对单个变量的调用IFS
。:
何时:
会包含这些组件?
:
是在大多数UNIX / Linux文件系统上的文件名中使用的有效字符,因此完全可以使用名称包含的目录:
。也许某些shell :
通过使用诸如之类的东西在PATH中进行了转义\:
,然后您会看到出现的列不是实际的定界符(看来bash不允许这种转义。当遍历$PATH
搜索:
in 时使用的低级函数)一个C字符串:git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891)。
$PATH
示例:
更加清晰。
这可行吗?还是从本质上讲毫无意义,我应该直接将IFS设置回其后续使用所需的位置?
$' \t\n'
当您要做的只是为什么要冒输入错误设置IFS的风险时
OIFS=$IFS
do_your_thing
IFS=$OIFS
另外,如果不需要在以下范围内设置/修改任何变量,则可以调用子shell:
( IFS=:; do_your_thing; )
IFS
最初未设置,则无法使用。