Bagaimana cara menguji apakah suatu file menggunakan CRLF atau LF tanpa memodifikasinya?

48

Saya perlu secara berkala menjalankan perintah yang memastikan bahwa beberapa file teks disimpan dalam mode Linux. Sayangnya dos2unixselalu memodifikasi file, yang akan mengacaukan cap waktu file dan folder dan menyebabkan penulisan yang tidak perlu.

Script yang saya tulis ada di Bash, jadi saya lebih suka jawaban berdasarkan Bash.

Adam Ryczkowski
sumber

Jawaban:

41

Anda dapat menggunakan dos2unixsebagai filter dan membandingkan hasilnya dengan file asli:

dos2unix < myfile.txt | cmp -s - myfile.txt
Samuel Edwin Ward
sumber
2
Sangat cerdas dan bermanfaat, karena menguji file lengkap dan tidak hanya baris pertama atau beberapa.
halloleo
2
Mungkin Anda bisa mengganti testdengan myfile.txtdua kali dalam contoh Anda untuk menghindari kebingungan /usr/bin/test.
Peterino
1
NB Anda harus menghapus -sflag untuk melihat hasilnya. Dari halaman manual: -s, --quiet, --silent suppress all normal output
tobalr
24

Jika tujuannya hanya untuk menghindari memengaruhi timestamp, dos2unixmemiliki opsi -katau --keepdateyang akan menjaga timestamp tetap sama. Masih harus menulis untuk membuat file sementara dan mengganti nama, tetapi cap waktu Anda tidak akan terpengaruh.

Jika modifikasi file tidak dapat diterima, Anda dapat menggunakan solusi berikut dari jawaban ini .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
sumber
1
Apakah Anda benar-benar menulis CRLF sebagai 4 karakter C, R, L dan F?
bodacydo
7
Apakah Anda juga berarti bahwa grep dapat mengambil CR dan LF begitu saja?
bodacydo
@bodacydo Ini dijelaskan dalam jawaban yang dia tautkan, dan sekarang juga dalam suntingan Scott jawaban BertS di sini unix.stackexchange.com/a/79708/59699 .
dave_thompson_085
@ dave_thompson_085 Saya tidak melihat penjelasan. Itu hanya menyebutkan CRLF tetapi tidak menjelaskan apa itu.
bodacydo
1
@bodacydo stackoverflow.com/questions/73833/… mengatakan bahwa find ... -exec file ... | grep CRLFuntuk file dengan akhiran garis DOS (yaitu byte 0D 0A) "akan membuat Anda mendapatkan sesuatu seperti: ./1/dos1.txt: ASCII text, with CRLF line terminators Seperti yang Anda lihat ini berisi string CRLF yang sebenarnya dan karenanya dicocokkan dengan grepmencari string sederhana CRLF
dave_thompson_085
22

Anda dapat mencoba grepkode CRLF, oktal:

grep -U $'\015' myfile.txt

atau hex:

grep -U $'\x0D' myfile.txt
don_crissti
sumber
Tentu saja, asumsinya adalah ini adalah file teks.
mdpc
2
Saya suka greppenggunaan ini karena memungkinkan saya untuk dengan mudah mendaftarkan semua file seperti itu di direktori dengan grep -lU $'\x0D' *dan meneruskan output ke xargs.
Melebius
apa arti dari $ sebelum pola pencarian? @don_crissti
fersarr
1
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Karena versi 7.1dos2unix memiliki -i, --infoopsi untuk mendapatkan informasi tentang jeda baris. Anda dapat menggunakan dos2unix itu sendiri untuk menguji file mana yang perlu konversi.

Contoh:

dos2unix -ic *.txt | xargs dos2unix
Erwin Waterlander
sumber
Ini tautan ke changelog sendiri waterlan.home.xs4all.nl/dos2unix/NEWS.txt
Adam Ryczkowski
13

Metode pertama ( grep):

Hitung garis yang mengandung carriage return:

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

Hitung garis yang berakhir dengan carriage return:

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

Ini biasanya akan setara; kembalinya carriage di bagian dalam garis (yaitu, bukan di akhir) jarang terjadi.

Lebih efisien:

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

Ini lebih efisien

  1. karena tidak perlu mengonversi hitungan ke string ASCII, lalu mengonversi string itu kembali ke integer, dan membandingkannya dengan nol, dan
  2. karena grep -cperlu membaca seluruh file, untuk menghitung semua kemunculan pola, sementara grep -qdapat keluar setelah melihat kemunculan pola pertama.

Catatan:

  • Sepanjang hal di atas, Anda mungkin perlu menambahkan -Uopsi (yaitu, gunakan -cUatau -qU), karena GNU grepmenebak apakah file tersebut adalah file teks. Jika ia berpikir file tersebut adalah teks, ia mengabaikan carriage return di akhir baris, dalam upaya membuat $ekspresi reguler berfungsi "dengan benar" - bahkan jika ekspresi regulernya adalah \r$! Menentukan -U(atau --binary) mengesampingkan dugaan ini, menyebabkan grepmemperlakukan file sebagai biner dan meneruskan data ke mekanisme pencocokan kata demi kata, dengan CR-endings utuh.
  • Jangan lakukan grep … $'\r\n' myfile.txt, karena grepmemperlakukan \nsebagai pembatas pola. Sama seperti grep -E 'foo|'mencari baris yang berisi fooatau string nol, grep $'\r\n'mencari baris yang berisi \ratau string nol, dan setiap baris cocok dengan string nol.

Metode kedua ( file):

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

karena filemelaporkan sesuatu seperti:

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

Varian yang lebih aman:

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

dimana

  • file -bhanya menghasilkan jenis file, dan bukan nama file. Tanpa ini, file yang namanya termasuk karakterCRLF akan memicu false positive.
  • file - < filenamebekerja bahkan jika filenamedimulai dengan -Lihat skrip Bash: periksa apakah file adalah file teks .

Berhati-hatilah karena memeriksa output dari file mungkin tidak berfungsi di lokal non-Inggris.

BertS
sumber
1
Anda dapat menggantinya "$(echo -e '\r')"dengan yang lebih sederhana $'\r', meskipun secara pribadi saya akan gunakan $'\r\n'untuk mengurangi jumlah false positive.
rici
@rici grep $'\r\n'tampaknya cocok dengan semua file di sistem saya ...
depquid
@rici: tangkapan yang bagus. Saya mengedit jawaban saya sesuai dengan saran Anda. - depquid: Mungkin Anda berada di Windows? :-) Tip rici bekerja di sini.
BertS
@depquid (dan BertS): Sebenarnya, saya pikir doa yang benar adalah grep -U $'\r$', untuk mencegah grepmencoba menebak garis akhir.
rici
Juga, Anda dapat menggunakan -quntuk hanya mengatur kode kembali jika kecocokan ditemukan, alih-alih -cyang membutuhkan pemeriksaan tambahan. Secara pribadi saya suka solusi kedua Anda, meskipun itu sangat tergantung pada keinginan filedan mungkin tidak bekerja di lokal non-Inggris.
rici
11

Menggunakan cat -A

$ cat file
hello
hello

Sekarang jika file ini dibuat dalam sistem * NIX, itu akan ditampilkan

$ cat -A file
hello$
hello$

Tetapi jika file ini dibuat di Windows, itu akan ditampilkan

$ cat -A file
hello^M$
hello

^Mmewakili CRdan $mewakili LF. Perhatikan bahwa Windows tidak menyimpan baris terakhir denganCRLF

Ini tidak mengubah isi file juga.

GypsyCosmonaut
sumber
Solusi terbaik dan paling sederhana! membutuhkan lebih banyak suara.
user648026
1
+1 Sejauh ini jawaban terbaik. Tidak ada dependensi, tidak ada skrip bash yang rumit. Hanya -Auntuk kucing. Namun satu tip akan digunakan cat -A file | lessjika file terlalu besar. Saya yakin itu tidak biasa harus memeriksa ujung file untuk file yang sangat panjang. (Tekan quntuk meninggalkan lebih sedikit)
Nicholas Pipitone
4

fungsi bash untuk Anda:

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

Maka Anda dapat melakukan hal-hal seperti

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
sumber
3
Anda tidak harus menggunakan isDosFile()dalam contoh Anda: streamFile() { sed 's/\r$//' "$1" ; }.
1
Saya pikir ini adalah solusi yang paling elegan; itu tidak membaca seluruh file, hanya baris pertama.
Adam Ryczkowski
4

Jika sebuah file memiliki akhiran garis CR-LF gaya DOS / Windows, maka jika Anda melihatnya menggunakan alat berbasis Unix Anda akan melihat karakter CR ('\ r') di akhir setiap baris.

Perintah ini:

grep -l '^M$' filename

akan mencetak filenamejika file berisi satu atau lebih baris dengan ujung garis bergaya Windows, dan tidak akan mencetak apa pun jika tidak. Kecuali bahwa ^Mharus menjadi karakter carriage return literal, biasanya dimasukkan di terminal dengan mengetik Ctrl+ Vdiikuti oleh Enter (atau Ctrl+ Vdan kemudian Ctrl+ M). Bash shell memungkinkan Anda menulis carriage return secara literal seperti $'\r'( didokumentasikan di sini ), sehingga Anda dapat menulis:

grep -l $'\r$' filename

Kerang lain mungkin menyediakan fitur serupa.

Anda dapat menggunakan alat lain sebagai gantinya:

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

Ini akan keluar dengan status 1(pengaturan $?ke 1) jika file tersebut berisi ujung garis gaya Windows, dan dengan status 0jika tidak, menjadikannya berguna dalam ifpernyataan shell (perhatikan kurangnya [tanda kurung ]):

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

File dapat berisi campuran akhiran gaya Unix dan gaya Windows. Aku menduga di sini bahwa Anda ingin mendeteksi file yang memiliki setiap Windows gaya akhir baris.

Keith Thompson
sumber
1
Anda bisa menyandikan carriage return pada baris perintah dalam bash (dan beberapa shell lainnya) dengan mengetik $'\r', seperti yang disebutkan dalam jawaban lain untuk pertanyaan ini.
Scott
2

Gunakan 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
sumber
Gagasan ini telah dibahas jauh lebih menyeluruh dalam dua jawaban sebelumnya.
G-Man Mengatakan 'Reinstate Monica'
1

Saya telah menggunakan

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

yang sepertinya berhasil. Saya menemukan output sedikit lebih mudah dibaca daripada

dos2unix < filename.txt | diff - filename.txt

Ini juga berguna jika Anda tidak dapat menginstal dos2unixkarena suatu alasan.

Alex028502
sumber