将管道值作为参数传递给 xargs 以供 eval echo 使用

将管道值作为参数传递给 xargs 以供 eval echo 使用

我有一个用作模板的文本文件,它看起来像这样:

Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS

我的 bash 脚本设置两个变量,HOSTNAMEand HOSTADDRESS,读取模板文件,然后执行 aneval来扩展$HOSTNAMEand $HOSTADDRESS

HOSTNAME="SH_SQL_0089"
HOSTADDRESS="172.16.3.44"
TEMPLATE=`cat template.txt`
MESSAGE=`eval echo $TEMPLATE`

的结果值为MESSAGE

Hostname     : SH_SQL_0089
Host Address : 172.16.3.44

是否可以将最后两行简化为:

MESSAGE=$(cat template.txt | eval echo ????)

我尝试使用xargs

MESSAGE=$( cat template.txt | xargs -i bash -c "eval echo {}" )

但它只是替换$HOSTNAME并删除了回车符。

我想这样做的原因是我需要添加第三个处理步骤并将eval'd 输出通过管道传输到该步骤。我感觉我已经很接近了。

答案1

首先,xargs不能在这里工作,因为替换将在子进程(启动bash的进程xargs)中执行。但只有环境变量会传递给子进程,而不是 shell 变量。显然HOSTNAME在脚本启动时已经存在于您的脚本环境中,但HOSTADDRESS事实并非如此。此时,您可能会想导出所有变量,但即使这样xargs也不是一个好的解决方案,因为它会遇到许多引用问题 - 如果您的模板包含\"'或者您想保留空格,那么您就完蛋了。

现在,看看您当前的代码:除了引用问题之外,MESSAGE=`eval echo $TEMPLATE`这是一种复杂的编写方式。eval MESSAGE=$TEMPLATE而且你确实有引用问题;例如,您会注意到所有空白都已折叠。你听说过鲍比桌,不是吗? Shell 扩展规则非常复杂,但有一些规则可以让您保持理智:

  • "$foo"变量替换和命令替换始终用双引号引起来"$(bar)"。如果您了解为什么需要省略引号,则可能会违反此规则。
  • 使用$(…)而不是`…`用于命令替换。在反引号形式内引用是晦涩难懂且不可移植的,而在内部引用则$(…)正常。
  • eval仅在枪口下按下时使用。如果这样做,请务必小心,并使其尽可能简单。

那么会出现什么问题呢eval MESSAGE="$TEMPLATE"?它使外壳评估

MESSAGE=Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS

糟糕,我们需要在应该是 的值的部分加上引号MESSAGE。引号必须到达eval,因此它们需要从字面上经历 shell 扩展的第一阶段:eval MESSAGE="\"$TEMPLATE\""

MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS"

更好,但现在您已经有了 Bobby Tables 注入模式 - 如果模板包含引用怎么办?然后你需要转义引号。双引号之间具有特殊含义的四个字符是\"$`,并且您想$保留其含义,因此在其他三个字符之前添加反斜杠。

TEMPLATE=$(sed -e 's/[\\"`]/\\&/g' <template.txt)
eval MESSAGE="\"$TEMPLATE\""

现在 shell 将评估

MESSAGE="Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
Name         : Bobby \"drop\" O'Tables"

一切都很好。

请注意,此处正确的引用是为了防止意外的解析问题;控制模板的人仍然具有 shell 访问权限(使用$(hello))。

如果您希望将模板包含在 shell 脚本中,那么自然可以使用特雷多克

MESSAGE=$(cat <<EOF)
Hostname     : $HOSTNAME
Host Address : $HOSTADDRESS
EOF

但是使用外部模板,您需要执行两步评估,因此使用eval,并且当涉及到 内部的heredocs 之类的时髦内容时,bash 的解析非常有问题eval。肯定有一种可行的方法,至少对于 bash 4 来说是这样,但我不建议冒险。

答案2

您希望 shell 应用变量替换,这意味着模板文本必须由 shell 本身作为命令的一部分读取,但模板也是多行文本,因此进入面向行的文本有点棘手通常由 UNIX 命令完成的处理。

一种方法是使用 bash此处文档(在 shell 提示符下键入的所有命令均显示):

  1. 创建模板文件:

    $ cat template.txt
    Hi, $NAME
    
    Welcome to $PLACE
    
  2. 出口模板文本中应替换的变量:

    $ export NAME=Bob
    $ export PLACE='unix&linux'
    
  3. 创建一个包含单个“换行”\n 字符的 shell 变量;在 bash 中,最简单的方法是打开一个字符串,输入换行符并关闭它:

    $ newline='
    > '
    
  4. 最后,调用 bash 进行替换:

    $ bash -c "cat <<__EOF__${newline}$(cat template.txt)${newline}__EOF__"
    Hi, Bob
    
    Welcome to unix&linux
    

为什么这有效?让我们从底部解构它:您要求 bash 执行命令(通过选项-c),但变量替换${newline}和命令扩展 发生在实际执行$(cat ...)之前的父 shell 中。bash -c ...所以结果是bash -c看到一个嵌入换行符和整个模板文本的命令字符串。就好像您在 bash 提示符下输入了以下内容:

cat <<__EOF__
...contents of template.txt...
__EOF__

另请注意,需要导出替换变量,因为变量是在父 shell 中分配的,但执行替换的是“子”bash。

笔记然而,这种方法很容易导致引用问题:由于模板文本是由 bash shell 解释的,后退学被展开,这可能会导致命令在您的系统上执行。

答案3

eval为了进行实验,您甚至可以(双倍)嵌入单引号字符串:

export HOSTNAME HOSTADDRESS
(
NL='
'
DELIM=$'\177'
esc="'\''"
export IFS=""
HOSTNAME="SH_SQL_00'8'9"
HOSTADDRESS='172.16.3.44 Bobby "drop" O'\''Tables ${PWD} $(ls)'
printf '%s\n' 'Hostname : $HOSTNAME' 'Host Address : $HOSTADDRESS' > template.txt
TEMPLATE="$(<template.txt)"
TEMPLATE="${TEMPLATE//\'/${esc}}"       # escape every single quote: ' --> '\'' (which later will become ''\''' by '${TEMPLATE}')
TEMPLATE="${TEMPLATE//${NL}/${DELIM}}"  # replace every newline char with a delim char
evalstr="echo '${TEMPLATE}'"
#printf '\n%s\n\n' "${evalstr}" | LC_ALL=C vis -fotc
set -xv
eval eval "${evalstr}"
) | LC_ALL=C tr '\177' '\n' | nl

答案4

MESSAGE=$(eval $(cat template.txt))

相关内容