Pergunta

Eu gostaria de atualizar um grande número de arquivos de origem C ++ com um extra incluem directiva antes de qualquer # inclui existentes. Para este tipo de tarefa, eu normalmente usar um script pequeno, com sed para re-gravar o arquivo.

Como faço para obter sed para substituir apenas a primeira ocorrência de uma string em um arquivo em vez de substituir todas as ocorrências?

Se eu usar

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

substitui todos os #includes.

sugestões alternativas para conseguir a mesma coisa também são bem vindos.

Foi útil?

Solução

 # 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:. Trabalha com GNU sed única

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

Fonte

Outras dicas

Escrever um script sed que só irá substituir a primeira ocorrência de "Apple" por "Banana"

Exemplo Dados:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Este é o script simples: Nota do editor:. Trabalha com GNU sed única

sed '0,/Apple/{s/Apple/Banana/}' filename
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 trabalham com GNU sed única

Um Visão geral dos muitos respostas úteis existentes , complementadas com explicações :

Os exemplos aqui usam um caso de uso simplificado: substituir a palavra 'foo' com 'bar' em apenas a primeira linha de correspondência
. cadeias, devido ao uso de ANSI C-citados ($'...') para proporcionar a linhas de entrada de amostra, bash, ksh, ou zsh é assumida como a casca.


GNU sed apenas:

anwswer de

Ben Hoffstein nos mostra que GNU fornece um extensão do especificação POSIX para sed que permite o seguinte forma 2-endereço: 0,/re/ (re representa um expressão regular arbitrária aqui).

0,/re/ permite que o regex para jogo na primeira linha também . Em outras palavras:. Tal endereço irá criar uma gama de 1ª linha até e incluindo a linha que partidas re - se re ocorre na 1ª linha ou em qualquer linha posterior

  • Compare isso com a forma compatível com POSIX 1,/re/ , o que cria um intervalo que partidas de 1ª linha até e incluindo a linha que partidas re em subseqüentes linhas; Em outras palavras: este não detectar a primeira ocorrência de um jogo re se acontecer de ocorrer em linha e também impede o uso de taquigrafia // para reutilização do regex usado mais recentemente (ver ponto seguinte). [1]

Se você combinar um endereço 0,/re/ com uma chamada s/.../.../ (substituição) que utiliza o mesma expressão regular, o comando irá efetivamente só executar a substituição no início linha que corresponde re.
sed fornece uma conveniente atalho para reutilizar a expressão mais recentemente aplicado regularmente : um esvaziar par delimitador, // .

$ 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, possui somente sed como BSD (MacOS) sed (também irá trabalhar com GNU sed):

Desde 0,/re/ não pode ser utilizado eo 1,/re/ formulário não irá detectar re se acontecer de ocorrer na primeira linha (veja acima), é necessário tratamento especial para a linha de 1º .

de MikhailVS menciona a técnica, colocar 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 regex // vazia é empregada duas vezes aqui: uma vez para o ponto final da gama, e uma vez na chamada s; em ambos os casos, foo regex é implicitamente reutilizado, permitindo-nos para não ter que duplicá-lo, o que torna tanto para o código mais curto e mais sustentável.

  • POSIX sed precisa de novas linhas reais após certas funções, como após o nome de uma etiqueta ou mesmo sua omissão, como é o caso com t aqui; estrategicamente divisão do script em várias opções -e é uma alternativa ao uso de uma nova linha reais:. terminar cada pedaço roteiro -e onde uma nova linha normalmente precisa ir

1 s/foo/bar/ substitui foo na 1ª linha única, se encontrado lá. Se ramos assim, t até o fimdo script (saltos restantes comandos na linha). (A função de t ramos para uma etiqueta somente se a chamada s mais recente realizada uma substituição real; na ausência de um rótulo, como é o caso aqui, o fim do script é ramificado a)

.

Quando isso acontece, 1,// endereço de gama, que normalmente encontra a primeira ocorrência a partir da linha 2 , não jogo, eo intervalo será não ser processado, porque o endereço é avaliada quando a linha atual já é 2.

Por outro lado, se não há nenhum jogo na 1ª linha, 1,// irá ser inserido, e vai encontrar o verdadeiro primeiro jogo.

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

.

NÃO gama aproxima

resposta

de Potong demonstra laço técnicas que desvio a necessidade de uma gama ; desde que ele usa GNU sed sintaxe, aqui estão os equivalentes POSIX :

Curva técnica 1: Em primeiro jogo, realizar a substituição, em seguida, introduzir um ciclo que simplesmente imprime as linhas restantes como está :

$ 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 circuito 2, para arquivos bem pequenos única :. ler a entrada inteira na memória, em seguida, realizar uma única substituição nele

$ 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 s// posterior :
- os rendimentos sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' $'1bar\n2bar'; ou seja, ambos linhas foram atualizado, porque o número da linha 1 coincide com a linha 1, e /foo/ regex - o fim da gama - é só então olhou para a partir da próximo linha . Portanto, ambos linhas são selecionadas neste caso, e a substituição s/foo/bar/ é realizada em ambos.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' não : com sed: first RE may not be empty (BSD / MacOS) e sed: -e expression #1, char 0: no previous regular expression (GNU), porque, no momento da primeira linha está sendo processada (devido ao número da linha 1 iniciar o intervalo), não regex tem sido ainda aplicada, de modo // não se refere a nada.
Com a exceção de sintaxe especial sed do GNU 0,/re/, qualquer intervalo que começa com um número de linha efetivamente opõe-se usar de //.

Você pode usar awk para fazer algo semelhante ..

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

Explicação:

/#include/ && !done

Executa a declaração de ação entre {} quando a linha corresponde "#include" e nós ainda não o processou.

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

Isto imprime #include "newfile.h", precisamos escapar as aspas. Em seguida, defina a variável done para 1, de modo que não adicionar mais inclui.

1;

Isto significa "imprimir a linha" - um vazio padrões de ação para imprimir $ 0, que imprime toda a linha. Um um forro e mais fácil de entender do que sed IMO: -)

Muito coleção abrangente de respostas sobre linuxtopia sed FAQ . Ele também destaca que algumas respostas pessoas fornecidos não vai funcionar com a versão não-GNU do sed, por exemplo

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

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

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

No entanto, esta versão não vai funcionar com o GNU sed.

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

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

ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

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

sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Como isso funciona de script:. Para linhas entre 1 e no primeiro #include (depois linha 1), se a linha começa com #include, então preceder a linha especificado

No entanto, se o primeiro #include está em linha 1, então tanto a linha 1 ea próxima #include posterior terá a linha prefixado. Se você estiver usando GNU sed, tem uma extensão onde 0,/^#include/ (em vez de 1,) vai fazer a coisa certa.

Uma possível solução:

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

Explicação:

  • ler linhas até encontrar o #include, imprima estas linhas, em seguida, iniciar novo ciclo
  • Insira o novo incluem linha
  • inserir um loop que apenas lê linhas (por padrão sed também irá imprimir estas linhas), não vamos voltar para a primeira parte do script a partir daqui

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

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

Basicamente usar grep para encontrar a primeira ocorrência e parar por aí. Também imprimir número de linha ou seja, 5: linha. Tubo que em sed e remova o: e nada depois que você está acabado de sair com um número de linha. Tubo que em sed que adiciona s /.*/ substituir até o fim o que dá a um script 1 linha que é canalizada para o último sed para ser executado como um script no arquivo.

então se regex = #include e substituir = blah e os primeiros achados grep occurrance é na linha 5, em seguida, os dados canalizada para o último sed seria 5s /.*/ blá /.

Se alguém veio aqui para substituir um personagem para a 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

Ao mudar de 1 a 2 por exemplo, você pode substituir todo o segundo de uma única vez.

Gostaria de fazer isso com um script awk:

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

, em seguida, executá-lo com awk:

awk -f awkscript headerfile.h > headerfilenew.h

pode ser desleixado, eu sou novo para isso.

Como uma sugestão alternativa você pode querer olhar para o comando 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

Eu finalmente consegui isso para o trabalho em um script Bash usado para inserir um timestamp único em cada item em um feed RSS:

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

Ele muda apenas a primeira ocorrência.

${nowms} é o tempo em milissegundos definidas por um script Perl, $counter é um contador utilizado para controle de laço dentro do script, \ permite que o comando deve ser prosseguido na próxima linha.

O arquivo é lido e stdout é redirecionada para um arquivo de trabalho.

A forma como eu entendo, 1,/====RSSpermalink====/ diz ao sed quando parar, definindo uma limitação gama, e s/====RSSpermalink====/${nowms}/ então é o comando familiarizado sed para substituir a primeira corda com o segundo.

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

Usando FreeBSD ed e evitar ed de "nenhuma correspondência" erro no caso não há nenhuma declaração include em um arquivo para 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

Este trabalho poder para você (GNU sed):

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

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

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
da opção -z

Com GNU sed você poderia processar o arquivo inteiro como se fosse apenas uma linha. Dessa forma, um s/…/…/ só iria substituir o primeiro jogo em todo o arquivo. Lembre-se:. s/…/…/ só substitui a primeira correspondência em cada linha, mas com a opção -z sed trata o arquivo inteiro como uma única linha

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

No caso geral você tem que reescrever sua expressão sed já que o espaço padrão passou a deter todo o arquivo em vez de apenas uma linha. Alguns exemplos:

  • s/text.*// pode ser reescrita como s/text[^\n]*//. [^\n] combina com tudo , exceto o caractere de nova linha. [^\n]* irá corresponder a todos os símbolos após text até que uma nova linha é alcançado.
  • s/^text// pode ser reescrita como s/(^|\n)text//.
  • s/text$// pode ser reescrita como s/text(\n|$)//.

O comando a seguir remove a primeira ocorrência de uma string, dentro de um arquivo. Ele remove a linha vazia também. É apresentado em um arquivo XML, mas ele iria trabalhar com qualquer arquivo.

É útil se você trabalha com arquivos XML e você quiser remover um tag. Neste exemplo, ele remove a primeira ocorrência da tag "ISTAG".

Comando:

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

O 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 (muito antigos), mas ele funciona em Linux 2.6, sed versão 4.1.5

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

Exemplo: xwininfo -name unity-launcher produz uma saída 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

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

0x2200003

POSIXly (válido também no sed), Só um regex usada, a memória necessidade somente para uma linha (como de costume):

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

explicou:

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.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top