对于某些问题,例如在未知行数上匹配模式或“替换最后一次出现的...”,-z
GNU选项sed
确实很有帮助。我怎样才能实现同样的便携呢?
示例:我有一个文件
yellow, green,
blue, black, purple,
orange,
white, red, brown
are some colours
我想将文件的最后一个逗号替换为and
.请注意,逗号位于哪一行或该行中的何处是未知的。有了 GNUsed
我可以做到
sed -z 's/\(.*\),/ \1 and/'
得到所需的输出
yellow, green,
blue, black, purple,
orange,
white, red and brown
are some colours
我怎样才能以可移植的方式做到这一点,可以与任何 POSIX 一起运行sed
?
答案1
在纯 POSIX 中,sed
您必须自己粘贴所有行。虽然有些人N
在循环内执行此操作,但最简单的方法是使用以下模式附加到保留空间H;1h;$!d;x
:
H
将每一行附加到保留空间。不幸的是,附加第一行会在缓冲区的开头添加一个换行符,因此1h
将覆盖第一行的保留空间以避免错误的换行符。$!d
将结束除最后一行之外的所有行的处理。它们不需要打印,因为它们存储在保留空间中x
仅在最后一行之后执行(对于所有其他行,确实d
会停止进一步的命令处理),并且它将更改x
保留空间和模式空间,因此在此命令之后,在保留空间中收集的整个文件将位于模式空间中,就像-z
选择 GNU一样sed
。当然你也可以使用g
代替x
,但是这会产生大量的复制,所以x
速度更快。
因此该示例的脚本将如下所示:
sed 'H;1h;$!d;x;s/\(.*\),/\1 and/'
请注意对于非常大的文件来说,处理这样的文件并不是一个好主意,因为这会使用大量的 RAM。
答案2
sed 用于对单个字符串执行简单的 s/old/new 操作,仅此而已。几乎每次您发现自己使用 s、g 和 p(带 -n)以外的结构时,当然每次您发现自己在谈论“保留空间”时,您都在使用错误的工具。对于任何比 s/old/new 更复杂的事情,比如这个任务,你应该使用 awk 来代替。以下内容可以在任何 UNIX 机器上的任何 shell 中使用任何 awk,不会将整个文件存储在内存中,并且如果/当您想对文本进行其他操作时,调整起来很简单:
$ cat tst.awk
/,/ { printf "%s", prev; prev="" }
{ prev = prev $0 ORS }
END {
if ( match(prev,/.*,/) ) {
prev = substr(prev,1,RLENGTH-1) " and" substr(prev,RLENGTH+1)
}
printf "%s", prev
}
$ awk -f tst.awk file
yellow, green,
blue, black, purple,
orange,
white, red and brown
are some colours
你可以在 awk 中更简单地完成这项工作,将整个文件放入内存并编写这个神秘的符文:
$ awk '{r=r$0 ORS} END{h=r;sub(/,[^,]+$/,"",h);sub(/.*,/,"",r);printf "%s and%s",h,r}' file
yellow, green,
blue, black, purple,
orange,
white, red and brown
are some colours
但重点是,与 sed 不同,您不必这样做。