Как использовать 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 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]если вы объедините a
0,/re/
Сs/.../.../
(замещения) вызов, который использует то же самое регулярное выражение, ваша команда будет эффективно только выполнить замена на первый строка, которая соответствует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
A POSIX-features-only
sed
такие как BSD (macOS)sed
(также будет работать с GNUsed
):С
0,/re/
не может быть использован и форма1,/re/
не обнаружитre
если это произойдет в самой первой строке (см. выше), требуется специальная обработка для 1-й строки.ответ Михайлова упоминает технику, помещенную в конкретный пример здесь:
$ 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
вызов; в обоих случаях, регулярное выражениеfoo
неявно используется повторно, что позволяет нам не дублировать его, что делает как более короткий, так и более поддерживаемый код.POSIX
sed
нужны переводы строк после определенных функций, например, после имени метки или даже ее отсутствие, как в случае сt
здесь; стратегически разбиение скрипта на несколько-e
options является альтернативой использованию фактических новых строк: end each-e
фрагмент скрипта, где новая строка будет нормально надо идти.
1 s/foo/bar/
заменяетfoo
только на 1-й строке, если там есть. Если так, тоt
ветви до конца скрипта (пропускает оставшиеся команды в строке). (Тегt
функция ветвится на метку только в том случае, если последняяs
вызов выполнил фактическую подстановку; при отсутствии метки, как в данном случае, конец скрипта разветвляется на).когда это произойдет, диапазон адресов
1,//
, которым обычно находит первое вхождение начиная с строки 2, будет не матч, и диапазон будет не обрабатывается, потому что адрес вычисляется, когда текущая строка уже2
.наоборот, если нет совпадения на 1-й линии,
1,//
будет введите, и найдете истинный первый матч.чистый эффект такой же, как и с GNU
sed
' s0,/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-й строке, и регулярное выражение/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
начиная диапазон), регулярное выражение еще не применялось, так что//
не относится ни к чему.
За исключением GNUsed
'ы специальные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, поэтому мы не добавляем больше включений.
1;
Это означает "распечатать строку" - пустое действие по умолчанию для печати $0, которое печатает из всей линии. Один лайнер и легче понять, чем sed IMO : -)
довольно полный набор ответов на linuxtopia sed FAQ. Он также подчеркивает, что некоторые ответы, предоставленные людьми, не будут работать с версией 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
будет добавлена строка. Если вы используете GNUsed
, он имеет расширение, где0,/^#include/
(вместо1,
) будет делать правильные вещи.
возможное решение:
/#include/!{p;d;} i\ #include "newfile.h" : n b
объяснение:
- читать строки, пока мы не найдем #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
'S" нет совпадения " ошибка в случае, если нет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, чтобы найти первое появление и остановиться там. Также печатать номер строки, т. е. 5:линии. Труба, что в sed и удалить : и что-нибудь после того, как вы просто оставили с номером строки. Труба, что в sed, который добавляет s/.* / replace до конца, который дает сценарий 1 строки, который передается в последний sed для запуска в качестве сценария в файле.
Так что если регулярное выражение = #включить и заменить = бла и первое появление 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