Wie kann man testen, ob eine Datei CRLF oder LF verwendet, ohne sie zu ändern?

48

Ich muss regelmäßig einen Befehl ausführen, der sicherstellt, dass einige Textdateien im Linux-Modus gespeichert werden. Leider dos2unixändert sich die Datei immer, was die Zeitstempel von Dateien und Ordnern durcheinander bringt und unnötige Schreibvorgänge verursacht.

Das Skript, das ich schreibe, ist in Bash, daher bevorzuge ich Antworten, die auf Bash basieren.

Adam Ryczkowski
quelle

Antworten:

41

Sie können es dos2unixals Filter verwenden und seine Ausgabe mit der Originaldatei vergleichen:

dos2unix < myfile.txt | cmp -s - myfile.txt
Samuel Edwin Ward
quelle
2
Sehr schlau und nützlich, da es die gesamte Datei und nicht nur die erste oder einige wenige Zeilen testet .
halloleo
2
Vielleicht könnten Sie in Ihrem Beispiel testdurch myfile.txtzweimal ersetzen , um Verwechslungen zu vermeiden /usr/bin/test.
Peterino
1
NB Sie müssen das -sFlag löschen , um die Ausgabe zu sehen. Aus Manpages: -s, --quiet, --silent suppress all normal output
tobalr
24

Wenn das Ziel nur darin besteht, den Zeitstempel nicht zu beeinflussen, dos2unixhaben Sie eine -koder --keepdate-Option, mit der der Zeitstempel gleich bleibt. Es muss immer noch geschrieben werden, um die temporäre Datei zu erstellen und umzubenennen, aber Ihre Zeitstempel bleiben davon unberührt.

Wenn eine Änderung der Datei nicht akzeptabel ist, können Sie die folgende Lösung aus dieser Antwort verwenden .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
quelle
1
Meinen Sie, Sie schreiben CRLF buchstäblich als 4 Zeichen C, R, L und F?
Bodacydo
7
Meinen Sie damit auch, dass grep einfach so CR und LF nehmen kann?
Bodacydo
@bodacydo Erläutert wird dies in der Antwort, auf die er verweist, und jetzt auch in Scotts Bearbeitung der Antwort von BertS hier unter unix.stackexchange.com/a/79708/59699 .
Dave_thompson_085
@ Dave_Thompson_085 Ich sehe keine Erklärung. Es erwähnt nur CRLF, erklärt aber nicht, was es ist.
Bodacydo
1
@bodacydo stackoverflow.com/questions/73833/… sagt, dass find ... -exec file ... | grep CRLFfür eine Datei mit DOS-Zeilenenden (dh Bytes 0D 0A) " Folgendes angezeigt wird: ./1/dos1.txt: ASCII text, with CRLF line terminators Wie Sie sehen, enthält diese die tatsächliche Zeichenfolge CRLF und wird daher durch grepSuchen abgeglichen die einfache Zeichenfolge CRLF.
Dave_thompson_085
22

Sie könnten versuchen, grepfür CRLF-Code, oktal:

grep -U $'\015' myfile.txt

oder hex:

grep -U $'\x0D' myfile.txt
don_crissti
quelle
Voraussetzung ist natürlich, dass es sich um eine Textdatei handelt.
mdpc
2
Ich mag diese grepVerwendung, weil es mir ermöglicht, alle diese Dateien im Verzeichnis mit grep -lU $'\x0D' *aufzulisten und die Ausgabe an zu übergeben xargs.
Melebius
Was bedeutet das $ vor dem Suchmuster? @don_crissti
fersarr
1
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Seit Version 7.1dos2unix eine hat -i, --infoauf Option , um Informationen über Zeilenumbrüche zu erhalten. Sie können dos2unix selbst verwenden, um zu testen, welche Dateien konvertiert werden müssen.

Beispiel:

dos2unix -ic *.txt | xargs dos2unix
Erwin Waterlander
quelle
Hier ist der Link zum Changelog selbst waterlan.home.xs4all.nl/dos2unix/NEWS.txt
Adam Ryczkowski
13

Erste Methode ( grep):

Zählen Sie die Zeilen, die einen Wagenrücklauf enthalten:

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

Zählen Sie die Zeilen, die mit einem Wagenrücklauf enden :

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

Diese sind normalerweise gleichwertig. Ein Wagenrücklauf im Inneren einer Zeile (dh nicht am Ende) ist selten.

Effizienter:

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

Das ist effizienter

  1. weil es nicht erforderlich ist, die Anzahl in eine ASCII-Zeichenfolge umzuwandeln und diese Zeichenfolge dann wieder in eine Ganzzahl umzuwandeln und mit Null zu vergleichen, und
  2. Da grep -cmuss die gesamte Datei gelesen werden, um alle Vorkommen des Musters zu zählen, während grep -qbeim ersten Auftreten des Musters beendet werden kann.

Anmerkungen:

  • In all diesen Fällen müssen Sie möglicherweise die -UOption hinzufügen (dh -cUoder verwenden -qU), da GNU greperrät, ob es sich bei der Datei um eine Textdatei handelt. Wenn es sich bei der Datei um Text handelt, werden Zeilenumbrüche an den Zeilenenden ignoriert, um sicherzustellen, dass $reguläre Ausdrücke "korrekt" funktionieren - auch wenn es sich um reguläre Ausdrücke handelt \r$! Das Angeben -U(oder --binary) überschreibt diese Vermutung, wodurch grepdie Datei (en) als binär behandelt und die Daten wörtlich mit intakten CR-Endungen an den Abgleichsmechanismus übergeben werden.
  • Tun Sie dies nicht grep … $'\r\n' myfile.txt, da dies als Musterbegrenzer grepbehandelt wird \n. Sucht genauso wie grep -E 'foo|'nach Zeilen, die fooeine Nullzeichenfolge enthalten, grep $'\r\n'nach Zeilen, die \reine Nullzeichenfolge enthalten, und jede Zeile entspricht einer Nullzeichenfolge.

Zweite Methode ( file):

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

weil fileberichtet so etwas wie:

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

Sicherere Variante:

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

wo

Beachten Sie, dass die Überprüfung der Ausgabe file in einem nicht englischen Gebietsschema möglicherweise nicht funktioniert.

BertS
quelle
1
Sie können es durch "$(echo -e '\r')"das viel einfachere ersetzen $'\r', obwohl ich persönlich $'\r\n'die Anzahl der Fehlalarme verringern würde .
rici
@rici grep $'\r\n'scheint alle Dateien auf meinem System zu entsprechen ...
depquid
@rici: guter Fang. Ich habe meine Antwort gemäß Ihrem Vorschlag bearbeitet. - depquid: Vielleicht bist du auf Windows? :-) Ricis Tipp funktioniert hier.
BertS
@depquid (und BertS): Eigentlich denke ich, der richtige Aufruf ist grep -U $'\r$', zu verhindern, dass man grepversucht, Zeilenenden zu erraten.
rici
Sie können auch verwenden, -qum nur den Rückkehrcode festzulegen, wenn eine Übereinstimmung gefunden wird, anstatt -cdass eine zusätzliche Prüfung erforderlich ist. Persönlich mag ich Ihre zweite Lösung, obwohl sie stark von den Launen abhängt fileund möglicherweise nicht in einem nicht englischen Gebietsschema funktioniert.
rici
11

Verwenden cat -A

$ cat file
hello
hello

Wenn diese Datei in * NIX-Systemen erstellt wurde, wird sie angezeigt

$ cat -A file
hello$
hello$

Wenn diese Datei jedoch in Windows erstellt wurde, wird sie angezeigt

$ cat -A file
hello^M$
hello

^Mrepräsentiert CRund $repräsentiert LF. Beachten Sie, dass Windows die letzte Zeile nicht mit gespeichert hatCRLF

Dies ändert auch nicht den Inhalt der Datei.

GypsyCosmonaut
quelle
Die beste und einfachste Lösung! braucht mehr Stimmen.
user648026
1
+1 Mit Abstand die beste Antwort. Keine Abhängigkeiten, keine komplizierten Bash-Skripte. Nur -Aum Katze. Ein Tipp wäre allerdings, cat -A file | lesswenn die Datei zu groß ist. Ich bin mir sicher, dass es nicht ungewöhnlich ist, Dateiendungen auf besonders lange Dateien prüfen zu müssen. ( q
Drücken Sie
4

Eine Bash-Funktion für Sie:

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

Dann kannst du sowas machen

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

streamFile /tmp/foo.txt | process_lines_without_CR
Glenn Jackman
quelle
3
Sie müssen nicht verwenden isDosFile()in Ihrem Beispiel: streamFile() { sed 's/\r$//' "$1" ; }.
1
Ich denke, das ist die eleganteste Lösung. Es liest nicht die ganze Datei, nur die erste Zeile.
Adam Ryczkowski
4

Wenn eine Datei CR-LF-Zeilenenden im DOS- / Windows-Stil enthält, werden bei der Anzeige mit einem Unix-basierten Tool am Ende jeder Zeile CR-Zeichen ('\ r') angezeigt.

Dieser Befehl:

grep -l '^M$' filename

wird gedruckt, filenamewenn die Datei eine oder mehrere Zeilen mit Windows-ähnlichen Zeilenenden enthält, und es wird nichts gedruckt, wenn dies nicht der Fall ist. Das ^Mmuss jedoch ein Zeilenumbruchzeichen sein, das normalerweise in das Terminal eingegeben wird, indem Sie Ctrl+ Vgefolgt von Enter (oder Ctrl+ Vund dann Ctrl+ M) eingeben . Mit der Bash-Shell können Sie einen wörtlichen Wagenrücklauf als $'\r'( hier dokumentiert ) schreiben, sodass Sie Folgendes schreiben können:

grep -l $'\r$' filename

Andere Schalen bieten möglicherweise ein ähnliches Merkmal.

Sie können stattdessen ein anderes Tool verwenden:

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

Dies wird mit dem Status 1(Einstellung $?auf 1) beendet, wenn die Datei Zeilenenden im Windows-Stil enthält, und mit dem Status, 0wenn dies nicht der Fall ist, was sie in einer Shell- ifAnweisung nützlich macht (beachten Sie das Fehlen von [Klammern ]):

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

Eine Datei kann eine Mischung aus Zeilenenden im Unix- und Windows-Stil enthalten. Ich gehe davon aus, dass hier Sie Dateien erkennen mögen, haben alle im Windows-Stil Zeilenende.

Keith Thompson
quelle
1
Sie können einen Wagenrücklauf in der Befehlszeile in Bash (und einigen anderen Shells) codieren, indem Sie Folgendes eingeben $'\r', wie in den anderen Antworten auf diese Frage angegeben.
Scott
2

Verwendung 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
Dan Sorak
quelle
Diese Idee wurde in zwei vorhergehenden Antworten viel ausführlicher erörtert.
G-Man sagt, dass Monica
1

Ich habe verwendet

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

was zu funktionieren scheint. Ich finde die Ausgabe etwas leichter zu lesen als

dos2unix < filename.txt | diff - filename.txt

Es ist auch nützlich, wenn Sie dos2unixaus irgendeinem Grund nicht installieren können .

Alex028502
quelle