在Bash中在管道的第二个命令中设置环境变量不起作用

559
在给定的shell中,通常我会设置一个或多个变量,然后运行一个命令。最近我了解到了在命令前添加变量定义的概念。
FOO=bar somecommand someargs

这个方法有点用。但是当你改变一个LC_*变量(似乎只影响命令,而影响其参数,例如,[a-z]字符范围),或者将输出导入到另一个命令时,它就不起作用了:

FOO=bar somecommand someargs | somecommand2  # somecommand2 is unaware of FOO

我也可以在 somecommand2 前加上 FOO=bar,这样也可以工作,但会增加不必要的重复,并且不能解决根据变量解释参数的问题(例如,[a-z])。

那么,在一行中有什么好的方法呢?

我认为可以考虑以下方式:

FOO=bar (somecommand someargs | somecommand2)  # Doesn't actually work

我得到了很多好的答案!目标是保持这个代码成为一行,最好不使用 export。使用调用 Bash 的方法是最好的,虽然带有 export 的括号版本更加紧凑。使用重定向而不是管道的方法也很有趣。


2
(T=$(date) echo $T) 可以工作。 - vp_arth
在跨平台(包括Windows)脚本或基于npm的项目(JS或其他)上下文中,您可能需要查看cross-env模块 - Frank N
12
希望其中一个答案也能解释一下为什么这种方法只部分有效,即为什么它不能等同于在调用之前导出变量的方式。 - Brecht Machiels
5
这里解释了为什么要这样做:https://dev59.com/n2Yr5IYBdhLWcg3wDF_P - Brecht Machiels
为什么您不想使用 export,即使副作用不会污染您的环境?换句话说,如果变量在命令执行后没有被设置,那么使用 export 是否重要。例如:在子shell中。 - FreelanceConsultant
换句话说:(export foo="hello")echo $foo 输出一个空行,表明 foo 在子 shell 外未设置。 - FreelanceConsultant
6个回答

473
FOO=bar bash -c 'somecommand someargs | somecommand2'

3
这符合我的标准(一行代码而不需要使用“export”)…… 我理解这意味着没有办法在调用“bash -c”(例如,创造性地使用括号)的情况下完成这个任务? - MartyMacGyver
2
@MartyMacGyver:我想不出任何方法。它也不能使用花括号。 - Dennis Williamson
8
请注意,如果您需要将 somecommand 作为 sudo 运行,则需要传递 -E 标志以通过变量。因为变量可能会引入漏洞。https://dev59.com/D2oy5IYBdhLWcg3wUcZL#8633575 - ThorSummoner
17
请注意,如果您的命令已经有了两个级别的引号,那么这种方法会变得非常糟糕,因为会出现“引号地狱”的情况。在这种情况下,在子shell中进行导出要好得多。 - Pushpendre
1
在OSX上,FOO_X=foox bash -c 'echo $FOO_X'按预期工作,但是对于特定的变量名称,它会失败:DYLD_X=foox bash -c 'echo $DYLD_X'回显为空白。使用eval而不是bash -c可以使两者都正常工作。 - mwag
显示剩余5条评论

323

如何将变量导出,但仅在子shell中使用:

(export FOO=bar && somecommand someargs | somecommand2)

Keith的意见是,要无条件执行命令,请执行以下操作:

(export FOO=bar; somecommand someargs | somecommand2)

4
@MartyMacGyver说,&&会执行左边的命令,只有在左边的命令成功执行后才会执行右边的命令。无条件地执行两个命令。Windows批处理 (cmd.exe) 中 ; 的等效命令是 & - Keith Thompson
2
在zsh中,我似乎不需要为此版本使用export命令:(FOO=XXX; echo FOO=$FOO); echo FOO=$FOO会产生FOO=XXX\nFOO=\n的结果。 - rampion
4
为什么不在这种情况下使用 source (也称为 .)?此外,现在不应再使用反引号了,这也是使用 $(command) 更安全的原因之一。 - 0xC0000022L
6
简单而优雅。我喜欢你的回答比被接受的回答更好,因为它将启动一个与当前子Shell相等(可能不是bash,而是其他一些东西,例如dash),如果我必须在命令参数(someargs)中使用引号,我就不会遇到任何麻烦。 - Mecki
1
如果您想设置多个环境变量,可以运行(export FOO1='bar1'; export FOO2='bar2'; command...) - Honghao Z
显示剩余8条评论

78

使用env

例如,env FOO=BAR command。请注意,当command执行完成时,环境变量将恢复/不改变。

只需注意发生的Shell替换,即如果您想在同一命令行中显式引用$FOO,您可能需要对其进行转义,以使Shell解释器在运行env之前不执行替换。

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR

2
这是完全正确的答案,env 的整个目的就是解决所提出的问题。 - bradw2k
我测试过了,对我有效,谢谢! - Andrei Bastos
1
使用env为命令设置变量在shell中有点多余,因为你可以从命令中移除env,它会完成相同的事情。 - undefined
如果你在shell中,使用env来设置命令的变量有点毫无意义,因为你可以从命令中删除env,它会达到同样的效果。 - cambunctious
这个回答真的解决了问题吗?这个回答使用env有用吗?它的答案是否正确? - undefined

62

您也可以使用 eval

FOO=bar eval 'somecommand someargs | somecommand2'

因为这个使用eval的答案似乎不能让每个人都满意,所以让我澄清一些事情:在写作时,使用单引号,它是完全安全的。 它不会启动外部进程(像被接受的答案)也不会执行额外子shell中的命令(像其他答案)。

由于我们有一些常规观点,所以最好提供一个替代eval的方法,这将使每个人都满意,并具有所有好处(甚至更多!)的快速eval“技巧”。 只需使用一个函数!定义一个包含所有命令的函数:

mypipe() {
    somecommand someargs | somecommand2
}

然后使用您的环境变量执行它,就像这样:

FOO=bar mypipe

8
你也给被接受的回答点了踩吗?因为它表现出与“eval”相同的“问题”。 - gniourf_gniourf
12
@Alfe:不幸的是,我不同意你的批评。这个命令非常安全。你听起来像一个曾经读过“eval很邪恶”但并没有理解eval究竟何以邪恶的人。也许你真的没有完全理解这个答案(其实这个答案没有任何问题)。同样的道理:你会说ls很糟糕,因为 for file in $(ls)是错误的吗?(是的,你没有对被接受的回答点踩,你也没有留下评论)。有时候SO是一个奇怪而荒谬的地方。 - gniourf_gniourf
10
@Alfe:当我说“你听起来像一个曾经读过 eval 是邪恶的,但不理解 eval 到底有什么邪恶之处的人”时,我指的是你的那句话:“当谈到eval时,这个答案缺少所有必要的警告和解释。” eval 并不坏也不危险;就像 bash -c 一样。 - gniourf_gniourf
1
撇开投票不谈,@Alfe提供的评论似乎暗示了被接受的答案更加安全。更有帮助的是,你描述一下你认为使用eval存在什么不安全因素。在提供的答案中,参数已经用单引号括起来以防止变量扩展,所以我认为这个答案没有问题。 - Brett Ryan
1
我认为函数解决方案更清晰,尽管有点啰嗦,比“eval”解决方案更好。 - Derek Mahar
显示剩余11条评论

26

一种简单的方法是利用 ; 分隔符。

例如:

ENV=prod; ansible-playbook -i inventories/$ENV --extra-vars "env=$ENV"  deauthorize_users.yml --check

command1; command2 顺序执行指令,执行command1之后再执行command2,无论指令是否成功。


8
这段话的意思是,它能够生效是因为它在同一个 shell 的环境中定义了 ENV,随后的命令会在这个环境中执行。与其他答案不同的是,这种方法会将 ENV 定义到整个 shell 中,而不仅仅只是当前行。原问题的意图是仅在当前行改变环境。 - Derek Mahar
1
;unset ENV 添加到同一行将使其成为一行代码。但我忽略了它,因为它没有意义。 - Akhil

-6
使用一个 shell 脚本:
#!/bin/bash
# myscript
FOO=bar
somecommand someargs | somecommand2

> ./myscript

22
你仍然需要使用export关键字;否则$FOO将成为一个shell变量,而不是环境变量,因此对somecommandsomecommand2不可见。 - Keith Thompson
1
它可以工作,但这违背了拥有一行命令的目的(我正在尝试学习更多创造性的方法来避免使用多行或脚本处理相对简单的情况)。而且像@Keith所说的那样,至少导出将保持在脚本范围内。 - MartyMacGyver
@KeithThompson 您的评论教会了我一些新东西。我从未考虑过 shell vs 环境变量。我曾经认为是全局 vs 本地环境变量。现在我知道了正确的术语。 - Alireza Mohamadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接