Как использовать 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 вызов; в обоих случаях, regex foo неявно используется повторно, что позволяет нам не дублировать его, что делает как более короткий, так и более обслуживаемый код.

  • 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

просто добавьте число вхождения в конце:

sed s/#include/#include "newfile.h"\n#include/1

#!/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