Как использовать sed для замены только первого вхождения в файл?
Я хотел бы обновить большое количество исходных файлов C++ с помощью дополнительной директивы include перед любым существующим #include. Для такого рода задач я обычно использую небольшой скрипт bash с sed, чтобы переписать файл.
Как заставить sed
заменить только первое вхождение строки в файле, а не заменять каждое вхождение?
Если я использую
sed s/#include/#include "newfile.h"\n#include/
он заменяет все #include.
Альтернативные предложения для достижения того же самого также приветствуются.
Ответы
Ответ 1
# 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
Источник
Ответ 2
Напишите сценарий sed, который заменит только первое появление "Apple" на "Banana"
Пример ввода: вывод:
Apple Banana
Orange Orange
Apple Apple
Это простой скрипт: Примечание редактора: работает только с GNU sed
.
sed '0,/Apple/{s/Apple/Banana/}' filename
Ответ 3
sed '0,/pattern/s/pattern/replacement/' filename
это сработало для меня.
пример
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
Примечание редактора: оба работают только с GNU sed
.
Ответ 4
Обзор многих полезных существующих ответов, дополненный пояснениями:
Приведенные здесь примеры используют упрощенный вариант использования: замените слово "foo" на "bar" только в первой соответствующей строке.
Из-за использования строк в кавычках ANSI C ($'...'
) для предоставления примеров входных строк, bash
, ksh
или zsh
предполагается в качестве оболочки.
GNU sed
только:
Ответ Бен Хоффштейна показывает, что GNU предоставляет расширение для спецификации POSIX для sed
которое допускает следующую двухадресную форму: 0,/re/
(re
представляет здесь произвольное регулярное выражение).
0,/re/
позволяет регулярному выражению совпадать с самой первой строкой. Другими словами: такой адрес будет создать диапазон от 1 - й линии до и включая строку, соответствующую re
- ли re
происходит на 1 - й линии или на любой последующей строке.
- Сравните это с POSIX-совместимой формой
1,/re/
, которая создает диапазон, который соответствует от 1-й строки до и включает строку, которая соответствует re
в последующих строках; другими словами: это не будет определять первое вхождение re
совпадения, если оно происходит в 1-й строке, а также предотвращает использование сокращения //
для повторного использования последнего использованного регулярного выражения (см. следующий пункт). [1]
Если вы объедините 0,/re/
address с s/.../.../
(подстановка), который использует то же регулярное выражение, ваша команда будет эффективно выполнять подстановку только в первой строке, которая соответствует re
.
sed
предоставляет удобный ярлык для повторного использования самого последнего примененного регулярного выражения: пустой пары разделителей, //
.
$ 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
(будет также работать с ГНУ 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
; в обоих случаях regex foo
неявно используется повторно, что позволяет нам не дублировать его, что делает как более короткий, так и более понятный код.
-
POSIX sed
нуждается в фактических символах новой строки после определенных функций, таких как после имени метки или даже ее пропуска, как в случае с t
; стратегическое разделение сценария на несколько вариантов -e
является альтернативой использованию фактических строк новой строки: заканчивайте каждый -e
сценария -e
там, где обычно требуется переход на новую -e
.
1 s/foo/bar/
заменяет foo
на 1-й строке, если она там есть. Если это так, t
переходит к концу сценария (пропускает оставшиеся команды в строке). (Функция t
разветвляется на метку, только если последний вызов s
выполнил фактическую замену; при отсутствии метки, как в данном случае, конец сценария разветвляется).
Когда это происходит, адрес диапазона 1,//
, который обычно находит первое вхождение, начиная со строки 2, не будет совпадать, и диапазон не будет обрабатываться, потому что адрес оценивается, когда текущая строка уже равна 2
.
И наоборот, если в 1-й строке нет совпадений 1,//
будет введено 1,//
, и будет найдено истинное первое совпадение.
Чистый эффект такой же, как с GNU sed
0,/re/
: заменяется только первое вхождение, происходит ли оно в 1-й строке или в любом другом.
Недиапазонные подходы
ответ "потонг" демонстрирует петлевые техники, которые обходят необходимость диапазона; так как он использует синтаксис 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
начинающей диапазон), регулярное выражение еще не применено, поэтому //
не ссылается ни на что ,
За исключением GNU sed
special 0,/re/
syntax, любой диапазон, начинающийся с номера строки, эффективно исключает использование //
.
Ответ 5
Вы можете использовать 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: -)
Ответ 6
Довольно полный набор ответов на linuxtopia sed FAQ. Это также подчеркивает, что некоторые ответы, которые предоставили люди, не будут работать с не-GNU версией sed, например,
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 '}'
например:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
Ответ 7
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
Как работает этот script: для строк между 1 и первым #include
(после строки 1), если строка начинается с #include
, затем добавьте указанную строку.
Однако, если первая #include
находится в строке 1, то и первая строка, и следующая следующая #include
будут иметь добавленную линию. Если вы используете GNU sed
, у него есть расширение, где 0,/^#include/
(вместо 1,
) будет делать правильные вещи.
Ответ 8
Просто добавьте число вхождений в конец:
sed s/#include/#include "newfile.h"\n#include/1
Ответ 9
Возможное решение:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
Пояснение:
- читать строки, пока не найдем #include, напечатайте эти строки, а затем запустите новый цикл
- вставьте новую строку include
- введите цикл, который просто читает строки (по умолчанию sed также будет печатать эти строки), мы не вернемся к первой части script отсюда
Ответ 10
Я знаю, что это старый пост, но у меня было решение, которое я использовал:
grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file
В основном используйте grep, чтобы найти первое вхождение и остановиться там. Также напечатайте номер строки, т.е. 5: line. Труба, которая в sed и удалить: и что-нибудь после этого, вы просто остаетесь с номером строки. Труба, которая в sed добавляет s/.*/в конец, которая дает 1 строку script, которая отправляется в последний sed для работы в качестве файла script.
так что если regex = #include и replace = blah, а первое обнаружение grep найдено в строке 5, тогда данные, переданные в последний sed, будут 5s/.*/blah/.
Ответ 11
Если кто-то пришел сюда, чтобы заменить символ для первого появления во всех строках (например, я сам), используйте это:
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, вы можете вместо этого заменить только вторую.
Ответ 12
Я сделал бы это с помощью awk script:
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}
END {}
затем запустите его с помощью awk:
awk -f awkscript headerfile.h > headerfilenew.h
может быть неаккуратным, я новичок в этом.
Ответ 13
В качестве альтернативного варианта вы можете посмотреть команду ed
.
man 1 ed
teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'
# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
H
/# *include/i
#include "newfile.h"
.
,p
q
EOF
Ответ 14
Наконец, я получил это, чтобы работать в Bash script, который использовался для вставки уникальной метки времени в каждый элемент в фиде RSS:
sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter
Он изменяет только первое вхождение.
${nowms}
- это время в миллисекундах, установленное Perl script, $counter
- это счетчик, используемый для управления контуром в script, \
позволяет продолжить эту команду на следующей строке.
Файл читается, а stdout перенаправляется в рабочий файл.
Как я понимаю, 1,/====RSSpermalink====/
сообщает sed, когда останавливается, устанавливая ограничение диапазона, а затем s/====RSSpermalink====/${nowms}/
- это знакомая команда sed, которая заменяет первую строку вторым.
В моем случае я помещаю команду в двойные кавычки, потому что я использую ее в Bash script с переменными.
Ответ 15
Использование FreeBSD ed
и избежать ошибки ed
"no match" в случае, если в обрабатываемом файле нет include
:
teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'
# using FreeBSD ed
# to avoid ed "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
Ответ 16
Это может сработать для вас (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...
Ответ 17
С опцией GNU sed -z
вы можете обработать весь файл, как если бы он был только одной строкой. Таким образом, s/…/…/
заменит только первое совпадение во всем файле. Помните: s/…/…/
заменяет только первое совпадение в каждой строке, но с -z
опции -z
sed
обрабатывает весь файл как одну строку.
sed -z 's/#include/#include "newfile.h"\n#include'
В общем случае вам нужно переписать выражение sed, поскольку пространство шаблонов теперь содержит весь файл, а не одну строку. Некоторые примеры:
-
s/text.*//
можно переписать как s/text[^\n]*//
. [^\n]
соответствует всему, кроме символа новой строки. [^\n]*
будет соответствовать всем символам после text
до новой строки. -
s/^text//
можно переписать как s/(^|\n)text//
. -
s/text$//
можно переписать как s/text(\n|$)//
.
Ответ 18
Следующая команда удаляет первое вхождение строки внутри файла. Он также удаляет пустую строку. Он представлен в 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>
Файл результата (output.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
Ответ 19
Ничего нового, но, возможно, немного более конкретного ответа: sed -rn '0,/foo(bar).*/ s%%\1%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%%\1%p'
вызывает:
0x2200003
Ответ 20
POSIXly (также действует в sed), используется только одно регулярное выражение, требуется память только для одной строки (как обычно):
sed '/\(#include\).*/!b;//{h;s//\1 "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//\1 "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.
Ответ 21
Возможный вариант использования может заключаться в том, что ваши случаи распространяются по всему файлу, но вы знаете, что ваша единственная проблема - в первых 10, 20 или 100 строках.
Тогда простая адресация этих строк устраняет проблему - даже если формулировка ОП касается только первой.
sed '1,10s/#include/#include "newfile.h"\n#include/'
Ответ 22
sed имеет очень простой синтаксис для этого: "-i" является интерактивным (нет необходимости в новом файле).
Чтобы заменить только первый экземпляр:
sed -i 's/foo/bar/' file
чтобы заменить глобально, вы использовали бы
sed -i 's/foo/bar/g' file
В вашем примере я бы использовал (^ и $- начало и конец строки соответственно)
sed -i 's/^#include/#include\n#include/' file