强制bash扩展从文件加载的字符串中的变量


88

我试图找出如何使bash(强制?)扩展字符串(从文件加载)中的变量。

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

hello $FOO world

然后我跑

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

这将返回:

   hello $FOO world

即使设置了变量,它也不会扩展$ FOO。我无法评估或获取文件-因为它将尝试执行该文件(它本身不能执行-我只希望插入包含变量的字符串)。

有任何想法吗?



您是说要对下面的答案发表评论吗?
Michael Neale 2012年

我的意思是给你评论。提出了多个答案,建议eval务必要注意其中的含义。
暂停,直到另行通知。

@DennisWilliamson弄-我的回复有点晚了,但是很好的提示。当然,在随后的几年中,我们遇到了诸如“壳震荡”之类的问题,因此您的评论经受了时间的考验! 小费帽子
Michael Neale

这回答了你的问题了吗?Bash扩展变量中的变量
LoganMzz

Answers:


111

我偶然发现了我认为该问题的答案: envsubst命令。

envsubst < something.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  "$@"

5
+1。这是这是唯一的答案保证不做命令替换,引用删除,参数处理,等等
乔许·凯利

6
它仅适用于完全导出的shell变量(以bash格式)。例如S ='$ a $ b'; 一个=集; 出口b =出口; echo $ S | envsubst; 产量:“出口”
gaoithe

1
谢谢-我认为这些年来,我应该接受这个答案。
Michael Neale's

1
但是,它不能处理美元符号($)的转义,例如,echo '\$USER' | envsubst将输出带有前缀反斜杠not的当前用户名$USER
Rockallite

3
似乎可以在具有自制软件的Mac上获得此功能brew link --force gettext
KeepCalmAndCarryOn 17/12/22

25

许多答案的使用evalecho工作方式都是合理的,但是在各种事情上都存在问题,例如多行代码,试图转义外壳元字符,不打算通过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"
}

例如,您可以像这样使用它,aparameters.cfg实际上是只设置变量的shell脚本,atemplate.txt是使用这些变量的模板:

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

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


4
这是我到目前为止发现的最好的技巧之一:)。根据您的答案,构建一个函数很容易,该函数可以使用包含对其他变量的引用的字符串来扩展变量-只需在函数中将$ data替换为$ 1即可!我相信这是对原始问题BTW的最佳答案。
星系

即使这样也有通常的eval陷阱。尝试; $(eval date)在输入文件中任何行的末尾添加,它将运行date命令。
anubhava

1
@anubhava我不确定你的意思;在这种情况下,实际上就是所需的扩展行为。换句话说,是的,您应该能够执行此操作。
wjl

22

你可以试试

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

9
echo -e "$(eval "echo -e \"`<something.txt`\"")"如果需要保留新行,请使用。
pevik

像魅力一样工作
kyb

1
但仅当您template.txt是文字时。此操作很危险,因为它会执行(评估)命令。
kyb

2
但这删除了双引号
Thomas Decaux

责怪它在子壳上。
ingyhere

8

您不想打印每一行,而是要对其进行评估,以便Bash可以执行变量替换。

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

有关help eval更多信息,请参见或Bash手册。


2
eval是危险的; 您不仅得到了$varname扩展(与一样envsubst),而且得到了扩展$(rm -rf ~)
查尔斯·达菲

4

另一种方法(这似乎很棘手,但我还是把它放在了这里):

将something.txt的内容写入到临时文件中,并在其周围包裹一个echo语句:

something=$(cat something.txt)

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

然后将其返回到变量中:

RESULT=$(source temp.out)

$ RESULT将使所有内容扩展。但这似乎错了!


1
棘手,但最直接,最简单且有效。
kyb

3

如果只希望扩展变量引用(我自己有一个目标),则可以执行以下操作。

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

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


我已经尝试过了,但是$()在我的情况下删除了行尾,这破坏了结果的字符串
moz

我将评估行更改为,eval "echo \"$$contents\""并保留了空白
moz

1

以下解决方法:

  • 允许替换定义的变量

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

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

    ${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

1
  1. 如果something.txt只有一个线,bash方法,(短版的迈克尔·尼尔的“恶心”的答案),使用过程命令替换

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

    输出:

    hello 42 world
    

    请注意,这export不是必需的。

  2. 如果something.txt有一行或多行,则使用GNU valuate方法:sed e

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

1
我发现sed评估最适合我的目的。我需要从模板生成脚本,这些模板的值将从另一个配置文件中导出的变量填充。它可以正确地转义命令扩展,例如放入\$(date)模板以获取$(date)输出。谢谢!
gadamiak

0
$ 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.

0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

1
感谢您提供此代码段,它可能会提供一些有限的即时帮助。通过说明为什么这是一个很好的解决方案,正确的解释将大大提高其长期价值,对于其他存在类似问题的读者来说,这样做将更为有用。请编辑您的答案以添加一些解释,包括您所做的假设。
摩羯座

-1

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

就我而言,我需要替换文件的内容来替换变量名。envsubst要求将内容作为环境变量导出,而bash在导出大于一兆字节左右的环境变量时会出现问题。

awk解决方案

使用来自另一个问题的cuonglm解决方案

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

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


-3

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


这不仅效率低下,而且非常危险。它不仅运行像这样的安全扩展$foo,而且运行像$(rm -rf ~)
查尔斯·达菲
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.