为什么当我在命令前加上“一次性变量赋值”时,bash 不扩展这个变量

为什么当我在命令前加上“一次性变量赋值”时,bash 不扩展这个变量

如果我运行此 bash 命令并为语句添加前缀,则该变量fruit应该存在,但仅在此命令的持续时间内:

$ fruit=apple echo $fruit

$

结果是一个空行。为什么?

引用通配符的评论这个问题

参数扩展是由shell完成的,“fruit”变量不是shell变量;它只是“echo”命令环境中的一个环境变量

环境变量仍然是一个变量,所以这肯定仍然可以用于 echo 命令吗?

答案1

问题是当前 shell 过早地扩展了变量;它没有在其上下文中设置,因此该echo命令没有任何参数,即命令最终是:

$ fruit=apple echo

这是一种解决方法,由于单引号,变量不会过早扩展:

$ fruit=apple sh -c 'echo $fruit'

或者,您还可以使用单行 shell 脚本,该脚本演示fruit变量已正确传递给执行的命令:

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

由于这个问题引发了一些意想不到的争议和讨论,一些评论如下:

  • 变量fruit是否已导出这一事实并不影响其行为,重要的是在 shell 扩展它的精确时刻变量值是什么。
$ 出口水果=香蕉
$水果=苹果回声$水果
香蕉
  • 该命令是内置命令这一事实echo不会影响 OP 问题。但是,在某些情况下,使用具有此语法的内置函数或 shell 函数会产生意想不到的副作用,例如:
$ 出口水果=香蕉
$fruit=apple eval 'echo $fruit'
苹果
$回声$水果
苹果
  • 虽然两者之间有相似之处这里问的问题和那个,这不是完全相同的问题。对于另一个问题,IFS当 shell分词 其他$var此处的变量,fruit当 shell 执行时临时变量值尚不可用扩大相同的多变的。

  • 还有另一个问题OP 询问所使用语法的重要性,更准确地说是询问“为什么这样做?”。在这里,OP 意识到了其重要性,但报告了意外行为并询问其原因,即“为什么这不起作用?”。好吧,在更仔细地阅读了另一个问题上发布的糟糕的屏幕截图后,确实在那里描述了相同的情况(BAZ=jake echo $BAZ)所以是的,毕竟这是重复的

答案2

为了正确理解这一点,我们首先要区分外壳变量环境变量。

环境变量是所有进程都拥有的属性,无论它们是否在内部使用它们。即使sleep 10它运行时也有环境变量。正如所有进程都有 PID(进程标识符)、当前工作目录 (cwd)、PPID(父 PID)、参数列表(即使为空)等。所有进程也都有所谓的“环境”,它是在分叉时从父进程继承的。

从实用程序作者(用 C 语言编写代码的人)的角度来看,进程能够设置、取消设置或更改环境变量。然而,从脚本作者的角度来看,大多数工具不向用户提供该功能。相反,您使用您的更改进程环境,然后在执行您调用的命令(外部二进制文件)时继承该进程环境。 (您的 shell 本身的环境可以修改,并且修改会被继承,或者您可以指示您的 shell 在 fork 之后但在执行您调用的命令之前进行修改。无论哪种方式,环境都会被继承。我们将同时查看这两种情况接近。)

shell 变量是另一回事。虽然壳内它们的行为方式相同,区别在于单纯的“shell 变量”不会改变或影响您调用的命令的行为你的外壳。用正确的术语来说,这种区别实际上会有一点不同的表述。出口shell 变量将成为您调用的工具环境的一部分,而 shell 变量不是导出不会。但是,我发现引用未导出为“shell 变量”的 shell 变量和导出为“shell 变量”的 shell 变量对通信更有帮助。导出为“环境变量”,因为它们从 shell 派生的进程的角度来看环境变量。


这是很多文字。让我们看一些例子并描述正在发生的事情:

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

在这个例子中,somevar只是一个shell变量,没有什么特别的。壳参数扩展(见LESS='+/Parameter Expansion' man bash)发生可执行ls文件实际上已加载(“exec”),而ls命令(进程)甚至从未被加载看到字符串“美元符号 somevar”。它只看到字符串“myfile”,将其解释为当前工作目录中文件的路径,并获取并打印有关它的信息。

如果我们在命令export somevar之前运行ls,那么事实somevar=myfile将出现在环境进程的ls,但这不会影响任何事情,因为该ls命令不会对此变量执行任何操作。去看一个影响对于环境变量,我们必须选择一个环境变量,我们调用的进程将实际检查该环境变量并对其执行某些操作。


bc:基本计算器

可能有更好的例子,但这是我想出的一个并不太复杂的例子。首先您应该知道这bc是基本计算器,可以处理和计算数学表达式。 (处理完任何输入文件的内容后,它会处理其标准输入。我不会在示例中使用其标准输入;我只需按 Ctrl-D,这不会在下面的文本片段中显示。另外我用来-q在每次调用时抑制介绍性消息。)

我将说明的环境变量描述如下man bc

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

开始:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

这只是为了展示如何bc工作。在每种情况下,我都必须按 Ctrl-D 向 发出“输入结束”信号bc

现在,让我们传递一个环境变量直接地bc

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

请注意,我们在该变量中放入的内容是不是稍后可以通过echo命令看到。通过将赋值作为同一命令的一部分(也没有分号),我们将该变量赋值为部分的环境bc- 它使我们正在运行的 shell 本身不受影响。

现在让我们设置BC_ENV_ARGS多变的:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

在这里你可以看到我们的echo命令可以看到内容,但它不是环境的一部分,bc所以bc不能用它做任何特殊的事情。

当然,如果我们将变量本身放入bc的参数列表中,我们会看到一些内容:

$ bc -q "$BC_ENV_ARGS"
25
$ 

但在这里,却是展开变量,然后file1就是实际出现在bcs中的内容参数列表。 所以这仍然将其用作shell变量,而不是环境变量。

现在让我们“导出”这个变量,使其既是一个 shell 变量又是一个环境变量:

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

在这里您可以看到它file1是在之前处理的file2,尽管这里的命令行中没有提到。它是 shell 环境的一部分,并且bc当您运行该进程时成为 的环境的一部分,因此该环境变量的值为遗传并影响bc运作方式。

我们仍然可以在每个命令的基础上重写它,甚至将它重写为空值:

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

但正如您所看到的,该变量在我们的 shell 中保持设置和导出状态,对于 shell 本身以及任何后续bc命令都可见覆盖该值。除非我们“取消导出”或“取消设置”它,否则它将保持这种状态。我会做后者:

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

另一个例子,涉及生成另一个 shell:

在 shell 中依次键入以下命令并考虑结果。在运行之前看看您是否可以预测结果。

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

最后一个是额外的技巧:你能预测以下按顺序运行的命令的输出吗? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

请在评论中提及任何需要的澄清;我确信这可能会用到一些。但即便如此,它也应该有帮助。

答案3

因为命令行上的扩展发生在变量赋值之前。标准是这么说的

当需要执行给定的简单命令时,应执行以下操作:

  1. 被识别为变量赋值的单词将被保存以供在步骤 3 和 4 中进行处理。

  2. 非变量赋值或重定向的词应扩展。 [...]

  3. 重定向应按照重定向中的描述执行。

  4. 每个变量赋值应在赋值之前展开[...]。

注意顺序:在第一步中,作业仅已保存,然后扩展其他单词,并且只有最后才进行变量赋值。

当然,标准很可能这么说只是因为它一直都是这样,而且他们只是将现有的行为编入法典。它没有提及任何有关历史或背后原因的内容。 shell 必须识别在某些时候看起来像赋值的单词(如果只是为了不将它们作为命令的一部分),所以我认为它可以按其他顺序工作,首先分配变量,然后在命令行上展开任何内容。 (或者只是从左到右进行......)

相关内容