Esistono diversi motivi per cui la lettura di un intero file nello spazio modello può andare storta. Il problema logico nella domanda che circonda l'ultima riga è comune. È correlato al sed
ciclo di linee - quando non ci sono più linee e sed
incontra EOF che sta attraversando - termina l'elaborazione. E quindi se sei sull'ultima riga e ti ordini sed
di prenderne un altro, si fermerà proprio lì e non farà più nulla.
Detto questo, se hai davvero bisogno di leggere un intero file nello spazio modello, allora probabilmente vale la pena considerare un altro strumento. Il fatto è che sed
è l'omonimo stream editor - è progettato per lavorare una linea - o un blocco logico di dati - alla volta.
Esistono molti strumenti simili che sono meglio equipaggiati per gestire blocchi di file completi. ed
e ex
, ad esempio, può fare gran parte di ciò che sed
può fare e con una sintassi simile - e molto altro ancora - ma piuttosto che operare solo su un flusso di input mentre lo trasforma in output sed
, mantiene anche i file di backup temporanei nel file system . Il loro lavoro è bufferizzato su disco secondo necessità, e non si interrompono bruscamente alla fine del file (e tendono a implodere molto meno spesso sotto sforzo del buffer) . Inoltre offrono molte utili funzioni che sed
non hanno - del tipo che semplicemente non ha senso in un contesto di flusso - come segni di linea, annullamento, buffer con nome, join e altro.
sed
Il principale punto di forza è la capacità di elaborare i dati non appena li legge, in modo rapido, efficiente e in streaming. Quando si assorbe un file, lo si butta via e si tende a incorrere in difficoltà di edge case come l'ultimo problema di linea che si cita, buffer overrun e prestazioni abissali - man mano che i dati analizzati aumentano in lunghezza il tempo di elaborazione di un motore regexp quando si elencano le corrispondenze aumenta esponenzialmente .
Per quanto riguarda l'ultimo punto, a proposito: mentre capisco il s/a/A/g
caso di esempio è molto probabilmente solo un esempio ingenuo e probabilmente non è lo script reale per cui vuoi raccogliere un input, potresti trovare utile valere la pena y///
. Se ti ritrovi spesso a g
sostituire lobalmente un singolo personaggio con un altro, allora y
potrebbe esserti molto utile. È una trasformazione al contrario di una sostituzione ed è molto più veloce in quanto non implica una regexp. Quest'ultimo punto può anche essere utile quando si tenta di conservare e ripetere //
indirizzi vuoti perché non li influenza ma possono esserne interessati. In ogni caso, y/a/A/
è un mezzo più semplice per ottenere lo stesso risultato e gli swap sono possibili come:y/aA/Aa/
che scambiavano tutte le lettere maiuscole / minuscole come su una linea l'una per l'altra.
Dovresti anche notare che il comportamento che descrivi in realtà non è quello che dovrebbe accadere comunque.
Da GNU info sed
nella sezione BUG COMUNICATI SEGNALATI :
La POSIXLY_CORRECT
variabile d'ambiente è menzionata perché POSIX specifica che se sed
incontra EOF durante il tentativo N
dovrebbe uscire senza output, ma la versione GNU si rompe intenzionalmente con lo standard in questo caso. Si noti inoltre che, anche se il comportamento è giustificato in precedenza, il presupposto è che il caso di errore è quello della modifica dello stream, non l'assorbimento di un intero file in memoria.
Lo standard definisce N
quindi il comportamento:
N
Aggiungi la riga successiva di input, meno la sua \n
ewline finale, allo spazio del pattern, usando una \n
ewline incorporata per separare il materiale aggiunto dal materiale originale. Si noti che il numero di riga corrente cambia.
Se non è disponibile alcuna riga di input successiva, il N
verbo di comando si dirama verso la fine dello script e si chiude senza iniziare un nuovo ciclo o copiare lo spazio del pattern nell'output standard.
In quella nota, ci sono alcuni altri GNU-ismi dimostrati nella domanda - in particolare l'uso delle parentesi di :
etichetta, b
ranch e {
contesto di funzione }
. Come regola generale, qualsiasi sed
comando che accetta un parametro arbitrario è delimitato da una \n
ewline nello script. Quindi i comandi ...
:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...
... è molto probabile che si comportino in modo irregolare a seconda sed
dell'implementazione che li legge. Portabilmente dovrebbero essere scritti:
...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}
Lo stesso vale per r
, w
, t
, a
, i
, e c
(e forse un paio di più che sto dimenticando in questo momento) . In quasi tutti i casi potrebbero anche essere scritti:
sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
"//{ do arbitrary list of commands" -e \}
... dove la nuova -e
dichiarazione \n
xecution sostituisce il delimitatore ewline. Quindi, laddove il info
testo GNU suggerisce che un'implementazione tradizionale sed
ti costringerebbe a fare :
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
... dovrebbe piuttosto essere ...
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}
... ovviamente, neanche questo è vero. Scrivere la sceneggiatura in quel modo è un po 'sciocco. Esistono mezzi molto più semplici per fare lo stesso, come:
printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
//!g;x;$!d;:nd' -e 'l;$a\' \
-e 'this is the last line'
... che stampa:
foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line
... perché il t
comando est - come la maggior parte dei sed
comandi - dipende dal ciclo di linea per aggiornare il suo registro di ritorno e qui il ciclo di linea è autorizzato a fare la maggior parte del lavoro. Questo è un altro compromesso che si fa quando si assorbe un file: il ciclo di linea non si aggiorna mai più e così tanti test si comporteranno in modo anomalo.
Il comando sopra non rischia di superare l'input perché fa solo alcuni semplici test per verificare ciò che legge mentre lo legge. Con il H
vecchio tutte le linee vengono aggiunte allo spazio di attesa, ma se una linea corrisponde /foo/
, sovrascrive il h
vecchio spazio. I buffer vengono successivamente x
modificati e s///
viene tentato un ubstitution condizionale se il contenuto del buffer corrisponde //
all'ultimo modello indirizzato. In altre parole, //s/\n/&/3p
tenta di sostituire la terza riga nuova nello spazio di attesa con se stesso e di stampare i risultati se lo spazio di attesa corrisponde attualmente /foo/
. Se che t
ressi successo i rami script al n
ot d
label elete - che fa un l
OOK e avvolge lo script.
Nel caso in cui sia /foo/
una terza riga che una terza riga non possano essere accoppiate insieme nello spazio di attesa, allora //!g
sovrascriverà il buffer se /foo/
non è abbinato, o, se è abbinato, sovrascriverà il buffer se una \n
ewline non è abbinata (sostituendo quindi /foo/
con stesso) . Questo piccolo test sottile impedisce al buffer di riempirsi inutilmente per lunghi periodi di no /foo/
e garantisce che il processo rimanga scattante perché l'input non si accumula. In seguito in caso di no /foo/
o //s/\n/&/3p
fail i buffer vengono nuovamente scambiati e ogni riga tranne l'ultima è lì cancellata.
Quest'ultima - l'ultima riga $!d
- è una semplice dimostrazione di come è sed
possibile creare uno script top-down per gestire facilmente più casi. Quando il tuo metodo generale è quello di eliminare i casi indesiderati a partire dal più generale e lavorando verso il più specifico, i casi limite possono essere gestiti più facilmente perché sono semplicemente autorizzati a cadere fino alla fine dello script con gli altri dati desiderati e quando avvolge tutto ciò che rimane con solo i dati desiderati. Dover recuperare tali casi limite da un circuito chiuso può essere molto più difficile da fare, però.
E quindi ecco l'ultima cosa che devo dire: se devi davvero inserire un intero file, allora puoi sopportare di fare un po 'meno lavoro facendo affidamento sul ciclo di linea per farlo per te. Tipicamente N
useresti ext e n
ext per lookahead - perché avanzano prima del ciclo di linea. Piuttosto che implementare in modo ridondante un ciclo chiuso all'interno di un ciclo - poiché il sed
ciclo di linea è comunque solo un semplice ciclo di lettura - se il tuo scopo è solo quello di raccogliere input indiscriminatamente, è probabilmente più facile fare:
sed 'H;1h;$!d;x;...'
... che raccoglierà l'intero file o fallirà.
una nota a margine N
e il comportamento dell'ultima riga ...
mentre non ho gli strumenti a mia disposizione per testare, considera che N
durante la lettura e la modifica sul posto si comporta diversamente se il file modificato è il file di script per la lettura successiva.