为什么在bash中在命令前设置变量合法?


68

我刚刚遇到了几个答案,例如解析使用构造的带分隔符的文本文件...

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

在命令IFS前设置变量的位置read

我一直在阅读bash参考,但无法弄清楚为什么这是合法的。

我试过了

$ x="once upon" y="a time" echo $x $y

从bash命令提示符中获取,但没有回音。有人可以指出指向引用中定义IFS变量的语法的位置吗?这是特例还是我可以做一些其他变量的事情?


Answers:


69

它在Shell语法“简单命令”下(强调):

一个简单的命令是一系列可选的变量分配, 后跟用空格分隔的 单词和重定向,并由控制操作员终止。第一个单词指定要执行的命令,并作为参数零传递。其余的单词作为参数传递给调用的命令。

因此,您可以传递任何您想要的变量。您的echo示例不起作用,因为变量是传递给命令的,而不是在shell中设置的。外壳将展开,$x并且$y 调用命令之前。例如,这有效:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time

谢谢!在询问之前,我做了很多谷歌搜索,并试图找出参考文献中的内容,没有运气。我正在尝试更好地编写bash脚本,您的回答会有所帮助。
Mike Lippert 2014年

1
嗯,我想我需要找到一个更好的参考资料,我的(请参见上面的链接)并没有说它没有“壳语法”部分。
Mike Lippert 2014年

1
@MikeLippert参见该参考资料中的3.7.4(“可以通过在其前面加上参数分配来临时扩展任何简单命令或功能的环境”)。我认为该引用来自较旧版本的bash。我只是man bash在系统上运行...
derobert

29

在分叉的过程中,已定义的变量变得像环境变量。

如果你跑

A="b" echo $A

然后bash首先扩展$A""然后运行

A="b" echo

这是正确的方法:

x="once upon" y="a time" bash -c 'echo $x $y'

请注意中的单引号bash -c,否则您将遇到与上述相同的问题。

因此,您的循环示例是合法的,因为bash内置的“ read”命令将在其环境变量中查找IFS并查找,。因此,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

将打印 TEST is and I is test

最后,关于语法,在for循环中应该有一个字符串。因此,我不得不使用反引号将其变成命令。但是,while循环需要命令语法,例如IFS=, read xx yy zz


1
谢谢,我学到的东西比我从你的回答中得到的要多。我也会对您的答案投赞成票,但目前还不允许,我在第一个答案上标记了接受的答案。
Mike Lippert 2014年

1
谢谢,这就是我所希望的。而且,投票比听取您的赞赏没有意义!
Mike Fairhurst,2014年

为了澄清您在第一行代码下的评论:是的,使用unset A变量,bash扩展$A为空字符串,但是为了避免混淆,我不会使用,""因为该代码不等同于A="b" echo ""。不会有任何争论echo
pabouk 2015年

8

man bash

环境

可以通过在其前面加上参数分配来临时扩展任何简单命令或功能的环境,如上文在PARAMETERS中所述。这些赋值语句仅影响该命令看到的环境。

在进行变量分配之前,将对变量进行扩展。出于显而易见的原因,它var=x也将以其他方式起作用,但var=$othervar不会。即您$x需要在可用之前。但这不是主要问题。主要问题是命令行只能由shell环境修改,而分配不会成为shell环境的一部分。

您需要混合使用以下功能:您想要替换命令行,但将变量定义放入命令环境中。Shell必须进行命令行替换。该环境必须由调用的命令显式使用。是否以及如何完成此操作取决于命令。

这种用法的优点是您可以为子进程设置环境,而不会影响外壳环境。

x="once upon" y="a time" bash -c 'echo $x $y'

可以按预期工作,因为在这种情况下,两种功能都结合在一起:命令行替换不是由调用外壳程序完成,而是由子进程外壳程序完成。


1
这比它更微妙,因为该示例也适用于x="once upon" y="a time" eval 'echo $x $y'不涉及子流程(因为eval是内置的)的情况。我想手册中的相关报价是The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments。考虑问题的示例,因为read它也是一个内置函数,并且它的状态随时间变化,所以必须采用这种方式IFS
David Ongaro

4

您提供的命令是因为不同$x$y展开echo命令运行,所以他们在值电流外壳的使用,不是值echo将在其环境看它是否分别来看看。


命令运行后如何扩展命令行中的任何内容?
Hauke Laging

命令预分配来xy是该环境echo中运行,而不是环境在其中的参数echo被扩展。对于IFS=, read xx yy zz,整个字符串被read命令不拆分地读取。然后,该字符串根据的值分割IFS,与分配给相应的块xxyyzz
chepner 2014年

我只想指出,“在命令运行之前先扩展”一词并没有多大意义,因为在命令启动后,什么也不再扩展。此外:您是否只看过我的回答,还是相信我需要对正在发生的事情进行解释...?
Hauke Laging

1
我既没有声称您需要解释,也没有在命令运行后可以扩展任何内容。但是,的确是,bash首先解析给定的命令行,识别出有两个变量分配要应用于即将到来的命令的环境,识别要运行的命令(echo),展开在参数中找到的所有参数,然后运行该命令echo与扩展的参数。
chepner 2014年

echo我的情况下,我不确定它是否可以“看到”变量,因为它是内置命令,因此不会在可能具有自己环境的子shell中运行。但是我尝试了eval它也是一个内置函数,它的确知道这一点。例如,尝试a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $a显示即使在pid相同且环境未在eval期间更改的情况下,a也只能在其中设置eval!(要访问,/proc您需要在Linux下运行它。)bash似乎在这里起到了一些其他作用。
David Ongaro '17

2

我要从更大的角度来看“ 为什么合法”

答:这样您就可以调用或调用程序,并且该调用仅使用具有特定变量的变量。

例如:您有一个名为“ db_connection”的数据库连接参数,通常您将“ test”作为测试数据库连接的名称传递。实际上,您甚至可以将其设置为默认值,这样就无需显式传递。但是有时候您想使用ci数据库。因此,您将参数传递为“ ci”,然后被调用的程序使用数据库参数作为用于所有数据库调用的数据库名称。对于下一次运行,如果您不重复该方法而只调用程序,则变量将返回其先前的默认值。


0

您也可以使用;。由于它是命令分隔符,因此将在之前进行评估。

x="once upon" y="a time"; echo $x $y

1
这将创建两个Shell变量,并且不会在给定的实用程序中创建环境变量。这是完全不同的事情。还有一个副作用,即当前的shell中现在包含新变量。
库沙兰丹
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.