GNU sed 和带有多个脚本的换行符

GNU sed and newlines with multiple scripts

假设我们从这个字符串开始:

echo "1:apple:fruit.2:banana:fruit.3:cucumber:veggie.4:date:fruit.5:eggplant:veggie.">list.tmp

并希望得到这个结果:

1-apple:fruit
2-banana:fruit
3-cucumber:veggie
4-date:fruit
5-eggplant:veggie



为什么这样做:

sed -e 's/\./\n/g' -i list.tmp
sed -e 's/:/-/' list.tmp

但不是这个:

sed -e 's/\./\n/g' -e 's/:/-/' list.tmp



第二个命令产生这个,在每行上寻找第一次出现的“:”时显然忽略了新的换行符。

1-apple:fruit
2:banana:fruit
3:cucumber:veggie
4:date:fruit
5:eggplant:veggie

输入的扩展版本:

echo "one:apple:fruit.two:banana:fruit.three:cucumber:veggie.four:date:fruit.five:eggplant:veggie.">list.tmp

我想得到这个结果:

one-apple:fruit
two-banana:fruit
three-cucumber:veggie
four-date:fruit
five-eggplant:veggie

正在将关键评论转化为答案。

原始数据

您忘记了双 -e 公式中第二个命令的 g 修饰符。当第一个 -e 完成时,所有行仍在模式 space 中(sed 中的主要工作区域)——它们不会变成 5 个单独读取的行。你读一行;你还在处理它。请注意,您需要使用修改后的模式:

s/\([0-9]\):/-/g

结合这些,在 GNU sed(如问题标题中规定),你得到:

sed -e 's/\./\n/g' -e 's/\([0-9]\):/-/g' list.tmp

请注意 POSIX sedsed 的其他版本对于第一个 -e 表达式中的换行替换有不同的规则。

考虑使用 awk

如果将工具从 sed 更改为 awk 是一个选项,您可以在 awk 中更简单地完成,如 Ed Morton in a comment 所示。由于该解决方案不需要更改来处理修改后的数据,因此它显然具有优势 — 劣势在于它没有使用 sed。在 'the real world' 中,您使用最好的工具来完成工作 — 在这个例子中,就是 awk

扩展数据

输入'extended',没有方便的个位数,又想把每行的第一个冒号改成破折号,就得加把劲了:

sed -e 's/\./\n/g' \
    -e  's/^\([^:]*\):/-/' \
    -e 's/\(\n[^:]*\):/-/g' \
    list.tmp
  • 第一个-e不变。
  • 第二个在模式 space 的开头查找 non-colons 后跟冒号的序列,并将其替换为 non-colons 和破折号的序列。 g 修饰符与此处无关。
  • 第三个 -e 查找换行符后跟 non-colons 序列和冒号,并将其替换为换行符、non-colon 序列和破折号。 g 修饰符在这里非常重要。

您可以将它们全部平放在一行中,但如果将最后两个 -e 选项放在不同的行中,则更容易看出它们之间的相似之处。

您还可以使用 -E 选项试验 ERE(扩展正则表达式),并将两个单独的替换组合为一个:

{
echo "1:apple:fruit.2:banana:fruit.3:cucumber:veggie.4:date:fruit.5:eggplant:veggie."
echo "one:apple:fruit.two:banana:fruit.three:cucumber:veggie.four:date:fruit.five:eggplant:veggie."
} |
sed -E -e 's/\./\
/g' -e 's/((^|\n)[^:]+):/-/g'

产生:

1-apple:fruit
2-banana:fruit
3-cucumber:veggie
4-date:fruit
5-eggplant:veggie

one-apple:fruit
two-banana:fruit
three-cucumber:veggie
four-date:fruit
five-eggplant:veggie

如果您不想要多余的空行,请删除最后的换行符:

{
echo "1:apple:fruit.2:banana:fruit.3:cucumber:veggie.4:date:fruit.5:eggplant:veggie."
echo "one:apple:fruit.two:banana:fruit.three:cucumber:veggie.four:date:fruit.five:eggplant:veggie."
} |
sed -E -e 's/\./\
/g' -e 's/((^|\n)[^:]+):/-/g' -e 's/\n$//'

backslash-newline 符号在 GNU sed 和 POSIX(包括 BSD 和 macOS)中都能正常工作 sed;你可以 re-replace 在 GNU sed 中使用 \ns/// 命令替换部分中的 \n 在 BSD (macOS) sed 中不起作用。 POSIX sed 要求您在替换文本中使用反斜杠转义文字换行符:

A line can be split by substituting a <newline> into it. The application shall escape the <newline> in the replacement by preceding it by a <backslash>.

GNU sed 更灵活。

此外(根据 potong's answer),有一个 GNU-specific 修饰符 m,您可以使用它在一次操作中进行 multi-line 匹配。

这可能适合您 (GNU sed):

sed -E 'y/./\n/;s/^([^:]*):/-/mg' file

将所有句点转换为换行符。

使用 GNU 特定的 m 或多行标志,从模式 space 中每一行的开头替换(即 ^ 指示的行的开头是字符串的开头或换行符之后),任何非冒号字符后跟冒号,非冒号字符和破折号 -。这有效地将每行中的第一个冒号替换为破折号。