Почему текстовые файлы должны заканчиваться символом новой строки? - PullRequest
1273 голосов
/ 08 апреля 2009

Я предполагаю, что все здесь знакомы с пословицей, что все текстовые файлы должны заканчиваться символом новой строки. Я знал об этом «правиле» много лет, но всегда задавался вопросом - почему?

Ответы [ 18 ]

1203 голосов
/ 08 апреля 2009

Потому что это , как стандарт POSIX определяет строку :

3,206 Линия
Последовательность из нуля или более не символов плюс завершающий символ.

Следовательно, строки, не заканчивающиеся символом новой строки, не считаются действительными. Вот почему в некоторых программах возникают проблемы с обработкой последней строки файла, если он не завершен новой строкой.

При работе с эмулятором терминала есть по крайней мере одно серьезное преимущество для этого руководства: все инструменты Unix ожидают этого соглашения и работают с ним. Например, при объединении файлов с cat файл, завершенный символом новой строки, будет иметь другой эффект, чем файл без:

<i>$</i> <b>more</b> a.txt
foo
<i>$</i> <b>more</b> b.txt
bar<i>$</i> <b>more</b> c.txt
baz
<i>$</i> <b>cat</b> {a,b,c}.txt
foo
barbaz

И, как показывает предыдущий пример, при отображении файла в командной строке (например, с помощью more) файл с завершающей строкой приводит к правильному отображению. Неправильно завершенный файл может быть искажен (вторая строка).

Для согласованности очень полезно следовать этому правилу - в противном случае потребуется дополнительная работа при работе со стандартными инструментами Unix.


Подумайте об этом по-другому: если строки не заканчиваются символом новой строки, сделать такие команды, как cat полезными, гораздо сложнее: как сделать команду для объединения файлов, такую, что

  1. он помещает начало каждого файла в новую строку, что вам нужно в 95% случаев; но
  2. позволяет объединить последнюю и первую строку двух файлов, как в примере выше между b.txt и c.txt?

Конечно, это решаемо , но вам нужно сделать использование cat более сложным (добавив позиционные аргументы командной строки, например, cat a.txt --no-newline b.txt c.txt), а теперь command вместо того, чтобы каждый отдельный файл контролировал, как он вставляется вместе с другими файлами. Это почти наверняка не удобно.

... Или вам нужно ввести специальный символ стража, чтобы пометить строку, которая должна быть продолжена, а не завершена. Что ж, теперь вы застряли в той же ситуации, что и в POSIX, за исключением перевернутого (продолжение строки, а не символ завершения строки).


Теперь, в системах, не поддерживающих POSIX (в настоящее время это в основном Windows), вопрос спорный: файлы обычно не заканчиваются символом новой строки и (неформальным) определением строки например, это может быть «текст, который отделен переводом строк» ​​(обратите внимание на ударение). Это полностью верно. Однако для структурированных данных (например, программного кода) это делает синтаксический анализ минимально более сложным: это обычно означает, что анализаторы должны быть переписаны. Если синтаксический анализатор изначально был написан с учетом определения POSIX, то может быть проще изменить поток токенов, чем синтаксический анализатор - другими словами, добавить токен «искусственного перевода строки» в конец ввода.

263 голосов
/ 08 апреля 2009

Каждая строка должна заканчиваться символом новой строки, включая последнюю. В некоторых программах возникают проблемы с обработкой последней строки файла, если он не завершен новой строкой.

GCC предупреждает об этом не потому, что не может обработать файл, а потому, что должен как часть стандарта.

Стандарт языка C говорит Исходный файл, который не является пустым, должен заканчиваться символом новой строки, которому не должен предшествовать символ обратной косой черты.

Так как это условие «должен», мы должны выдать диагностическое сообщение о нарушении этого правила.

Это в разделе 2.1.1.2 стандарта ANSI C 1989 года. Раздел 5.1.1.2 стандарта ISO C 1999 (и, возможно, также стандарта ISO C 1990).

Ссылка: Почтовый архив GCC / GNU .

101 голосов
/ 15 августа 2014

Этот ответ - скорее попытка технического ответа, чем мнения.

Если мы хотим быть пуристами POSIX, мы определяем строку как:

Последовательность из нуля или более не символов плюс завершающий символ.

Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Неполная строка как:

Последовательность из одного или нескольких не символов в конце файла.

Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

Текстовый файл как:

Файл, содержащий символы, организованные в ноль или более строк. Строки не содержат символов NUL, и ни одна из них не может превышать длину {LINE_MAX} байтов, включая символ . Хотя POSIX.1-2008 не делает различий между текстовыми файлами и двоичными файлами (см. Стандарт ISO C), многие утилиты производят только предсказуемый или значимый вывод при работе с текстовыми файлами. Стандартные утилиты, имеющие такие ограничения, всегда указывают «текстовые файлы» в своих разделах STDIN или INPUT FILES.

Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Строка как:

Непрерывная последовательность байтов, оканчивающаяся первым нулевым байтом и включающая его.

Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

Отсюда мы можем вывести, что единственный раз, когда мы потенциально столкнемся с какими-либо проблемами, мы будем иметь дело только с понятием строки файла или файла как текстовый файл (поскольку текстовый файл представляет собой организацию из нуля или более строк, и строка, которую мы знаем, должна заканчиваться ).

Показательный пример: wc -l filename.

Из руководства wc мы читаем:

Строка определяется как строка символов, разделенных символом .

Какое значение имеют файлы JavaScript, HTML и CSS в том, что они являются текстовыми файлами?

В браузерах, современных IDE и других интерфейсных приложениях нет проблем с пропуском EOL при EOF. Приложения будут правильно анализировать файлы. Это связано с тем, что не все операционные системы соответствуют стандарту POSIX, поэтому неинструментальным инструментам (например, браузерам) было бы непрактично обрабатывать файлы в соответствии со стандартом POSIX (или любым стандартом уровня операционной системы).

В результате мы можем быть относительно уверены, что EOL в EOF практически не окажет негативного влияния на уровне приложений - независимо от того, работает ли он в ОС UNIX.

На данный момент мы можем с уверенностью сказать, что пропуск EOL в EOF безопасен при работе с JS, HTML, CSS на стороне клиента. На самом деле, мы можем утверждать, что минимизация любого из этих файлов, не содержащих , безопасна.

Мы можем сделать еще один шаг и сказать, что в отношении NodeJS он также не может придерживаться стандарта POSIX, поскольку он может работать в средах, не поддерживающих POSIX.

Что же нам тогда осталось? Инструменты системного уровня.

Это означает, что единственные проблемы, которые могут возникнуть, связаны с инструментами, которые прилагают усилия, чтобы привязать свою функциональность к семантике POSIX (например, определение строки, как показано в wc).

Несмотря на это, не все оболочки будут автоматически придерживаться POSIX. Например, Bash не использует POSIX по умолчанию. Для этого есть переключатель: POSIXLY_CORRECT.

Пища для размышления о значении EOL : https://www.rfc -editor.org / old / EOLstory.txt

Оставаясь на дорожке инструмента, для всех практических целей и задач, давайте рассмотрим это:

Давайте работать с файлом, у которого нет EOL. На момент написания статьи файл в этом примере представлял собой уменьшенный JavaScript без 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

Обратите внимание, что размер файла cat точно равен сумме его отдельных частей. Если объединение файлов JavaScript является проблемой для файлов JS, более подходящей задачей будет запуск каждого файла JavaScript с точкой с запятой.

Как кто-то еще упомянул в этой теме: что, если вы хотите cat два файла, вывод которых становится одной строкой вместо двух? Другими словами, cat делает то, что должен.

В man из cat упоминается только чтение ввода до EOF, а не . Обратите внимание, что -n переключатель cat также выведет не завершенную строку (или неполную строку ) в виде строки - при этом отсчет начинается с 1 (согласно man.)

-n Количество выходных строк, начиная с 1.

Теперь, когда мы понимаем, как POSIX определяет строку , это поведение становится неоднозначным или действительно несовместимым.

Понимание цели и соответствия данного инструмента поможет определить, насколько важно завершить файлы EOL. В C, C ++, Java (JAR) и т. Д. ... некоторые стандарты будут предписывать новую строку для валидности - для JS, HTML, CSS такого стандарта не существует.

Например, вместо использования wc -l filename можно сделать awk '{x++}END{ print x}' filename, и будьте уверены, что успех задачи не будет поставлен под угрозу файлом, который мы можем захотеть обработать, но который мы не записали (например, сторонней библиотекой, такой как минимизированный JS мы curl d) - если только мы не собирались действительно считать строк в смысле совместимости с POSIX.

Заключение

В реальных случаях будет очень мало случаев, когда пропуск EOL в EOF для определенных текстовых файлов, таких как JS, HTML и CSS, будет иметь негативное влияние - если вообще будет. Если мы полагаемся на присутствие , мы ограничиваем надежность наших инструментов только теми файлами, которые мы создаем, и открываем себя для потенциальных ошибок, допущенных сторонними файлами.

Мораль истории: инструментальные средства инженера, у которых нет слабости полагаться на EOL в EOF.

Не стесняйтесь публиковать варианты использования, так как они относятся к JS, HTML и CSS, где мы можем изучить, как пропуск EOL отрицательно сказывается.

60 голосов
/ 08 апреля 2009

Это может быть связано с разницей между :

  • текстовый файл (каждая строка должна заканчиваться концом строки)
  • двоичный файл (нет настоящих «строк», о которых следует говорить, и длина файла должна быть сохранена)

Если каждая строка заканчивается концом строки, это позволяет избежать, например, того, что объединение двух текстовых файлов приведет к тому, что последняя строка первого запуска переместится в первую строку второй.

Кроме того, редактор может при загрузке проверить, заканчивается ли файл концом строки, сохранить его в локальном параметре 'eol' и использовать его при записи файла.

Несколько лет назад (2005 г.) многие редакторы (ZDE, Eclipse, Scite, ...) «забыли» тот последний EOL, , который не очень ценился .
Кроме того, они неправильно интерпретировали этот конечный EOL как «начать новую строку» и фактически начали отображать другую строку, как если бы она уже существовала.
Это было очень заметно в «правильном» текстовом файле с хорошим текстовым редактором, например vim, по сравнению с открытием его в одном из вышеуказанных редакторов. Он отображал дополнительную строку ниже реальной последней строки файла. Вы видите что-то вроде этого:

1 first line
2 middle line
3 last line
4
40 голосов
/ 12 октября 2011

Некоторые инструменты ожидают этого. Например, wc ожидает этого:

$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
19 голосов
/ 08 апреля 2009

В основном, есть много программ, которые не будут правильно обрабатывать файлы, если они не получат окончательный EOL EOF.

GCC предупреждает вас об этом, потому что это ожидается как часть стандарта C. (очевидно, раздел 5.1.1.2)

Предупреждение компилятора "Нет новой строки в конце файла"

12 голосов
/ 08 апреля 2009

Это происходит с самых первых дней, когда использовались простые терминалы. Символ новой строки использовался для запуска «сброса» переданных данных.

Сегодня символ новой строки больше не требуется. Конечно, многие приложения по-прежнему имеют проблемы, если новой строки нет, но я бы посчитал это ошибкой в ​​этих приложениях.

Если, однако, у вас есть формат текстового файла, в котором требуется перевод строки, вы получаете простую проверку данных очень дешево: если файл заканчивается строкой, в конце которой нет символа новой строки, вы знаете файл сломано. Имея только один дополнительный байт для каждой строки, вы можете обнаруживать поврежденные файлы с высокой точностью и почти без процессорного времени.

12 голосов
/ 05 сентября 2016

Отдельный вариант использования: когда ваш текстовый файл контролируется версией (в данном случае специально для git, хотя это относится и к другим). Если содержимое добавляется в конец файла, то строка, которая ранее была последней строкой, будет отредактирована для включения символа новой строки. Это означает, что blame в файле, чтобы выяснить, когда эта строка была отредактирована в последний раз, будет показано добавление текста, а не фиксация до того, что вы действительно хотели увидеть.

11 голосов
/ 25 сентября 2015

В дополнение к вышеуказанным практическим причинам, меня не удивило бы, если бы создатели Unix (Thompson, Ritchie, et al.) Или их предшественники Multics поняли, что есть теоретическая причина использовать терминаторы строки, а не разделители строк : С помощью разделителей строк вы можете кодировать все возможные файлы строк. С разделителями строк нет никакой разницы между файлом нулевых строк и файлом, содержащим одну пустую строку; оба они закодированы как файл, содержащий ноль символов.

Итак, причины:

  1. Потому что именно так POSIX определяет это.
  2. Потому что некоторые инструменты ожидают этого или «плохо себя ведут» без него. Например, wc -l не будет считать последнюю «строку», если она не заканчивается новой строкой.
  3. Потому что это просто и удобно. В Unix cat просто работает и работает без осложнений. Он просто копирует байты каждого файла без какой-либо интерпретации. Я не думаю, что есть DOS, эквивалентный cat. Использование copy a+b c приведет к объединению последней строки файла a с первой строкой файла b.
  4. Поскольку файл (или поток) с нулевыми строками можно отличить от файла с одной пустой строкой.
10 голосов
/ 04 ноября 2011

Существует также практическая проблема программирования с файлами, в которых отсутствуют символы новой строки: встроенная read Bash (я не знаю о других read реализациях) не работает должным образом:

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

Это печатает только foo! Причина в том, что когда read встречает последнюю строку, он записывает содержимое в $line, но возвращает код выхода 1, поскольку достиг EOF. Это нарушает цикл while, поэтому мы никогда не достигнем части echo $line. Если вы хотите справиться с этой ситуацией, вы должны сделать следующее:

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

То есть, выполните echo, если read не удалось из-за непустой строки в конце файла. Естественно, в этом случае в выводе будет одна дополнительная новая строка, которой не было на входе.

...