Perché l’output del mio strumento si sovrascrive da solo e come lo risolvo?

L’intento di questa domanda è di fornire una risposta alle domande quotidiane la cui risposta è “hai terminazioni di linea DOS”, quindi possiamo semplicemente chiuderle come duplicati di questo senza ripetere le stesse risposte fino alla nausea .

NOTA: questo NON è un duplicato di alcuna domanda esistente . L’intento di questa sessione di domande e risposte non è solo quello di fornire una risposta “esegui questo strumento” ma anche di spiegare il problema in modo tale da poter indicare a chiunque una domanda correlata qui e troveranno una chiara spiegazione del motivo per cui sono stati indicati anche qui come lo strumento per correre in modo da risolvere il loro problema. Ho passato ore a leggere tutte le domande e risposte esistenti e mancano tutte della spiegazione del problema, degli strumenti alternativi che possono essere utilizzati per risolverlo e / o dei pro / contro / avvertenze delle possibili soluzioni. Inoltre alcuni di loro hanno accettato risposte che sono semplicemente pericolose e non dovrebbero mai essere usate.

Ora torniamo alla domanda tipica che comporterebbe un riferimento qui:

Ho un file contenente 1 riga:

what isgoingon 

e quando lo stampo usando questo script awk per invertire l’ordine dei campi:

 awk '{print $2, $1}' file 

invece di vedere l’output mi aspetto:

 isgoingon what 

Ricevo il campo che dovrebbe trovarsi alla fine della riga all’inizio della riga, sovrascrivendo del testo all’inizio della riga:

  whatngon 

o ottengo l’uscita divisa in 2 righe:

 isgoingon what 

Quale potrebbe essere il problema e come risolverlo?

Il problema è che il tuo file di input utilizza terminazioni di riga DOS di CRLF invece delle terminazioni di riga UNIX di appena LF e su di esso è in esecuzione uno strumento UNIX in modo che il CR resti parte dei dati gestiti dallo strumento UNIX. CR è comunemente indicato con \r e può essere visto come un controllo-M ( ^M ) quando si esegue cat -vE sul file mentre LF è \n e appare come $ con cat -vE .

Quindi il tuo file di input non era proprio solo:

 what isgoingon 

in realtà era:

 what isgoingon\r\n 

come puoi vedere con cat -v :

 $ cat -vE file what isgoingon^M$ 

e od -c :

 $ od -c file 0000000 whatisgoingon \r \n 0000020 

quindi quando si esegue uno strumento UNIX come awk (che tratta \n come la fine della riga) sul file, il \n viene consumato dall’atto di leggere la riga, ma lascia i 2 campi come:

   

Notare il \r alla fine del secondo campo. \r significa Carriage Return che è letteralmente un’istruzione per riportare il cursore all’inizio della riga, quindi quando lo fai:

 print $2, $1 

awk stamperà isgoingon e quindi restituirà il cursore all’inizio della riga prima di stampare quale sia il motivo per cui what sembra sovrascrivere l’inizio di isgoingon .

Per risolvere il problema, esegui una delle seguenti operazioni:

 dos2unix file sed 's/\r$//' file awk '{sub(/\r$/,"")}1' file perl -pe 's/\r$//' file 

Apparentemente dos2unix è aka frodos in alcune varianti UNIX (ad esempio Ubuntu).

Fai attenzione se decidi di usare tr -d '\r' come spesso viene suggerito in quanto eliminerà tutti \r s nel tuo file, non solo quelli alla fine di ogni riga.

Nota che GNU awk ti permetterà di analizzare i file che hanno terminazioni di linea DOS semplicemente impostando RS appropriato:

 gawk -v RS='\r\n' '...' file 

ma altri awk non permetteranno che POSIX richieda solo awk per supportare un singolo carattere RS e la maggior parte degli altri awk troncerà tranquillamente RS='\r\n' a RS='\r' . Potrebbe essere necessario aggiungere -v BINMODE=3 per gawk per vedere anche i \r s anche se i primitivi C sottostanti li -v BINMODE=3 su alcune piattaforms, ad es. Cygwin.

Una cosa a cui prestare attenzione è che i CSV creati da strumenti di Windows come Excel useranno CRLF come terminazioni di linea, ma possono avere LF s incorporati in un campo specifico del CSV, ad esempio:

 "field1","field2.1 field2.2","field3" 

è veramente:

 "field1","field2.1\nfield2.2","field3"\r\n 

quindi se si converte \r\n s in \n s allora non è più ansible indicare i ritorni di riga nei campi dai ritorni a capo come terminazioni di riga, quindi se si desidera farlo, si consiglia di convertire tutti i linefeed intra-campo in qualcos’altro prima, ad esempio questo convertirà tutti i LFs intra-field in tab e convertirà tutte le linee che terminano da CRLF a LF s:

 gawk -v RS='\r\n' '{gsub(/\n/,"\t")}1' file 

Facendo simile senza GNU awk lasciato come esercizio ma con altri awk comporta il combinare le righe che non finiscono in CR mentre vengono lette.

Esegui dos2unix . Mentre puoi manipolare le terminazioni di linea con il codice che hai scritto tu stesso, ci sono utility che esistono nel mondo Linux / Unix che già fanno questo per te.

Se su un sistema Fedora, dnf install dos2unix inserirà lo strumento dos2unix (nel caso non fosse installato).

Esiste un pacchetto deb dos2unix simile disponibile per i sistemi basati su Debian.

Dal punto di vista della programmazione, la conversione è semplice. Cerca tutti i caratteri in un file per la sequenza \r\n e sostituiscili con \n .

Questo significa che ci sono dozzine di modi per convertire da DOS a Unix usando quasi tutti gli strumenti immaginabili. Un modo semplice è usare il comando tr dove semplicemente si sostituisce \r con niente!

 tr -d '\r' < infile > outfile 

Puoi utilizzare la class di caratteri abbreviata di \R in PCRE per i file con terminazioni di riga sconosciute. Ci sono ancora più linee che si possono considerare con Unicode o altre piattaforms. La forma \R è una class di caratteri raccomandata dal consorzio Unicode per rappresentare tutte le forms di una nuova linea generica.

Quindi se hai un ‘extra’ puoi trovarlo e rimuoverlo con la regex s/\R$/\n/ normalizzerà qualsiasi combinazione di terminazioni di riga in \n . In alternativa, puoi usare s/\R/\n/g per acquisire qualsiasi nozione di ‘line ending’ e standardizzare in un \n carattere.

Dato:

 $ printf "what\risgoingon\r\n" > file $ od -c file 0000000 what \risgoingon \r \n 0000020 

Perl e Ruby e la maggior parte delle versioni di PCRE implementano \R combinazione con la fine dell’asserzione di stringa $ (fine della riga in modalità multi-linea):

 $ perl -pe 's/\R$/\n/' file | od -c 0000000 what \risgoingon \n 0000017 $ ruby -pe '$_.sub!(/\R$/,"\n")' file | od -c 0000000 what \risgoingon \n 0000017 

(Nota che il \r tra le due parole è correttamente lasciato solo)

Se non si dispone di \R è ansible utilizzare l’equivalente di (?>\r\n|\v) in PCRE.

Con gli strumenti POSIX semplici, la tua migliore scommessa è probabilmente awk modo:

 $ awk '{sub(/\r$/,"")} 1' file | od -c 0000000 what \risgoingon \n 0000017 

Cose che funzionano (ma conosci i tuoi limiti):

tr cancella tutto \r anche se usato in un altro contesto (l’uso di \r è raro e l’elaborazione XML richiede che venga cancellato, quindi tr è un’ottima soluzione):

 $ tr -d "\r" < file | od -c 0000000 whatisgoingon \n 0000016 

GNU sed funziona, ma non POSIX sed poiché \r e \x0D non sono supportati su POSIX.

Solo GNU:

 $ sed 's/\x0D//' file | od -c # also sed 's/\r//' 0000000 what \risgoingon \n 0000017 

La Unicode Regular Expression Guide è probabilmente la migliore scommessa su quale sia il trattamento definitivo di cosa sia una "nuova linea".