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 sed
和 sed
的其他版本对于第一个 -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
中使用 \n
。
s///
命令替换部分中的 \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 中每一行的开头替换(即 ^
指示的行的开头是字符串的开头或换行符之后),任何非冒号字符后跟冒号,非冒号字符和破折号 -
。这有效地将每行中的第一个冒号替换为破折号。
假设我们从这个字符串开始:
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 sed
和 sed
的其他版本对于第一个 -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
中使用 \n
。
s///
命令替换部分中的 \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 中每一行的开头替换(即 ^
指示的行的开头是字符串的开头或换行符之后),任何非冒号字符后跟冒号,非冒号字符和破折号 -
。这有效地将每行中的第一个冒号替换为破折号。