为什么使用命令替换分配变量,然后回显该变量总是失败?

为什么使用命令替换分配变量,然后回显该变量总是失败?

为什么下面的代码在 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 确实会执行将此命令分解成几部分的操作。此操作会删除引号。因此,各个部分如下所示:

  1. 测试
  2. =
  3. 你好世界!
  4. 回声
  5. (空值)

好的,现在 shell 终于完成了替换工作,因此它可以继续下一个任务,即找到要运行的可执行代码。

它注意到命令行以A=B C( 的形式开始,其中A代表变量名,等号指定将为变量分配一个值,B代表要分配的值,然后C是可选部分,代表要运行的命令。

当 shell 注意到命令行符合这一一般模式时,它将要运行的可执行代码就是处理等号的代码。基本上,等号类似于可执行文件名,因为等号最终控制着 shell 将要执行的代码。shell 继续执行代码,将名为 的变量赋值TESTHello world!

然后,完成后,shell 将启动请求的命令,该命令使用上面确定的第 4 和第 5 个部分(其中第 4 部分是命令echo,第 5 部分是空字符串)。

我回答的原因是(呃……我是说,sqrt-1 的答案) 起作用的原因是第二个 && 导致 shell 运行TEST="$(cat test.txt)"代码的返回值,注意是零,然后才继续处理第二个之后的内容&&(因此该echo "$TEST"部分将$TEST处理变量,现在一切正常)。

请注意,sqrt-1 提供的答案显示了一些有点浪费的代码,尽管这可以理解,因为它确实直接回答了提出的问题。它基本上做的是:

首先,将命令分为三个部分:

  • 回显“Hello world!”> test.txt
  • 测试="$(cat 测试.txt)"
  • 回显“$TEST”
  • 由于有两个运算符,只有第二部分(运行并)成功时,第三部分才会运行,而只有第一个命令成功时,第二部分才会运行&&

    另一种变化可能是:

    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.
    

    ShellCheck:SC2097 – 只有分叉进程才能看到此分配。

    答案4

    根据@DavidPostill 提供的链接,我会使用

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
    

    应编辑
    正如许多用户正确指出的那样,我的答案根本就不是一个有用的答案:它让任何人都无法了解该主题的任何内容。
    请参考@TOOGAM 的邮政下面是最好的答案(我的个人观点),并充分揭示了为什么@Peter Mortensen 的代码不起作用,bash 变量扩展如何工作,甚至为什么我使用第二个&&不是最佳选择。

    相关内容