为什么下面的代码在 Bash 中不起作用?
# Ensure TEST is unset
export TEST=''
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
由于某种原因,它总是返回一个空白行。
在不同的行上执行此操作将得到预期的结果:
# Ensure TEST is unset
export TEST=''
echo "Hello world" > test.txt
TEST=$(cat test.txt)
echo "$TEST"
Hello world!
我怎样才能在一行中完成这项工作?
我需要这个来解密通用石油气包含特殊字符的字符串的文件,然后我将其传递给Ansible。
ANSIBLE_VAULT_PASSWORD="$( gpg -d ~/gpg_encrypted_vault_password_file 2>/dev/null )" ansible-playbook etc etc etc
出于某种原因,Ansible 开发人员认为将保险库密码存储在纯文本文件中是安全的,并且我不喜欢每次测试时都输入(或复制粘贴)长安全密码,因此使用带有加密文件的 GPG 代理是我能想到的唯一安全的解决方法。
答案1
不要与任何其他现有的答案冲突,但我认为逐步分析可能会有所帮助。(u1686_grawity 的回答看起来准确,但我认为这可能更清楚......)
# ensure TEST is unset export TEST=''
好消息:这可能正如您所期望的那样。# 将第一行变成注释,然后下一行使 TEST 变量具有空字符串(零字节)。
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
shell 注意到了 &&。它将运行代码的第一部分...
echo "Hello world!" > test.txt
然后,它处理 &&。它查看 echo 命令的返回值,发现返回值为零。因此,它可以继续运行第二条命令。第二条命令是:
TEST="$(cat test.txt)" echo "$TEST"
因此,shell 要做的第一件事就是替换变量。这里的“命令替换”就像一个变量,因此“cat text.txt”命令运行。从 shell 的角度来看,该命令现在看起来更像是:
TEST="Hello world!" echo "$TEST"
现在,到目前为止,一切可能看起来都很好,但这里有一个大问题。shell 尚未开始运行 ' TEST="Hello world!"
' 命令。相反,shell 注意到仍然有对 的引用$TEST
,因此 shell 替换了它。
现在,shell 计划运行的命令如下所示:
TEST="Hello world!" echo ""
我想,如果我要详细说明的话,我会指出 shell 确实会执行将此命令分解成几部分的操作。此操作会删除引号。因此,各个部分如下所示:
- 测试
- =
- 你好世界!
- 回声
- (空值)
好的,现在 shell 终于完成了替换工作,因此它可以继续下一个任务,即找到要运行的可执行代码。
它注意到命令行以A=B C
( 的形式开始,其中A
代表变量名,等号指定将为变量分配一个值,B
代表要分配的值,然后C
是可选部分,代表要运行的命令。
当 shell 注意到命令行符合这一一般模式时,它将要运行的可执行代码就是处理等号的代码。基本上,等号类似于可执行文件名,因为等号最终控制着 shell 将要执行的代码。shell 继续执行代码,将名为 的变量赋值TEST
为Hello world!
。
然后,完成后,shell 将启动请求的命令,该命令使用上面确定的第 4 和第 5 个部分(其中第 4 部分是命令echo
,第 5 部分是空字符串)。
我回答的原因是(呃……我是说,sqrt-1 的答案) 起作用的原因是第二个 && 导致 shell 运行TEST="$(cat test.txt)"
代码的返回值,注意是零,然后才继续处理第二个之后的内容&&
(因此该echo "$TEST"
部分将$TEST
处理变量,现在一切正常)。
请注意,sqrt-1 提供的答案显示了一些有点浪费的代码,尽管这可以理解,因为它确实直接回答了提出的问题。它基本上做的是:
首先,将命令分为三个部分:
由于有两个运算符,只有第二部分(运行并)成功时,第三部分才会运行,而只有第一个命令成功时,第二部分才会运行&&
。
另一种变化可能是:
echo "Hello world!" > test.txt && ( TEST="$(cat test.txt)" ; echo "$TEST" )
有了分号,TEST="$(cat test.txt)"
就不会对 ' ' 的结果进行求值来确定 ' echo "$TEST"' will be run. When I see a &&
,我倾向于尝试弄清楚要求值什么,以及为什么这很重要。因此,尽管需要使用括号增加了复杂性,但这种使用分号的变体在我看来实际上可能更容易在心理上处理。
答案2
它不起作用,因为$var
扩展是在命令行运行之前由 shell 执行的,即不是通过‘echo’命令。
同时,VAR=value somecommand
并不是一个常规的赋值——它只是用于将环境变量注入新进程,但在该进程创建之前对 shell 变量没有影响,即它与完全不同VAR=value; somecommand
。
换句话说,它发生的顺序与你的帖子标题描述的顺序相反。首先, (stil-empty)"$TEST"
扩展为""
,然后 echo ""
TEST=...
使用一个它不关心的附加环境变量运行。
由于你的测试命令应该模仿 Ansible 的行为,因此它们实际上应该查看他们的环境变量而不依赖于 shell 的预扩展;类似VAR=value env
或的东西VAR=value printenv VAR
会更合适。
答案3
为什么下面的命令在 bash 中不起作用?
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
如果你运行它ShellCheck – shell脚本分析工具它会告诉你原因:
$ shellcheck myscript
Line 2:
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
^-- SC2097 (warning): This assignment is only seen by the forked process.
>> ^-- SC2098 (warning): This expansion will not see the mentioned assignment.
答案4
根据@DavidPostill 提供的链接,我会使用
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
应编辑
正如许多用户正确指出的那样,我的答案根本就不是一个有用的答案:它让任何人都无法了解该主题的任何内容。
请参考@TOOGAM 的邮政下面是最好的答案(我的个人观点),并充分揭示了为什么@Peter Mortensen 的代码不起作用,bash 变量扩展如何工作,甚至为什么我使用第二个&&
不是最佳选择。