我想将一个“模板”文件的输出管道到MySQL中,该文件具有像${dbName}这样的变量。替换这些实例并将输出转储到标准输出的命令行实用程序是什么?

输入文件被认为是安全的,但是可能存在错误的替换定义。执行替换应避免执行意外的代码执行。


如果您愿意使用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

使用/bin/sh.创建一个设置变量的小shell脚本,然后使用shell本身解析模板。如下所示(编辑以正确处理换行):

文件template.txt:

the number is ${i}
the word is ${word}

文件script.sh:

#!/bin/sh

#Set variables
i=1
word="dog"

#Read in template one line at the time, and replace variables (more
#natural (and efficient) way, thanks to Jonathan Leffler).
while read line
do
    eval echo "$line"
done < "./template.txt"

输出:

#sh script.sh
the number is 1
the word is dog

如果可以控制配置文件格式,则可以在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,瞧,希望你不会破坏你的数据库:-)。


Sed!

鉴于template.txt:

The number is ${i}
The word is ${word}

我们只能说:

sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.txt

感谢Jonathan Leffler提供的将多个-e参数传递给同一个sed调用的技巧。


template.txt

Variable 1 value: ${var1}
Variable 2 value: ${var2}

data.sh

#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"

parser.sh

#!/usr/bin/env bash

# args
declare file_data=$1
declare file_input=$2
declare file_output=$3

source $file_data
eval "echo \"$(< $file_input)\"" > $file_output

./parser.sh data.sh template.txt parsed_file.txt

parsed_file.txt

Variable 1 value: value 1
Variable 2 value: value 2

我在想同样的事情时发现了这条线索。这启发了我(注意反节拍)

$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world

鉴于最近的兴趣,我又在考虑这个问题,我认为我最初想到的工具是m4,用于autotools的宏处理器。所以不是我最初指定的变量,你会使用:

$echo 'I am a DBNAME' | m4 -DDBNAME="database name"

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

这是我的解决方案与perl基于以前的答案,替换环境变量:

perl -p -e 's/\$\{(\w+)\}/(exists $ENV{$1}?$ENV{$1}:"missing variable $1")/eg' < infile > outfile

更新

下面是yottatsa关于类似问题的解决方案,它只替换像$VAR或${VAR}这样的变量,并且是一个简短的一行程序

i=32 word=foo envsubst < template.txt

当然,如果i和word在你的环境中,那么它只是

envsubst < template.txt

在我的Mac上,它看起来是作为gettext和MacGPG2的一部分安装的

旧的答案

这里是一个改进的解决方案从mogsie对类似的问题,我的解决方案不需要你的双引号,mogsie的做,但他是一个一行!

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

这两种解决方案的强大之处在于,您只能获得几种正常情况下不会发生的shell扩展类型$((…)),'…',和$(…),虽然反斜杠在这里是一个转义字符,但您不必担心解析有bug,它可以很好地处理多行。


这里有很多选择,但我想把我的扔到堆里。它是基于perl的,只针对形式为${…},将要处理的文件作为参数,并在stdout上输出转换后的文件:

use Env;
Env::import();

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

print "$text";

当然,我不是一个真正的perl人,所以很容易有一个致命的缺陷(对我来说是如此)。


我建议使用像Sigil这样的东西: https://github.com/gliderlabs/sigil

它被编译成一个二进制文件,所以在系统上安装它非常容易。

然后你可以像下面这样简单的一行代码:

cat my-file.conf.template | sigil -p $(env) > my-file.conf

这比eval更安全,比使用regex或sed更容易


这里有一个健壮的Bash函数,尽管使用了eval,但使用起来应该是安全的。

输入文本中的所有${varName}变量引用都是基于调用shell的变量展开的。

没有其他内容被展开:没有包含在{…}(例如$varName)、命令替换($(…)和遗留语法'…')、算术替换($((…))和遗留语法$[…])。

要将$作为一个文字,\-转义它;例如:\ ${回家}

注意,输入只通过stdin被接受。

例子:

$ expandVarsStrict <<<'$HOME is "${HOME}"; `date` and \$(ls)' # only ${HOME} is expanded
$HOME is "/Users/jdoe"; `date` and $(ls)

函数源代码:

expandVarsStrict(){
  local line lineEscaped
  while IFS= read -r line || [[ -n $line ]]; do  # the `||` clause ensures that the last line is read even if it doesn't end with \n
    # Escape ALL chars. that could trigger an expansion..
    IFS= read -r -d '' lineEscaped < <(printf %s "$line" | tr '`([$' '\1\2\3\4')
    # ... then selectively reenable ${ references
    lineEscaped=${lineEscaped//$'\4'{/\${}
    # Finally, escape embedded double quotes to preserve them.
    lineEscaped=${lineEscaped//\"/\\\"}
    eval "printf '%s\n' \"$lineEscaped\"" | tr '\1\2\3\4' '`([$'
  done
}

该函数假设输入中不存在0x1、0x2、0x3和0x4控制字符,因为这些字符。在内部使用—因为函数处理文本,所以这应该是一个安全的假设。


创建rendertemplate.sh:

#!/usr/bin/env bash

eval "echo \"$(cat $1)\""

和template.tmpl:

Hello, ${WORLD}
Goodbye, ${CHEESE}

渲染模板:

$ export WORLD=Foo
$ CHEESE=Bar ./rendertemplate.sh template.tmpl 
Hello, Foo
Goodbye, Bar

下面是一种让shell为您执行替换的方法,就像在双引号之间输入文件的内容一样。

以包含内容的template.txt为例:

The number is ${i}
The word is ${word}

下面的代码行将使shell插入template.txt的内容并将结果写入标准输出。

i='1' word='dog' sh -c 'echo "'"$(cat template.txt)"'"'

解释:

I和word作为环境变量传递给sh的执行。 Sh执行传入的字符串的内容。 相邻的字符串变成一个字符串,这个字符串就是: “echo”+“$(cat template.txt)”+ " ' 由于替换在"之间,"$(cat template.txt)"成为cat template.txt的输出。 因此sh -c执行的命令变成: echo“数字是${i}\n字是${word}”, 其中I和word是指定的环境变量。


在地方perl编辑潜在的多个文件,与备份。

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

我创建了一个名为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


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

带有嵌套模板的示例

创建模板文件,变量是常规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 

解析模板

source template.source && eval echo "$(<template.txt)"

输出

Hello royman!
Nice to have you here royman :)


envsubst

请不要用其他任何东西。不要eval)