如何正确临时保存和恢复 IFS 变量?

Kus*_*nda 25 shell variable

如何正确运行一些更改了IFS变量值的命令(以更改字段拆分的工作方式和"$*"处理方式),然后恢复 的原始值IFS

我知道我能做到

(
    IFS='my value here'
    my-commands here
)
Run Code Online (Sandbox Code Playgroud)

将 的更改本地化IFS到子外壳,但我真的不想启动子外壳,尤其是如果我需要更改或设置需要在子外壳之外可见的变量的值时更是如此。

我知道我可以使用

saved_IFS=$IFS; IFS='my value here'
my-commands here
IFS=$saved_IFS
Run Code Online (Sandbox Code Playgroud)

IFS在原始IFS实际上未设置的情况下,这似乎无法正确恢复。

寻找与 shell 无关(但 POSIX)的答案。

澄清:上面的最后一行意味着我对bash-exclusive 解决方案不感兴趣。事实上,我使用最多的系统 OpenBSDbash在默认情况下甚至根本没有安装,而且bash它不是我用来回答本网站问题以外的任何其他事情的外壳。看到我可以在bash或其他类似 POSIX 的 shell 中使用的解决方案更有趣,而无需努力编写不可移植的代码。

Kus*_*nda 26

是的,在 whenIFS未设置的情况下,恢复值$saved_IFS实际上会将值设置IFS(为空值)。

这会影响未加引号扩展的字段拆分方式,它会影响read内置实用程序的字段拆分,并且会影响使用"$*".

如果设置, IFS这些事情会发生,好像 IFS具有空格、制表符和换行符的值,但如果值为,则不会进行字段拆分,并且位置参数将连接成没有分隔符的字符串当使用"$*". 所以,有区别。

要正确恢复IFS,请考虑saved_IFS仅在IFS实际设置为某些内容时进行设置。

unset saved_IFS
[ -n "${IFS+set}" ] && saved_IFS=$IFS
Run Code Online (Sandbox Code Playgroud)

参数替换仅在设置时${IFS+set}扩展为字符串,即使它设置为空字符串。如果未设置,则扩展为空字符串,这意味着测试将为假并保持未设置状态。setIFSIFS-nsaved_IFS

现在,saved_IFS如果IFS最初未设置,则未设置,或者它具有具有的值,IFS您可以设置所需的任何值IFS并运行您的代码。

恢复时IFS,您会做类似的事情:

unset IFS
[ -n "${saved_IFS+set}" ] && { IFS=$saved_IFS; unset saved_IFS; }
Run Code Online (Sandbox Code Playgroud)

finalunset saved_IFS并不是真正必要的,但从环境中清除旧变量可能会很好。


LL3 在评论(现已删除)中建议的另一种方法是在unset命令前面加上前缀:,这是一个什么都不做的内置实用程序,在不需要时有效地注释掉unset

saved_IFS=$IFS
${IFS+':'} unset saved_IFS
Run Code Online (Sandbox Code Playgroud)

这将设置saved_IFS为 的值$IFS,但如果IFS未设置,则将其取消设置。

然后设置IFS为您的值并运行您的命令。然后用

IFS=$saved_IFS
${saved_IFS+':'} unset IFS
Run Code Online (Sandbox Code Playgroud)

unset saved_IFS如果您也想清理该变量,则可能紧随其后)。

请注意,:必须像上面一样引用或转义为\:,这样它就不会被$IFS包含修改:(毕竟,未引用的参数替换会调用字段拆分)。

  • 请注意,这些类型的方法是不可重入的,例如,在 *setting* 和 *restoring* 之间,您不能调用使用相同方法的函数。 (4认同)
  • 你的`$IFS+:` 方法让我想起了 https://groups.google.com/g/comp.unix.shell/c/25QYE-0toQA/m/uFy1F0lEamAJ :-) (2认同)
  • 从 `${IFS+:}` 到 `${IFS:+':'}` 的变化本来可以作为对旧版本 `zsh` 的一种解决方法,在 `sh` 仿真中,`${IFS+:}`如果`$IFS` 包含`:`(`:` 正在进行IFS 拆分),则将扩展为两个空字符串 (2认同)

Pet*_*des 6

bash函数内部,您可以使用local IFS=$'\n'或 任何东西来隐藏该函数范围内的 while的全局(或父函数local)值IFS。进一步分配到IFS仍将修改您的本地版本。

bash

local不在函数内时使用是错误的。

因此,如果您不编写函数或使用不带local(或等效物)的 shell,这将无济于事,但是如果您是(并且您知道IFS在所有点都想要的值,直到它返回),有一个简单而好的方法解决方案。

只要您使用
foo(){ ...; }而不是foo() ( ... ).

  • @BrianDrake:不,zwol 似乎认为编写 shell 脚本的唯一原因是可移植性,这意味着仅使用 POSIX `sh` 功能。(并且只有在任何重要 shell 上都没有已知问题的功能,请参阅 zwol 的回答)。有了这种心态,就没有理由编写纯 bash 脚本了。(这当然是有缺陷的逻辑;例如,自动完成脚本非常特定于 shell,并且出于性能和其他原因用 shell 自己的语言编写。) (3认同)
  • `local` 不是 POSIX,但 Bash/Dash/Busybox 确实有它。不过,Ksh 在这里是个问题。 (2认同)
  • @Kusalananda,关于这一点,请参阅[支持 \`local\` 关键字定义局部变量的 shell 列表](//unix.stackexchange.com/q/493729) (2认同)