sed,将捕获组值传递给子命令

sed,将捕获组值传递给子命令

考虑以下例子:

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=\2/"

输出

abc=123

即与输入相同。我们将符号之前的所有内容匹配=为捕获组 1,将符号之后的所有内容匹配=为捕获组 2。这只是为了解释示例。

我可以像这样从 sed 调用子命令:

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$(date)/"

生产:

abc=Tue Nov 17 08:35:13 PM CET 2020

所以我可以调用零元函数来进行替换。

问题:如果我想获取捕获组内容并在其上调用一些命令以获得实际替换,该怎么办?说:

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$('\2 * 2' | bc)/"

生产:

bash: \2 * 2: command not found
abc=123

我需要捕获一些文本,并在其上运行一些命令来获取替换。该怎么做?

答案1

$(date)不会“调用来自的子命令”。shell 在运行之前sed会扩展双引号。来自的输出出现在命令中,这就是$(…)seddatesed 获取. 你的命令

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$(date)/"

变成了这样:

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=Tue Nov 17 08:35:13 PM CET 2020/"

然后才sed开始。使用这种方法,您无法将任何东西从其sed自身传递到date(或bc,或任何其他东西),因为内部在开始$(…)之前就已完成sed

标准sed无法满足您的要求。GNUsed可以感谢e标志。让我们用 GNUsed和构建一个示例bc

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/printf '%s' '\1='; printf '%s\n' '\2 * 2' | bc/e"
#                                      ----------------------------------------------

不带e下划线的片段将是替换。带下划线的片段e执行在 shell 中,其输出将成为替代。

\n之所以存在,是因为在我的测试中,bc不完整的行无法工作。请注意,这\n是由sed它自己解释的,printf将获得一个实际的换行符。需要解释的一点printf\n%s\\\\n这是因为您当前的 shell 将\\\\n双引号字符串更改为\\nsed解释\\\,内壳被单引号括起来\n,然后才printf获得\n(比 中的级别更多)这个问题,但类似)。\n通过解释sed不会破坏命令,因此您可以坚持使用这种简单的形式。

abc=如果您可以保留零件完好无损并仅替换 ,那就太好了123。为此,后向功能很有用,但是sed不支持答案之一链接的问题建议在替换字符串中使用捕获组和反向引用,这正是我们所做的\1


以这种方式运行 shell 命令的一个大问题是存在代码注入漏洞。在即将成为 shell 命令的地方,我们用单引号括住\1\2。如果sed用包含内壳的字符串替换其中任何一个,'将过早关闭引号。尝试以下命令(部分sed与上面相同):

echo "abc=123'; rm -i /very/important/file'" \
 | sed "s/\([^=]*\)=\(.*\)/printf '%s' '\1='; printf '%s\n' '\2 * 2' | bc/e"

(我以防rm -i万一你/very/important/file的系统中确实有。)

您需要确保输入不会注入代码。或者您需要重写正则表达式([^=]*.*部分),这样可能危险的'就无法从任意输入到 shell 命令中。请注意,如果我们用单引号括住整个sed表达式并用双引号括住\1和,\2则双引号将到达内壳,然后输入中的"(非)将是危险的,还有,等等!如果我们在内壳的上下文中保留或取消引用,那么情况会更糟。'$var$(code)\1\2

一般来说,应该将任意数据作为命令行参数传递给 shell,而不是通过命令字符串。例如find -exec 一个人可以做对这件事。除非实际的(静态)shell 命令执行了位置参数或错误处理了某些东西,否则代码注入就不可能了。不幸的是,从 GNU 调用 shell 时,sed您只能传递命令字符串。

相关内容