简体   繁体   English

sed 替换最后一行匹配模式

[英]sed replace last line matching pattern

Given a file like this:给定一个这样的文件:

a
b
a
b

I'd like to be able to use sed to replace just the last line that contains an instance of "a" in the file.我希望能够使用sed来替换文件中包含“a”实例的最后一行。 So if I wanted to replace it with "c", then the output should look like:所以如果我想用“c”替换它,那么输出应该是这样的:

a
b
c
b

Note that I need this to work irrespective of how many matches it might encounter, or the details of exactly what the desired pattern or file contents might be.请注意,无论它可能遇到多少匹配项,或者所需模式或文件内容可能是什么的详细信息,我都需要它来工作。 Thanks in advance.提前致谢。

Not quite sed only:不完全是 sed:

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

testing测试

% 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

Reverse the file, then for the first match, make the substitution and then unconditionally slurp up the rest of the file.反转文件,然后对于第一个匹配项,进行替换,然后无条件地吞下文件的其余部分。 Then re-reverse the file.然后重新反转文件。

Note, an empty regex (here as s//c/ ) means re-use the previous regex ( /a/ )注意,一个空的正则表达式(这里是s//c/ )意味着重用之前的正则表达式( /a/

I'm not a huge sed fan, beyond very simple programs.除了非常简单的程序之外,我不是 sed 的忠实粉丝。 I would use awk:我会使用 awk:

tac file | awk '/a/ && !seen {sub(/a/, "c"); seen=1} 1' | tac

Many good answers here;这里有很多很好的答案; here's a conceptually simple two-pass sed solution assisted by tail that is POSIX-compliant and doesn't read the whole file into memory, similar to Eran Ben-Natan's approach :这是一个概念上简单的两遍sed解决方案,tail辅助,符合 POSIX并且不会将整个文件读入内存,类似于Eran Ben-Natan 的方法

sed "$(sed -n '/a/ =' file | tail -n 1)"' s/a/c/' file
  • sed -n '/a/=' file outputs the numbers of the lines (function = ) matching regex a , and tail -n 1 extracts the output's last line, ie the number of the line in file file containing the last occurrence of the regex. sed -n '/a/=' file输出(函数的行= )匹配正则表达式a ,和tail -n 1提取输出的最后一行,在文件中的行的,即数量file包含的最后一次出现正则表达式。

  • Placing command substitution $(sed -n '/a/=' file | tail -n 1) directly before ' s/a/c' results in an outer sed script such as 3 s/a/c/ (with the sample input), which performs the desired substitution only on the last on which the regex occurred.将命令替换$(sed -n '/a/=' file | tail -n 1)直接放在' s/a/c'会产生外部sed脚本,例如3 s/a/c/ (带有示例输入),它仅在最后一个出现正则表达式的地方执行所需的替换。

If the pattern is not found in the input file, the whole command is an effective no-op.如果在输入文件中找不到模式,则整个命令是有效的空操作。

This might work for you (GNU sed):这可能对你有用(GNU sed):

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

or another way:或其他方式:

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

or if you like:或者如果你喜欢:

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

On reflection, this solution may replace the first two:经过反思,这个解决方案可能会取代前两个:

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

If the regexp is no where to found in the file, the file will pass through unchanged.如果在文件中找不到正则表达式,文件将原封不动地通过。 Once the regex matches, all lines will be stored in the hold space and will be printed when one or both conditions are met.一旦正则表达式匹配,所有行都将存储在保持空间中,并在满足一个或两个条件时打印。 If a subsequent regex is encountered, the contents of the hold space is printed and the latest regex replaces it.如果遇到后续的正则表达式,则打印保留空间的内容并用最新的正则表达式替换它。 At the end of file the first line of the hold space will hold the last matching regex and this can be replaced.在文件末尾,保留空间的第一行将保留最后一个匹配的正则表达式,并且可以替换它。

Another one:另一个:

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

in action:在行动:

$ printf "%s\n" a b a b a b | tr '\n' ' ' | sed 's/\(.*\)a/\1c/' | tr ' ' '\n'
a
b
a
b
c
b

Another approach:另一种方法:

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

The advantage of this approach is that you run sequentially on the file twice, and not read it to memory.这种方法的优点是您可以顺序地对文件运行两次,而不是将其读入内存。 This can be meaningful in large files.这在大文件中可能很有意义。

A two-pass solution for when buffering the entire input is intolerable:缓冲整个输入时的两遍解决方案是无法忍受的:

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

(the earlier version of this hit a bug with history expansion encountered on a redhat bash-4.1 install, this way avoids a $!d that was being mistakenly expanded.) (此版本的早期版本在安装 redhat bash-4.1 时遇到了历史扩展的错误,这样可以避免错误地扩展$!d 。)

A one-pass solution that buffers as little as possible:缓冲尽可能少的一次性解决方案:

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

Simplest:最简单:

tac | sed '0,/a/ s/a/c/' | tac

Here is all done in one single awk这一切都在一个awk

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

This reads the file twice.这会读取文件两次。 First run to find last a , second run to change it.第一次运行找到最后a ,第二次运行改变它。

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

第一个tac反转infile.txt的行, sed表达式(参见https://stackoverflow.com/a/9149155/2467140 )用 'c' 替换 'a' 的第一个匹配项并打印剩余的行,并且last tac将行反转回原来的顺序。

Here is a way with only using awk :这是一种仅使用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

It can also be done in perl:也可以在 perl 中完成:

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

Tested:测试:

> 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
> 

Here's the command:这是命令:

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

And here it is in action:这是在行动:

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

> sed '$s/.*/a/' /tmp/file.txt
a
b
a
a

Here's another option:这是另一种选择:

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

The first command appends an a and the second deletes the last line.第一个命令附加一个a ,第二个命令删除最后一行。 From the sed(1) man page :sed(1)手册页

$ Match the last line. $匹配最后一行。

d Delete pattern space. d删除模式空间。 Start next cycle.开始下一个循环。

a text Append text, which has each embedded newline preceded by a backslash. a text附加文本,其中每个嵌入的换行符前面都有一个反斜杠。

awk -only solution: awk解决方案:

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

Explanation:解释:

  • When a line matches a , all lines between the previous a up to (not including) current a (ie the content stored in the variable all ) is printed当一行匹配a ,打印前a到(不包括)当前a (即存储在变量all的内容)之间的所有行
  • When a line doesn't match a , it gets appended to the variable all .当一行与a不匹配时,它会被附加到变量all
  • The last line matching a would not be able to get its all content printed, so you manually print it out in the END block.匹配a的最后一行将无法打印其all内容,因此您可以在END块中手动将其打印出来。 Before that though, you can substitute the line matching a with whatever you desire.不过,在此之前,您可以将匹配a的行替换为您想要的任何内容。

Given:鉴于:

$ cat file
a
b
a
b

You can use POSIX grep to count the matches:您可以使用 POSIX grep来计算匹配项:

$ grep -c '^a' file
2

Then feed that number into awk to print a replacement:然后将该数字输入awk以打印替换:

$ awk -v last=$(grep -c '^a' file) '/^a/ && ++cnt==last{ print "c"; next } 1' file
a
b
c
b

Could someone please help me on how to pass variable to the below command? 有人可以帮我如何将变量传递给以下命令吗?

sed "$(sed -n '/110600002019/ =' tmuser.cf | tail -n 1)"' s/110600002019 /120700002019/' tmuser.cf

I want variable to be passed like below. 我希望像下面这样传递变量。

sed "$(sed -n '/$a/ =' tmuser.cf | tail -n 1)"' s/$a /$c/' tmuser.cf

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM