sed заменил шаблон соответствия последней строки

regex bash sed

12186 просмотра

14 ответа

Учитывая файл как это:

a
b
a
b

Я хотел бы иметь возможность sedзаменить только последнюю строку, которая содержит экземпляр «а» в файле. Так что, если я хочу заменить его на «c», то результат должен выглядеть следующим образом:

a
b
c
b

Обратите внимание, что мне нужно, чтобы это работало независимо от того, сколько совпадений он может встретить, или от точных деталей того, каким может быть желаемый шаблон или содержимое файла. Заранее спасибо.

Автор: TTT Источник Размещён: 13.11.2019 11:32

Ответы (14)


12 плюса

Решение

Не совсем седь только

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
Автор: glenn jackman Размещён: 14.06.2013 07:02

4 плюса

Здесь много хороших ответов; вот концептуально простой двухпроходная sedрешение помогает , tailчто POSIX-совместимых и не читает весь файл в память, подобный подход Эран Бен-Натана :

sed "$(sed -n '/a/ =' file | tail -n 1)"' s/a/c/' file
  • sed -n '/a/=' fileвыводит номера строк (функции =), соответствующих регулярному выражению a, и tail -n 1извлекает последнюю строку вывода , то есть номер строки в файле, fileсодержащей последнее вхождение регулярного выражения.

  • Помещение подстановки команды $(sed -n '/a/=' file | tail -n 1)непосредственно перед ' s/a/c'результатом приводит к внешнему sedсценарию, такому как 3 s/a/c/(с вводом примера), который выполняет желаемую подстановку только для последнего, с которым произошло регулярное выражение.

Если шаблон не найден во входном файле, вся команда является эффективной командой no-op.

Автор: mklement0 Размещён: 25.01.2017 06:12

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

Если регулярное выражение не где найти в файле, файл будет проходить без изменений. Как только регулярное выражение совпадет, все строки будут сохранены в области удержания и будут напечатаны при выполнении одного или обоих условий. Если последующее регулярное выражение встречается, содержимое области удержания печатается, и последнее регулярное выражение заменяет его. В конце файла первая строка пространства удержания будет содержать последнее соответствующее регулярное выражение, и это можно заменить.

Автор: potong Размещён: 15.06.2013 07:18

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
Автор: Fredrik Pihl Размещён: 14.06.2013 07:31

3 плюса

Другой подход:

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

Преимущество этого подхода заключается в том, что вы дважды запускаете файл и не читаете его в память. Это может иметь смысл в больших файлах.

Автор: Eran Ben-Natan Размещён: 17.06.2013 08:00

2 плюса

Здесь все сделано в одном awk

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

Это читает файл дважды. Первый запуск, чтобы найти последний a, второй запуск, чтобы изменить его.

Автор: Jotne Размещён: 04.12.2013 07:47

2 плюса

Двухпроходное решение для буферизации всего ввода недопустимо:

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

(в более ранней версии этой ошибки возникла ошибка с расширением истории при установке 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
Автор: jthill Размещён: 17.06.2013 07:36

1 плюс

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

Первая tacпереворачивает строки infile.txt, sedвыражение (см. Https://stackoverflow.com/a/9149155/2467140 ) заменяет первое совпадение «a» на «c» и печатает оставшиеся строки, а последняя tacпереворачивает строки обратно в их первоначальном порядке.

Автор: gbrener Размещён: 14.06.2013 07:14

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
Автор: jaypal singh Размещён: 14.06.2013 07:56

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
> 
Автор: Vijay Размещён: 17.06.2013 11:35

0 плюса

Вот еще один вариант:

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

Первая команда добавляет, aа вторая удаляет последнюю строку. Со sed(1)страницы руководства :

$ Соответствуйте последней строке.

d Удалить шаблон пространства. Начать следующий цикл.

a text Добавить текст, в котором каждой новой строке вставлена ​​обратная косая черта.

Автор: Carl Norum Размещён: 14.06.2013 06:21

0 плюса

Вот команда:

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

И вот оно в действии:

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

> sed '$s/.*/a/' /tmp/file.txt
a
b
a
a
Автор: Thomas Kelley Размещён: 14.06.2013 06:19

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, все строки между предыдущим a(не включая) текущим a(т.е. содержимым, хранящимся в переменной all), печатаются
  • Когда строка не совпадает a, она добавляется к переменной all.
  • Соответствие последней строки aне сможет allраспечатать содержимое, поэтому вы вручную распечатываете его в ENDблоке. Однако до этого вы можете заменить соответствие строки aлюбым, что пожелаете.
Автор: doubleDown Размещён: 15.06.2013 06:02

0 плюса

Дано:

$ cat file
a
b
a
b

Вы можете использовать POSIX grepдля подсчета совпадений:

$ 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
Автор: dawg Размещён: 25.01.2017 04:49
Вопросы из категории :
32x32