【问题标题】:Forcing bash to expand variables in a string loaded from a file强制 bash 扩展从文件加载的字符串中的变量
【发布时间】:2012-05-27 20:11:28
【问题描述】:

我正在尝试研究如何使 bash(强制?)扩展字符串(从文件中加载)中的变量。

我有一个名为“something.txt”的文件,内容如下:

hello $FOO world

然后我跑

export FOO=42
echo $(cat something.txt)

返回:

   hello $FOO world

即使设置了变量,它也没有扩展 $FOO。我无法评估或获取文件 - 因为它会尝试执行它(它不是可执行的 - 我只想要插入变量的字符串)。

有什么想法吗?

【问题讨论】:

  • 你的意思是评论是为了下面的答案吗?
  • 我的意思是给你的评论。不止一个答案建议使用eval,重要的是要了解其中的含义。
  • @DennisWilliamson gotcha - 我回复得有点晚了,但提示很好。当然,在这几年中,我们遇到了诸如“炮弹休克”之类的事情,因此您的评论经受住了时间的考验! 提示帽子
  • 这能回答你的问题吗? Bash expand variable in a variable

标签: linux bash shell unix


【解决方案1】:

我偶然发现了这个问题的答案:envsubst 命令。

envsubst < something.txt

示例:替换文件 source.txt 中的变量并将其写入destination.txt 以供进一步处理

envsubst < "source.txt" > "destination.txt"

如果它在您的发行版中不可用,它位于 GNU 包gettext

@Rockallite

  • 我编写了一个小包装脚本来处理“$”问题。

(顺便说一句,envsubst 有一个“功能”,解释于 https://unix.stackexchange.com/a/294400/7088 只扩展输入中的一些变量,但我 同意转义异常要方便得多。)

这是我的脚本:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"

【讨论】:

  • +1。这是保证不进行命令替换、引号删除、参数处理等的唯一答案。
  • 它只适用于完全导出的 shell 变量(在 bash 中)。例如S='$a$b';一个=设置;出口 b=出口;回声 $S |envsubst;产量:“出口”
  • 谢谢 - 我想这么多年过去了,我应该接受这个答案。
  • 但是,它不处理美元符号 ($) 转义,例如echo '\$USER' | envsubst 将输出带有前缀反斜杠的当前用户名,而不是 $USER
  • 似乎可以在需要自制软件的 Mac 上获得此功能brew link --force gettext
【解决方案2】:

另一种方法(看起来很恶心,但我还是把它放在这里):

将 something.txt 的内容写入临时文件,并在其周围加上 echo 语句:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

然后将其返回到变量中:

RESULT=$(source temp.out)

$RESULT 将把它全部展开。但是好像大错特错!


不需要临时文件的单行解决方案:

RESULT=$(source <(echo "echo \"$(cat something.txt)\""))
#or
RESULT=$(source <(echo "echo \"$(<something.txt)\""))

【讨论】:

  • 很糟糕,但它最直接、最简单且有效。
【解决方案3】:
expenv () {
    LF=$'\n'
    echo "cat <<END_OF_TEXT${LF}$(< "$1")${LF}END_OF_TEXT" | bash
    return $?
}

expenv "file name"

【讨论】:

  • 代码转储不能提供好的答案。您应该解释如何以及为什么这可以解决他们的问题。我推荐阅读,How do I write a good answer?
【解决方案4】:
  1. 如果 something.txt 只有 一个 行,bash 方法,(Michael Neale's "icky" answer 的较短版本), 使用进程 & 命令替换

    FOO=42 . <(echo -e echo $(<something.txt))
    

    输出:

    hello 42 world
    

    请注意,export 不是必需的。

  2. 如果 something.txt 有一行或多行,则 GNU sed e评估方法:

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    

【讨论】:

  • 我发现sed 评估最适合我的目的。我需要从模板生成脚本,这些脚本的值是从另一个配置文件中导出的变量中填充的。它可以正确处理命令扩展的转义,例如将\$(date) 放入模板中以在输出中获得$(date)。谢谢!
【解决方案5】:
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

【讨论】:

  • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请edit您的回答添加一些解释,包括您所做的假设。
【解决方案6】:

envsubst 是一个很好的解决方案(请参阅 LenW 的回答),如果您要替换的内容是“合理”的长度。

在我的例子中,我需要替换文件的内容来替换变量名。 envsubst要求将内容导出为环境变量,而bash在导出超过1兆字节左右的环境变量时会出现问题。

awk 解决方案

在另一个问题中使用 cuonglm 的 solution

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

此解决方案甚至适用于大文件。

【讨论】:

    【解决方案7】:
    $ eval echo $(cat something.txt)
    hello 42 world
    $ bash --version
    GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
    Copyright (C) 2007 Free Software Foundation, Inc.
    

    【讨论】:

      【解决方案8】:

      以下作品:bash -c "echo \"$(cat something.txt)"\"

      【讨论】:

      • 这不仅效率低下,而且极其危险;它不仅可以运行像 $foo 这样的安全扩展,还可以运行像 $(rm -rf ~) 这样的不安全扩展。
      【解决方案9】:

      以下解决方案:

      • 允许替换已定义的变量

      • 保留未定义的变量占位符不变。这在自动部署期间特别有用。

      • 支持以下格式的变量替换:

        ${var_NAME}

        $var_NAME

      • 报告哪些变量未在环境中定义并针对此类情况返回错误代码

      
      
          TARGET_FILE=someFile.txt;
          ERR_CNT=0;
      
          for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
            VAR_VALUE=${!VARNAME};
            VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
            VAR_VALUE2=${!VARNAME2};
      
            if [ "xxx" = "xxx$VAR_VALUE2" ]; then
               echo "$VARNAME is undefined ";
               ERR_CNT=$((ERR_CNT+1));
            else
               echo "replacing $VARNAME with $VAR_VALUE2" ;
               sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
            fi      
          done
      
          if [ ${ERR_CNT} -gt 0 ]; then
              echo "Found $ERR_CNT undefined environment variables";
              exit 1 
          fi
      

      【讨论】:

        【解决方案10】:

        如果您只想扩展变量引用(我自己的目标),您可以执行以下操作。

        contents="$(cat something.txt)"
        echo $(eval echo \"$contents\")
        

        ($contents 周围的转义引号是这里的关键)

        【讨论】:

        • 我已经尝试过这个,但是 $() 在我的情况下会删除行尾,这会破坏结果字符串
        • 我将 eval 行更改为 eval "echo \"$$contents\"" 并保留了空格
        【解决方案11】:

        许多使用evalecho 的答案都可以工作,但会在各种事情上中断,例如多行,试图转义shell 元字符,在不打算通过bash 扩展的模板内转义,等等

        我遇到了同样的问题,并编写了这个 shell 函数,据我所知,它可以正确处理所有内容。由于 bash 的命令替换规则,这仍然只会从模板中删除尾随换行符,但只要其他所有内容保持不变,我从未发现这是一个问题。

        apply_shell_expansion() {
            declare file="$1"
            declare data=$(< "$file")
            declare delimiter="__apply_shell_expansion_delimiter__"
            declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
            eval "$command"
        }
        

        例如,您可以将它与 parameters.cfg(实际上是一个只设置变量的 shell 脚本)和 template.txt(使用这些变量的模板)一起使用:

        . parameters.cfg
        printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt
        

        在实践中,我将其用作一种轻量级模板系统。

        【讨论】:

        • 这是迄今为止我发现的最好的技巧之一 :)。根据您的回答,构建一个使用包含对其他变量的引用的字符串扩展变量的函数非常容易——只需在您的函数中将 $data 替换为 $1 ,我们开始吧!我相信这是对原始问题的最佳答案,顺便说一句。
        • 即使这样也有常见的eval 陷阱。尝试在输入文件中任何行的末尾添加; $(eval date),它将运行date 命令。
        • @anubhava 我不确定你的意思;在这种情况下,这实际上是所需的扩展行为。换句话说,是的,你应该可以用这个来执行任意的 shell 代码。
        【解决方案12】:

        你可以试试

        echo $(eval echo $(cat something.txt))
        

        【讨论】:

        • 如果您需要保留新行,请使用echo -e "$(eval "echo -e \"`&lt;something.txt`\"")"
        • 像魅力一样工作
        • 但前提是您的 template.txt 是文本。 此操作很危险,因为它会执行(评估)命令。
        • 但这删除了双引号
        • 归咎于子shell。
        【解决方案13】:

        你不想打印每一行,你想评估它以便 Bash 可以执行变量替换。

        FOO=42
        while read; do
            eval echo "$REPLY"
        done < something.txt
        

        有关详细信息,请参阅 help eval 或 Bash 手册。

        【讨论】:

        • eval is dangerous;您不仅可以扩展$varname(就像使用envsubst 一样),还可以扩展$(rm -rf ~)
        猜你喜欢
        • 1970-01-01
        • 2013-01-29
        • 1970-01-01
        • 2015-11-21
        • 2016-08-22
        • 1970-01-01
        • 2013-06-02
        • 2017-10-11
        • 1970-01-01
        相关资源
        最近更新 更多