Câu hỏi Tại sao tệp văn bản kết thúc bằng dòng mới?


Tôi cho rằng tất cả mọi người ở đây đều quen thuộc với câu ngạn ngữ rằng tất cả các tệp văn bản phải kết thúc bằng một dòng mới. Tôi đã biết về "quy tắc" này trong nhiều năm nhưng tôi luôn tự hỏi - tại sao?


1096
2018-04-08 12:16


gốc


chỉ là một nitpick. nó không phải là một "dòng mới" ở phần cuối của tập tin. Đó là "ngắt dòng" ở cuối dòng cuối cùng. Ngoài ra, hãy xem câu trả lời hay nhất về câu hỏi có liên quan: stackoverflow.com/questions/16222530/… - gcb
Chỉ cần để nitpick một số chi tiết, ông đã không thực sự viết "dòng mới", ông đã viết "dòng mới", đó là chính xác. - sindrenm
không quen thuộc, nhưng tự hỏi tôi thực sự là vì số lượng các trường hợp mà dòng thừa thừa mới thực sự phá vỡ mọi thứ hơi quá cao so với sở thích của tôi - tobibeer
Tôi hiện đang sử dụng các luồng Node.js để phân tích cú pháp dòng dữ liệu văn bản thuần túy và thiếu ngắt dòng đầu cuối gây phiền nhiễu, vì tôi phải thêm logic bổ sung cho khi đầu vào của luồng được hoàn thành / đóng để đảm bảo rằng dòng cuối cùng được xử lý. - Mark K Cowan
Các cách Unix liên quan hành vi chung của nó ở phần cuối của tệp như sau: \ n ký tự không bắt đầu dòng; thay vào đó, họ kết thúc chúng. Vì vậy, \ n là một trình kết thúc dòng, không phải là dấu tách dòng. Dòng đầu tiên (giống như tất cả các dòng) không cần \ n để khởi động nó. Dòng cuối cùng (giống như tất cả các dòng) cần \ n để kết thúc. \ N ở cuối tệp không tạo thêm dòng. Tuy nhiên, đôi khi, trình chỉnh sửa văn bản sẽ thêm một dòng trống có thể nhìn thấy ở đó. Ngay cả emacs cũng vậy, tùy chọn. - MarkDBlackwell


Các câu trả lời:


Bởi vì đó là cách tiêu chuẩn POSIX định nghĩa hàng:

Dòng 3.206
Một chuỗi các ký tự không <newline> không hoặc nhiều hơn cộng với ký tự kết thúc <newline>.

Do đó, các dòng không kết thúc bằng một ký tự dòng mới không được coi là các dòng thực tế. Đó là lý do tại sao một số chương trình gặp sự cố khi xử lý dòng cuối cùng của tệp nếu nó không phải là dòng mới chấm dứt.

Có ít nhất một lợi thế khó khăn cho hướng dẫn này khi làm việc trên một trình giả lập thiết bị đầu cuối: Tất cả các công cụ Unix mong đợi quy ước này và làm việc với nó. Ví dụ: khi ghép các tệp với cat, tệp được chấm dứt bằng dòng mới sẽ có hiệu ứng khác với tệp không có:

$ more a.txt
foo$ more b.txt
bar
$ more c.txt
baz
$ cat *.txt
foobar
baz

Và, như ví dụ trước cũng thể hiện, khi hiển thị tệp trên dòng lệnh (ví dụ: qua more), một tệp được kết thúc bằng dòng mới sẽ hiển thị chính xác. Tệp bị chấm dứt không đúng có thể bị cắt xén (dòng thứ hai).

Để nhất quán, sẽ rất hữu ích nếu tuân thủ quy tắc này - làm theo cách khác sẽ phải chịu thêm công việc khi giao dịch với các công cụ Unix mặc định.

Bây giờ không tuân thủ POSIX hệ thống (ngày nay hầu hết là Windows), vấn đề là tranh luận: các tệp thường không kết thúc bằng một dòng mới và định nghĩa (không chính thức) của một dòng có thể là "văn bản ly thân bằng dòng mới ”(lưu ý sự nhấn mạnh). Điều này là hoàn toàn hợp lệ. Tuy nhiên, đối với dữ liệu có cấu trúc (ví dụ: mã lập trình), nó làm cho phân tích cú pháp phức tạp hơn một chút: thường có nghĩa là các trình phân tích cú pháp phải được viết lại. Nếu một trình phân tích cú pháp ban đầu được viết với định nghĩa POSIX, thì có thể dễ dàng sửa đổi luồng mã thông báo hơn là trình phân tích cú pháp - nói cách khác, thêm một mã thông báo “dòng mới nhân tạo” vào cuối đầu vào.


1029
2018-04-08 12:46





Mỗi dòng phải được kết thúc bằng một ký tự dòng mới, kể cả ký tự cuối cùng. Một số chương trình gặp sự cố khi xử lý dòng cuối cùng của tệp nếu nó không phải là dòng mới được chấm dứt.

GCC cảnh báo về nó không phải vì nó không thể xử lý tệp, nhưng vì nó phải như một phần của tiêu chuẩn.

Tiêu chuẩn ngôn ngữ C nói   Tệp nguồn không rỗng sẽ kết thúc bằng một ký tự dòng mới, không được đặt ngay trước ký tự dấu gạch chéo ngược.

Vì đây là mệnh đề "phải", chúng ta phải phát ra thông báo chẩn đoán vì vi phạm quy tắc này.

Đây là phần 2.1.1.2 của tiêu chuẩn ANSI C 1989. Mục 5.1.1.2 của tiêu chuẩn ISO C 1999 (và có thể là tiêu chuẩn ISO C 1990).

Tài liệu tham khảo: Lưu trữ thư GCC / GNU.


246
2018-04-08 12:26



xin vui lòng viết chương trình tốt sau đó hoặc cho phép để chèn rằng newline khi cần thiết trong khi chế biến hoặc có thể xử lý đúng "thiếu" những người ... đó là, trên thực tế, không mất tích - tobibeer
@ BilltheLizard, Một số ví dụ về "Một số chương trình có sự cố khi xử lý dòng cuối cùng của tệp nếu nó không phải là dòng mới chấm dứt"? - Pacerier
@Pacerier wc -l sẽ không tính dòng cuối cùng của một tệp nếu nó không phải là dòng mới chấm dứt. Cũng thế, cat sẽ nối dòng cuối cùng của một tệp với dòng đầu tiên của tệp tiếp theo vào một tệp nếu dòng cuối cùng của tệp đầu tiên không phải là dòng mới được chấm dứt. Khá nhiều bất kỳ chương trình đang tìm kiếm dòng mới như một dấu phân cách có tiềm năng để mess này lên. - Bill the Lizard
@BilltheLizard, ý tôi là wc có đã được đề cập.... - Pacerier
@BilltheLizard, xấu của tôi, để làm rõ: một số ví dụ về các chương trình có vấn đề xử lý dòng cuối cùng của một tập tin nếu nó không phải là dòng mới chấm dứt (bên cạnh những người đã được đề cập hàng loạt trên các chủ đề như cat và wc)? - Pacerier


Câu trả lời này là một nỗ lực trong một câu trả lời kỹ thuật chứ không phải là ý kiến.

Nếu chúng ta muốn là POSIX purists, chúng ta định nghĩa một dòng là:

Một chuỗi các ký tự không <newline> không hoặc nhiều hơn cộng với ký tự kết thúc <newline>.

Nguồn: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Một dòng không đầy đủ là:

Một chuỗi gồm một hoặc nhiều ký tự không <newline> ở cuối tệp.

Nguồn: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

Tệp văn bản dưới dạng:

Một tệp chứa các ký tự được sắp xếp thành 0 hoặc nhiều dòng. Các dòng không chứa các ký tự NUL và không được vượt quá {LINE_MAX} byte, bao gồm ký tự <newline>. Mặc dù POSIX.1-2008 không phân biệt giữa các tệp văn bản và tệp nhị phân (xem tiêu chuẩn ISO C), nhiều tiện ích chỉ tạo ra đầu ra có thể dự đoán hoặc có ý nghĩa khi hoạt động trên các tệp văn bản. Các tiện ích chuẩn có các hạn chế như vậy luôn luôn chỉ định "các tệp văn bản" trong các phần STDIN hoặc INPUT FILES của chúng.

Nguồn: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Một chuỗi là:

Một chuỗi liên tiếp các byte được kết thúc bởi và bao gồm byte null đầu tiên.

Nguồn: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

Từ đó, chúng ta có thể lấy được rằng lần duy nhất chúng ta sẽ có khả năng gặp phải bất kỳ loại vấn đề nào nếu chúng ta đối phó với khái niệm về hàng của tệp hoặc tệp dưới dạng tệp văn bản (đó là một tệp văn bản là một tổ chức có số không hoặc nhiều dòng và một dòng mà chúng ta biết phải kết thúc bằng <newline>).

Trường hợp tại điểm: wc -l filename.

Từ wc's hướng dẫn sử dụng, chúng tôi đọc:

Một dòng được định nghĩa là một chuỗi ký tự được giới hạn bởi ký tự <newline>.

Ý nghĩa của các tệp JavaScript, HTML và CSS là chúng là gì bản văn  các tập tin?

Trong các trình duyệt, các IDE hiện đại và các ứng dụng front-end khác không có vấn đề gì với việc bỏ qua EOL tại EOF. Các ứng dụng sẽ phân tích cú pháp các tệp một cách chính xác. Do không phải tất cả Hệ điều hành đều tuân thủ tiêu chuẩn POSIX nên sẽ không thực tế đối với các công cụ không phải HĐH (ví dụ: trình duyệt) để xử lý tệp theo tiêu chuẩn POSIX (hoặc bất kỳ tiêu chuẩn cấp hệ điều hành nào).

Kết quả là, chúng ta có thể tương đối tự tin rằng EOL tại EOF sẽ hầu như không có tác động tiêu cực ở cấp ứng dụng - bất kể nó đang chạy trên hệ điều hành UNIX hay không.

Tại thời điểm này, chúng ta có thể tự tin nói rằng bỏ qua EOL tại EOF là an toàn khi giao dịch với JS, HTML, CSS ở phía máy khách. Trên thực tế, chúng tôi có thể tuyên bố rằng việc rút gọn bất kỳ tệp nào trong số những tệp này, không chứa <newline> là an toàn.

Chúng ta có thể tiến thêm một bước nữa và nói rằng theo NodeJS thì nó cũng không thể tuân thủ tiêu chuẩn POSIX là nó có thể chạy trong các môi trường tương thích không POSIX.

Vậy thì chúng ta còn lại gì? Công cụ mức hệ thống.

Điều này có nghĩa là các vấn đề duy nhất có thể phát sinh là với các công cụ cố gắng tuân thủ chức năng của chúng đối với ngữ nghĩa của POSIX (ví dụ: định nghĩa của một dòng như được hiển thị trong wc).

Mặc dù vậy, không phải tất cả các hệ vỏ sẽ tự động tuân theo POSIX. Ví dụ Bash không mặc định đối với hành vi POSIX. Có một công tắc để kích hoạt nó: POSIXLY_CORRECT.

Thức ăn cho suy nghĩ về giá trị của EOL là <newline>: http://www.rfc-editor.org/EOLstory.txt

Tiếp tục theo dõi công cụ, cho tất cả các mục đích thực tế và mục đích, hãy xem xét điều này:

Hãy làm việc với một tệp không có EOL. Vì điều này, tập tin trong ví dụ này là một JavaScript được rút gọn không có EOL.

curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js

$ cat x.js y.js > z.js

-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 x.js
-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 y.js
-rw-r--r--  1 milanadamovsky  15810 Aug 14 23:18 z.js

Lưu ý cat kích thước tệp chính xác là tổng các phần riêng lẻ của nó. Nếu việc ghép nối các tệp JavaScript là một mối quan tâm đối với các tệp JS, thì mối quan tâm thích hợp hơn là bắt đầu mỗi tệp JavaScript bằng dấu chấm phẩy.

Là người khác được đề cập trong chủ đề này: nếu bạn muốn cat hai tệp có đầu ra chỉ trở thành một dòng thay vì hai? Nói cách khác, cat làm những gì nó phải làm.

Các man của cat chỉ đề cập đến việc đọc đầu vào lên đến EOF, chứ không phải <newline>. Lưu ý rằng -n chuyển đổi cat cũng sẽ in ra một dòng <newline> không kết thúc (hoặc dòng không đầy đủ) như một hàng - là số lượng bắt đầu tại 1 (theo man.)

-n Số dòng đầu ra, bắt đầu từ 1.

Bây giờ chúng ta đã hiểu cách POSIX định nghĩa hàng , hành vi này trở nên mơ hồ, hoặc thực sự, không tuân thủ.

Hiểu được mục đích và sự tuân thủ của một công cụ cụ thể sẽ giúp xác định mức độ quan trọng của việc kết thúc các tệp bằng EOL. Trong C, C ++, Java (JAR), vv ... một số tiêu chuẩn sẽ ra lệnh cho một dòng mới cho tính hợp lệ - không có tiêu chuẩn như vậy tồn tại cho JS, HTML, CSS.

Ví dụ: thay vì sử dụng wc -l filename người ta có thể làm awk '{x++}END{ print x}' filename và yên tâm rằng thành công của công việc không bị gây nguy hiểm bởi một tệp chúng tôi có thể muốn xử lý mà chúng tôi không viết (ví dụ: thư viện của bên thứ ba chẳng hạn như JS được rút gọn, chúng tôi curld) - trừ khi ý định của chúng tôi thực sự được tính dòng theo ý nghĩa tuân thủ POSIX.

Phần kết luận

Sẽ có rất ít trường hợp sử dụng thực tế khi bỏ qua EOL tại EOF đối với một số tệp văn bản nhất định như JS, HTML và CSS sẽ có tác động tiêu cực - nếu có. Nếu chúng ta dựa vào <newline> hiện diện, chúng ta đang hạn chế độ tin cậy của công cụ chỉ cho các tệp mà chúng ta tạo ra và mở ra các lỗi tiềm ẩn do các tệp của bên thứ ba giới thiệu.

Đạo đức của câu chuyện: Dụng cụ kỹ sư không có điểm yếu dựa vào EOL tại EOF.

Vui lòng đăng các trường hợp sử dụng khi chúng áp dụng cho JS, HTML và CSS, nơi chúng tôi có thể kiểm tra cách bỏ qua EOL có ảnh hưởng xấu.


89
2017-08-15 06:31



Liên kết trình soạn thảo rfc phải là rfc-editor.org/old/EOLstory.txt - gcali
POSIX không được gắn thẻ trong câu hỏi ... wat về kết thúc dòng MVS / OS? hoặc kết thúc dòng MS-DOS? Bằng cách này, tất cả các hệ thống posix đã biết cho phép các tệp văn bản không có dòng cuối cùng kết thúc (không có trường hợp nào được tìm thấy trong hệ thống xác nhận quyền sở hữu posix mà "tệp văn bản" có điều trị đặc biệt trong hạt nhân để chèn một dòng mới thích hợp trong trường hợp nó không có nó) - Luis Colorado
Tôi đã sửa liên kết cho EOLstory.txt nhưng vì tôi chỉ thêm /cũ nó sẽ không để tôi cứu nó. - user34660


Nó có thể liên quan đến sự khác biệt giữa:

  • tệp văn bản (mỗi dòng được cho là kết thúc ở cuối dòng)
  • tệp nhị phân (không có "dòng" thực sự để nói và độ dài của tệp phải được giữ nguyên)

Nếu mỗi dòng kết thúc trong một dòng cuối cùng, điều này tránh, ví dụ, nối hai tệp văn bản sẽ làm cho dòng cuối cùng của lần chạy đầu tiên vào dòng đầu tiên của dòng thứ hai.

Thêm vào đó, một trình soạn thảo có thể kiểm tra lúc tải tệp có kết thúc bằng một dòng cuối hay không, lưu nó trong tùy chọn cục bộ 'eol' và sử dụng nó khi ghi tệp.

Một vài năm trước (2005), nhiều biên tập viên (ZDE, Eclipse, Scite, ...) đã "quên" EOL cuối cùng đó, không được đánh giá cao lắm.
Không chỉ vậy, nhưng họ giải thích rằng EOL cuối cùng không chính xác, như 'bắt đầu một dòng mới', và thực sự bắt đầu hiển thị một dòng khác như thể nó đã tồn tại.
Điều này rất rõ ràng với một tập tin văn bản 'thích hợp' với một trình soạn thảo văn bản được xử lý tốt như vim, so với việc mở nó ở một trong các trình soạn thảo ở trên. Nó hiển thị một dòng phụ bên dưới dòng cuối cùng thực của tệp. Bạn thấy một cái gì đó như thế này:

1 first line
2 middle line
3 last line
4

59
2018-04-08 12:29



+1. Tôi đã tìm thấy câu hỏi SO này trong khi gặp phải vấn đề này. Nó là rất gây phiền nhiễu cho Eclipse để hiển thị dòng cuối cùng "giả" này, và nếu tôi loại bỏ nó, thì git (và tất cả các công cụ unix khác mong đợi EOL) sẽ phàn nàn. Ngoài ra, lưu ý rằng đây không chỉ là trong năm 2005: Eclipse 4.2 Juno vẫn có vấn đề này. - MestreLion
@MestreLion, Tiếp tục tại stackoverflow.com/questions/729692/… - Pacerier


Một số công cụ mong đợi điều này. Ví dụ, wc mong đợi điều này:

$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1

38
2017-10-12 14:16



Tôi sẽ không nói "một số", tôi nói phần lớn các công cụ mong đợi rằng đối với các tệp văn bản, nếu không phải tất cả. mèo, git, diff, wc, grep, sed ... danh sách là rất lớn - MestreLion
Có thể người ta có thể nói rằng wc không chờ đợi điều này, nhiều như nó chỉ đơn giản là làm việc trong định nghĩa POSIX của một "dòng" như trái ngược với sự hiểu biết trực quan của hầu hết mọi người "dòng". - Guildenstern
@Guildenstern Định nghĩa trực quan sẽ dành cho wc -l để in 1 trong cả hai trường hợp, nhưng một số người có thể nói trường hợp thứ hai nên in 2. - Flimm
@Flimm Nếu bạn nghĩ về \n như là một terminator dòng, chứ không phải là một tách dòng, như POSIX / UNIX, sau đó mong đợi trường hợp thứ hai để in 2 là hoàn toàn điên rồ. - semicolon


Về cơ bản có nhiều chương trình sẽ không xử lý tệp chính xác nếu chúng không nhận được EOL EOF cuối cùng.

GCC cảnh báo bạn về điều này vì nó được dự kiến ​​là một phần của tiêu chuẩn C. (phần 5.1.1.2 rõ ràng)

Cảnh báo trình biên dịch "Không có dòng mới ở cuối tệp"


18
2018-04-08 12:21



GCC không có khả năng xử lý tệp, nó phải đưa ra cảnh báo như là một phần của tiêu chuẩn C. - Bill the Lizard
Điểm tốt, cập nhật với phần thích hợp) - cgp
IIRC, MSVC 2005 đã phàn nàn về các tệp C đã kết thúc với các dòng không đầy đủ và có thể từ chối biên dịch chúng. - Mark K Cowan


Điều này bắt nguồn từ những ngày đầu tiên khi các thiết bị đầu cuối đơn giản được sử dụng. Char mới được sử dụng để kích hoạt dữ liệu được truyền.

Hôm nay, char newline không được yêu cầu nữa. Chắc chắn, nhiều ứng dụng vẫn gặp sự cố nếu dòng mới không có ở đó, nhưng tôi cho rằng một lỗi trong các ứng dụng đó.

Tuy nhiên, nếu bạn có định dạng tệp văn bản nơi bạn yêu cầu dòng mới, bạn nhận được xác minh dữ liệu đơn giản rất rẻ: nếu tệp kết thúc bằng một dòng không có dòng mới ở cuối, bạn biết tệp bị hỏng. Chỉ với một byte phụ cho mỗi dòng, bạn có thể phát hiện các tệp bị hỏng với độ chính xác cao và hầu như không có thời gian CPU.


12
2018-04-08 12:41



ngày nay là dòng mới tại EOF cho bản văn các tệp có thể không phải là yêu cầu, nhưng nó có ích quy ước làm cho hầu hết các công cụ unix hoạt động cùng với kết quả nhất quán. Nó không phải là lỗi. - MestreLion
Nhiều người trong chúng ta không sử dụng các công cụ Unix, và chúng tôi không quan tâm. - DaveWalley
Nó không chỉ là công cụ unix, bất kỳ công cụ nào cũng hoạt động tốt hơn và / hoặc được mã hóa đơn giản hơn nếu nó có thể giả định định dạng tệp hợp lý. - Sam Watkins
@MestreLion Đây là một di sản vô dụng từ một tập hợp các công cụ xấu phù hợp với tiêu chuẩn ngu ngốc. Những hiện vật của lập trình cực đoan(tức là tất cả mọi thứ của tập tin! tất cả mọi thứ nên nói văn bản đơn giản!) Không chết ngay sau khi phát minh của họ bởi vì họ là những công cụ duy nhất của loại tại một thời điểm nhất định của lịch sử. C được thay thế bởi C ++, nó không phải là một phần của POSIX, nó không yêu cầu EOL tại EOF, và việc sử dụng nó là (rõ ràng) không được khuyến khích bởi những kẻ ăn thịt * nix. - polkovnikov.ph
@ polkovnikov.ph "C bị thay thế bởi C ++" ummmm ... - minexew


Ngoài ra còn có một vấn đề lập trình thực tế với các tập tin thiếu newlines ở cuối: read Bash được xây dựng trong (tôi không biết về read triển khai) không hoạt động như mong đợi:

printf $'foo\nbar' | while read line
do
    echo $line
done

Bản in này chỉ có foo! Lý do là khi read gặp dòng cuối cùng, nó viết nội dung $line nhưng trả về mã thoát 1 vì nó đã đạt đến EOF. Điều này phá vỡ while vòng lặp, vì vậy chúng tôi không bao giờ đạt được echo $line phần. Nếu bạn muốn xử lý tình huống này, bạn phải làm như sau:

while read line || [ -n "${line-}" ]
do
    echo $line
done < <(printf $'foo\nbar')

Đó là, làm echo nếu read không thành công vì dòng không trống ở cuối tệp. Đương nhiên, trong trường hợp này sẽ có thêm một dòng mới trong đầu ra không có trong đầu vào.


10
2017-11-04 10:12





Một trường hợp sử dụng riêng biệt: khi tệp văn bản của bạn được kiểm soát phiên bản (trong trường hợp này cụ thể dưới git mặc dù nó cũng áp dụng cho những người khác). Nếu nội dung được thêm vào cuối tệp, thì dòng trước đó dòng cuối cùng sẽ được chỉnh sửa để bao gồm ký tự dòng mới. Điều này có nghĩa rằng blameing các tập tin để tìm hiểu khi dòng đó được chỉnh sửa lần cuối sẽ hiển thị thêm văn bản, không phải là cam kết trước khi bạn thực sự muốn xem.


10
2017-09-05 13:17