Vuoi sostituire solo la prima occorrenza con sed


27

File originale

claudio
antonio
claudio
michele

Voglio cambiare solo la prima occorrenza di "claudio" con "claudia" in modo che il risultato del file

claudia
antonio
claudio
michele

Ho provato

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

Ma esegui una sostituzione globale. Perché?


Guarda qui linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… e anche info sed: ( 0,/REGEXP/: un numero di riga 0 può essere usato in una specifica di indirizzo come 0,/REGEXP/quella sedche cercherà di far corrispondere REGEXP anche nella prima riga di input. In altre parole, 0,/REGEXP/è simile a 1,/REGEXP/, tranne per il fatto che se ADDR2 corrisponde alla prima riga di input, 0, / REGEXP / form lo considererà come fine dell'intervallo, mentre 1, / REGEXP / form corrisponderanno all'inizio del suo intervallo e quindi estenderanno l'intervallo fino alla seconda occorrenza dell'espressione regolare)
jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomidovrebbe fare
Adam Katz il

Risposte:


23

Se stai usando GNU sed, prova:

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

sednon inizia a verificare la regex che termina un intervallo fino a dopo la riga che inizia tale intervallo.

Da man sed(manpage POSIX, sottolineatura mia):

Un comando di modifica con due indirizzi seleziona l'intervallo inclusivo
dal primo spazio modello che corrisponde al primo indirizzo attraverso il
spazio modello successivo che corrisponde al secondo. 

utilizzando awk

Le gamme awkfunzionano di più come ti aspettavi:

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

Spiegazione:

  • NR==1,/claudio/

    Questo è un intervallo che inizia con la riga 1 e termina con la prima occorrenza di claudio.

  • sub(/claudio/, "claudia")

    Mentre siamo nell'intervallo, questo comando sostitutivo viene eseguito.

  • 1

    Questa stenografia criptica di questo awk per stampare la linea.


1
Ciò presuppone sedperò GNU .
Stéphane Chazelas,

@ StéphaneChazelas Funziona anche se è impostato POSIXLY_CORRECT ma immagino che ciò non significhi tanto quanto vorrei. Risposta aggiornata (mi manca per le macchine di prova BSD).
Giovanni 1024

Il awk può, IMO, essere più semplice con una variabile di stato booleana:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman,

@glennjackman oppureawk !x{x=sub(/claudio/,"claudia")}1

Inoltre non ho potuto usare con successo un delimitatore diverso nella prima parte:0,/claudio/
Pat Myron,

4

Qui ci sono altri 2 sforzi programmatici con sed: entrambi leggono l'intero file in una singola stringa, quindi la ricerca sostituirà solo il primo.

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

Con commento:

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

3

Una nuova versione di GNU sedsupporta l' -zopzione.

Normalmente sed legge una riga leggendo una stringa di caratteri fino al carattere di fine riga (nuova riga o ritorno a capo).
La versione GNU di sed ha aggiunto una funzione nella versione 4.2.2 per utilizzare invece il carattere "NULL". Ciò può essere utile se si dispone di file che utilizzano NULL come separatore di record. Alcune utility GNU possono generare output che utilizza un NULL anziché una nuova riga, come "find. -Print0" o "grep -lZ".

È possibile utilizzare questa opzione quando si desidera sedlavorare su linee diverse.

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

ritorna

claudia
antonio
claudio
michele

1

È possibile utilizzare awkcon un flag per sapere se la sostituzione è già stata eseguita. In caso contrario, procedere:

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

1

In realtà è davvero semplice se si imposta solo un po 'di ritardo - non è necessario andare per estensioni inaffidabili:

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

Ciò difende la prima riga dalla seconda e la seconda dalla terza, ecc.

Stampa:

claudia
antonio
claudio
michele

1

E un'altra opzione

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

Il vantaggio è che usa la doppia virgoletta, quindi puoi usare le variabili all'interno, ad es.

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

1
Si hai ragione. L'idea generale è la stessa. Ma, per favore, prova a sostituire le virgolette singole, tra virgolette direttamente e vedi se funziona. Il diavolo sta nei dettagli. In questo esempio questi sono spazi e una via di fuga. Credo che questa continuazione delle risposte precedenti possa far risparmiare tempo a qualcuno. Ed è per questo che ho deciso di pubblicare il post.
utom

1

Questo può essere fatto anche senza lo spazio di attesa e senza concedere tutte le linee nello spazio del motivo:

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

Spiegazione: Cerchiamo di trovare "claudio" e se lo facciamo saltiamo nel piccolo ciclo di caricamento della stampa tra :xe bx. Altrimenti stampiamo e riavviamo lo script con la riga successiva.

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

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

1
Ti sei preoccupato di leggere la domanda?
don_crissti,

1

Sumary

Sintassi GNU:

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

O anche (per usare una sola volta la parola da sostituire:

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

Oppure, nella sintassi POSIX:

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

funziona su qualsiasi sed, elabora solo tutte le righe necessarie per trovare la prima claudio, funziona anche se si claudiotrova nella prima riga ed è più corta in quanto utilizza solo una stringa regex.

Dettaglio

Per cambiare solo una riga devi selezionare solo una riga.

Usando un 1,/claudio/(dalla tua domanda) seleziona:

  • dalla prima riga (incondizionatamente)
  • alla riga successiva che contiene la stringa claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

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

Per selezionare qualsiasi riga che contiene claudio, utilizzare:

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

E per selezionare solo il primo claudio nel file, utilizzare:

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

Quindi, puoi effettuare una sostituzione solo su quella linea:

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

Il che cambierà solo la prima occorrenza della corrispondenza regex sulla riga, anche se potrebbe essercene più di una, sulla prima riga corrispondente alla regex.

Naturalmente, il /claudio/regex potrebbe essere semplificato per:

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

E, quindi, l'unica cosa che manca è stampare tutte le altre righe non modificate:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.