Как использовать sed для замены только первого вхождения в файл?
Я хочу обновить большое количество исходных файлов C++ с дополнительной директивой include перед любым существующим #includes. Для такого рода задач я обычно использую небольшой скрипт bash с sed для перезаписи файла.
Как заставить sed заменить только первое вхождение строки в файле, а не заменять каждое вхождение?
Если я использую
sed s/#include/#include "newfile.h"n#include/
Он заменяет все #includes.
альтернативные предложения для достижения того же вещь также приветствуется.
20 ответов
# sed script to change "foo" to "bar" only on the first occurrence
1{x;s/^/first/;x;}
1,/foo/{x;/first/s///;x;s/foo/bar/;}
#---end of script---
или, если вы предпочитаете: Примечание редактора: работает с GNU sed
только.
sed '0,/RE/s//to_that/' file
напишите сценарий sed, который заменит только первое появление "Apple"на " Banana"
Пример: Вход: Выход:
Apple Banana
Orange Orange
Apple Apple
Это простой скрипт: Примечание редактора: работает с GNU sed
только.
sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename
это работает для меня.
пример
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
Примечание редактора: оба работают с GNU sed
только.
An обзор из многих полезных существующие ответы в сочетании с объяснениями:
в примерах здесь используется упрощенный вариант использования: замените слово " foo " на " bar " только в первой соответствующей строке.
Из-за использования ANSI C-цитируемые строки ($'...'
) данный образец входных линий, bash
, ksh
или zsh
считается ракушка.
GNU sed
только:
Бен Hoffstein по anwswer показывает нам, что GNU предоставляет расширение до спецификация POSIX для sed
что позволяет следующую 2-адресную форму:0,/re/
(re
представляет произвольное регулярное выражение здесь).
0,/re/
позволяет регулярное выражение для матч на очень первая строка также. Другими словами: такой адрес создаст диапазон от 1-й строки до строки, которая соответствует re
- ли re
происходит в 1-й строке или в любой последующей строке.
- сравните это с POSIX-совместимой формой
1,/re/
, который создает диапазон, который соответствует от 1-й строки до и включая строку, которая соответствуетre
on в последующем линии; другими словами: это не будет обнаруживать первое появлениеre
матч, если это происходит на 1-й строка и предотвращает использование стенографии//
для повторного использования последнего используемого регулярного выражения (см. Следующий пункт).[1]
если вы объедините 0,/re/
С s/.../.../
(substitution) вызов, который использует то же самое регулярное выражение, ваша команда будет эффективно только выполнить замена на первый строка, которая соответствует re
.sed
обеспечивает удобный ярлык для повторного использования последнего примененного регулярного выражения: an пустой пара разделителей, //
.
$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
POSIX-функции-только sed
такие как BSD (macOS) sed
(также будет работать с GNU sed
):
С 0,/re/
нельзя использовать и форма 1,/re/
не обнаружит re
если это происходит в самой первой строке (см. выше), требуется специальная обработка для 1-й строки.
MikhailVS это упоминает технику, приведенную в конкретном примере здесь:
$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
Примечание:
пустое выражение
//
ярлык используется здесь дважды: один раз для конечной точки диапазона и один раз вs
вызов; в обоих случаях, regexfoo
неявно используется повторно, что позволяет нам не дублировать его, что делает как более короткий, так и более обслуживаемый код.POSIX
sed
нужны фактические новые строки после определенных функций, таких как после имени метки или даже ее опущения, как в случае сt
здесь; стратегическое разделение скрипта на несколько-e
options является альтернативой использованию фактических новых строк: end each-e
фрагмент сценария, где новая строка обычно нужно идти.
1 s/foo/bar/
заменяет foo
только на 1-й строке, если найдется там.
Если так, то t
ветви до конца скрипта (пропускает оставшиеся команды в строке). (The t
функция ветвится на метку, только если самая последняя s
вызов выполнил фактическую подстановку; при отсутствии метки, как в данном случае, конец скрипта разветвляется на).
когда это произойдет, диапазон адресов 1,//
, которым обычно находит первое вхождение начиная с строки 2, будет не матч, и диапазон будет не обрабатывается, потому что адрес оценивается, когда текущая строка уже 2
.
наоборот, если нет совпадения на 1-й линии, 1,//
будет будет введен и найдет истинное первое совпадение.
чистый эффект такой же, как с GNU sed
' s 0,/re/
: только первое вхождение заменяется, происходит ли это на 1-й строке или любой другой.
подходы вне диапазона
potong это показывает цикл методы это обойти необходимость в диапазоне; так как он использует GNU sed
синтаксис, вот POSIX-совместимые эквиваленты:
техника петли 1: на первом матче выполните замену, затем введите цикл, который просто печатает оставшиеся строки как-есть:
$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
петля техника 2, для только небольшие файлы: прочитайте весь вход в память, затем выполните одну подстановку на нем.
$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
[1] 1.61803 приведены примеры того, что происходит с 1,/re/
, С и без последующего s//
:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
доходность $'1bar\n2bar'
, т. е., и строки были обновлены, потому что номер строки 1
соответствует 1-й строке и regex /foo/
- конец диапазона-тогда только искали начало на далее линии. Следовательно,и в этом случае выбираются строки и s/foo/bar/
замена выполняется на обоих из них.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
не: с sed: first RE may not be empty
(BSD/macOS) и sed: -e expression #1, char 0: no previous regular expression
(GNU), потому что в момент обработки 1-й строки (из-за строки номер 1
запуск диапазона), регулярное выражение еще не применено, поэтому //
не относится ни к чему.
За исключением GNU sed
'ы специальные 0,/re/
синтаксис любой диапазон, который начинается с номер строки эффективно исключает использование //
.
вы можете использовать awk, чтобы сделать что-то подобное..
awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c
объяснение:
/#include/ && !done
запускает оператор действия между {}, когда строка соответствует "#include", и мы еще не обработали ее.
{print "#include \"newfile.h\""; done=1;}
это печатает #include " newfile.h", нам нужно избежать цитат. Затем мы устанавливаем переменную done в 1, поэтому мы не добавляем больше includes.
1;
это означает "распечатать строку" - пустое действие по умолчанию печатает $0, которое печатает всю линию. Один лайнер и легче понять, чем sed IMO : -)
довольно полная коллекция ответов на linuxtopia СЭД часто задаваемые вопросы. Он также подчеркивает, что некоторые ответы, предоставленные людьми, не будут работать с версией sed, отличной от GNU, например
sed '0,/RE/s//to_that/' file
в версии, отличной от GNU, должно быть
sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'
однако эта версия не будет работать с gnu sed.
вот версия, которая работает с обоими:
-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'
ex:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
как работает этот скрипт: для строк между 1 и первым #include
(после строка 1), если строка начинается с #include
, затем добавьте указанную строку.
однако, если первый #include
в строке 1, затем обе линии 1 и последующем #include
будет добавлена строка. Если вы используете GNU sed
, он имеет расширение, где 0,/^#include/
(вместо 1,
) будет делать правильные вещи.
возможное решение:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
объяснение:
- читать строки, пока мы не найдем #include, распечатать эти строки, а затем начать новый цикл
- вставить новую строку include
- ввести цикл, который просто читает строки (по умолчанию sed также будет печатать эти строки), мы не вернемся к первой части скрипта отсюда
Я бы сделал это с помощью сценария awk:
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print }
END {}
затем запустите его с помощью awk:
awk -f awkscript headerfile.h > headerfilenew.h
может быть небрежно, Я новичок в этом.
я, наконец, получил это для работы в скрипте Bash, используемом для вставки уникальной метки времени в каждый элемент в RSS-ленте:
sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter
он изменяет только первое вхождение.
${nowms}
- время в миллисекундах, заданное скриптом Perl,$counter
- счетчик, используемый для управления циклом в скрипте,\
позволяет продолжить выполнение команды в следующей строке.
файл считывается и stdout перенаправляется в рабочий файл.
Я пойми это,1,/====RSSpermalink====/
сообщает sed, когда остановиться, установив ограничение диапазона, а затем s/====RSSpermalink====/${nowms}/
- знакомая команда sed для замены первой строки на вторую.
в моем случае я помещаю команду в двойные кавычки, потому что я использую ее в скрипте Bash с переменными.
используя FreeBSD ed
, а не ed
ошибка "нет совпадения" в случае, если нет include
инструкция в файле для обработки:
teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'
# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
H
,g/# *include/u\
u\
i\
#include "newfile.h"\
.
,p
q
EOF
Это может сработать для вас (GNU sed):
sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....
или если память не является проблемой:
sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
Я знаю, что это старый пост, но у меня было решение, которое я использовал:
grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file
в основном используйте grep, чтобы найти первое появление и остановиться там. Также выведите номер строки ie 5: line. Вставьте это в sed и удалите : и что-нибудь после этого, чтобы вы просто остались с номером строки. Передайте это в sed, который добавляет s/.* / replace до конца, который дает сценарий 1 строки, который передается в последний sed для запуска как сценарий в файле.
Итак, если regex = #include и replace = бла и первое вхождение grep находит в строке 5, тогда данные, переданные в последний sed, будут 5s/.*/чепуха./
Если кто-то пришел сюда, чтобы заменить символ для первого вхождения во всех строках (например, я), используйте это:
sed '/old/s/old/new/1' file
-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12
изменив 1 на 2, например, вы можете заменить все вторые a только вместо этого.
следующая команда удаляет первое вхождение строки в файл. Он также удаляет пустую строку. Он представлен в xml-файле, но он будет работать с любым файлом.
полезно, если вы работаете с xml-файлами и хотите удалить тег. В этом примере он удаляет первое вхождение тега "isTag".
:sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//} -e 's/ *$//' -e '/^$/d' source.txt > output.txt
исходный файл (source.txt)
<xml>
<testdata>
<canUseUpdate>true</canUseUpdate>
<isTag>false</isTag>
<moduleLocations>
<module>esa_jee6</module>
<isTag>false</isTag>
</moduleLocations>
<node>
<isTag>false</isTag>
</node>
</testdata>
</xml>
файл результатов (выходных данных.txt)
<xml>
<testdata>
<canUseUpdate>true</canUseUpdate>
<moduleLocations>
<module>esa_jee6</module>
<isTag>false</isTag>
</moduleLocations>
<node>
<isTag>false</isTag>
</node>
</testdata>
</xml>
ps: он не работал для меня на Solaris SunOS 5.10 (довольно старый), но он работает на Linux 2.6, sed версии 4.1.5
ничего нового, но, возможно, немного более конкретный ответ: sed -rn '0,/foo(bar).*/ s%%%p'
пример: xwininfo -name unity-launcher
выпускает продукцию как:
xwininfo: Window id: 0x2200003 "unity-launcher"
Absolute upper-left X: -2980
Absolute upper-left Y: -198
Relative upper-left X: 0
Relative upper-left Y: 0
Width: 2880
Height: 98
Depth: 24
Visual: 0x21
Visual Class: TrueColor
Border width: 0
Class: InputOutput
Colormap: 0x20 (installed)
Bit Gravity State: ForgetGravity
Window Gravity State: NorthWestGravity
Backing Store State: NotUseful
Save Under State: no
Map State: IsViewable
Override Redirect State: no
Corners: +-2980+-198 -2980+-198 -2980-1900 +-2980-1900
-geometry 2880x98+-2980+-198
извлечение идентификатора окна с xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%%p'
выдает:
0x2200003
POSIXly (также действует в sed), только один используется регулярное выражение, нужна память только для одной строки (как обычно):
sed '/\(#include\).*/!b;//{h;s// "newfile.h"/;G};:1;n;b1'
пояснил:
sed '
/\(#include\).*/!b # Only one regex used. On lines not matching
# the text `#include` **yet**,
# branch to end, cause the default print. Re-start.
//{ # On first line matching previous regex.
h # hold the line.
s// "newfile.h"/ # append ` "newfile.h"` to the `#include` matched.
G # append a newline.
} # end of replacement.
:1 # Once **one** replacement got done (the first match)
n # Loop continually reading a line each time
b1 # and printing it by default.
' # end of sed script.
sed имеет очень простой синтаксис для этого,' - i ' является интерактивным (нет необходимости в newfile). Чтобы заменить только первый экземпляр:
sed -i 's/foo/bar/' file
для замены глобально вы бы использовали
sed -i 's/foo/bar/g' file
в вашем примере я бы использовал (^и $ - начало и конец строки соответственно)
sed -i 's/^#include/#include\n#include/' file