Sed заменить последний шаблон соответствия линии
Для такого файла:
a
b
a
b
Я бы хотел использовать sed
для замены только последней строки, содержащей экземпляр "a" в файле. Поэтому, если бы я захотел заменить его на "c", результат должен выглядеть так:
a
b
c
b
Обратите внимание, что мне нужно, чтобы это работало независимо от того, сколько совпадений он может встретить, или подробности о том, какими должны быть требуемые шаблоны или содержимое файла. Спасибо заранее.
Ответы
Ответ 1
Не совсем 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
Отмените файл, затем для соответствия first, выполните замену, а затем безоговорочно разложите остальную часть файла. Затем переверните файл.
Обратите внимание: пустое регулярное выражение (здесь как s//c/
) означает повторное использование предыдущего регулярного выражения (/a/
)
Я не большой поклонник сериала, за пределами очень простых программ. Я бы использовал awk:
tac file | awk '/a/ && !seen {sub(/a/, "c"); seen=1} 1' | tac
Ответ 2
Другой:
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
Ответ 3
Это может сработать для вас (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
Ответ 4
Другой подход:
sed "`grep -n '^a$' a | cut -d \: -f 1 | tail -1`s/a/c/" a
Преимущество этого подхода заключается в том, что вы дважды запускаете файл дважды, а не читаете его в памяти. Это может иметь смысл в больших файлах.
Ответ 5
Решение с двумя проходами при буферизации всего ввода недопустимо:
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
Ответ 6
Здесь все сделано в одном awk
awk 'FNR==NR {if ($0~/a/) f=NR;next} FNR==f {$0="c"} 1' file file
a
b
c
b
Это дважды читает файл. Сначала запустите, чтобы найти последний a
, второй запустить, чтобы изменить его.
Ответ 7
Здесь много хороших ответов; здесь концептуально простое двухпроходное sed
решение с поддержкой tail
, которое совместимо с POSIX и не считывает весь файл в память, подобно подход Эрана Бен-Натана:
sed "$(sed -n '/a/ =' file | tail -n 1)"' s/a/c/' file
-
sed -n '/a/=' file
выводит номера строк (функция =
), соответствующие regex a
, а tail -n 1
извлекает выходную последнюю строку, то есть номер строки в файле file
, содержащий последнее вхождение регулярного выражения.
-
Размещение замены подстановки $(sed -n '/a/=' file | tail -n 1)
непосредственно перед ' s/a/c'
приводит к внешнему sed
script, например 3 s/a/c/
(с образцом ввода), который выполняет желаемую замену только на последнем, на котором возникло регулярное выражение.
Если шаблон не найден во входном файле, вся команда является эффективной no-op.
Ответ 8
tac infile.txt | sed "s/a/c/; ta ; b ; :a ; N ; ba" | tac
Первый tac
меняет линии infile.txt
, выражение sed
(см. fooobar.com/questions/51302/...) заменяет первое совпадение 'a' с 'c' и печатает оставшиеся строки, а последний tac
возвращает строки обратно в исходный порядок.
Ответ 9
Вот только с помощью 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
Ответ 10
Это также можно сделать в 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
>
Ответ 11
Здесь команда:
sed '$s/.*/a/' filename.txt
И здесь он находится в действии:
> echo "a
> b
> a
> b" > /tmp/file.txt
> sed '$s/.*/a/' /tmp/file.txt
a
b
a
a
Ответ 12
Вот еще один вариант:
sed -e '$ a a' -e '$ d' file
Первая команда добавляет a
, а вторая удаляет последнюю строку. На странице sed(1)
:
$
Соответствует последней строке.
d
Удалить пространство шаблонов. Начать следующий цикл.
a text
Добавить текст, в котором каждая внедренная новая строка предшествует обратную косую черту.
Ответ 13
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
, тем, что вы хотите.
Ответ 14
Дано:
$ 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