如何在文本文件中替换 ${} 占位符?

231
我想将一个包含像${dbName}这样的变量的“模板”文件的输出导入到MySQL中。有什么命令行工具可以替换这些实例并将输出转储到标准输出?输入文件被认为是安全的,但可能存在错误的替换定义。执行替换应避免执行意外的代码执行。
19个回答

5

如果您愿意使用Perl,那将是我的建议。虽然可能有一些sed和/或AWK专家知道如何更轻松地完成此操作。如果您有一个更复杂的映射,不仅仅是用于替换dbName,您可以很容易地扩展它,但在那时,您也可以将其放入标准的Perl脚本中。

perl -p -e 's/\$\{dbName\}/testdb/s' yourfile | mysql

一个简短的Perl脚本,用于处理稍微复杂一些的内容(处理多个键):
#!/usr/bin/env perl
my %replace = ( 'dbName' => 'testdb', 'somethingElse' => 'fooBar' );
undef $/;
my $buf = <STDIN>;
$buf =~ s/\$\{$_\}/$replace{$_}/g for keys %replace;
print $buf;

如果您将上述脚本命名为replace-script,则可以按以下方式使用它:
replace-script < yourfile | mysql

1
适用于单个变量,但如何为其他变量包括“或”? - Dana the Sane
2
你可以使用Perl完成这个任务,具体取决于你想要多么复杂和/或安全。更复杂的示例可以在这里找到:http://www.perlmonks.org/?node_id=718936 - Beau Simensen
3
使用Perl比尝试使用Shell更加简洁。花时间使其正常工作,而不是尝试使用其他基于Shell的解决方案。 - jdigital
1
最近也遇到了类似的问题。最终我选择了perl(envsubst看起来很有前途,但是控制起来太难了)。 - sfitts

4

file.tpl:

The following bash function should only replace ${var1} syntax and ignore 
other shell special chars such as `backticks` or $var2 or "double quotes". 
If I have missed anything - let me know.

script.sh:

template(){
    # usage: template file.tpl
    while read -r line ; do
            line=${line//\"/\\\"}
            line=${line//\`/\\\`}
            line=${line//\$/\\\$}
            line=${line//\\\${/\${}
            eval "echo \"$line\""; 
    done < ${1}
}

var1="*replaced*"
var2="*not replaced*"

template file.tpl > result.txt

2
这种方法不安全,因为如果模板中有一个以反斜杠开头的命令替换,例如\$(date),它将会被执行。 - Peter Dolberg
1
除了Peter的有效观点之外:我建议您使用while IFS= read -r line; do作为read命令,否则您将从每个输入行中剥离前导和尾随空格。此外,echo可能会将一行的开头误认为是其命令行选项之一,因此最好使用printf '%s\n'。 最后,最好对${1}进行双引号。 - mklement0

3
我在寻找答案时发现了这个帖子。它启发了我做出以下的尝试(注意反引号的使用)。
$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world

5
一个简化$(cat file)的Bash快捷方式是 $(< file) - glenn jackman
3
显然,这种方法会干扰换行符,也就是说,我的文件输出时变成了一整行。 - Arthur Corenzan
@ArthurCorenzan:确实,换行符会被替换为空格。要解决这个问题,你需要使用 eval echo "\"$(cat FILE)\"",但是输入中的双引号可能仍然会被丢弃。 - mklement0
正如其他地方所指出的那样:仅在您完全信任或控制输入时使用此功能,因为嵌入在输入中的命令替换(\…` $(…))由于使用了 eval`,允许执行任意命令。 - mklement0
请参考以下链接:https://dev59.com/7XE85IYBdhLWcg3wKwKD#17030906 - jetnet

2

这里有很多选择,但我想分享我的。它是基于Perl的,只针对形如${...}的变量,将要处理的文件作为参数,并将转换后的文件输出到标准输出:

use Env;
Env::import();

while(<>) { $_ =~ s/(\${\w+})/$1/eeg; $text .= $_; }

print "$text";

当然,我并不是真正的Perl专家,所以很可能存在致命缺陷(但对我来说可行)。

1
运行良好。您可以删除 Env::import(); 行 - 导入已经被 use 隐含了。此外,我建议不要先在内存中构建整个输出:只需在循环内使用 print; 而不是 $text .= $_;,并删除循环后的 print 命令。 - mklement0

1

如果您可以控制配置文件的格式,那么可以在bash本身中完成此操作。您只需要通过源代码(".")来读取配置文件,而不是作为子shell来执行它。这可以确保变量是在当前shell的上下文中创建(并继续存在),而不是在子shell中(变量在子shell退出时会消失)。

$ cat config.data
    export parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA
    export parm_user=pax
    export parm_pwd=never_you_mind

$ cat go.bash
    . config.data
    echo "JDBC string is " $parm_jdbc
    echo "Username is    " $parm_user
    echo "Password is    " $parm_pwd

$ bash go.bash
    JDBC string is  jdbc:db2://box7.co.uk:5000/INSTA
    Username is     pax
    Password is     never_you_mind

如果您的配置文件不能是一个shell脚本,您可以在执行之前进行“编译”,这样就可以了(编译取决于输入格式)。
$ cat config.data
    parm_jdbc=jdbc:db2://box7.co.uk:5000/INSTA # JDBC URL
    parm_user=pax                              # user name
    parm_pwd=never_you_mind                    # password

$ cat go.bash
    cat config.data
        | sed 's/#.*$//'
        | sed 's/[ \t]*$//'
        | sed 's/^[ \t]*//'
        | grep -v '^$'
        | sed 's/^/export '
        >config.data-compiled
    . config.data-compiled
    echo "JDBC string is " $parm_jdbc
    echo "Username is    " $parm_user
    echo "Password is    " $parm_pwd

$ bash go.bash
    JDBC string is  jdbc:db2://box7.co.uk:5000/INSTA
    Username is     pax
    Password is     never_you_mind

在您的特定情况下,您可以使用类似以下的代码:
$ cat config.data
    export p_p1=val1
    export p_p2=val2
$ cat go.bash
    . ./config.data
    echo "select * from dbtable where p1 = '$p_p1' and p2 like '$p_p2%' order by p1"
$ bash go.bash
    select * from dbtable where p1 = 'val1' and p2 like 'val2%' order by p1

然后将 go.bash 的输出导入到 MySQL 中,完成了,希望你不会破坏你的数据库 :-).


1
你不必从config.data文件中导出变量;仅设置它们就足够了。你似乎也没有在任何时候读取模板文件。或者,也许模板文件被修改并包含了“echo”操作...或者我漏掉了什么? - Jonathan Leffler
1
关于导出的观点很好,我默认这样做,以便它们可用于子shell,并且不会造成任何伤害,因为它们在go退出时会死亡。 “模板”文件是脚本本身,带有其echo语句。没有必要引入第三个文件-它基本上是一种邮件合并类型的操作。 - paxdiablo
1
“脚本本身及其回显语句”不是模板,而是脚本。考虑以下代码的可读性(和可维护性)差异: <xml type="$TYPE"> 和 echo '<xml type="'$TYPE'">' - Pierre-Olivier Vares
1
@Pierre,我的配置脚本中没有回显语句,它们只是导出语句,并且我已经展示了如何通过最小量的预处理来避免这种情况。如果你在谈论我其他脚本(比如go.bash)中的回显语句,那么你可能误解了 - 它们不是解决方案的一部分,只是一种展示变量被正确设置的方式。 - paxdiablo
1
@paxdiablo:看起来你忘记了问题:<<我想将“模板”文件的输出导入MySQL>>。因此,使用模板是问题,而不是“误解问题”。在另一个脚本中导出变量并将其回显根本没有回答问题。 - Pierre-Olivier Vares
@Pierre,我建议重新阅读问题:什么是命令行实用程序来替换这些实例并将输出转储到标准输出? 这正是我的答案所做的,也是许多其他人在这里所做的,包括被OP接受的答案。 但我怀疑我不会改变你的想法,我敢肯定你也不会改变我的想法 :-) 所以我们可能会把它留在那里。 - paxdiablo

0

对我来说,这是最简单和最强大的解决方案,您甚至可以使用相同的命令eval echo "$(<template.txt)包含其他模板:

嵌套模板示例

  1. 创建模板文件,变量采用常规bash语法${VARIABLE_NAME}$VARIABLE_NAME

在模板中必须使用\转义特殊字符,否则它们将被eval解释。

template.txt

Hello ${name}!
eval echo $(<nested-template.txt)

nested-template.txt

Nice to have you here ${name} :\)

创建源文件。

template.source

declare name=royman 
  1. 解析模板
source template.source && eval echo "$(<template.txt)"
  1. 输出结果
Hello royman!
Nice to have you here royman :)


0

使用 Perl 在原位编辑多个文件,并备份。

  perl -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : ""/eg' \
    -i.orig \
    -p config/test/*

-1

使用 envsubst

请不要使用其他任何东西(例如,请勿使用 eval

简单用法:

export name="Dana the Sane"

创建带有占位符的模板(template.txt):
Hello ${name}

将占位变量安全地替换。将模板输入到envsubst的标准输入中。
envsubst < template.txt
Hello Dana the Sane

请注意,命令envsubst < template.txt > template.txt不起作用。重定向会在读取之前将输出文件清零。

现在,使用envsubst的解决方案已经被接受为答案。 - Dana the Sane
eval是bash文档中某些用例的推荐方法。 - Eugen Rieck
还有呢?@EugenRieck 这不是最好的方法,因为它不安全。不要这样做。 - ocodo
只有在不安全的情况下使用才会不安全。当然,经验不足的开发人员可能会倾向于说它“只是不安全”。BASH文档推荐使用它,但你可能缺乏安全使用的技能。在这种情况下,envsubst就是你唯一剩下的选择 - 如果你唯一能够使用的工具是锤子,那么所有事物都会看起来像钉子。 - Eugen Rieck
Bash文档并没有明确推荐eval而不是envsubst。也许作为一名初级开发者,你会认为提到eval就是一种推荐。也许你可以为我们所有人引用这个推荐。 - ocodo
显示剩余2条评论

-1

我创建了一个名为shtpl的shell模板脚本。我的shtpl使用类似于jinja的语法,现在我经常使用ansible,对此非常熟悉:

$ cat /tmp/test
{{ aux=4 }}
{{ myarray=( a b c d ) }}
{{ A_RANDOM=$RANDOM }}
$A_RANDOM
{% if $(( $A_RANDOM%2 )) == 0 %}
$A_RANDOM is even
{% else %}
$A_RANDOM is odd
{% endif %}
{% if $(( $A_RANDOM%2 )) == 0 %}
{% for n in 1 2 3 $aux %}
\$myarray[$((n-1))]: ${myarray[$((n-1))]}
/etc/passwd field #$n: $(grep $USER /etc/passwd | cut -d: -f$n)
{% endfor %}
{% else %}
{% for n in {1..4} %}
\$myarray[$((n-1))]: ${myarray[$((n-1))]}
/etc/group field #$n: $(grep ^$USER /etc/group | cut -d: -f$n)
{% endfor %}
{% endif %}


$ ./shtpl < /tmp/test
6535
6535 is odd
$myarray[0]: a
/etc/group field #1: myusername
$myarray[1]: b
/etc/group field #2: x
$myarray[2]: c
/etc/group field #3: 1001
$myarray[3]: d
/etc/group field #4: 

更多关于我的GitHub的信息


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接