如何在每个命令的基础上正确分配临时 Bash 变量?

vin*_*nes 4 bash scope ifs

Bash 似乎在临时的、每个命令的变量分配方面表现得不可预测,特别是IFS.

我经常IFSread命令一起分配一个临时值。我想使用相同的机制来定制输出,但目前使用函数或子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
Run Code Online (Sandbox Code Playgroud)

如果下面块中的第二个赋值不影响命令的环境,并且不产生错误,那么它是做什么用的?

$ foo=bar
$ foo=qux echo $foo
bar
Run Code Online (Sandbox Code Playgroud)

gle*_*man 6

答案比其他答案更简单:

\n
$ foo=bar\n$ foo=qux echo $foo\nbar\n
Run Code Online (Sandbox Code Playgroud)\n

我们看到“bar”是因为外壳$foo 之前膨胀了在设置foo=qux

\n

简单的命令扩展——这里有很多东西需要完成,所以请耐心等待......

\n
\n

当执行一个简单的命令时,shell 从左到右执行以下扩展、赋值和重定向。

\n
    \n
  1. 解析器标记为变量赋值的单词(命令名称前面的单词)和重定向将被保存以供以后处理
  2. \n
  3. 不是变量赋值或重定向的单词会被扩展(参见Shell 扩展)。如果扩展后仍有任何单词,则第一个单词将被视为命令名称,其余单词将被视为参数。
  4. \n
  5. 重定向的执行方式如上所述(请参阅重定向)。
  6. \n
  7. 每个变量赋值中 \xe2\x80\x98=\xe2\x80\x99 之后的文本在赋值给变量之前会经历波形符扩展、参数扩展、命令替换、算术扩展和引号删除。
  8. \n
\n

如果没有命令名结果,则变量分配会影响当前 shell 环境。否则,变量将添加到执行命令的环境中,不会影响当前的 shell 环境。如果任何赋值尝试将值赋给只读变量,则会发生错误,并且命令以非零状态退出。

\n

如果没有命令名结果,则会执行重定向,但不会影响当前的 shell 环境。重定向错误会导致命令以非零状态退出。

\n

如果扩展后有剩余命令名,则继续执行则按如下所述继续执行。否则,命令退出。如果其中一个扩展包含命令替换,则该命令的退出状态是最后执行的命令替换的退出状态。如果没有命令替换,该命令将以零状态退出。

\n
\n

所以:

\n
    \n
  • 外壳看到foo=qux并保存它供以后使用
  • \n
  • 外壳看到$foo并将其扩展为“bar”
  • \n
  • 那么我们现在有:foo=qux echo bar
  • \n
\n

一旦你真正理解了 bash 做事的顺序,很多谜团就迎刃而解了。

\n

  • 是的,我到了那里。`foo=qux eval 'echo $foo'` 返回 `qux` (2认同)

Geo*_*rge 5

$ foo=bar
$ foo=qux echo $foo
bar
Run Code Online (Sandbox Code Playgroud)

这是一个常见的 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.
Run Code Online (Sandbox Code Playgroud)

问题是第一个foo=bar是设置 bash 变量,而不是环境变量。然后,内联foo=qux语法用于设置环境变量echo- 但echo实际上从未查看该变量。而是$foo被识别为 bash 变量并替换为bar.

所以回到你的主要问题,你基本上是在使用 subshel​​l 进行最后一次尝试——除了你实际上并不需要 subshel​​l:

while IFS=, read -a A; do
  IFS=,; echo "${A[*]:1:2}"
done <<< alpha,bravo,charlie
Run Code Online (Sandbox Code Playgroud)

输出:

bravo,charlie
Run Code Online (Sandbox Code Playgroud)

为了完整起见,这里是最后一个示例,它读取多行并使用不同的输出分隔符来证明不同的 IFS 分配不会相互影响:

while IFS=, read -a A; do
  IFS=:; echo "${A[*]:1:2}"
done < <(echo -e 'alpha,bravo,charlie\nfoo,bar,baz')
Run Code Online (Sandbox Code Playgroud)

输出:

bravo:charlie
bar:baz
Run Code Online (Sandbox Code Playgroud)