Nei commenti a questa domanda è emerso un caso in cui varie implementazioni sed non erano d'accordo su un programma abbastanza semplice e noi (o almeno io) non siamo stati in grado di determinare ciò che le specifiche effettivamente richiedono per esso.
Il problema è il comportamento di un intervallo che inizia in una riga eliminata:
1d;1,2d
La riga 2 dovrebbe essere eliminata anche se l'inizio dell'intervallo è stato rimosso prima di raggiungere quel comando? La mia aspettativa iniziale era "no" in linea con BSD sed, mentre GNU sed dice "sì" e controllare il testo delle specifiche non risolve completamente la questione.
Abbinando le mie aspettative sono (almeno) macOS e Solaris sed
e BSD sed
. In disaccordo sono (almeno) GNU e Busybox sed
, e numerose persone qui. I primi due sono certificati SUS mentre gli altri sono probabilmente più diffusi. Quale comportamento è corretto?
Il testo delle specifiche per intervalli di due indirizzi dice:
L' utilità sed deve quindi applicare in sequenza tutti i comandi i cui indirizzi selezionano quello spazio modello, fino a quando un comando inizia il ciclo successivo o si chiude.
e
Un comando di modifica con due indirizzi deve selezionare l'intervallo inclusivo dal primo spazio del modello che corrisponde al primo indirizzo attraverso il successivo spazio del modello che corrisponde al secondo. [...] A partire dalla prima riga che segue l'intervallo selezionato, sed cercherà di nuovo il primo indirizzo. Successivamente, il processo deve essere ripetuto.
Probabilmente, la linea 2 è nel "range compreso dal primo spazio modello che corrisponde al primo indirizzo attraverso lo spazio modello successivo che corrisponde al secondo", indipendentemente dal fatto che il punto di partenza è stato eliminato. D'altra parte, mi aspettavo che il primo d
passasse al ciclo successivo e non desse alla gamma la possibilità di iniziare. Le implementazioni certificate UNIX ™ fanno ciò che mi aspettavo, ma potenzialmente non ciò che le specifiche impongono.
Seguono alcuni esperimenti illustrativi, ma la domanda chiave è: cosa dovrebbe sed
fare quando inizia un intervallo su una linea eliminata?
Esperimenti ed esempi
Una dimostrazione semplificata del problema è questa, che stampa copie extra delle linee anziché eliminarle:
printf 'a\nb\n' | sed -e '1d;1,2p'
Ciò fornisce sed
due righe di input a
e b
. Il programma fa due cose:
Elimina la prima riga con
1d
. Ild
comando lo faràElimina lo spazio del motivo e avvia il ciclo successivo. e
- Seleziona l'intervallo di righe da 1 a 2 e le stampa esplicitamente, oltre alla stampa automatica che ogni riga riceve. Una linea inclusa nell'intervallo dovrebbe quindi apparire due volte.
La mia aspettativa era che questo dovesse stampare
b
solo, con l'intervallo non applicabile poiché 1,2
non viene mai raggiunto durante la riga 1 (perché è già passato d
al ciclo / riga successivo) e quindi l'inclusione dell'intervallo non inizia mai, mentre a
è stata eliminata. Gli Unix conformi sed
di macOS e Solaris 10 producono questo output, così come i non POSIX sed
in Solaris e BSD sed
in generale.
GNU sed, d'altra parte, stampa
b
b
indicando che ha interpretato l'intervallo. Ciò si verifica sia in modalità POSIX che non. La sed di Busybox ha lo stesso comportamento (ma non sempre un comportamento identico, quindi non sembra essere il risultato di un codice condiviso).
Ulteriore sperimentazione con
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/c/p'
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/d/p'
trova che sembra trattare un intervallo che inizia su una riga eliminata come se iniziasse sulla riga seguente . Ciò è visibile perché /c/
non corrisponde alla fine dell'intervallo. L'uso /b/
per avviare l'intervallo non si comporta come 2
.
L'esempio di lavoro iniziale che stavo usando era
printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'
come un modo per eliminare tutte le righe fino alla prima /a/
corrispondenza, anche se si trova sulla prima riga (ciò che GNU sed userebbe 0,/a/d
per - questa era una tentata versione compatibile con POSIX).
È stato suggerito che questo dovrebbe invece eliminare fino alla seconda corrispondenza /a/
se la prima riga corrisponde (o l'intero file se non esiste una seconda corrispondenza), il che sembra plausibile, ma ancora una volta GNU sed lo fa. Sia macOS sed che Solaris sed producono
b
c
d
e
per questo, come mi aspettavo (GNU sed produce l'output vuoto rimuovendo l'intervallo non terminato; Busybox sed stampa solo d
e e
, il che è chiaramente sbagliato, non importa quale). Generalmente suppongo che aver superato i test di conformità alla certificazione significhi che il loro comportamento è corretto, ma abbastanza persone hanno suggerito altrimenti che non sono sicuro, il testo delle specifiche non è completamente convincente e la suite di test non può essere perfettamente completo.
Chiaramente non è praticamente portatile scrivere quel codice oggi data l'incoerenza, ma teoricamente dovrebbe essere equivalente ovunque con un significato o l'altro. Penso che questo sia un bug, ma non so a quali implementazioni segnalarlo. La mia opinione al momento è che il comportamento di GNU e Busybox sed non è coerente con le specifiche, ma potrei sbagliarmi su questo.
Cosa richiede POSIX qui?
ed
, ignorando delsed
tutto?