Deseja substituir apenas a primeira ocorrência por sed

26

Arquivo original

claudio
antonio
claudio
michele

Quero alterar apenas a primeira ocorrência de "claudio" por "claudia" para que o resultado do arquivo

claudia
antonio
claudio
michele

eu tentei

sed -e '1,/claudio/s/claudio/claudia/' nomi

Mas faça uma substituição global.

elbarna
fonte
Olhe aqui linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/... e também info sed: ( 0,/REGEXP/: Um número de linha de 0 pode ser usado em uma especificação de endereço como 0,/REGEXP/de modo que sedvai tentar igualar REGEXP na primeira linha de entrada demasiado Em outras palavras,. 0,/REGEXP/É semelhante a 1,/REGEXP/, exceto que se o ADDR2 corresponder à primeira linha de entrada, o formulário 0, / REGEXP / considerará que ele encerra o intervalo, enquanto o formulário 1, / REGEXP / corresponderá ao início do seu intervalo e, portanto, aumentará o alcance. até a segunda ocorrência da expressão regular)
jimmij 04/03
awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomideve fazer #
Adam Katz
stackoverflow.com/questions/148451/…
Ciro Santilli #: 26416 Ciro Santilli escreveu:

Respostas:

23

Se você estiver usando o GNU sed, tente:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sednão inicia a verificação do regex que termina um intervalo até depois da linha que inicia esse intervalo.

De man sed(página de manual do POSIX, ênfase minha):

Um comando de edição com dois endereços deve selecionar o intervalo inclusivo
do primeiro espaço de padrão que corresponde ao primeiro endereço através do
próximo espaço padrão que corresponde ao segundo. 

Usando awk

Varia no awktrabalho mais do que você esperava:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

Explicação:

  • NR==1,/claudio/

    Esse é um intervalo que começa com a linha 1 e termina com a primeira ocorrência de claudio.

  • sub(/claudio/, "claudia")

    Enquanto estamos no intervalo, este comando substituto é executado.

  • 1

    Esta abreviação enigmática deste awk para imprimir a linha.

John1024
fonte
11
Isso assume o GNU, no sedentanto.
Stéphane Chazelas 4/03/15
@ StéphaneChazelas Também funciona se POSIXLY_CORRECT estiver definido, mas acho que isso não significa tanto quanto eu gostaria. Resposta atualizada (falta de máquinas de teste BSD).
precisa saber é
O awk pode, IMO, ser mais simples com uma variável de status boolean:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
Glenn Jackman
@glennjackman ouawk !x{x=sub(/claudio/,"claudia")}1
Também não consegui usar com êxito um delimitador diferente na primeira parte:0,/claudio/
Pat Myron
4

Aqui estão mais dois esforços programáticos com o sed: ambos lêem o arquivo inteiro em uma única sequência, então a pesquisa substituirá apenas a primeira.

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

Com comentários:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file
Glenn Jackman
fonte
3

Uma nova versão do GNU sedsuporta a -zopção.

Normalmente, sed lê uma linha lendo uma sequência de caracteres até o final de linha (nova linha ou retorno de carro).
A versão GNU do sed adicionou um recurso na versão 4.2.2 para usar o caractere "NULL". Isso pode ser útil se você tiver arquivos que usam o NULL como um separador de registros. Alguns utilitários GNU podem gerar saídas que usam um NULL em vez de uma nova linha, como "find. -Print0" ou "grep -lZ".

Você pode usar esta opção quando quiser sedtrabalhar em linhas diferentes.

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

retorna

claudia
antonio
claudio
michele
Walter A
fonte
1

Você pode usar awkcom um sinalizador para saber se a substituição já foi feita. Caso contrário, continue:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele
fedorqui
fonte
1

Na verdade, é muito fácil se você apenas configurar um pequeno atraso - não é necessário procurar extensões não confiáveis:

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

Isso apenas adia a primeira linha para a segunda e a segunda para a terceira e etc.

Imprime:

claudia
antonio
claudio
michele
mikeserv
fonte
1

E mais uma opção

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

A vantagem é que ele usa aspas duplas, para que você possa usar variáveis ​​dentro, ou seja.

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi
utom
fonte
11
Sim você está certo. A ideia geral é a mesma. Mas, por favor, tente substituir simples, entre aspas duplas diretamente e veja se funciona. O diabo está nos detalhes. Neste exemplo, esses são espaços e uma saída. Acredito que essa continuação das respostas anteriores pode economizar o tempo de alguém. E foi por isso que decidi publicar o post.
utom
1

Isso também pode ser feito sem o espaço de espera e sem concatar todas as linhas no espaço do padrão:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

Explicação: Tentamos encontrar "claudio" e, se o fizermos, pulamos no pequeno loop de carga de impressão entre :xe bx. Caso contrário, imprimiremos e reiniciaremos o script com a próxima linha.

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi
Lucas
fonte
1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block
jgshawkey
fonte
11
Você se incomodou em ler a pergunta?
31416 don_crissti
1

Sumary

Sintaxe GNU:

sed '/claudio/{s//claudia/;:p;n;bp}' file

Ou mesmo (para usar apenas uma vez a palavra a ser substituída:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

Ou, na sintaxe POSIX:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

funciona em qualquer sed, processa apenas quantas linhas claudioforem necessárias para encontrar a primeira , funciona mesmo que claudioesteja na primeira linha e é mais curto, pois usa apenas uma sequência de expressões regulares.

Detalhe

Para alterar apenas uma linha, você precisa selecionar apenas uma linha.

O uso de 1,/claudio/(da sua pergunta) seleciona:

  • da primeira linha (incondicionalmente)
  • para a próxima linha que contém a sequência claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

Para selecionar qualquer linha que contenha claudio, use:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

E para selecionar apenas o primeiro claudio no arquivo, use:

sed -n '/claudio/{p;q}' file
claudio 1

Em seguida, você pode fazer uma substituição somente nessa linha:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

O que alterará apenas a primeira ocorrência da correspondência de regex na linha, mesmo que possa haver mais de uma, na primeira linha que corresponda à regex.

Obviamente, a /claudio/regex poderia ser simplificada para:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

E, então, a única coisa que falta é imprimir todas as outras linhas sem modificação:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Isaac
fonte