Jak sprawdzić, czy plik używa CRLF lub LF bez modyfikowania go?

48

Muszę okresowo uruchamiać polecenie, które zapewnia, że ​​niektóre pliki tekstowe są przechowywane w trybie Linux. Niestety dos2unixzawsze modyfikuje plik, co zepsułoby znaczniki czasu pliku i folderu i spowodowało niepotrzebne zapisy.

Skrypt, który piszę jest w języku Bash, więc wolałbym odpowiedzi oparte na Bash.

Adam Ryczkowski
źródło

Odpowiedzi:

41

Możesz użyć dos2unixjako filtru i porównać jego wyniki z oryginalnym plikiem:

dos2unix < myfile.txt | cmp -s - myfile.txt
Samuel Edwin Ward
źródło
2
Bardzo inteligentny i użyteczny, ponieważ testuje cały plik, a nie tylko pierwszą lub kilka linii.
halloleo
2
Być może można zastąpić testprzez myfile.txtdwa razy w swoim przykładzie, aby uniknąć nieporozumień z /usr/bin/test.
Peterino
1
Uwaga: -saby zobaczyć wynik, musisz usunąć flagę. Ze stron -s, --quiet, --silent suppress all normal output
podręcznika
24

Jeśli celem jest uniknięcie wpływu na znacznik czasu, dos2unixma opcję -klub --keepdate, która zachowa ten sam znacznik czasu. Nadal będzie musiał wykonać zapis, aby utworzyć plik tymczasowy i zmienić jego nazwę, ale nie wpłynie to na twoje znaczniki czasu.

Jeśli jakakolwiek modyfikacja pliku jest niedopuszczalna, możesz użyć następującego rozwiązania z tej odpowiedzi .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
źródło
1
Czy masz na myśli, że dosłownie piszesz CRLF jako 4 znaki C, R, L i F?
bodacydo
7
Czy masz również na myśli, że grep może przyjmować CR i LF w ten sposób?
bodacydo
@ bodacydo Zostało to wyjaśnione w odpowiedzi, do której prowadzi, a teraz także w edycji Scotta odpowiedzi BertS tutaj unix.stackexchange.com/a/79708/59699 .
dave_thompson_085
@ dave_thompson_085 Nie widzę wyjaśnienia. Wymienia tylko CRLF, ale nie wyjaśnia, co to jest.
bodacydo
1
@ bodacydo stackoverflow.com/questions/73833/... mówi, że find ... -exec file ... | grep CRLFdla pliku z zakończeniami linii DOS (tj. bajtów 0D 0A) „dostaniesz coś takiego: ./1/dos1.txt: ASCII text, with CRLF line terminators Jak widzisz, zawiera on rzeczywisty ciąg CRLF i dlatego dopasowuje go grepszukając prosty ciąg CRLF.
dave_thompson_085
22

Możesz spróbować grepdla kodu CRLF, ósemkowe:

grep -U $'\015' myfile.txt

lub hex:

grep -U $'\x0D' myfile.txt
don_crissti
źródło
Oczywiście założono, że jest to plik tekstowy.
mdpc
2
Podoba mi się to grepużycie, ponieważ pozwala mi łatwo wyświetlić listę wszystkich takich plików w katalogu grep -lU $'\x0D' *i przekazać dane wyjściowe do xargs.
Melebius
jakie jest znaczenie $ przed wzorcem wyszukiwania? @don_crissti
fersarr
1
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Od wersji 7.1dos2unix ma -i, --infoopcja, aby uzyskać informacje na temat łamania linii. Możesz użyć samego dos2unix do przetestowania, które pliki wymagają konwersji.

Przykład:

dos2unix -ic *.txt | xargs dos2unix
Erwin Waterlander
źródło
Oto link do samego dziennika
Adam Ryczkowski
13

Pierwsza metoda ( grep):

Policz linie zawierające znak powrotu karetki:

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

Policz linie kończące się znakiem powrotu karetki:

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

Zazwyczaj będą one równoważne; powrót karetki we wnętrzu linii (tj. nie na końcu) jest rzadki.

Bardziej wydajny:

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

To jest bardziej wydajne

  1. ponieważ nie musi konwertować liczby na ciąg ASCII, a następnie przekonwertować ten ciąg z powrotem na liczbę całkowitą i porównać go do zera, a
  2. ponieważ grep -cmusi odczytać cały plik, policzyć wszystkie wystąpienia wzorca, a grep -qmoże wyjść po zobaczeniu pierwszego wystąpienia wzorca.

Uwagi:

  • W powyższym przypadku może być konieczne dodanie -Uopcji (tj. Użyj -cUlub -qU), ponieważ GNU grepzgaduje, czy plik jest plikiem tekstowym. Jeśli uważa, że ​​plik jest tekstem, ignoruje znaki powrotu karetki na końcach wierszy, próbując sprawić, by $wyrażenia regularne działały „poprawnie” - nawet jeśli wyrażenie regularne jest \r$! Podanie -U(lub --binary) unieważnia zgadywanie, powodując greptraktowanie plików jako plików binarnych i przekazywanie danych dosłownie do mechanizmu dopasowywania, z nienaruszonymi zakończeniami CR.
  • Nie rób grep … $'\r\n' myfile.txt, ponieważ greptraktuje \njak separator wzorca. Podobnie jak grep -E 'foo|'linie zawierające foolub łańcuch zerowy, grep $'\r\n'szuka linii zawierających \rlub łańcuch zerowy, a każda linia odpowiada łańcuchowi zerowemu.

Druga metoda ( file):

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

ponieważ filezgłasza coś takiego:

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

Bezpieczniejszy wariant:

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

gdzie

  • file -bwyświetla tylko typ pliku, a nie nazwę pliku. Bez tego plik, którego nazwa zawiera znakiCRLF , wyzwala fałszywy wynik dodatni.
  • file - < filenamedziała, nawet jeśli filenamezaczyna się od -Zobacz skrypt Bash: sprawdź, czy plik jest plikiem tekstowym .

Uwaga: sprawdzanie danych wyjściowych file może nie działać w ustawieniach narodowych innych niż angielski.

BertS
źródło
1
Możesz zastąpić "$(echo -e '\r')"je znacznie prostszym $'\r', choć osobiście użyłbym tego, $'\r\n'aby zmniejszyć liczbę fałszywych trafień.
rici
@rici grep $'\r\n'wydaje się pasować do wszystkich plików w moim systemie ...
depquid
@rici: dobry połów. Zredagowałem swoją odpowiedź zgodnie z twoją sugestią. - depquid: Może jesteś na Windowsie? :-) Wskazówka rici działa tutaj.
BertS
@depquid (i BertS): Właściwie uważam, że poprawne wywołanie ma grep -U $'\r$'zapobiec próbom odgadnięcia końca greplinii.
rici
Możesz także użyć, -qaby ustawić kod powrotu, jeśli zostanie znalezione dopasowanie, zamiast tego -cwymaga dodatkowego sprawdzenia. Osobiście podoba mi się twoje drugie rozwiązanie, chociaż jest w dużym stopniu zależne od kaprysów filei może nie działać w nieanglojęzycznych lokalizacjach.
rici
11

Posługiwać się cat -A

$ cat file
hello
hello

Teraz, jeśli ten plik został utworzony w systemach * NIX, zostanie wyświetlony

$ cat -A file
hello$
hello$

Ale jeśli ten plik został utworzony w systemie Windows, zostanie wyświetlony

$ cat -A file
hello^M$
hello

^Mreprezentuje CRi $reprezentuje LF. Zauważ, że Windows nie zapisał ostatniego wierszaCRLF

To również nie zmienia zawartości pliku.

Cygański kosmonauta
źródło
Najlepsze i najprostsze rozwiązanie! potrzebuje więcej głosów.
user648026
1
+1 Zdecydowanie najlepsza odpowiedź. Bez zależności, bez skomplikowanych skryptów bash. Tylko -Akotu. Jedną wskazówką byłoby użycie, cat -A file | lessjeśli plik jest zbyt duży. Jestem pewien, że nierzadko trzeba sprawdzać zakończenia pliku dla szczególnie długiego pliku. (Naciśnij, qaby wyjść mniej)
Nicholas Pipitone
4

funkcja bash dla Ciebie:

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

Następnie możesz robić takie rzeczy jak

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
źródło
3
Nie trzeba używać isDosFile()w np streamFile() { sed 's/\r$//' "$1" ; }.
1
Myślę, że to najbardziej eleganckie rozwiązanie; nie odczytuje całego pliku, tylko pierwszy wiersz.
Adam Ryczkowski
4

Jeśli plik ma zakończenia linii CR-LF w stylu DOS / Windows, to jeśli spojrzysz na nie za pomocą narzędzia uniksowego, zobaczysz znaki CR („\ r”) na końcu każdej linii.

To polecenie:

grep -l '^M$' filename

wydrukuje, filenamejeśli plik zawiera jedną lub więcej linii z zakończeniami w stylu Windows, i nie wydrukuje nic, jeśli nie będzie. Tyle, że ^Mmusi to być dosłowny znak powrotu karetki, zwykle wprowadzany w terminalu, wpisując Ctrl+, Va następnie Enter (lub Ctrl+, Va następnie Ctrl+ M). Powłoka bash pozwala napisać dosłowny znak powrotu karetki jako $'\r'( udokumentowany tutaj ), dzięki czemu możesz napisać:

grep -l $'\r$' filename

Inne powłoki mogą zapewniać podobną funkcję.

Zamiast tego możesz użyć innego narzędzia:

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

Spowoduje to wyjście ze statusem 1(ustawienie $?na 1), jeśli plik zawiera jakieś zakończenia linii w stylu Windows, oraz ze statusem 0jeśli nie, co czyni go przydatnym w ifinstrukcji powłoki (zauważ brak [nawiasów ]):

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

Plik może zawierać mieszaninę zakończeń linii w stylu Unix i Windows. Zakładam, że chcesz wykryć pliki, które mają dowolne zakończenia linii w stylu Windows.

Keith Thompson
źródło
1
Możesz zakodować powrót karetki w wierszu poleceń w bash (i niektórych innych powłokach), wpisując $'\r', jak wspomniano w innych odpowiedziach na to pytanie.
Scott
2

Użyj 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
źródło
Pomysł ten został omówiony znacznie dokładniej w dwóch poprzednich odpowiedziach.
G-Man mówi „Przywróć Monikę”
1

Używałem

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

który wydaje się działać. Uważam, że wynik jest nieco łatwiejszy do odczytania niż

dos2unix < filename.txt | diff - filename.txt

Jest to również przydatne, jeśli nie możesz zainstalować dos2unixz jakiegoś powodu.

Alex028502
źródło