Cosa richiede POSIX sed per `1d; 1,2d` in cui un intervallo di indirizzi inizia da una linea già eliminata?


11

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 sede 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 dpassasse 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 seddue righe di input ae b. Il programma fa due cose:

  1. Elimina la prima riga con 1d. Il dcomando lo farà

    Elimina lo spazio del motivo e avvia il ciclo successivo. e

  2. 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,2non viene mai raggiunto durante la riga 1 (perché è già passato dal ciclo / riga successivo) e quindi l'inclusione dell'intervallo non inizia mai, mentre aè stata eliminata. Gli Unix conformi seddi macOS e Solaris 10 producono questo output, così come i non POSIX sedin Solaris e BSD sedin 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/dper - 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 de 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?


Come soluzione temporanea, scrivere in un file temporaneo ed elaborarlo con POSIX ed, ignorando del sedtutto?
D. Ben Knoble,

Risposte:


9

Questo è stato sollevato nella mailing list del gruppo di Austin nel marzo 2012. Ecco l'ultimo messaggio su questo (di Geoff Clare del gruppo Austin (l'ente che mantiene POSIX), che è anche colui che ha sollevato il problema in primo luogo). Qui copiato dall'interfaccia Nmtg di gmane:

Date: Fri, 16 Mar 2012 17:09:42 +0000
From: Geoff Clare <gwc-7882/jkIBncuagvECLh61g@public.gmane.org>
To: austin-group-l-7882/jkIBncuagvECLh61g@public.gmane.org
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Strange addressing issue in sed

Stephane Chazelas <stephane_chazelas-Qt13gs6zZMY@public.gmane.org> wrote, on 16 Mar 2012:
>
> 2012-03-16 15:44:35 +0000, Geoff Clare:
> > I've been alerted to an odd behaviour of sed on certified UNIX
> > systems that doesn't seem to match the requirements of the
> > standard.  It concerns an interaction between the 'n' command
> > and address matching.
> > 
> > According to the standard, this command:
> > 
> > printf 'A\nB\nC\nD\n' | sed '1,3s/A/B/;1,3n;1,3s/B/C/'
> > 
> > should produce the output:
> > 
> > B
> > C
> > C
> > D
> > 
> > GNU sed does produce this, but certified UNIX systems produce this:
> > 
> > B
> > B
> > C
> > D
> > 
> > However, if I change the 1,3s/B/C/ to 2,3s/B/C/ then they produce
> > the expected output (tested on Solaris and HP-UX).
> > 
> > Is this just an obscure bug from common ancestor code, or is there
> > some legitimate reason why this address change alters the behaviour?
> [...]
> 
> I suppose the idea is that for the second 1,3cmd, line "1" has
> not been seen, so the 1,3 range is not entered.

Ah yes, now it makes sense, and it looks like the standard does
require this slightly strange behaviour, given how the processing
of the "two addresses" case is specified:

    An editing command with two addresses shall select the inclusive
    range from the first pattern space that matches the first address
    through the next pattern space that matches the second.  (If the
    second address is a number less than or equal to the line number
    first selected, only one line shall be selected.) Starting at the
    first line following the selected range, sed shall look again for
    the first address. Thereafter, the process shall be repeated.

It's specified this way because the addresses can be BREs, but if
the same matching process is applied to the line numbers (even though
they can only match at most once), then the 1,3 range on that last
command is never entered.

-- 
Geoff Clare <g.clare-7882/jkIBncuagvECLh61g@public.gmane.org>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Ed ecco la parte rilevante del resto del messaggio (da me) che Geoff stava citando:

I suppose the idea is that for the second 1,3cmd, line "1" has
not been seen, so the 1,3 range is not entered.

Same idea as in

printf '%s\n' A B C | sed -n '1d;1,2p'

whose behavior differ in traditional (heirloom toolchest at
least) and GNU.

It's unclear to me whether POSIX wants one behavior or the
other.

Quindi, (secondo Geoff) POSIX è chiaro che il comportamento GNU non è conforme.

Ed è vero che è meno coerente (confronta seq 10 | sed -n '1d;1,2p'con seq 10 | sed -n '1d;/^1$/,2p') anche se potenzialmente meno sorprendente per le persone che non si rendono conto di come vengono elaborati gli intervalli (anche Geoff inizialmente ha trovato "strano" il comportamento conforme ).

Nessuno si è preoccupato di segnalarlo come un bug per la gente di GNU. Non sono sicuro di qualificarlo come un bug. Probabilmente l'opzione migliore sarebbe l'aggiornamento delle specifiche POSIX per consentire ad entrambi i comportamenti di chiarire che non si può fare affidamento su nessuno dei due.

Modifica . Ora ho dato un'occhiata sedall'implementazione originale in Unix V7 della fine degli anni '70, e sembra quasi che quel comportamento per gli indirizzi numerici non fosse inteso o almeno non pensato fino in fondo.

Con la lettura di Geoff delle specifiche (e la mia interpretazione originale del perché succede), al contrario, in:

seq 5 | sed -n '3d;1,3p'

le righe 1, 2, 4 e 5 dovrebbero essere emesse, perché questa volta è l'indirizzo finale che non viene mai incontrato dal 1,3pcomando a distanza, come inseq 5 | sed -n '3d;/1/,/3/p'

Tuttavia, ciò non accade nell'implementazione originale, né in qualsiasi altra implementazione che ho provato (busybox sedrestituisce le righe 1, 2 e 4 che sembrano più un bug).

Se si osserva il codice UNIX v7 , verifica il caso in cui il numero di riga corrente è maggiore dell'indirizzo (numerico) finale e quindi esce dall'intervallo. Il fatto che non lo faccia per l'indirizzo iniziale sembra più una supervisione che una progettazione intenzionale.

Ciò significa che al momento non esiste alcuna implementazione che sia effettivamente conforme a quella interpretazione delle specifiche POSIX a tale riguardo.

Un altro comportamento confuso con l'implementazione GNU è:

$ seq 5 | sed -n '2d;2,/3/p'
3
4
5

Poiché la riga 2 è stata ignorata, 2,/3/viene immessa sulla riga 3 (la prima riga il cui numero è> = 2). Ma poiché è la riga che ci ha fatto inserire l'intervallo, non viene verificato l' indirizzo finale . Peggiora con busybox sedin:

$ seq 10 | busybox sed -n '2,7d; 2,3p'
8

Poiché le righe da 2 a 7 sono state eliminate, la riga 8 è la prima che è> = 2, quindi viene inserito l'intervallo 2,3 !


1
Quindi sembra che il problema sia ancora irrisolto - sono d'accordo con il tuo ragionamento sul perché sta accadendo, ma anche che non è chiaro se questo fosse ciò che era desiderato - anche se sembra anche che Geoff fosse convinto dal testo citato che le implementazioni di UNIX ™ erano corretti. Anche questa è la tua lettura?
Michael Homer,

1
@MichaelHomer, l'idea è che (secondo Geoff) POSIX è chiaro che il comportamento GNU non è conforme. Ed è vero che è meno coerente (confronta seq 10 | sed -n '1d;1,2p'con seq 10 | sed -n '1d;/^1$/,2p') anche se potenzialmente meno sorprendente per le persone non si renderebbe conto di come vengono elaborati gli intervalli. Nessuno si è preoccupato di segnalarlo come un bug per la gente di GNU. Non sono sicuro che lo classificherei come un bug, probabilmente l'opzione migliore sarebbe quella di aggiornare le specifiche POSIX per consentire a entrambi i comportamenti di chiarire che non si può fare affidamento su nessuno dei due.
Stéphane Chazelas,

2
In realtà, poiché la definizione POSIX non afferma che gli indirizzi debbano essere "visti" per avviare o terminare un intervallo di indirizzi, l'implementazione di GNU IMO segue più rigorosamente la formulazione POSIX (sorprendente per GNU!). Questo è anche il comportamento desiderato per la maggior parte dei casi del mondo reale che conosco. Ma, come fai notare, dovrebbe essere coerente. E controllare ogni linea per i modelli di intervallo anche dopo che dnon è solo un problema di prestazioni, porta a ulteriori problemi di implementazione in quanto i modelli "invisibili" necessari per gli intervalli non possono avere effetto su ulteriori schemi vuoti ... un casino!
Philippos,

@Philippos, in quello 1d;1,2pscript il 1,2pcomando non viene eseguito sulla prima riga, quindi al primo indirizzo non corrisponde alcuno spazio modello , che è un modo per interpretare quel testo. In ogni caso, dovrebbe essere ovvio che la valutazione degli indirizzi dovrebbe essere effettuata al momento dell'esecuzione del comando. Come insed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas il

2
@Isaac, questo è il nocciolo del problema. Nel linguaggio POSIX 1e /1/sono entrambi gli indirizzi, 1è l'indirizzo quando il numero di riga è 1, /1/è l'indirizzo quando lo spazio modello contiene 1, la domanda è se entrambi i tipi di indirizzo debbano essere trattati allo stesso modo, o se gli intervalli di numeri di riga debbano essere considerati " nell'assoluto "indipendentemente dal fatto che corrispondessero. Vedi anche la mia ultima modifica per un contesto più storico.
Stéphane Chazelas il
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.