16

给定这样的文件:

a
b
a
b

我希望能够用来sed替换文件中包含“a”实例的最后一行。所以如果我想用“c”替换它,那么输出应该是这样的:

a
b
c
b

请注意,无论它可能遇到多少匹配,或者所需模式或文件内容可能是什么的详细信息,我都需要它来工作。提前致谢。

4

14 回答 14

14

不完全是 sed:

tac file | sed '/a/ {s//c/; :loop; n; b loop}' | tac

测试

% printf "%s\n" a b a b a b | tac | sed '/a/ {s//c/; :loop; n; b loop}' | tac
a
b
a
b
c
b

反转文件,然后对于第一个匹配项,进行替换,然后无条件地吞下文件的其余部分。然后重新反转文件。

请注意,空的正则表达式(此处为s//c/)意味着重新使用之前的正则表达式 ( /a/)

除了非常简单的程序之外,我不是一个巨大的 sed 粉丝。我会使用 awk:

tac file | awk '/a/ && !seen {sub(/a/, "c"); seen=1} 1' | tac
于 2013-06-14T19:02:55.650 回答
6

这里有很多好的答案;这是一个概念上简单的两遍sed解决方案tail它与POSIX 兼容,并且不会将整个文件读入内存,类似于Eran Ben-Natan 的方法

sed "$(sed -n '/a/ =' file | tail -n 1)"' s/a/c/' file
  • sed -n '/a/=' file输出匹配正则表达式的行(函数) ,并提取输出的最后一行,即文件中包含正则表达式最后一次出现的行号。=atail -n 1file

  • 将命令替换$(sed -n '/a/=' file | tail -n 1)直接放在前面' s/a/c'会产生一个外部sed脚本,例如3 s/a/c/(使用示例输入),它仅在出现正则表达式的最后一个执行所需的替换。

如果在输入文件中找不到该模式,则整个命令是有效的空操作。

于 2017-01-25T06:12:29.127 回答
4

这可能对您有用(GNU sed):

sed -r '/^PATTERN/!b;:a;$!{N;/^(.*)\n(PATTERN.*)/{h;s//\1/p;g;s//\2/};ba};s/^PATTERN/REPLACEMENT/' file

或其他方式:

sed '/^PATTERN/{x;/./p;x;h;$ba;d};x;/./{x;H;$ba;d};x;b;:a;x;/./{s/^PATTERN/REPLACEMENT/p;d};x' file

或者如果你喜欢:

sed -r ':a;$!{N;ba};s/^(.*\n?)PATTERN/\1REPLACEMENT/' file

仔细想想,这个解决方案可能会取代前两个:

sed  '/a/,$!b;/a/{x;/./p;x;h};/a/!H;$!d;x;s/^a$/c/M' file

如果在文件中找不到正则表达式,则文件将通过不变。一旦正则表达式匹配,所有行都将存储在保留空间中,并在满足一个或两个条件时打印。如果遇到后续的正则表达式,则打印保留空间的内容并用最新的正则表达式替换它。在文件末尾,保留空间的第一行将保留最后一个匹配的正则表达式,并且可以替换它。

于 2013-06-15T07:18:35.983 回答
3

另一个:

tr '\n' ' ' | sed 's/\(.*\)a/\1c/' | tr ' ' '\n'

在行动:

$ printf "%s\n" a b a b a b | tr '\n' ' ' | sed 's/\(.*\)a/\1c/' | tr ' ' '\n'
a
b
a
b
c
b
于 2013-06-14T19:31:26.077 回答
3

另一种方法:

sed "`grep -n '^a$' a | cut -d \: -f 1 | tail -1`s/a/c/" a

这种方法的优点是您在文件上按顺序运行两次,而不是将其读入内存。这在大文件中可能很有意义。

于 2013-06-17T08:00:14.820 回答
2

缓冲整个输入时的两遍解决方案是不能容忍的:

sed "$(sed -n /a/= file | sed -n '$s/$/ s,a,c,/p' )" file

(此版本的早期版本遇到了在 redhat bash-4.1 安装中遇到历史扩展的错误,这样可以避免$!d错误地扩展。)

尽可能少缓冲的一次性解决方案:

sed '/a/!{1h;1!H};/a/{x;1!p};$!d;g;s/a/c/'

最简单的:

tac | sed '0,/a/ s/a/c/' | tac
于 2013-06-17T19:36:22.870 回答
2

这一切都在一个单一的完成awk

awk 'FNR==NR {if ($0~/a/) f=NR;next} FNR==f {$0="c"} 1' file file
a
b
c
b

这会读取文件两次。第一次运行找到 last a,第二次运行来改变它。

于 2013-12-04T07:47:20.737 回答
1

也可以在 perl 中完成:

perl -e '@a=reverse<>;END{for(@a){if(/a/){s/a/c/;last}}print reverse @a}' temp > your_new_file

测试:

> cat temp
a
b
c
a
b
> perl -e '@a=reverse<>;END{for(@a){if(/a/){s/a/c/;last}}print reverse @a}' temp
a
b
c
c
b
> 
于 2013-06-17T11:35:59.507 回答
1

tac infile.txt | sed "s/a/c/; ta ; b ; :a ; N ; ba" | tac

第一个tac反转 的行infile.txtsed表达式(请参阅https://stackoverflow.com/a/9149155/2467140)将 'a' 的第一个匹配项替换为 'c' 并打印剩余的行,最后一个tac反转行到他们原来的顺序。

于 2013-06-14T19:14:08.600 回答
1

这是一种仅使用的方法awk

awk '{a[NR]=$1}END{x=NR;cnt=1;while(x>0){a[x]=((a[x]=="a"&&--cnt==0)?"c <===":a[x]);x--};for(i=1;i<=NR;i++)print a[i]}' file
$ cat f
a
b
a
b
f
s
f
e
a
v
$ awk '{a[NR]=$1}END{x=NR;cnt=1;while(x>0){a[x]=((a[x]=="a"&&--cnt==0)?"c <===":a[x]);x--};for(i=1;i<=NR;i++)print a[i]}' f
a
b
a
b
f
s
f
e
c <===
v
于 2013-06-14T19:56:44.253 回答
0

这是另一种选择:

sed -e '$ a a' -e '$ d' file 

第一个命令附加一个a,第二个命令删除最后一行。从sed(1)手册页

$匹配最后一行。

d 删除模式空间。开始下一个循环。

a text附加文本,其中每个嵌入的换行符前面都有一个反斜杠。

于 2013-06-14T18:21:47.310 回答
0

这是命令:

sed '$s/.*/a/' filename.txt

它在行动中:

> echo "a
> b
> a
> b" > /tmp/file.txt

> sed '$s/.*/a/' /tmp/file.txt
a
b
a
a
于 2013-06-14T18:19:11.160 回答
0

awk- 唯一的解决方案:

awk '/a/{printf "%s", all; all=$0"\n"; next}{all=all $0"\n"} END {sub(/^[^\n]*/,"c",all); printf "%s", all}' file

解释:

  • 当一行匹配时,打印前一行到(不包括)当前行(即存储在变量中的内容)a之间的所有行aaall
  • 当一行不匹配a时,它被附加到变量all中。
  • 最后一行匹配a将无法打印其内容,因此您在块all中手动将其打印出来。END不过,在此之前,您可以将匹配的行替换为a您想要的任何内容。
于 2013-06-15T06:02:20.397 回答
0

鉴于:

$ cat file
a
b
a
b

您可以使用 POSIXgrep来计算匹配项:

$ grep -c '^a' file
2

然后将该号码输入awk以打印替换:

$ awk -v last=$(grep -c '^a' file) '/^a/ && ++cnt==last{ print "c"; next } 1' file
a
b
c
b
于 2017-01-25T04:49:03.960 回答