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.
sed
Apenas 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/
( re
representa 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 re
ocorrerá 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 re
correspondência se ocorrer na 1ª 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
.
sed
fornece 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 sed
como 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á re
se 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 s
chamada; 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 sed
precisa 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 t
aqui; dividir estrategicamente o script em várias -e
opções é uma alternativa ao uso de novas linhas reais: finalize cada -e
parte do script para onde normalmente uma nova linha precisaria ir.
1 s/foo/bar/
substitui apenas foo
na 1ª linha, se encontrada lá. Nesse caso, t
ramifica para o final do script (ignora os comandos restantes na linha). (A t
função ramifica para um rótulo somente se a s
chamada 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 1
corresponde à 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 1
iniciar o intervalo), nenhuma regex foi aplicada ainda, portanto//
não se refere a nada.
Com exceção da sintaxe sed
especial do GNU 0,/re/
, qualquer intervalo que comece com um número de linha efetivamente impede o uso de //
.