如何在 Bash 环境中将 sed 命令分解为嵌套部分?

如何在 Bash 环境中将 sed 命令分解为嵌套部分?

我在 CentOS Bash 环境中工作,并且有一个很长的sed命令,作为一个不是专业系统管理员并且sed可能两年使用一次的人,我发现有点“令人困惑”,至少作为一个长命令:

read new_email_address
sed -i 's/$to = ".*";$/$to = "'"$new_email_address"'";/' FILE

我想将sed命令分解为类似以下嵌套伪代码的内容:

sed -i
    '
        s/
            $to = ".*";$
        /
            $to = "'"$new_email_address"'";
        /g
    '
FILE_PATH

答案1

我会用perl这里。-i是一个非标准选项,一些sed实现已经复制了它,perl但它不可移植。使用sed这种方式也是一个命令注入漏洞,因为内容$new_email_address最终被解释为sed代码(GNU 语言中有一些命令sed可以运行任意命令,例如尝试/;ereboot;#在该read提示符下输入)。

IFS= read -r new_email_address

REPLACEMENT="$new_email_address" perl -pi -e '
  s{
     (\$to \s* = \s* ") .* (" \s* ; \s* )$
   }{$1$ENV{REPLACEMENT}$2}gx
 ' FILE

perl

  • 您可以使用s{...}{...}flagsextra ,s/.../.../flags这样可以更轻松地查看匹配对(并且只要它们匹配,就允许在内部使用 , ){}
  • 使用该x标志,您可以在正则表达式内添加空格(甚至注释),以使其更易于阅读(请注意,这些空格不是正则表达式的一部分,而是\s*匹配任意数量的空格)。
  • 您可以安全地在替换中使用任何任意字符串,甚至可以通过环境变量传递包含&、 、反斜杠或换行符的字符串。/
  • 只要您不使用-C// -Mlocale...-Mopen=locale选项,perl就在字节级别工作,因此.*即使输入在语言环境中未形成有效文本,它也永远不会失败匹配。
  • 与某些sed实现相反,perl对行长度没有限制(可用内存除外),并且不会因包含 NUL 字节或不以换行符结尾的输入而阻塞。

为了在替换部分中也允许空格,您可以添加e导致替换为perl代码的标志:

REPLACEMENT="$new_email_address" perl -pi -e '
  s{
     (\$to \s* = \s* ") .* (" \s* ; \s* )$
   }{
     $1 . $ENV{REPLACEMENT} . $2
   }gxe
 ' FILE

例如。还要记住,使用readwithout 设置$IFS和 without-r很少有意义。

答案2

不确定它是否对您来说更具可读性,但您可以sed首先使用构建表达式printf,然后将其与以下方式一起使用sed

sed_expr=$(printf 's/$to = ".*";$/$to = "%s";/' "$new_email_address")
sed -i "$sed_expr" FILE

这样,在我看来,可以更容易地了解 sed 的整体功能,以及输入在其中到底扮演什么角色。

答案3

引用的命令类似于我在最近的一个回答中写道(已-i添加):

sed 's/$to = ".*";$/$to = "'"$new_email_address"'";/' file

这是sed通过单个编辑命令调用的。表达式中使用的命令seds命令,它执行替换,即将与正则表达式匹配的内容替换为其他内容。

该命令的一般形式srange s/pattern/replacement/flags。在我们在这里处理的命令中,没有range表达式(该s命令应用于全部输入文本中的行),并且没有flags.因此,我们sed在通用表单上有一个编辑脚本

s/pattern/replacement/

正如您在命令中看到的,该pattern位是

$to = ".*";$

$to = "此模式与后跟的文字文本匹配任何事物(任何字符的任何长度的序列),后跟文字文本";$at the end 强制最后一位在行";的最末端匹配。

然后我们就有了replacement

由于您想要替换为取决于 shell 变量值的内容,因此我们必须暂时脱离单引号字符串(即表达式)sed。我们在之后执行此操作

$to = "

在替换中。 shell 变量的值new_email_address被插入,并用双引号正确引用,以便 shell 不会将其拆分为空格或对其值执行文件名通配。

插入值后,我们以以下命令结束命令replacement部分s

";

这是完整的replacement字段,$to = "后跟一些值(新的电子邮件地址),然后是";

因此,将其分解并明确该命令的每一位的作用和含义:

命令结构sed

    sed 's/$to = ".*";$/$to = "'"$new_email_address"'";/' file
         s/  pattern   /          replacement          /

sed组成shell 中表达式的字符串位:

    sed 's/$to = ".*";$/$to = "'"$new_email_address"'";/' file
         ^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^
         single-quoted string    double-quoted       final single-quoted bit
                                 string for shell
                                 variable expansion

以上以更示意性的方式

    sed 'something here'"$variable_value_here"'ending here' file

something here位恰好以双引号结尾,并且该ending here位恰好以双引号开头。

答案4

保持引号分开的一种方法是通过使用多个 -e sed 代码来分隔搜索和替换来破坏 sed 命令。

q=\"; # a double quote character
sed -i \
    -e '/$to = ".*";$/c\'  \
    -e "\$to = $q$new_email_address$q;" \
FILE

sed -i \
    -e '/$to = ".*";$/!b' \
    -e "s//\$to = $q$new_email_address$q;/" \
FILE;

相关内容