考虑以下例子:
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
会扩展双引号。来自的输出出现在命令中,这就是$(…)
sed
date
sed
获取. 你的命令
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
双引号字符串更改为\\n
,sed
解释\\
为\
,内壳被单引号括起来\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
您只能传递命令字符串。