Вопрос:

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

regex bash sed

12186 просмотра

14 ответа

591 Репутация автора

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

a
b
a
b

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

a
b
c
b

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

Автор: TTT Источник Размещён: 14.06.2013 06:14

Ответы (14)


0 плюса

8580 Репутация автора

Вот команда:

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 плюса

183357 Репутация автора

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

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

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

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

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

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

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

12 плюса

181138 Репутация автора

Решение

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

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

1 плюс

3567 Репутация автора

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

3 плюса

36690 Репутация автора

Другой:

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

1 плюс

61299 Репутация автора

Вот способ только с использованием 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

0 плюса

6109 Репутация автора

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

4 плюса

39509 Репутация автора

Это может работать для вас (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 плюса

2055 Репутация автора

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

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

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

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

1 плюс

34480 Репутация автора

Это также можно сделать в 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

2 плюса

32918 Репутация автора

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

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

2 плюса

34777 Репутация автора

Здесь все сделано в одном 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

0 плюса

64191 Репутация автора

Дано:

$ 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

4 плюса

164697 Репутация автора

Здесь много хороших ответов; вот концептуально простой двухпроходная 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
Вопросы из категории :
32x32