Как использовать sed для замены только первого вхождения в файл?


Я хочу обновить большое количество исходных файлов C++ с дополнительной директивой include перед любым существующим #includes. Для такого рода задач я обычно использую небольшой скрипт bash с sed для перезаписи файла.

Как заставить sed заменить только первое вхождение строки в файле, а не заменять каждое вхождение?

Если я использую

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

Он заменяет все #includes.

альтернативные предложения для достижения того же вещь также приветствуется.

20 165

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---

или, если вы предпочитаете: Примечание редактора: работает с GNUsed только.

sed '0,/RE/s//to_that/' file 

источник

напишите сценарий sed, который заменит только первое появление "Яблока"на " банан"

Пример: Вход: Выход:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Это простой скрипт: Примечание редактора: работает с GNUsed только.

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

Примечание редактора: оба работают с GNUsed только.

An обзор из многих полезных существующие ответы в сочетании с объяснениями:

в примерах здесь используется упрощенный вариант использования: замените слово " foo " на " bar " только в первой строке соответствия.
За счет использования ANSI C-строки в кавычках ($'...') чтобы обеспечить образцы входных линий,bash,ksh или zsh предполагается, что ракушка.


GNUsed только:

Бен 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 ' s 0,/re/: только первое вхождение заменен, если это происходит на 1-й Линии или любой другой.


не-спектр подходов

potong это показывает цикл методы это обойти необходимость в диапазоне; так как он использует GNUsed синтаксис, здесь представлены 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'ы специальные 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

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

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, распечатать эти строки, а затем начать новый цикл
  • вставить новую строку включения
  • ввести цикл, который просто читает строки (по умолчанию 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 с переменными.

используя FreeBSDed, а не 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