Я предполагаю, что все здесь знакомы с пословицей, что все текстовые файлы должны заканчиваться новой строкой. Я знал об этом «правиле» годами, но всегда задавался вопросом - почему?
Я предполагаю, что все здесь знакомы с пословицей, что все текстовые файлы должны заканчиваться новой строкой. Я знал об этом «правиле» годами, но всегда задавался вопросом - почему?
Ответы:
Потому что так стандарт POSIX определяет строку :
- 3.206 Линия
- Последовательность из нуля или более не-символов <newline> плюс завершающий символ <newline>.
Поэтому строки, не заканчивающиеся символом новой строки, не считаются фактическими. Вот почему некоторые программы имеют проблемы с обработкой последней строки файла, если он не завершен новой строкой.
При работе с эмулятором терминала у этого руководства есть по крайней мере одно серьезное преимущество: все инструменты Unix ожидают этого соглашения и работают с ним. Например, при объединении файлов с cat
файлом, оканчивающимся символом новой строки, эффект будет другим, чем без:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
И, как и в предыдущем примере, также показано, что при отображении файла в командной строке (например, через more
) файл с завершающей строкой приводит к правильному отображению. Неправильно завершенный файл может быть искажен (вторая строка).
Для согласованности очень полезно следовать этому правилу - в противном случае работа с инструментами Unix по умолчанию потребует дополнительной работы.
Подумайте об этом по-другому: если строки не заканчиваются символом новой строки, сделать такие команды как cat
полезные гораздо сложнее: как сделать команду для объединения файлов таким образом, чтобы
b.txt
и c.txt
?Конечно, это решаемо, но вам нужно сделать использование cat
более сложным (например, добавив позиционные аргументы командной строки cat a.txt --no-newline b.txt c.txt
), и теперь команда, а не каждый отдельный файл, контролирует, как она вставляется вместе с другими файлами. Это почти наверняка не удобно.
... Или вам нужно ввести специальный символ стража, чтобы отметить строку, которая должна быть продолжена, а не завершена. Что ж, теперь вы застряли в той же ситуации, что и в POSIX, за исключением инвертированного (продолжение строки, а не символ завершения строки).
Теперь, в не POSIX-совместимых системах (в настоящее время это в основном Windows), смысл спорный: файлы обычно не заканчиваются символом новой строки, и (неофициальное) определение строки может, например, быть «текстом, разделенным символами новой строки» (обратите внимание на акцент). Это полностью верно. Однако для структурированных данных (например, программного кода) это делает синтаксический анализ минимально более сложным: обычно это означает, что анализаторы должны быть переписаны. Если парсер изначально был написан с учетом определения POSIX, то может быть проще изменить поток токенов, чем синтаксический анализатор - другими словами, добавить токен «искусственной новой строки» в конец ввода.
cat
полезные и непротиворечивые.
Каждая строка должна заканчиваться символом новой строки, включая последнюю. В некоторых программах возникают проблемы с обработкой последней строки файла, если он не завершен новой строкой.
GCC предупреждает об этом не потому, что не может обработать файл, а потому, что это должно быть частью стандарта.
Стандарт языка C говорит, что исходный файл, который не является пустым, должен заканчиваться символом новой строки, которому не должен предшествовать символ обратной косой черты.
Так как это условие «должен», мы должны выдать диагностическое сообщение о нарушении этого правила.
Это в разделе 2.1.1.2 стандарта ANSI C 1989 года. Раздел 5.1.1.2 стандарта ISO C 1999 (и, вероятно, также стандарта ISO C 1990).
Справка: почтовый архив GCC / GNU .
wc -l
не будет считать последнюю строку файла, если он не завершен новой строкой. Также, cat
объединит последнюю строку файла с первой строкой следующего файла в одну, если последняя строка первого файла не завершена новой строкой. Практически любая программа, которая ищет новые строки в качестве разделителя, может испортить это.
wc
, уже упоминалось ....
cat
и wc
)?
Этот ответ является попыткой технического ответа, а не мнения.
Если мы хотим быть пуристами POSIX, мы определяем строку как:
Последовательность из нуля или более не-символов <newline> плюс завершающий символ <newline>.
Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206
Неполная строка как:
Последовательность из одного или нескольких не <newline> символов в конце файла.
Источник: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195
Текстовый файл как:
Файл, который содержит символы, организованные в ноль или более строк. Строки не содержат символов NUL, и ни одна из них не может превышать длину {LINE_MAX} байтов, включая символ <newline>. Хотя 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
Из этого мы можем сделать вывод, что единственное время, когда мы потенциально можем столкнуться с проблемами любого типа, - это если мы имеем дело с концепцией строки файла или файла как текстового файла (поскольку текстовый файл является организацией с нулевым или больше строк, и известная нам строка должна заканчиваться символом <newline>).
Дело в точке: wc -l filename
.
Из wc
руководства мы читаем:
Строка определяется как строка символов, разделенных символом <newline>.
Каковы последствия для файлов JavaScript, HTML и CSS в том, что они являются текстовыми файлами?
В браузерах, современных IDE и других интерфейсных приложениях нет проблем с пропуском EOL в EOF. Приложения будут правильно анализировать файлы. Это связано с тем, что не все операционные системы соответствуют стандарту POSIX, поэтому было бы непрактично для инструментов, не относящихся к ОС (например, браузеров), обрабатывать файлы в соответствии со стандартом POSIX (или любым стандартом на уровне ОС).
В результате мы можем быть относительно уверены, что EOL в EOF практически не окажет негативного влияния на уровне приложений - независимо от того, работает ли он на ОС UNIX.
На данный момент мы можем с уверенностью сказать, что пропуск EOL в EOF безопасен при работе с JS, HTML, CSS на стороне клиента. На самом деле, мы можем утверждать, что минимизация любого из этих файлов, не содержащих <newline>, безопасна.
Мы можем сделать еще один шаг вперед и сказать, что в отношении NodeJS он также не может придерживаться стандарта POSIX, поскольку он может работать в средах, не поддерживающих POSIX.
Что же нам тогда осталось? Инструменты системного уровня.
Это означает, что единственные проблемы, которые могут возникнуть, связаны с инструментами, которые прилагают усилия, чтобы привязать их функциональность к семантике POSIX (например, определение строки, как показано на рисунке wc
).
Тем не менее, не все оболочки будут автоматически придерживаться POSIX. Например, Bash не использует POSIX по умолчанию. Существует переключатель , чтобы включить его: POSIXLY_CORRECT
.
Пищу для размышлений о ценности EOL, являющейся <newline>: 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, будет иметь негативное влияние - если вообще будет. Если мы полагаемся на присутствие <newline>, мы ограничиваем надежность наших инструментов только теми файлами, которые мы создаем, и открываем себя для потенциальных ошибок, допущенных сторонними файлами.
Мораль истории: Инженерные инструменты, у которых нет слабости полагаться на 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
wc
этого не ожидает , поскольку он просто работает в рамках определения «линии» в POSIX, а не в интуитивном понимании «линии» большинством людей.
wc -l
печати 1
в обоих случаях, но некоторые люди могут сказать, что второй случай должен печатать 2
.
\n
терминатор строки, а не разделитель строк, как это делает POSIX / UNIX, то ожидать, что второй случай выведет 2, просто безумие.
В основном есть много программ, которые не будут правильно обрабатывать файлы, если они не получат окончательный EOL EOF.
GCC предупреждает вас об этом, потому что это ожидается как часть стандарта C. (раздел 5.1.1.2 очевидно)
Это происходит с самых первых дней, когда использовались простые терминалы. Символ новой строки использовался для запуска «сброса» переданных данных.
Сегодня символ новой строки больше не требуется. Конечно, многие приложения по-прежнему имеют проблемы, если новой строки нет, но я бы посчитал это ошибкой в этих приложениях.
Однако, если у вас есть формат текстового файла, в котором вам требуется новая строка, простая проверка данных становится очень дешевой: если файл заканчивается строкой, в конце которой нет новой строки, вы знаете, что файл поврежден. Имея только один дополнительный байт для каждой строки, вы можете обнаружить битые файлы с высокой точностью и почти без использования процессорного времени.
Отдельный вариант использования: когда ваш текстовый файл контролируется версией (в данном случае специально для git, хотя это относится и к другим). Если содержимое добавляется в конец файла, то строка, которая ранее была последней строкой, будет отредактирована для включения символа новой строки. Это означает, что blame
в файле, чтобы узнать, когда эта строка была отредактирована в последний раз, будет показано добавление текста, а не фиксация до того, что вы действительно хотели увидеть.
\n
). Задача решена.
В дополнение к вышеупомянутым практическим причинам, меня не удивило бы, если бы создатели Unix (Thompson, Ritchie, et al.) Или их предшественники Multics поняли, что есть теоретическая причина использовать терминаторы строки вместо разделителей строки: терминаторы, вы можете кодировать все возможные файлы строк. С разделителями строк нет никакой разницы между файлом нулевых строк и файлом, содержащим одну пустую строку; оба они закодированы как файл, содержащий ноль символов.
Итак, причины:
wc -l
не будет считаться окончательная «строка», если она не заканчивается новой строкой.cat
просто работает и работает без осложнений. Он просто копирует байты каждого файла без какой-либо интерпретации. Я не думаю, что есть эквивалент DOS cat
. Использование copy a+b c
приведет к слиянию последней строки файла a
с первой строкой файла b
.Я сам удивлялся этому годами. Но я столкнулся с веской причиной сегодня.
Представьте себе файл с записью в каждой строке (например, файл CSV). И что компьютер писал записи в конце файла. Но это внезапно рухнуло. Ну и дела была последняя строка завершена? (не очень хорошая ситуация)
Но если мы всегда завершаем последнюю строку, мы бы знали (просто проверьте, завершена ли последняя строка). В противном случае нам, вероятно, придется каждый раз сбрасывать последнюю строку, чтобы быть в безопасности.
Предположительно просто, что некоторый код синтаксического анализа ожидал, что это будет там.
Я не уверен, что считаю это «правилом», и я определенно не придерживаюсь этого принципа. Наиболее разумный код будет знать, как анализировать текст (включая кодировки) построчно (любой выбор конца строки), с новой строкой или без нее на последней строке.
Действительно - если вы заканчиваете новой строкой: есть ли (в теории) пустая конечная строка между 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
Файл был создан на машине вдов, и оконные сценарии обрабатывали этот файл с помощью команд PowerShell. Все хорошо.
Когда мы обработали тот же файл с помощью 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.