Почему текстовые файлы должны заканчиваться символом новой строки?
Я предполагаю, что все здесь знакомы с пословицей, что все текстовые файлы должны заканчиваться новой строкой. Я знал об этом "правиле" много лет, но всегда задавался вопросом - почему?
19 ответов
Потому что так стандарт POSIX определяет строку:
- 3.206 Линия
- Последовательность из нуля или более не-символов
плюс завершающий символ .
Поэтому строки, не заканчивающиеся символом новой строки, не считаются фактическими. Вот почему некоторые программы имеют проблемы с обработкой последней строки файла, если он не завершен новой строкой.
При работе с эмулятором терминала у этого руководства есть по крайней мере одно серьезное преимущество: все инструменты Unix ожидают этого соглашения и работают с ним. Например, при объединении файлов с cat
файл, завершенный символом новой строки, будет иметь другой эффект, чем файл без:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
И, как показывает предыдущий пример, при отображении файла в командной строке (например, через more
), файл, заканчивающийся символом новой строки, приводит к правильному отображению. Неправильно завершенный файл может быть искажен (вторая строка).
Для согласованности очень полезно следовать этому правилу - в противном случае работа с инструментами Unix по умолчанию потребует дополнительной работы.
Подумайте об этом по-другому: если строки не заканчиваются символом новой строки, такие команды, как cat
полезнее гораздо сложнее: как сделать команду для объединения файлов таким образом, чтобы
- он помещает начало каждого файла в новую строку, чего вы хотите в 95% случаев; но
- это позволяет объединить последнюю и первую строку двух файлов, как в примере выше между
b.txt
а такжеc.txt
?
Конечно, это решаемо, но вы должны использовать cat
более сложный (путем добавления позиционных аргументов командной строки, например, cat a.txt --no-newline b.txt c.txt
), и теперь команда, а не каждый отдельный файл, контролирует, как она вставляется вместе с другими файлами. Это почти наверняка не удобно.
... Или вам нужно ввести специальный символ стража, чтобы пометить строку, которая должна быть продолжена, а не завершена. Что ж, теперь вы застряли в той же ситуации, что и в POSIX, за исключением перевернутого (продолжение строки, а не символ завершения строки).
Теперь, в не POSIX-совместимых системах (в настоящее время это в основном Windows), смысл спорный: файлы обычно не заканчиваются символом новой строки, и (неофициальное) определение строки может, например, быть "текстом, разделенным символами новой строки" (обратите внимание на акцент). Это полностью верно. Однако для структурированных данных (например, программного кода) это делает синтаксический анализ минимально более сложным: обычно это означает, что анализаторы должны быть переписаны. Если синтаксический анализатор изначально был написан с учетом определения POSIX, то может быть проще изменить поток токенов, чем синтаксический анализатор - другими словами, добавить токен "искусственного перевода строки" в конец ввода.
Каждая строка должна заканчиваться символом новой строки, включая последнюю. В некоторых программах возникают проблемы с обработкой последней строки файла, если он не завершен новой строкой.
GCC предупреждает об этом не потому, что не может обработать файл, а потому, что это должно быть частью стандарта.
Стандарт языка Си говорит, что исходный файл, который не является пустым, должен заканчиваться символом новой строки, которому не должен предшествовать символ обратной косой черты.
Так как это условие "должен", мы должны выдать диагностическое сообщение о нарушении этого правила.
Это в разделе 2.1.1.2 стандарта ANSI C 1989 года. Раздел 5.1.1.2 стандарта ISO C 1999 (и, вероятно, также стандарта ISO C 1990).
Справка: почтовый архив GCC/GNU.
Этот ответ является попыткой технического ответа, а не мнения.
Если мы хотим быть пуристами POSIX, мы определяем строку как:
Последовательность из нуля или более не-символов
плюс завершающий символ .
Источник: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
Неполная строка как:
Последовательность из одного или нескольких не
символов в конце файла.
Источник: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
Текстовый файл как:
Файл, содержащий символы, организованные в ноль или более строк. Строки не содержат символов NUL, и ни одна из них не может превышать длину {LINE_MAX} байтов, включая символ
. Хотя POSIX.1-2008 не делает различий между текстовыми файлами и двоичными файлами (см. Стандарт ISO C), многие утилиты производят только предсказуемый или значимый вывод при работе с текстовыми файлами. Стандартные утилиты, имеющие такие ограничения, всегда указывают "текстовые файлы" в своих разделах STDIN или INPUT FILES.
Источник: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
Строка как:
Непрерывная последовательность байтов, завершенная первым нулевым байтом и включающая его.
Источник: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
Из этого мы можем сделать вывод, что единственное время, когда мы потенциально можем столкнуться с проблемами любого типа, это если мы имеем дело с концепцией строки файла или файла как текстового файла (поскольку текстовый файл является организацией с нулевым или больше строк, и известная нам строка должна заканчиваться символом
Дело в точке: 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, являющейся
Оставаясь на пути к инструменту, для всех практических целей и задач, давайте рассмотрим это:
Давайте работать с файлом, который не имеет 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
также выведет в качестве строки строку, не оканчивающуюся на man
.)
-n Количество выходных строк, начиная с 1.
Теперь, когда мы понимаем, как POSIX определяет строку, это поведение становится неоднозначным или действительно несовместимым.
Понимание цели и соответствия данного инструмента поможет определить, насколько важно завершить файлы EOL. В C, C++, Java (JAR) и т. Д.... некоторые стандарты будут предписывать новую строку для достоверности - такого стандарта для JS, HTML, CSS не существует.
Например, вместо использования wc -l filename
можно было бы сделать awk '{x++}END{ print x}' filename
и будьте уверены, что выполнение задачи не ставится под угрозу файлом, который мы можем захотеть обработать, но который мы не записали (например, сторонней библиотекой, такой как минимизированный JS, который мы curl
г) - если только мы не собирались действительно считать строки в POSIX-совместимом смысле.
Заключение
В реальных случаях будет очень мало случаев, когда пропуск EOL в EOF для определенных текстовых файлов, таких как JS, HTML и CSS, будет иметь негативное влияние - если вообще будет. Если мы полагаемся на присутствие
Мораль истории: Инженерные инструменты, у которых нет слабости полагаться на EOL в EOF.
Не стесняйтесь публиковать варианты использования, так как они относятся к JS, HTML и CSS, где мы можем изучить, как пропуск EOL отрицательно сказывается.
Это может быть связано с разницей между:
- текстовый файл (каждая строка должна заканчиваться концом строки)
- бинарный файл (нет настоящих "строк", о которых нужно говорить, и длина файла должна быть сохранена)
Если каждая строка заканчивается концом строки, это позволяет избежать, например, того, что при объединении двух текстовых файлов последняя строка первого запуска переходит в первую строку второй.
Кроме того, редактор может при загрузке проверить, заканчивается ли файл в конце строки, сохранить его в локальном параметре 'eol' и использовать его при записи файла.
Несколько лет назад (2005) многие редакторы (ZDE, Eclipse, Scite, ...) "забыли" эту последнюю версию EOL, которая не была оценена по достоинству.
И не только это, но они неверно истолковали этот окончательный EOL как "начать новую строку" и фактически начали отображать другую строку, как если бы она уже существовала.
Это было очень хорошо видно в "правильном" текстовом файле с хорошим текстовым редактором, например vim, по сравнению с открытием его в одном из вышеуказанных редакторов. Он отображал дополнительную строку ниже реальной последней строки файла. Вы видите что-то вроде этого:
1 first line
2 middle line
3 last line
4
Некоторые инструменты ожидают этого. Например, wc
ожидает этого:
$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
Отдельный вариант использования: когда ваш текстовый файл контролируется версией (в данном случае специально для git, хотя это относится и к другим). Если содержимое добавляется в конец файла, то строка, которая ранее была последней строкой, будет отредактирована для включения символа новой строки. Это означает, что blame
Если в файле выяснить, когда эта строка была отредактирована в последний раз, будет показано добавление текста, а не фиксация до того, что вы действительно хотели увидеть.
Почему текстовые файлы должны заканчиваться новой строкой?
Потому что это самый разумный выбор.
Возьмите файл со следующим содержанием,
one\n
two\n
three
где означает символ новой строки, который в Windows
\r\n
, символ возврата, за которым следует перевод строки, потому что это так круто, не так ли?
Сколько строк в этом файле? Windows говорит 3, мы говорим 3, POSIX (Linux) говорит, что файл поврежден, потому что в конце должен быть символ.
В любом случае, что бы вы сказали в его последней строке? Я думаю, что кто-нибудь согласится с этим
three
это последняя строка файла, но POSIX говорит, что это поврежденная строка.
А какая у него вторая строка? Ой, вот и первое сильное разделение :
- Windows говорит
two
потому что файл - это «строки, разделенные символами новой строки» (wth?); - POSIX говорит
two\n
, добавив, что это верная, честная позиция.
В чем же тогда последствия выбора Windows? Простой:
Вы не можете сказать, что файл состоит из строк
Почему? Попробуйте взять последнюю строчку из предыдущего файла и повторить ее несколько раз ... Что у вас получится? Этот:
one\n
two\n
threethreethreethree
Вместо этого попробуйте поменять местами вторую и третью строку ... И вы получите следующее:
one\n
threetwo\n
Следовательно
Вы должны сказать, что текстовый файл - это чередование строк и s, которое начинается со строки и заканчивается строкой
что довольно много, не так ли?
И вы хотите еще одного странного следствия?
Вы должны согласиться с тем, что пустой файл (0 бит) - это однострочный файл, волшебным образом, всегда, потому что они крутые в Microsoft
Это настоящее безумие, вам не кажется?
Каковы последствия выбора POSIX?
То, что файл вверху немного поврежден, и нам нужно взломать его, чтобы с ним справиться.
Быть серьезным
В предыдущем тексте я провокационный по той причине, что при работе с текстовыми файлами, не имеющими
\n
в конце заставляет вас лечить их специальными клещами / взломами. Вам всегда нужен
if
/
else
где-нибудь, чтобы заставить вещи работать, где ветвь, имеющая дело с поврежденной линией, работает только с поврежденной линией, а все другие линии занимают другую ветвь. Это немного расистски, не так ли?
Мой вывод
Я поддерживаю определение строки в POSIX по следующим причинам:
- Файл, естественно, представляет собой последовательность строк.
- Строка не должна быть той или иной в зависимости от того, где она находится в файле
- Пустой файл - это не однострочный файл, давай!
- Вы не должны быть принуждены к взлому вашего кода
Я сам удивлялся этому годами. Но я столкнулся с веской причиной сегодня.
Представьте себе файл с записью в каждой строке (например, файл CSV). И что компьютер писал записи в конце файла. Но это внезапно рухнуло. Ну и дела была последняя строка завершена? (не очень хорошая ситуация)
Но если мы всегда завершаем последнюю строку, то мы бы знали (просто проверьте, завершена ли последняя строка). В противном случае нам, вероятно, придется каждый раз сбрасывать последнюю строку, чтобы быть в безопасности.
В основном есть много программ, которые не будут правильно обрабатывать файлы, если они не получат окончательный EOL EOF.
GCC предупреждает вас об этом, потому что это ожидается как часть стандарта C. (раздел 5.1.1.2 очевидно)
Это происходит с самых первых дней, когда использовались простые терминалы. Символ новой строки использовался для запуска "сброса" переданных данных.
Сегодня символ новой строки больше не требуется. Конечно, многие приложения по-прежнему имеют проблемы, если новой строки нет, но я бы посчитал это ошибкой в этих приложениях.
Однако, если у вас есть формат текстового файла, в котором вам требуется новая строка, вы получаете простую проверку данных очень дешево: если файл заканчивается строкой, в конце которой нет новой строки, вы знаете, что файл поврежден. Имея только один дополнительный байт для каждой строки, вы можете обнаружить битые файлы с высокой точностью и почти без использования процессорного времени.
В дополнение к вышеупомянутым практическим причинам, меня не удивило бы, если бы создатели Unix (Thompson, Ritchie и др.) Или их предшественники Multics поняли, что есть теоретическая причина использовать терминаторы строки, а не разделители строк: терминаторы, вы можете кодировать все возможные файлы строк. С разделителями строк нет никакой разницы между файлом нулевых строк и файлом, содержащим одну пустую строку; оба они закодированы как файл, содержащий ноль символов.
Итак, причины:
- Потому что именно так POSIX определяет это.
- Потому что некоторые инструменты ожидают этого или "плохо себя ведут" без него. Например,
wc -l
не будет считать окончательную "строку", если она не заканчивается новой строкой. - Потому что это просто и удобно. В Unix,
cat
просто работает и работает без осложнений. Он просто копирует байты каждого файла без какой-либо интерпретации. Я не думаю, что есть DOS эквивалентcat
, С помощьюcopy a+b c
закончится слиянием последней строки файлаa
с первой строкой файлаb
, - Потому что файл (или поток) из нулевых строк можно отличить от файла из одной пустой строки.
Предположительно просто, что некоторый код синтаксического анализа ожидал, что это будет там.
Я не уверен, что считаю это "правилом", и, конечно, я не придерживаюсь этого в религиозном отношении. Наиболее разумный код будет знать, как анализировать текст (включая кодировки) построчно (любой выбор конца строки), с новой строкой или без нее на последней строке.
Действительно - если вы заканчиваете новой строкой: есть ли (в теории) пустая конечная строка между EOL и EOF? Один задуматься...
Есть также практическая проблема программирования с файлами, в которых отсутствуют символы новой строки: 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
не удалось из-за непустой строки в конце файла. Естественно, в этом случае в выводе будет одна дополнительная новая строка, которой не было во входных данных.
Почему (текстовые) файлы должны заканчиваться символом новой строки?
Как хорошо выражаются многие, потому что:
Многие программы плохо себя ведут или терпят неудачу без него.
Даже программы, которые хорошо обрабатывают файл, не имеют конца
'\n'
функциональность инструмента может не соответствовать ожиданиям пользователя, что может быть неясно в данном случае.Программы редко запрещают финал
'\n'
(Я не знаю ни одного).
Тем не менее, напрашивается следующий вопрос:
Что должен делать код с текстовыми файлами без перевода строки?
Самое важное - не писать код, который предполагает, что текстовый файл заканчивается новой строкой. Предположение, что файл соответствует формату, приводит к повреждению данных, хакерским атакам и сбоям. Пример:
// Bad code while (fgets(buf, sizeof buf, instream)) { // What happens if there is no \n, buf[] is truncated leading to who knows what buf[strlen(buf) - 1] = '\0'; // attempt to rid trailing \n ... }
Если последний трейлинг
'\n'
необходимо предупредить пользователя об его отсутствии и предпринятых действиях. IOWs, проверьте формат файла. Примечание. Это может включать ограничение максимальной длины строки, кодировки символов и т. Д.Четко определите, документируйте, обработку кода отсутствующим финалом
'\n'
,Не, по возможности, сгенерируйте файл с отсутствующим окончанием
'\n'
,
Здесь очень поздно, но я столкнулся с одной ошибкой в обработке файлов, которая возникла из-за того, что файлы не заканчивались пустым символом новой строки. Мы обрабатывали текстовые файлы с sed
а также sed
пропускал последнюю строку в выводе, что приводило к неверной структуре json и отправке остальной части процесса в состояние сбоя.
Все, что мы делали, было:
Существует один пример файла: foo.txt
с некоторыми json
содержание внутри него.
[{
someProp: value
},
{
someProp: value
}] <-- No newline here
Файл был создан на машине вдов, и оконные скрипты обрабатывали этот файл с помощью команд powershall. Все хорошо.
Когда мы обработали тот же файл, используя sed
команда sed 's|value|newValue|g' foo.txt > foo.txt.tmp
Вновь созданный файл был
[{
someProp: value
},
{
someProp: value
и бум, он отказал остальным процессам из-за недопустимого JSON.
Поэтому всегда полезно заканчивать свой файл пустой новой строкой.
У меня всегда было впечатление, что правило пришло со времен, когда анализ файла без завершающего перевода строки был трудным. То есть, вы должны написать код, в котором конец строки был определен символом EOL или EOF. Проще было предположить, что строка заканчивается EOL.
Однако я считаю, что правило основано на компиляторах C, требующих перевода строки. И, как указано в предупреждении компилятора "Нет новой строки в конце файла", #include не будет добавлять новую строку.
Представьте, что файл обрабатывается, пока файл еще генерируется другим процессом.
Это может быть связано с этим? Флаг, который указывает, что файл готов к обработке.
Мне лично нравятся новые строки в конце файлов исходного кода.
Он может иметь свое происхождение от Linux или всех систем UNIX в этом отношении. Я помню там ошибки компиляции (gcc, если я не ошибаюсь), потому что файлы исходного кода не заканчивались пустой новой строкой. Почему так сделано, остается только удивляться.
ИМХО, это вопрос личного стиля и мнения.
В старые времена я не ставил этот перевод строки. Сохраненный символ означает большую скорость через этот модем 14.4K.
Позже я поместил эту новую строку, чтобы было легче выбрать последнюю строку, используя shift+downarrow.