Como usar o sed para substituir apenas a primeira ocorrência em um arquivo?

217

Gostaria de atualizar um grande número de arquivos de origem C ++ com uma diretiva de inclusão extra antes de qualquer #include existente. Para esse tipo de tarefa, normalmente uso um pequeno script bash com sed para reescrever o arquivo.

Como faço sedpara substituir apenas a primeira ocorrência de uma seqüência de caracteres em um arquivo, em vez de substituir todas as ocorrências?

Se eu usar

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

ele substitui todos os #includes.

Sugestões alternativas para alcançar a mesma coisa também são bem-vindas.

David Dibben
fonte

Respostas:

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

ou, se preferir: Nota do editor: funciona apenas com GNU sed .

sed '0,/foo/s//bar/' file 

Fonte

Ben Hoffstein
fonte
86
Acho que prefiro a solução 'ou se você preferir'. Também seria bom explicar as respostas - e fazer com que a resposta resolvesse a pergunta diretamente e depois generalizasse, em vez de apenas generalizar. Mas boa resposta.
27616 Jonathan Leffler
7
FYI para usuários de Mac, você deve substituir o 0 por um 1, de modo que: sed '1, / RE / s // to_that /' arquivo
mhost
1
@mhost Não, ele substituirá se o padrão for encontrado na linha 1 e não se o padrão estiver em outra linha, mas ainda é o primeiro padrão encontrado. PS deve ser mencionar que este 0,só funciona comgnu sed
JOTNE
11
Alguém poderia explicar a solução 'ou se preferir'? Não sei onde colocar o padrão "de".
Jean-Luc Nacif Coelho 2/02
3
@ Jean-LucNacifCoelho: using s//- ie, um regex vazio - significa que o regex aplicado mais recentemente é implicitamente reutilizado; neste caso RE,. Esse atalho conveniente significa que você não precisa duplicar a regex de final de intervalo na sua schamada.
mklement0
289

UMA sed script que substituirá apenas a primeira ocorrência de "Apple" por "Banana"

Exemplo

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

Este é o script simples: Nota do editor: funciona apenas com GNU sed .

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Os dois primeiros parâmetros 0e /Apple/são o especificador de intervalo. O s/Apple/Banana/é o que é executado dentro desse intervalo. Portanto, neste caso "dentro do intervalo do início ( 0) até a primeira instância de Apple, substitua Applepor Banana. Somente a primeira Appleserá substituída.

Antecedentes: No tradicional, sedo especificador de intervalo também é "comece aqui" e "termine aqui" (inclusive). No entanto, o "início" mais baixo é a primeira linha (linha 1) e, se o "fim aqui" for uma expressão regular, só será tentada a correspondência na próxima linha depois de "início", portanto, o fim mais cedo possível será a linha 2. Portanto, como o intervalo é inclusivo, o menor intervalo possível é "2 linhas" e o menor intervalo inicial é ambas as linhas 1 e 2 (ou seja, se houver uma ocorrência na linha 1, as ocorrências na linha 2 também serão alteradas, não desejadas neste caso ) GNUO sed adiciona sua própria extensão, permitindo especificar o início como o "pseudo", line 0para que o final do intervalo possa serline 1 , permitindo um intervalo de "apenas a primeira linha"

Ou uma versão simplificada (um tipo de ER vazio //significa reutilizar a especificada anteriormente, portanto isso é equivalente):

sed '0,/Apple/{s//Banana/}' input_filename

E as chaves são opcionais para o scomando, então isso também é equivalente:

sed '0,/Apple/s//Banana/' input_filename

Todos esses trabalhos no GNU sed apenas .

Você também pode instalar o GNU sed no OS X usando o homebrew brew install gnu-sed.

tim
fonte
166
traduzido para o idioma humano: comece na linha 0, continue até combinar com 'Apple', execute a substituição entre colchetes. cfr: grymoire.com/Unix/Sed.html#uh-29
mariotomo
8
No OS X, recebosed: 1: "…": bad flag in substitute command: '}'
ELLIOTTCABLE
5
@ELLIOTTCABLE no OS X, use sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'. Da resposta de @ MikhailVS (atualmente) bem abaixo.
djb
8
Também funciona sem os colchetes:sed '0,/foo/s/foo/bar/'
Innokenty
5
Eu fico sed: -e expression #1, char 3: unexpected , ' `com este
Jonathan
56
sed '0,/pattern/s/pattern/replacement/' filename

isso funcionou para mim.

exemplo

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Nota do editor: ambos funcionam apenas com o GNU sed .

Sushil
fonte
2
@ Landys, isso ainda substitui instâncias nas outras linhas também; não apenas a primeira instância
sarat
1
@sarat Sim, você está certo. sed '1,/pattern/s/pattern/replacement/' filenamesó funciona se "o padrão não ocorrer na primeira linha" no Mac. Excluirei meu comentário anterior, pois não é preciso. Os detalhes podem ser encontrados aqui ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ). A resposta de Andy funciona apenas para o GNU sed, mas não para o Mac.
Landys
45

Uma visão geral das muitas respostas existentes úteis , complementadas com explicações :

Os exemplos aqui usam um caso de uso simplificado: substitua a palavra 'foo' por 'bar' apenas na primeira linha correspondente.
Devido ao uso de cordas ANSI C-citados ( $'...') para proporcionar as linhas de entrada de amostra, bash, ksh, ou zshé assumida como a casca.


sedApenas GNU :

A resposta de Ben Hoffstein nos mostra que o GNU fornece uma extensão para a especificação POSIX,sed que permite o seguinte formato de 2 endereços : 0,/re/( rerepresenta uma expressão regular arbitrária aqui).

0,/re/permite que o regex corresponda também na primeira linha . Em outras palavras: esse endereço criará um intervalo da 1ª linha até a linha correspondente re- inclusive se reocorrerá na 1ª linha ou em qualquer linha subsequente.

  • Compare isso com o formulário compatível com POSIX 1,/re/, que cria um intervalo que corresponde da 1ª linha até a linha correspondente reàs linhas subseqüentes ; em outras palavras: isso não detectará a primeira ocorrência de uma recorrespondência se ocorrer na linha e também evita o uso de taquigrafia// para reutilizar o regex usado mais recentemente (consulte o próximo ponto). 1

Se você combinar um 0,/re/endereço com uma s/.../.../chamada (substituição) que use a mesma expressão regular, seu comando efetivamente executará a substituição apenas na primeira linha correspondente re.
sedfornece um atalho conveniente para reutilizar a expressão regular aplicada mais recentemente : um par de delimitadores vazio// ,.

$ 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

Um recurso POSIX somente sedcomo BSD (macOS)sed (também funcionará com o GNU sed ):

Como 0,/re/não pode ser usado e o formulário 1,/re/não detectará rese ocorrer na primeira linha (veja acima), é necessário um tratamento especial para a 1ª linha .

A resposta do MikhailVS menciona a técnica, colocada em um exemplo concreto aqui:

$ 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

Nota:

  • O //atalho de regex vazio é empregado duas vezes aqui: uma para o ponto final do intervalo e uma vez na schamada; nos dois casos, a regex fooé reutilizada implicitamente, permitindo que não tenhamos que duplicá-la, o que resulta em código mais curto e mais sustentável.

  • O POSIX sedprecisa de novas linhas reais após determinadas funções, como após o nome de um rótulo ou mesmo sua omissão, como é o caso taqui; dividir estrategicamente o script em várias -eopções é uma alternativa ao uso de novas linhas reais: finalize cada -eparte do script para onde normalmente uma nova linha precisaria ir.

1 s/foo/bar/substitui apenas foona 1ª linha, se encontrada lá. Nesse caso, tramifica para o final do script (ignora os comandos restantes na linha). (A tfunção ramifica para um rótulo somente se a schamada mais recente executou uma substituição real; na ausência de um rótulo, como é o caso aqui, o final do script é ramificado).

Quando isso acontecer, o endereço do intervalo 1,//, que normalmente encontra a primeira ocorrência iniciando na linha 2 , não corresponderá e o intervalo não será processado, porque o endereço é avaliado quando a linha atual já está 2.

Por outro lado, se não houver correspondência na 1ª linha, 1,// será inserida e encontrará a primeira correspondência verdadeira.

O efeito líquido é o mesmo que com GNU sed's 0,/re/: apenas a primeira ocorrência é substituído, se ocorre na linha 1 ou qualquer outro.


Abordagens fora da faixa

a resposta de potong demonstra técnicas de loop que ignoram a necessidade de um intervalo ; como ele usa a sintaxe GNU sed , eis os equivalentes compatíveis com POSIX :

Técnica de loop 1: Na primeira partida, execute a substituição e insira um loop que simplesmente imprima as linhas restantes como estão :

$ 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

Técnica de loop 2, apenas para arquivos pequenos : leia toda a entrada na memória e execute uma única substituição nela .

$ 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 fornece exemplos do que acontece com 1,/re/, com e sem subsequentes s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'rendimentos $'1bar\n2bar'; ou seja, ambas as linhas foram atualizadas, porque o número da linha 1corresponde à 1ª linha e a regex /foo/- o final do intervalo - é procurada apenas para iniciar na próxima linha. Portanto, as duas linhas são selecionadas nesse caso e a s/foo/bar/substituição é realizada em ambas.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' falha : com sed: first RE may not be empty(BSD / macOS) esed: -e expression #1, char 0: no previous regular expression (GNU), porque, no momento em que a 1ª linha está sendo processada (devido ao número da linha 1iniciar o intervalo), nenhuma regex foi aplicada ainda, portanto//não se refere a nada.
Com exceção da sintaxe sedespecial do GNU 0,/re/, qualquer intervalo que comece com um número de linha efetivamente impede o uso de //.

mklement0
fonte
25

Você pode usar o awk para fazer algo semelhante.

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Explicação:

/#include/ && !done

Executa a instrução de ação entre {} quando a linha corresponde a "#include" e ainda não a processamos.

{print "#include \"newfile.h\""; done=1;}

Isso imprime #include "newfile.h", precisamos escapar das aspas. Em seguida, definimos a variável done como 1, para não adicionar mais inclusões.

1;

Isso significa "imprimir a linha" - uma ação vazia é padronizada para imprimir $ 0, que imprime a linha inteira. Um liner e mais fácil de entender do que o sed IMO :-)

richq
fonte
4
Esta resposta é mais portátil do que as soluções sed, que contam com o GNU sed etc .. (por exemplo, sed no OS-X é uma merda!)
Jay Taylor
1
Isso é realmente mais compreensível, mas, para mim, adiciona uma linha em vez de substituí-la; comando usado: awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
Assassino de cereais
mesmo aqui, a maioria comando compreensível, mas adiciona uma linha acima do texto encontrado em vez de substituí-lo
Flion
1
A resposta é bastante legível. Aqui está minha versão que substitui a string em vez de adicionar uma nova linha. awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Orsiris de Jong 21/05/19
18

Uma coleção bastante abrangente de respostas às perguntas frequentes sobre linuxtopia sed . Ele também destaca que algumas respostas fornecidas pelas pessoas não funcionarão com a versão não-GNU do sed, por exemplo,

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

na versão não-GNU terá que ser

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

No entanto, esta versão não funcionará com o gnu sed.

Aqui está uma versão que funciona com ambos:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
MikhailVS
fonte
De fato, testado trabalhando no Ubuntu Linux v16 e FreeBSD v10.2. Te agradece.
Sopalajo de Arrierez
12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Como este script funciona: Para linhas entre 1 e a primeira #include(após a linha 1), se a linha começar com #include, em seguida, acrescente a linha especificada.

No entanto, se o primeiro #includeestiver na linha 1, a linha 1 e o próximo subsequente #includeterão a linha anexada. Se você estiver usando o GNU sed, ele possui uma extensão em que 0,/^#include/(em vez de 1,) fará a coisa certa.

Chris Jester-Young
fonte
11

Basta adicionar o número de ocorrência no final:

sed s/#include/#include "newfile.h"\n#include/1
inexistente
fonte
7
Infelizmente, isso não funciona. Ele substitui a primeira ocorrência em cada linha do arquivo e não a primeira ocorrência no arquivo.
David Dibben 29/09/08
1
Além disso, é uma extensão sed GNU, não um recurso sed padrão.
31416 Jonathan Leffler
10
Hmmm ... o tempo passa. POSIX 2008/2013 para sedespecifica o comando substitute com: [2addr]s/BRE/replacement/flagse observa que "O valor dos sinalizadores deve ser zero ou mais de: n Substitua pela enésima ocorrência apenas do BRE encontrado no espaço do padrão". Portanto, pelo menos no POSIX 2008, o trailing 1não é uma sedextensão GNU . De fato, mesmo no padrão SUS / POSIX 1997 , isso era suportado, então eu estava muito fora de linha em 2008.
Jonathan Leffler
7

Uma solução possível:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

Explicação:

  • leia as linhas até encontrar o #include, imprima essas linhas e inicie um novo ciclo
  • insira a nova linha de inclusão
  • insira um loop que apenas leia linhas (por padrão, sed também imprimirá essas linhas), não voltaremos à primeira parte do script daqui
mitchnull
fonte
sed: file me4.sed line 4: ":" lacks a label
rogerdpack
aparentemente, algo mudou nas versões sed recentes e rótulos vazios não são mais permitidos. atualizou a resposta
mitchnull 5/01
4

Eu sei que este é um post antigo, mas eu tinha uma solução que costumava usar:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

Basicamente, use grep para imprimir a primeira ocorrência e parar por aí. Além disso, imprima o número da linha, ou seja,5:line . Canalize isso para o sed e remova o: e qualquer outro item para que você fique com um número de linha. Canalize isso para o sed, que adiciona s /.*/, substitua o número final, o que resulta em um script de 1 linha que é canalizado para o último sed para ser executado como um script no arquivo.

portanto, se regex = #includee replace = blahe a primeira ocorrência que grep encontrar estiver na linha 5, os dados encaminhados para o último sed seriam 5s/.*/blah/.

Funciona mesmo se a primeira ocorrência estiver na primeira linha.

Michael Edwards
fonte
Eu odeio completamente scripts sed multilinhas ou comandos sed com qualquer coisa, exceto se um número de roupa, então estou de acordo com essa abordagem. Aqui está o que eu usei para o meu caso de usuário (funciona com o bash): filepath = / etc / hosts; patt = '^ \ (127 \ .0 \ .0 \ .1. * \)'; repl = '\ 1 newhostalias'; sed $ (IFS =: linearray = ($ (grep -E -m 1 -n "$ patt" "$ caminho do arquivo")) && echo $ {linearray [0]}) s / "$ patt" / "$ repl" / "$
filepath
Funciona. Embora apenas com o sed que são suficientemente inteligentes para aceitar sed -f -que alguns não são, mas você pode trabalhar em torno dele :)
rogerdpack
3

Se alguém veio aqui para substituir um caractere pela primeira ocorrência em todas as linhas (como eu), use o seguinte:

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

Alterando 1 para 2, por exemplo, você pode substituir todos os segundos a apenas.

FatihSarigol
fonte
2
Você não precisa fazer tudo isso, 's/a/b/'significa match aedo just first match for every matching line
Samveen
Eu estou com Samveen. Além disso, isso não responde à pergunta feita aqui . Eu aconselho a excluir esta resposta.
Socowi 12/02/19
A pergunta era "primeira ocorrência em um arquivo" não "primeira ocorrência em uma linha"
Manuel Romeiro
3

Com a -zopção do GNU sed, você pode processar o arquivo inteiro como se fosse apenas uma linha. Dessa forma, um s/…/…/substituirá apenas a primeira correspondência no arquivo inteiro. Lembre-se: s/…/…/substitui apenas a primeira correspondência em cada linha, mas com a -zopção sedtrata o arquivo inteiro como uma única linha.

sed -z 's/#include/#include "newfile.h"\n#include'

No caso geral, você deve reescrever sua expressão sed, pois o espaço do padrão agora contém o arquivo inteiro em vez de apenas uma linha. Alguns exemplos:

  • s/text.*//pode ser reescrito como s/text[^\n]*//. [^\n]corresponde a tudo, exceto o caractere de nova linha. [^\n]*corresponderá a todos os símbolos apóstext uma nova linha for atingida.
  • s/^text//pode ser reescrito como s/(^|\n)text//.
  • s/text$//pode ser reescrito como s/text(\n|$)//.
Socowi
fonte
2

eu faria isso com um script awk:

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

em seguida, execute-o com o awk:

awk -f awkscript headerfile.h > headerfilenew.h

pode ser desleixado, sou novo nisso.

wakingrufus
fonte
2

Como sugestão alternativa, você pode querer olhar para o edcomando.

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
timo
fonte
2

Finalmente consegui que isso funcionasse em um script Bash usado para inserir um registro de data e hora exclusivo em cada item em um feed RSS:

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

Altera apenas a primeira ocorrência.

${nowms}é o tempo em milissegundos definido por um script Perl, $counteré um contador usado para controle de loop dentro do script, \permite que o comando continue na próxima linha.

O arquivo é lido e o stdout é redirecionado para um arquivo de trabalho.

Do jeito que eu entendo, 1,/====RSSpermalink====/ diz ao sed quando parar definindo uma limitação de alcance e, em seguida,s/====RSSpermalink====/${nowms}/ é o comando sed conhecido para substituir a primeira corda pela segunda.

No meu caso, coloquei o comando entre aspas duplas porque estou usando-o em um script Bash com variáveis.

Michael Cook
fonte
2

Usando o FreeBSD ed e evite edo erro "no match" caso não exista uma includedeclaração em um arquivo a ser processado:

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
nazq
fonte
Isso é notavelmente semelhante à resposta de timo , mas foi adicionado mais de um ano depois.
Jonathan Leffler
2

Isso pode funcionar para você (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

ou se a memória não for um problema:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
potong
fonte
0

O comando a seguir remove a primeira ocorrência de uma seqüência de caracteres em um arquivo. Também remove a linha vazia. É apresentado em um arquivo xml, mas funcionaria com qualquer arquivo.

Útil se você trabalha com arquivos xml e deseja remover uma tag. Neste exemplo, ele remove a primeira ocorrência da marca "isTag".

Comando:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Arquivo de origem (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>

Arquivo de resultado (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps: não funcionou para mim no Solaris SunOS 5.10 (bastante antigo), mas funciona no Linux 2.6, sed versão 4.1.5

Andreas Panagiotidis
fonte
Isso parece notavelmente com a mesma idéia básica de várias respostas anteriores, com a mesma ressalva de que ele só funciona com o GNU sed(portanto, não funcionou com o Solaris). Você deve excluir isso, por favor - ele realmente não fornece novas informações distintivas para uma pergunta que já tinha 4 anos e meio de idade quando você respondeu. Concedido, ele tem um exemplo bem trabalhado, mas isso é de valor discutível quando a pergunta tem tantas respostas quanto essa.
precisa saber é o seguinte
0

Nada de novo, mas talvez uma resposta um pouco mais concreta: sed -rn '0,/foo(bar).*/ s%%\1%p'

Exemplo: xwininfo -name unity-launcherproduz resultados como:

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

Extrair o ID da janela com xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'produz:

0x2200003
Stephen Niedzielski
fonte
0

POSIXly (também válido no sed), apenas um regex usado, precisa de memória apenas para uma linha (como de costume):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Explicado:

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.
Isaac
fonte
0

Talvez o caso de uso seja que suas ocorrências estejam espalhadas por todo o arquivo, mas você sabe que sua única preocupação está nas primeiras 10, 20 ou 100 linhas.

Em seguida, o simples tratamento dessas linhas corrige o problema - mesmo que o texto do OP seja o primeiro.

sed '1,10s/#include/#include "newfile.h"\n#include/'
sastorsl
fonte
0

Uma solução possível aqui pode ser dizer ao compilador para incluir o cabeçalho sem que seja mencionado nos arquivos de origem. No GCC, existem estas opções:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

O compilador da Microsoft possui o / FI (inclusão forçada).

Esse recurso pode ser útil para alguns cabeçalhos comuns, como a configuração da plataforma. O Makefile do kernel Linux usa -includepara isso.

Kaz
fonte
-1
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
Warhansen
fonte