我想将“模板”文件的输出通过管道传输到 MySQL,该文件中散布着 ${dbName}
等变量。替换这些实例并将输出转储到标准输出的命令行实用程序是什么?
更新
这是来自 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 扩展 $((...))、`...` 和 $(...),尽管反斜杠是此处转义字符,但您不必担心解析有错误,并且它可以很好地执行多行。
Sed!
给定模板.txt:
The number is ${i} The word is ${word}
我们只需要说:
sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.txt
感谢 Jonathan Leffler 提供的将多个 -e
参数传递给同一个 sed
调用的提示。
cat
。您只需要 sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text
。
sed
将期待一个转义文本,这很麻烦。
sed -e "s/\${i}/1/" -e "s/\${word}/dog/" template.text | tee newFile
使用 /bin/sh
。创建一个设置变量的小型 shell 脚本,然后使用 shell 本身解析模板。像这样(编辑以正确处理换行符):
文件模板.txt:
the number is ${i}
the word is ${word}
文件脚本.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
命令。如果模板是:“单词是;rm -rf $HOME”,您将丢失文件。
read
命令,如所写,修剪每行的前导和尾随空格并“吃掉”\
个字符。,(c)仅当您完全信任或控制输入时才使用此选项,因为嵌入在输入中的命令替换(`…`
或 $(…)
)允许由于使用 eval
而执行任意命令。最后,echo
将一行的开头误认为是它的命令行选项之一的可能性很小。
考虑到最近的兴趣,我又在考虑这个问题,我认为我最初想到的工具是 m4
,即自动工具的宏处理器。因此,您可以使用以下变量,而不是我最初指定的变量:
$echo 'I am a DBNAME' | m4 -DDBNAME="database name"
envsubst
进行这种简单的变量替换/模板用法,如其他答案中所述。 m4
是一个很棒的工具,但它是一个成熟的预处理器,具有更多的功能和复杂性,如果您只是想替换一些变量,则可能不需要它。
创建 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
$(rm -rf ~)
,则您将其作为代码运行。
eval "echo \"$(cat $1)\""
效果很好!
模板.txt
Variable 1 value: ${var1}
Variable 2 value: ${var2}
数据.sh
#!/usr/bin/env bash
declare var1="value 1"
declare var2="value 2"
解析器.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
`…`
或 $(…)
)允许执行任意命令,因为使用 eval
,并且直接由于使用 source
而执行 shell 代码。此外,输入中的双引号会被悄悄地丢弃,并且 echo
可能会将行首误认为是它的命令行选项之一。
这是一个强大的 Bash 函数,尽管使用了 eval
,但应该可以安全使用。
输入文本中的所有 ${varName}
变量引用都基于调用 shell 的变量进行扩展。
Nothing else 被扩展:既没有 not 包含在 {...}
中的变量引用(例如 $varName
),也没有命令替换($(...)
和旧语法 { 4}),也不是算术替换($((...))
和旧语法 $[...]
)。
要将 $
视为文字,\
- 转义它;例如:\${HOME}
请注意,仅通过标准输入接受输入。
例子:
$ 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
控制字符,因为这些字符。在内部使用 - 因为函数处理 text,这应该是一个安全的假设。
eval
也很安全。
"
!)
${FOO:-bar}
提供默认值,或者仅在设置时才输出一些东西 - ${HOME+Home is ${HOME}}
。我怀疑通过一点扩展它也可以返回缺少变量 ${FOO?Foo is missing}
的退出代码,但如果有帮助,tldp.org/LDP/abs/html/parameter-substitution.html 目前没有这些列表
这是我基于以前答案的 perl 解决方案,替换了环境变量:
perl -p -e 's/\$\{(\w+)\}/(exists $ENV{$1}?$ENV{$1}:"missing variable $1")/eg' < infile > outfile
我建议使用 Sigil 之类的东西:https://github.com/gliderlabs/sigil
它被编译为单个二进制文件,因此在系统上安装非常容易。
然后你可以做一个简单的单行,如下所示:
cat my-file.conf.template | sigil -p $(env) > my-file.conf
这比 eval
更安全,并且比使用正则表达式或 sed
更容易
cat
并改用 <my-file.conf.template
,这样您就可以给 sigil
一个真正的文件句柄而不是 FIFO。
这是一种让 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)" + '"'
'echo "' + "$(cat template.txt)" + '"'
由于替换是在 ", "$(cat template.txt)" 之间变成了 cat template.txt 的输出。
所以 sh -c 执行的命令变成:echo "The number is ${i}\nThe word is ${word}",其中i和word是指定的环境变量。
echo "数字是${i}\n单词是${word}",
其中 i 和 word 是指定的环境变量。
'$(rm -rf ~)'$(rm -rf ~)
,则模板文件中的文字引号将与您在扩展之前添加的引号相匹配。
'$(echo a)'$(echo a)
。它产生 'a'a
。发生的主要事情是正在评估 '
中的第一个 echo a
,这可能不是您所期望的,因为它在 '
中,但与在 "
中包含 '
的行为相同带引号的字符串。
"
引用的字符串(包括 $(...)
)是重点。
${varname}
,而不是其他更高安全风险的扩展。
echo "
,然后是带有文字内容 template.txt
的双引号字符串,然后是另一个文字字符串 "
,全部连接成一个传递给 sh -c
的参数。您是对的,'
无法匹配(因为它被外壳消耗而不是传递给内部),但 "
肯定可以,因此可以执行包含 Gotcha"; rm -rf ~; echo "
的模板.
如果您愿意使用 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
文件.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.
脚本.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
\$(date)
,它将在模板中执行命令替换
while IFS= read -r line; do
作为 read
命令,否则您将删除每个输入行的前导和尾随空格。此外,echo
可能会将行首误认为是它的命令行选项之一,因此最好使用 printf '%s\n'
。最后,双引号 $while IFS= read -r line; do
更安全。
我在想同样的事情时发现了这个线程。它启发了我(小心反引号)
$ echo $MYTEST
pass!
$ cat FILE
hello $MYTEST world
$ eval echo `cat FILE`
hello pass! world
$(cat file)
的 bash 简写是 $(< file)
eval echo "\"$(cat FILE)\""
但这可能仍然不足,因为输入中的双引号被丢弃。
`…`
或 $(…)
)允许执行任意命令,因为使用 eval
。
这里有很多选择,但我想我会把我的扔在堆上。它基于 perl,仅针对 ${...} 形式的变量,将要处理的文件作为参数并将转换后的文件输出到 stdout:
use Env;
Env::import();
while(<>) { $_ =~ s/(\${\w+})/$1/eeg; $text .= $_; }
print "$text";
当然我不是一个真正的 perl 人,所以很容易出现致命的缺陷(虽然对我有用)。
Env::import();
行 - use
暗示导入。另外,我建议不要先在内存中构建整个输出:只需在循环内使用 print;
而不是 $text .= $_;
,然后删除循环后 print
命令。
如果您可以控制配置文件格式,则可以在 bash 本身中完成。您只需要 source (".") 配置文件而不是 subshell。这确保了在当前 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 和瞧,希望你不会破坏你的数据库:-)。
go.bash
)中的 echo 语句,那么您就大错特错了 - 它们不是解决方案的一部分,它们只是一种表明变量设置正确。
使用备份对可能的多个文件进行 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
不起作用。envsubst
,顾名思义,只识别 environment 变量,而不识别 shell 变量。还值得注意的是,envsubst
是一个 GNU 实用程序,因此并非在所有平台上都预装或可用。