Come verificare se un file utilizza CRLF o LF senza modificarlo?


48

Devo eseguire periodicamente un comando che assicuri che alcuni file di testo siano mantenuti in modalità Linux. Sfortunatamente dos2unixmodifica sempre il file, che rovinerebbe i timestamp di file e cartelle e causerebbe scritture non necessarie.

La sceneggiatura che scrivo è in Bash, quindi preferirei le risposte basate su Bash.

Risposte:


41

Puoi utilizzare dos2unixcome filtro e confrontare il suo output con il file originale:

dos2unix < myfile.txt | cmp -s - myfile.txt

2
Molto intelligente e utile, perché verifica il file completo e non solo la prima o alcune righe.
halloleo,

2
Forse si potrebbe sostituire testcon myfile.txtdue volte nel tuo esempio per evitare confusione con /usr/bin/test.
Peterino,

1
NB sarà necessario eliminare il -sflag per vedere l'output. Dalle pagine man: -s, --quiet, --silent suppress all normal output
tobalr

24

Se l'obiettivo è solo quello di evitare di influenzare il timestamp, dos2unixha un'opzione -ko --keepdateche manterrà lo stesso timestamp. Dovrà comunque scrivere per creare il file temporaneo e rinominarlo, ma i timestamp non saranno interessati.

Se qualsiasi modifica del file è inaccettabile, è possibile utilizzare la seguente soluzione da questa risposta .

find . -not -type d -exec file "{}" ";" | grep CRLF

1
Intendi letteralmente scrivere CRLF come 4 caratteri C, R, L e F?
bodacydo,

7
Intendi anche che grep può prendere CR e LF in questo modo?
bodacydo,

@bodacydo È spiegato nella risposta a cui si collega, e ora anche nella modifica di Scott della risposta di BertS qui unix.stackexchange.com/a/79708/59699 .
dave_thompson_085,

@ dave_thompson_085 Non vedo spiegazione. Menziona solo CRLF ma non spiega di cosa si tratta.
bodacydo,

1
@bodacydo stackoverflow.com/questions/73833/... dice che find ... -exec file ... | grep CRLFun file con i fine riga DOS (ad esempio byte 0D 0A) "ti porterà qualcosa di simile a: ./1/dos1.txt: ASCII text, with CRLF line terminators Come si può vedere questo contiene la CRLF stringa effettiva e quindi si accompagna grepalla ricerca di la semplice stringa CRLF.
dave_thompson_085 il

22

Puoi provare a grepper il codice CRLF, ottale:

grep -U $'\015' myfile.txt

o esadecimale:

grep -U $'\x0D' myfile.txt

Naturalmente, il presupposto è che questo è un file di testo.
mdpc,

2
Mi piace questo greputilizzo perché mi permette di elencare facilmente tutti questi file nella directory con grep -lU $'\x0D' *e passare l'output a xargs.
Melebio

qual è il significato di $ prima del modello di ricerca? @don_crissti
fersarr,


21

Poiché la versione 7.1dos2unix ha un'opzione -i, --infoper ottenere informazioni sulle interruzioni di riga. Puoi usare dos2unix per verificare quali file necessitano di conversione.

Esempio:

dos2unix -ic *.txt | xargs dos2unix


13

Primo metodo ( grep):

Contare le righe che contengono un ritorno a capo:

[[ $(grep -c $'\r' myfile.txt) -gt 0 ]] && echo dos

Contare le linee che terminano con un ritorno a capo :

[[ $(grep -c $'\r$' myfile.txt) -gt 0 ]] && echo dos

Questi saranno in genere equivalenti; un ritorno a capo all'interno di una linea (cioè non alla fine) è raro.

Più efficiente:

grep -q $'\r' myfile.txt && echo dos

Questo è più efficiente

  1. perché non è necessario convertire il conteggio in una stringa ASCII, quindi riconvertire quella stringa in un numero intero e confrontarlo con zero e
  2. perché grep -cdeve leggere l'intero file, contare tutte le occorrenze del pattern, mentre grep -qpuò uscire vedendo la prima occorrenza del pattern.

Appunti:

  • In tutto quanto sopra, potrebbe essere necessario aggiungere l' -Uopzione (ovvero, utilizzare -cUo -qU), poiché GNU grepindovina se il file è un file di testo. Se pensa che il file sia un testo, ignora i ritorni a capo alle estremità delle righe, nel tentativo di far $funzionare "correttamente" le espressioni regolari, anche se l'espressione regolare lo è \r$! Specificare -U(o --binary) sovrascrivere questa ipotesi, causando il greptrattamento dei file come binari e il passaggio dei dati al meccanismo di corrispondenza alla lettera, con intatti CR-endings.
  • Non farlo grep … $'\r\n' myfile.txt, perché grepconsidera \nun delimitatore di pattern. Proprio come grep -E 'foo|'cerca le righe contenenti fooo una stringa nulla, grep $'\r\n'cerca le righe contenenti \ro una stringa nulla e ogni riga corrisponde a una stringa nulla.

Secondo metodo ( file):

[[ $(file myfile.txt) =~ CRLF ]] && echo dos

perché filesegnala qualcosa del tipo:

myfile.txt: UTF-8 Unicode text, with CRLF line terminators

Variante più sicura:

[[ $(file -b - < myfile.txt) =~ CRLF ]] && echo dos

dove

  • file -bgenera solo il tipo di file e non il nome del file. Senza questo, un file il cui nome includeva i caratteriCRLF avrebbe attivato un falso positivo.
  • file - < filenamefunziona anche se filenameinizia con -Vedi script Bash: controlla se un file è un file di testo .

Attenzione che il controllo dell'output file potrebbe non funzionare in una lingua non inglese.


1
Puoi sostituirlo "$(echo -e '\r')"con il molto più semplice $'\r', anche se personalmente userei $'\r\n'per ridurre il numero di falsi positivi.
rici,

@rici grep $'\r\n'sembra corrispondere a tutti i file sul mio sistema ...
depquid

@rici: buona cattura. Ho modificato la mia risposta in base al tuo suggerimento. - depquid: forse sei su Windows? :-) Il consiglio di rici funziona qui.
BertS,

@depquid (e BertS): In realtà, penso che sia l'invocazione corretta grep -U $'\r$', per evitare di grepprovare a indovinare i finali di linea.
rici,

Inoltre, è possibile utilizzare -qsolo per impostare il codice di ritorno se viene trovata una corrispondenza, invece di -crichiedere un controllo aggiuntivo. Personalmente mi piace la tua seconda soluzione, anche se è fortemente dipendente dai capricci di filee potrebbe non funzionare in un ambiente non inglese.
rici,

11

Uso cat -A

$ cat file
hello
hello

Ora se questo file fosse creato in sistemi * NIX, verrebbe visualizzato

$ cat -A file
hello$
hello$

Ma se questo file fosse creato in Windows, verrebbe visualizzato

$ cat -A file
hello^M$
hello

^Mrappresenta CRe $rappresenta LF. Si noti che Windows non ha salvato l'ultima riga conCRLF

Ciò non modifica neanche il contenuto del file.


La soluzione migliore e più semplice! ha bisogno di più voti positivi.
user648026

1
+1 Di gran lunga la risposta migliore. Nessuna dipendenza, nessun script bash complicato. Solo -Aper il gatto. Un consiglio però sarebbe da usare cat -A file | lessse il file è troppo grande. Sono sicuro che non è raro dover controllare la fine dei file per un file particolarmente lungo. (Premi qper lasciare meno)
Nicholas Pipitone il

4

una funzione bash per te:

# return 0 (true) if first line ends in CR
isDosFile() {
    [[ $(head -1 "$1") == *$'\r' ]]  
}

Quindi puoi fare cose come

streamFile () {
    if isDosFile /tmp/foo.txt; then
        sed 's/\r$//' "$1"
    else
        cat "$1"
    fi
}

streamFile /tmp/foo.txt | process_lines_without_CR

3
Non è necessario usare isDosFile()nel tuo esempio: streamFile() { sed 's/\r$//' "$1" ; }.

1
Penso che questa sia la soluzione più elegante; non legge l'intero file, solo la prima riga.
Adam Ryczkowski il

4

Se un file ha terminazioni di riga CR-LF in stile DOS / Windows, quindi se lo guardi utilizzando uno strumento basato su Unix vedrai i caratteri CR ('\ r') alla fine di ogni riga.

Questo comando:

grep -l '^M$' filename

stamperà filenamese il file contiene una o più righe con terminazioni di linea in stile Windows e non stampa nulla in caso contrario. Tranne che ^Mdeve essere un carattere di ritorno a capo letterale, tipicamente inserito nel terminale digitando Ctrl+ Vseguito da Enter (o Ctrl+ Ve quindi Ctrl+ M). La shell bash ti consente di scrivere un ritorno a capo letterale come $'\r'( documentato qui ), in modo da poter scrivere:

grep -l $'\r$' filename

Altre shell possono fornire una funzionalità simile.

Puoi invece usare un altro strumento:

awk '/\r$/ { exit(1) }' filename

Questo terminerà con uno stato di 1(impostazione $?su 1) se il file contiene terminazioni di riga in stile Windows e con uno stato di in 0caso contrario, rendendolo utile in ifun'istruzione shell (notare la mancanza di [parentesi ]):

if awk '/\r$/ { exit(1) }' filename ; then
    echo filename has Unix-style line endings
else
    echo filename has at least one Windows-style line ending
fi

Un file può contenere una combinazione di terminazioni di linea in stile Unix e in stile Windows. Sto assumendo qui che si desidera rilevare i file che hanno nessun fine riga di tipo di Windows.


1
È possibile codificare un ritorno a capo sulla riga di comando in bash (e alcune altre shell) digitando $'\r', come indicato in altre risposte a questa domanda.
Scott,

2

Utilizzare file:

$ file README.md
README.md: ASCII text, with CRLF line terminators

$ dos2unix README.md
dos2unix: converting file README.md to Unix format...

$ file README.md
README.md: ASCII text

Questa idea è stata discussa in modo molto più approfondito in due risposte precedenti.
G-Man dice "Reinstate Monica"

1

Sto usando

cat -v filename.txt | diff - filename.txt

che sembra funzionare. Trovo che l'output sia un po 'più facile da leggere rispetto a

dos2unix < filename.txt | diff - filename.txt

È utile anche se non è possibile installare dos2unixper qualche motivo.

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.