Làm cách nào để kiểm tra xem một tệp sử dụng CRLF hay LF mà không sửa đổi nó?


48

Tôi cần định kỳ chạy một lệnh đảm bảo rằng một số tệp văn bản được giữ ở chế độ Linux. Thật không may, dos2unixluôn luôn sửa đổi tệp, sẽ làm rối loạn dấu thời gian của tệp và thư mục và gây ra việc ghi không cần thiết.

Kịch bản tôi viết là ở Bash, vì vậy tôi thích câu trả lời dựa trên Bash.

Câu trả lời:


41

Bạn có thể sử dụng dos2unixlàm bộ lọc và so sánh đầu ra của nó với tệp gốc:

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

2
Rất thông minh và hữu ích, bởi vì nó kiểm tra tập tin hoàn chỉnh và không chỉ dòng đầu tiên hoặc một vài dòng.
halloleo

2
Có lẽ bạn có thể thay thế testbằng myfile.txthai lần trong ví dụ của bạn để tránh nhầm lẫn với /usr/bin/test.
Peterino

1
NB bạn sẽ cần xóa -scờ để xem đầu ra. Từ trang người đàn ông: -s, --quiet, --silent suppress all normal output
tobalr

24

Nếu mục tiêu chỉ là để tránh ảnh hưởng đến dấu thời gian, dos2unixcó một -khoặc --keepdatetùy chọn sẽ giữ dấu thời gian như cũ. Nó vẫn sẽ phải thực hiện ghi để tạo tệp tạm thời và đổi tên nó, nhưng dấu thời gian của bạn sẽ không bị ảnh hưởng.

Nếu bất kỳ sửa đổi nào của tập tin là không thể chấp nhận, bạn có thể sử dụng giải pháp sau đây từ câu trả lời này .

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

1
Bạn có nghĩa là bạn viết CRLF theo nghĩa đen là 4 ký tự C, R, L và F?
Bodacydo

7
Bạn cũng có nghĩa là grep có thể lấy CR và LF như vậy?
Bodacydo

@bodacydo Điều đó được giải thích trong câu trả lời mà anh ấy liên kết đến, và bây giờ cũng là trong bản chỉnh sửa của Scott về câu trả lời của BertS ở đây unix.stackexchange.com/a/79708/59699 .
dave_thndry_085

@ dave_thndry_085 Tôi không thấy giải thích. Nó chỉ đề cập đến CRLF nhưng không giải thích nó là gì.
Bodacydo

1
@bodacydo stackoverflow.com/questions/73833/... nói rằng find ... -exec file ... | grep CRLFcho một tập tin với kết thúc dòng hệ điều hành DOS (tức là byte 0D 0A) "sẽ giúp bạn có được một cái gì đó như: ./1/dos1.txt: ASCII text, with CRLF line terminators Như bạn có thể thấy điều này có chứa các CRLF chuỗi thực tế và do đó được kết hợp bởi greptìm kiếm chuỗi CRLF đơn giản.
dave_thndry_085

22

Bạn có thể thử grepmã CRLF, bát phân:

grep -U $'\015' myfile.txt

hoặc hex:

grep -U $'\x0D' myfile.txt

Tất nhiên, giả định rằng đây là một tệp văn bản.
mdpc

2
Tôi thích grepcách sử dụng này vì nó cho phép tôi dễ dàng liệt kê tất cả các tệp như vậy trong thư mục grep -lU $'\x0D' *và chuyển đầu ra tới xargs.
Melebius

Ý nghĩa của $ trước mẫu tìm kiếm là gì? @don_crissti
fersarr



13

Phương thức đầu tiên ( grep):

Đếm các dòng có chứa lợi nhuận vận chuyển:

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

Đếm các dòng kết thúc bằng trả lại xe ngựa:

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

Chúng thường sẽ tương đương; một sự trở lại vận chuyển trong nội thất của một dòng (tức là, không phải ở cuối) là hiếm.

Hiệu quả hơn:

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

Đây là hiệu quả hơn

  1. bởi vì nó không cần phải chuyển đổi số đếm thành chuỗi ASCII, sau đó chuyển đổi chuỗi đó thành số nguyên và so sánh nó với số không, và
  2. bởi vì grep -ccần phải đọc toàn bộ tệp, để đếm tất cả các lần xuất hiện của mẫu, trong khi grep -qcó thể thoát khi thấy sự xuất hiện đầu tiên của mẫu.

Ghi chú:

  • Trong suốt phần trên, bạn có thể cần thêm -Utùy chọn (nghĩa là sử dụng -cUhoặc -qU), vì GNU grepđoán xem tệp có phải là tệp văn bản không. Nếu nó nghĩ rằng tệp là văn bản, nó sẽ bỏ qua trả về vận chuyển ở cuối dòng, trong một nỗ lực để làm cho $các biểu thức thông thường hoạt động "chính xác" - ngay cả khi biểu thức thông thường là \r$! Chỉ định -U(hoặc --binary) ghi đè lên phỏng đoán này, gây ra grepcoi (các) tệp là nhị phân và chuyển dữ liệu đến cơ chế khớp nguyên văn, với kết thúc CR còn nguyên vẹn.
  • Đừng làm grep … $'\r\n' myfile.txt, bởi vì grepcoi \nnhư một dấu phân cách mẫu. Cũng giống như grep -E 'foo|'tìm kiếm các dòng chứa foohoặc một chuỗi null, grep $'\r\n'tìm kiếm các dòng chứa \rhoặc một chuỗi null và mỗi dòng khớp với một chuỗi null.

Phương thức thứ hai ( file):

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

bởi vì filebáo cáo một cái gì đó như:

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

Biến thể an toàn hơn:

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

Ở đâu

Coi chừng việc kiểm tra đầu ra từ file có thể không hoạt động ở một địa điểm không phải tiếng Anh.


1
Bạn có thể thay thế "$(echo -e '\r')"bằng cách đơn giản hơn nhiều $'\r', mặc dù cá nhân tôi sẽ sử dụng $'\r\n'để giảm số lượng dương tính giả.
rici

@rici grep $'\r\n'dường như khớp với tất cả các tệp trên hệ thống của tôi ...
depquid

@rici: bắt tốt. Tôi chỉnh sửa câu trả lời của tôi theo đề nghị của bạn. - depquid: Có lẽ bạn đang ở trên Windows? :-) mẹo của rici hoạt động ở đây.
BertS

@depquid (và BertS): Trên thực tế, tôi nghĩ rằng cách gọi chính xác là grep -U $'\r$', để tránh grepcố gắng đoán kết thúc dòng thứ hai.
rici

Ngoài ra, bạn có thể sử dụng -qđể chỉ đặt mã trả về nếu tìm thấy kết quả khớp, thay vì -cyêu cầu kiểm tra bổ sung. Cá nhân tôi thích giải pháp thứ hai của bạn, mặc dù nó phụ thuộc rất nhiều vào ý tưởng bất chợt filevà có thể không hoạt động ở một địa phương không phải tiếng Anh.
rici

11

Sử dụng cat -A

$ cat file
hello
hello

Bây giờ nếu tệp này được tạo trong các hệ thống * NIX, nó sẽ hiển thị

$ cat -A file
hello$
hello$

Nhưng nếu tệp này được tạo trong Windows, nó sẽ hiển thị

$ cat -A file
hello^M$
hello

^Mđại diện CR$đại diện LF. Lưu ý rằng Windows đã không lưu dòng cuối cùng vớiCRLF

Điều này cũng không thay đổi nội dung tập tin.


Giải pháp tốt nhất và đơn giản nhất! cần nhiều phiếu hơn.
dùng648026

1
+1 Cho đến nay câu trả lời tốt nhất. Không phụ thuộc, không có tập lệnh bash phức tạp. Chỉ -Ađể mèo. Một mẹo mặc dù sẽ là sử dụng cat -A file | lessnếu tệp quá lớn. Tôi chắc chắn rằng sẽ không có gì lạ khi phải kiểm tra các kết thúc tệp cho một tệp đặc biệt dài. (Nhấn qđể rời đi ít hơn)
Nicholas Pipitone

4

một hàm bash cho bạn:

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

Sau đó, bạn có thể làm những thứ như

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

streamFile /tmp/foo.txt | process_lines_without_CR

3
Bạn không phải sử dụng isDosFile()trong ví dụ của mình : streamFile() { sed 's/\r$//' "$1" ; }.

1
Tôi nghĩ rằng đây là giải pháp thanh lịch nhất; Nó không đọc toàn bộ tập tin, chỉ là dòng đầu tiên.
Adam Ryczkowski

4

Nếu một tệp có các kết thúc dòng CR-LF kiểu DOS / Windows, thì nếu bạn xem nó bằng công cụ dựa trên Unix, bạn sẽ thấy các ký tự CR ('\ r') ở cuối mỗi dòng.

Lệnh này:

grep -l '^M$' filename

sẽ in filenamenếu tệp chứa một hoặc nhiều dòng có kết thúc dòng kiểu Windows và sẽ không in gì nếu không có. Ngoại trừ việc ^Mphải là một ký tự trở lại vận chuyển theo nghĩa đen, thường được nhập vào thiết bị đầu cuối bằng cách gõ Ctrl+ Vtheo sau Enter (hoặc Ctrl+ Vvà sau đó Ctrl+ M). Shell bash cho phép bạn viết trở lại vận chuyển theo nghĩa đen như $'\r'( tài liệu ở đây ), vì vậy bạn có thể viết:

grep -l $'\r$' filename

Các vỏ khác có thể cung cấp một tính năng tương tự.

Bạn có thể sử dụng một công cụ khác thay thế:

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

Điều này sẽ thoát với trạng thái 1(cài đặt $?thành 1) nếu tệp chứa bất kỳ kết thúc dòng kiểu Windows nào và với trạng thái 0nếu không, làm cho nó hữu ích trong ifcâu lệnh shell (lưu ý thiếu [dấu ngoặc ]):

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

Một tệp có thể chứa hỗn hợp các kết thúc dòng kiểu Unix và Windows. Tôi giả sử ở đây rằng bạn muốn phát hiện các tệp có bất kỳ kết thúc dòng kiểu Windows nào .


1
Bạn có thể mã hóa trở lại vận chuyển trên dòng lệnh trong bash (và một số shell khác) bằng cách nhập $'\r', như đã đề cập trong các câu trả lời khác cho câu hỏi này.
Scott

2

Sử dụng 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

Ý tưởng này đã được thảo luận kỹ lưỡng hơn nhiều trong hai câu trả lời trước.
G-Man nói 'Phục hồi Monica'

1

tôi đã từng sử dụng

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

mà dường như làm việc Tôi thấy đầu ra dễ đọc hơn một chút

dos2unix < filename.txt | diff - filename.txt

Nó cũng hữu ích nếu bạn không thể cài đặt dos2unixvì một số lý do.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.