【问题标题】:How can I interpret variables on the fly in the shell script?如何在 shell 脚本中即时解释变量?
【发布时间】:2017-11-01 19:03:39
【问题描述】:

我正在使用 JQ 在 shell 脚本中读取 JSON。在这里,我无法即时解释我的 shell 脚本中的变量 $HOME、$HOST、$PEMFILE。

JSON 文件:

{
    "script": {
    "install": "${HOME}/lib/install.sh $HOST $PEMFILE",
    "Setup": "${HOME}/lib/setup.sh $HOST $PEMFILE $VAR1 $VAR2"
    }

}

Shell 脚本:

#!/bin/bash
examplefile="../lib/example.json"
HOST=ec2-..-...-...-...us-west-2.compute.amazonaws.com
PEMFILE=${HOME}/test.pem

installScript=($(jq '.script.install' $examplefile))
bash "$installScript"

有没有一种方法可以在不修改 JSON 的情况下即时解释这些变量?

P.S 我不想使用 eval。

【问题讨论】:

  • 你应该改变 JSON;将任意命令存储为单个字符串是一个坏主意。实际上,没有正确引用要扩展的参数。
  • 您还需要使用bash -c,否则"$installScript"将被视为要执行的脚本的名称。
  • installScript=($(jq '.script.install' $examplefile)) 在这里是一种奇怪的形式——您将字符串结果拆分为一个数组,但是在bash "$installScript" 中您只是扩展了该数组的第一个元素。并且字符串分割在手头的使用中通常是错误的——参见BashFAQ #50
  • ...也就是说 -- bash -c "$(jq -r '.script.install' "$exampleFile")" 可能是你想要的。只要变量被export 分配到环境中,bash 的副本本身就会执行替换——无需提前这样做。 (如果您还没有导出它们,请执行此操作:export HOST PEMFILE 就可以了)。
  • ...oh -- 并且要明确一点:Shell 参数扩展 不完全相同 只是进行字符串替换。例如,假设你有VAR1=$'"hello" * \' * \' $(touch /tmp/i-could-have-deleted-your-files) "world"'——如果你运行echo "$VAR1"* 都不会被文件列表替换,/tmp/i-could-have-deleted-your-files 也不会被创建。另一方面,如果你在将内容作为代码传递之前使用字符串替换——就像你问我们怎么做一样——替换的结果将被解析为语法。

标签: json bash shell unix jq


【解决方案1】:

使用gnu实用程序envsubst很容易:

installScript=$(jq -r '.script.install' "$examplefile" | envsubst)

【讨论】:

  • 这是我目前 +1 的两种方法之一。也就是说,回首往事,我不确定是否应该不加警告地推荐它——在将结果解析为 shell 命令之前用代码替换变量肯定比让 shell 更安全自行更换。
【解决方案2】:

这是使用envgsub 执行替换的解决方案。

请注意,env 要求将变量作为环境变量而不是 shell 变量传递。

#!/bin/bash

examplefile="../lib/example.json"
HOST=ec2-..-...-...-...us-west-2.compute.amazonaws.com
PEMFILE=${HOME}/test.pem

export HOST
export PEMFILE
installScript=$(jq -Mr '
   .script.install | gsub("(?<x>[$][{]?\\w+[}]?)"; env[.x|gsub("[${}]+";"")] )
' $examplefile)

echo $installScript

样本输出

/home/runner/lib/install.sh ec2-..-...-...-...us-west-2.compute.amazonaws.com /home/runner/test.pem

Try it online!

【讨论】:

  • 因为 OP 甚至在编辑之前就指定他们的输入是 JSON(而不是 jq 代码),我不确定原始版本是否对此做出响应。不过修改后的版本很好——也许你可以重新考虑它(把它放在第一位,如果不彻底删除原始版本的话)?
  • 这是有道理的。我已经替换了我原来的建议。谢谢!
  • (竖起大拇指)。顺便说一句,在分配变量之前添加set -a 会使它们自动exported。
【解决方案3】:

具体解决办法

这是针对上述问题的 jq 解决方案,尽管它仅适用于“全局”环境变量。

def substitute:
  gsub("\\${HOME}"; env.HOME)
  | gsub("\\$HOST"; env.HOST)
  | gsub("\\$PEMFILE"; env.PEMFILE)
  | gsub("\\$VAR1"; env.VAR1)
  | gsub("\\$VAR2"; env.VAR2)
  ;

walk( if type=="string" then substitute else . end )

如果您的 jq 还没有 walk/1,请升级您的 jq 或从 https://github.com/stedolan/jq/blob/master/src/builtin.jq 获取 def

上面的解决方案有点脆弱,但它可以很容易地被鲁棒化或泛化,如下一节所示。

一般解决方案

walk(if type == "string"
     then gsub("\\$(?<x>[A-Za-z_][A-Za-z0-9_]+)"; "\(env[.x])") 
          | gsub("\\${(?<x>[A-Za-z_][A-Za-z0-9_]+)}"; "\(env[.x])") 
     else . end)

【讨论】:

    【解决方案4】:
    #!/bin/sh
    
    TMP=$(mktemp /tmp/$$.XXX)
    
    cat<<E_O_F > $TMP
    cat <<EOF
    $(cat so-dollar-variables.json)
    EOF
    E_O_F
    
    . $TMP
    
    /bin/rm "$TMP"
    

    【讨论】:

    • /tmp/$$.txt 很危险——PID 很容易被攻击者预测;控制以非特权用户身份运行的代码(例如,nobody)的人,如果他们知道您的脚本以root 身份运行,则可以创建一堆符号链接,其中潜在/可能的 PID 指向他们想要的根拥有的文件删除/覆盖。
    • 我知道的大多数 mktemp 版本都要求模板至少有六个占位符/填充字符。
    【解决方案5】:

    多年来,我一直断断续续地使用它。我想我终于得到了一个不错的纯 bash 解决方案:使用正则表达式匹配和间接参数替换

    # read the file
    json=$(< file.json)
    echo step 0
    echo "$json"
    
    # set the relevant vars, just plain shell variables
    HOST=_host_
    PEMFILE=_pemfile_
    VAR1=_var1_
    VAR2=_var2_
    
    # replace '$var' forms
    while [[ $json =~ ("$"([[:alnum:]_]+)) ]]; do 
        json=${json//${BASH_REMATCH[1]}/${!BASH_REMATCH[2]}}
    done; 
    echo
    echo step 1
    echo "$json"
    
    # replace '${var}' forms
    while [[ $json =~ ("$""{"([[:alnum:]_]+)"}") ]]; do 
        json=${json//${BASH_REMATCH[1]}/${!BASH_REMATCH[2]}}
    done
    echo
    echo step 2
    echo "$json"
    

    输出

    step 0
    {
        "script": {
        "install": "${HOME}/lib/install.sh $HOST $PEMFILE",
        "Setup": "${HOME}/lib/setup.sh $HOST $PEMFILE $VAR1 $VAR2"
        }
    
    }
    
    step 1
    {
        "script": {
        "install": "${HOME}/lib/install.sh _host_ _pemfile_",
        "Setup": "${HOME}/lib/setup.sh _host_ _pemfile_ _var1_ _var2_"
        }
    
    }
    
    step 2
    {
        "script": {
        "install": "/home/jackman/lib/install.sh _host_ _pemfile_",
        "Setup": "/home/jackman/lib/setup.sh _host_ _pemfile_ _var1_ _var2_"
        }
    
    }
    

    魔法是:

    1. 正则表达式,我在其中捕获了$VARVAR,并且

      [[ $json =~ ("$"([[:alnum:]_]+)) ]]
      # ..........1   2             21
      
    2. 参数替换,这里我搜索字符串"$VAR"并将其替换为间接变量扩展${!VAR}

      ${json//${BASH_REMATCH[1]}/${!BASH_REMATCH[2]}}
      

    【讨论】:

      猜你喜欢
      • 2020-12-21
      • 1970-01-01
      • 2017-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多