Sed 命令删除“\”导致“*** multiple target patterns. Stop.”错误

Sed command to delete "\" which causes "*** multiple target patterns. Stop." error

在文件中,我有这样的行 -

a.lo a.o: abc/util.c \
/usr/lib/def.h
b.lo b.o: hash/imp.h \
/usr/lib/toy.c \
c.lo c.o: high/scan.c \
high/scan_f.c

在这里您可以在第 4 行 (/usr/lib/toy.c ) 的末尾看到一个额外的 \(反斜杠)。如何使用 sed 命令删除此 /(反斜杠)?因此,我得到“*** 多个目标模式。停止。”错误。

P.S。 - 我在我的文件中的多个地方有这个额外的 \(反斜杠)。因此使用 sed 按行号删除它是不可行的。需要一些可以检查 .lo .o 并检查之前的行的东西,如果它找到 \(反斜杠)将其删除。

我同意@G.M。一般情况下,但这会起作用。

sed 在以“\”开头的行上的尾随“\”(如果存在)之前捕获文本,并仅在这些行上打印该文本。当然也打印所有其他文本

sed -e 's/\(.* \)\$//' input_file

也许不是最简单的,但应该可以:

sed -nE '${s/\$//;p;};N;s/\([^\]*:)//;P;D' input_file

主要思想是在模式space(一个sed内部文本缓冲区)中连接输入行,这样它总是包含2个连续的行,由一个换行符。然后我们只删除 : 之前的最后一个 \,如果有的话,打印 2 行中的第一行并将其从模式 space 中删除,然后再继续下一行。

sed 命令由半列 (;) 分隔,并用大括号 ({...}) 分组。它们前面可选地有行规范,例如 $ 代表输入的最后一行。因此,在我们的例子中,${s/\$//;p;} 仅适用于最后一行,而其余 (N;s/\([^\]*:)//;P;D) 适用于所有行。

  • -n 选项抑制默认输出。我们需要它来使用 p(打印)命令自己控制输出。

  • -E 选项允许使用扩展的正则表达式。

让我们先解释一下棘手的部分:N;s/\([^\]*:)//;P;D。它是 4 个命令的列表,每行输入 运行,因为在命令之前没有行规范。

  • sed 开始处理输入时,模式 space 已经包含第一行(在您的示例中为 a.lo a.o: abc/util.c \)。这就是 sed 的工作方式:默认情况下,它将当前行置于模式 space 中,应用命令并从下一行重新开始。

  • N 将下一个输入行 (/usr/lib/def.h) 附加到模式 space 并使用换行符作为分隔符。模式 space 现在包含:

      a.lo a.o: abc/util.c \
      /usr/lib/def.h
    

    N 也会增加当前行号,变成 2。

  • s/\([^\]*:)// 删除模式 space 中第一个 : 之前的最后一个 \,如果有的话。在我们的示例中,唯一的 \ 之后第一个 :。模式 space 未修改。

  • P 打印模式的第一部分 space,直到第一个换行符。在我们的示例中,打印的是:

      a.lo a.o: abc/util.c \
    
  • D 删除模式 space 的第一部分,直到第一个换行符(刚刚打印的内容)。模式 space 包含:

      /usr/lib/def.h
    

    D 也开始一个新的循环,但与正常的 sed 处理不同,它不读取下一行并保留模式 space 和当前行号未修改。所以当重新启动模式时 space 包含行号 2 而当前行号仍然是 2.

通过归纳,我们看到,每次 sed 重新开始执行命令列表时,模式 space 都会正常包含当前行。在处理示例的第 4 行时,它包含:

/usr/lib/toy.c \

N之后包含:

/usr/lib/toy.c \
c.lo c.o: high/scan.c \

然后,替换命令 (s/\([^\]*:)//) 匹配并删除第一个 \:

/usr/lib/toy.c 
c.lo c.o: high/scan.c \

因此:

/usr/lib/toy.c 

打印并从模式中删除的 space。正是你想要的。

最后一行需要特殊处理。当我们开始处理它时,模式 space 包含:

high/scan_f.c

如果我们不做任何特殊的事情 N 不改变它(没有下一行要连接)并终止处理。最后一行从不打印。

这就是为什么需要另一个命令列表的原因,就在最后一行:${s/\$//;p;}。它仅适用于最后一行,因为它前面有行规范($ 表示最后一行)。列表中的第一个命令(替代 s/\$//)删除尾随 \,如果有的话。第二个 (p) 打印模式 space.

注意:如果您知道最后一行不以反斜杠结尾,您可以稍微简化一下:

sed -nE '$p;N;s/\([^\]*:)//;P;D' input_file

关于如何识别应从中删除尾随反斜杠的行的问题有点不清楚,但由于输入看起来像一组 makefile 格式的先决条件列表,其中一些行已被删除,我以 objective 为删除列表中最后一个(剩余)先决条件之后出现的反斜杠。这需要向前看下一行,因此在向前看时利用 sedhold space 来存储数据会很有帮助下一行找出如何处理它。

对于该问题,这将是一个非常可靠的解决方案:

sed -nE 's/\s*(\){0,1}$/ \/; :a; /:/ { x; s/\s*\$//; p; d; }; H; $ { s/.*/:/; b a }' input

这会在保留 space 中构建每个先决条件列表,并嵌入反斜杠和换行符,然后在下一个目标列表或输入末尾到达时将其转储。

详情:

  • -n 选项关闭在每行后自动打印模式 space
  • -E 选项打开扩展正则表达式
  • sed 表达式包含几个子表达式,由分号连接:
    • s/\s*(\){0,1}$/ \/ :确保模式 space 中的当前行以 space 和反斜杠结尾,而不向已经有一个 [=70= 的行添加第二个反斜杠]
    • :a : 指向脚本中的标签 'a'
    • /:/ { x; s/\s*\$//; p; d; } :在包含冒号的行上,交换模式并按住 spaces,从模式 space 的(新内容)中删除结尾的反斜杠,打印结果,然后开始下一个循环
    • H :(如果控制到达这一点)将换行符和模式 space 的内容附加到保留 space
    • $ { s/.*/:/; b a } :在输入触发器的最后一行通过在模式 space 中放置一个冒号并跳转到标签 'a'[=70 来转储保留 space =]
    • [表达式结束]:将下一行读入模式 space 并重新开始

或者,它会更准确地遵循您的要求,并避免引入前导空行,这样做:

sed -n ':a; /\$/! { p; d; }; h; :b; $ { x; s/\//; p; }; n; /:/ { x; s/\$//; p; x; b a; }; H; /\$/ b b; s/.*//; x; p' input

在最终打印之前,它也在货舱 space 中组装零件,但它以不同的方式进行:

  • 它从检查模式 space 中的行是否以反斜杠结尾开始(在标签 a 处)。如果不是 (/\$/!),则打印模式 space 并开始下一个循环。
  • 否则,它将保留 space 的当前内容替换为模式 space 的内容(必须已经以反斜杠结尾),然后
  • (at label b) 如果当前行是最后一行,则它检索 hold space 的内容,去除尾随的换行符,并打印结果 ($ { x; s/\//; p; } ).无论哪种方式,
  • 它尝试读取下一个输入行,如果没有(n)则终止。
  • 如果这导致模式 space 中包含一个冒号,则打印保留 space 的内容,减去尾随反斜杠,并将控制发送回标签 a 将包含冒号的行作为新的第一行处理 (/:/ { x; s/\$//; p; x; b a; })。
  • 否则,换行符和模式 space 的内容将附加到保留 space (H).
  • 如果模式 space 以反斜杠结尾,则控制分支返回标签 b 以考虑读取另一行 (/\$/ b b)。
  • 否则,保留 space 被打印并清除 (s/.*//; x; p),并且
  • 如果还有更多行,则读取下一行并开始新的循环。

这对输入的性质做出了更少的假设,但有点复杂。