Как использовать sed, чтобы заменить только первое вхождение в файле? - PullRequest
183 голосов
/ 29 сентября 2008

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

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

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

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

заменяет все #include.

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

Ответы [ 20 ]

3 голосов
/ 20 июля 2017

Если кто-то пришел сюда, чтобы заменить символ для первого вхождения во всех строках (как я), используйте это:

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.

2 голосов
/ 31 июля 2013

Это может работать для вас (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...
2 голосов
/ 17 ноября 2011

Я наконец-то заставил это работать в скрипте Bash, который использовался для вставки уникальной метки времени в каждый элемент RSS-канала:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

Изменяет только первое вхождение.

${nowms} - это время в миллисекундах, установленное скриптом Perl, $counter - счетчик, используемый для управления циклом в скрипте, \ позволяет продолжить выполнение команды на следующей строке.

Файл считывается, и стандартный вывод перенаправляется в рабочий файл.

Как я понимаю, 1,/====RSSpermalink====/ сообщает sed, когда останавливаться, устанавливая ограничение диапазона, а затем s/====RSSpermalink====/${nowms}/ - это знакомая команда sed, которая заменяет первую строку второй.

В моем случае я поставил команду в двойных кавычках, потому что я использую ее в скрипте Bash с переменными.

2 голосов
/ 28 апреля 2011

В качестве альтернативы вы можете посмотреть на команду 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
2 голосов
/ 18 ноября 2008

я бы сделал это с помощью сценария awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

затем запустите его с помощью awk:

awk -f awkscript headerfile.h > headerfilenew.h

может быть небрежно, я новичок в этом.

2 голосов
/ 16 мая 2012

Использование 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
0 голосов
/ 12 февраля 2019

С опцией GNU sed -z вы можете обработать весь файл, как если бы он был только одной строкой. Таким образом, s/…/…/ заменит только первое совпадение во всем файле. Помните: s/…/…/ заменяет только первое совпадение в каждой строке, но с параметром -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|$)//.
0 голосов
/ 04 февраля 2013

Следующая команда удаляет первое вхождение строки в файле. Это также удаляет пустую строку. Он представлен в файле 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

0 голосов
/ 11 октября 2018

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.
0 голосов
/ 17 января 2017

Ничего нового, но, возможно, более конкретный ответ: 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
...