Como usar sed para substituir apenas a primeira ocorrência em um arquivo?
-
02-07-2019 - |
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.
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
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:
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 partidasre
em subseqüentes linhas; Em outras palavras: este não detectar a primeira ocorrência de um jogore
se acontecer de ocorrer em 1º 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 chamadas
; 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 comt
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,
$ 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...
-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 comos/text[^\n]*//
.[^\n]
combina com tudo , exceto o caractere de nova linha.[^\n]*
irá corresponder a todos os símbolos apóstext
até que uma nova linha é alcançado. -
s/^text//
pode ser reescrita comos/(^|\n)text//
. -
s/text$//
pode ser reescrita comos/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.