Ответ 1
В стандарте C говорится, что текстовые файлы должны заканчиваться символом новой строки или данными после того, как последняя строка новой строки может быть неправильно прочитана.
ISO/IEC 9899: 2011 §7.21.2 Потоки
Текстовый поток представляет собой упорядоченную последовательность символов, состоящую из строк, каждая строка состоит из нуля или более символов плюс завершающий символ новой строки. Независимо от того, соответствует ли последняя строка завершающему символу новой строки, определяется ли реализация. Символы могут быть добавлены, изменены или удалены на входе и выходе, чтобы соответствовать различным соглашениям для представления текста в среде хоста. Таким образом, между символами в потоке и во внешнем представлении не должно быть взаимно однозначного соответствия. Данные, считываемые из текстового потока, обязательно будут сравниваться с данными, которые ранее были записаны в этот поток, только если: данные состоят только из печатных символов, а контрольные символы - горизонтальной вкладкой и новой строкой; никакому символу новой строки не предшествуют символы пробела; а последний символ - символ новой строки. Будут ли появляться символы пробела, которые выписываются непосредственно перед символом новой строки при чтении, определяется реализацией.
У меня не было бы неожиданной недостающей строки в конце файла, чтобы вызвать проблемы в bash
(или любой оболочке Unix), но это похоже на проблему воспроизводимо ($
- подсказка в этом выпуске):
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
Это также не ограничивается bash
- оболочка Korn (ksh
) и zsh
ведут себя так же. Я живу, я учусь; спасибо за поднятие вопроса.
Как показано в приведенном выше коде, команда cat
считывает весь файл. for line in 'cat $DATAFILE'
собирает все выходные данные и заменяет произвольные последовательности пробелов одним пробелом (я делаю вывод, что каждая строка в файле не содержит пробелов).
Протестировано на Mac OS X 10.7.5.
Что говорит POSIX?
В спецификации команды read
POSIX написано:
Утилита чтения должна считывать одну строку из стандартного ввода.
По умолчанию, если опция
-r
не указана, <обратная косая черта> будет действовать как escape-символ. Неизолированная <обратная косая черта> сохраняет литеральное значение следующего символа, за исключением <новой строки>. Если <backline> следует за <обратным слэшем>, программа чтения должна интерпретировать это как продолжение строки. Символы <обратная косая черта> и<newline>
должны быть удалены перед разбиением ввода на поля. Все остальные символы без обратного следа должны быть удалены после разделения ввода на поля.Если стандартный ввод является терминальным устройством, а вызывающая оболочка является интерактивной, чтение должно запрашивать продолжение строки, когда она считывает строку ввода, заканчивающуюся <backslash> <newline>, если не
-r
параметр-r
.Отключающая <newline> (если таковая имеется) должна быть удалена из ввода, и результаты должны быть разделены на поля, как в оболочке, для результатов расширения параметров (см. Раздел "Разделение поля"); [...]
Обратите внимание, что "(если есть)" (выделено в цитате)! Мне кажется, что, если нет новой строки, она все равно должна прочитать результат. С другой стороны, в нем также говорится:
STDIN
Стандартный ввод должен представлять собой текстовый файл.
и затем вы возвращаетесь к дискуссиям о том, является ли файл, который не заканчивается символом новой строки, текстовым файлом или нет.
Однако обоснование на тех же страницах документов:
Хотя стандартный ввод необходим как текстовый файл и поэтому всегда заканчивается символом <newline> (если он не является пустым файлом), обработка строк продолжения, когда опция
-r
не используется, может привести к вводу не заканчивается на <newline>. Это происходит, если последняя строка входного файла заканчивается символом <backslash> <newline>. Именно по этой причине "если есть" используется в "Прекращение <новой строки> (если оно есть) должно быть удалено из ввода" в описании. Это не релаксация требования для стандартного ввода как текстового файла.
Это обоснование должно означать, что текстовый файл должен заканчиваться новой строкой.
Определение текстового файла в POSIX:
3.395 Текстовый файл
Файл, содержащий символы, помещенные в ноль или более строк. Строки не содержат символов NUL, и ни один из них не может превышать длину {LINE_MAX} байтов, включая символ <newline>. Хотя POSIX.1-2008 не различает текстовые файлы и двоичные файлы (см. Стандарт ISO C), многие утилиты производят только предсказуемый или значимый вывод при работе с текстовыми файлами. Стандартные утилиты, которые имеют такие ограничения, всегда указывают "текстовые файлы" в своих разделах STDIN или INPUT FILES.
Это не оговаривает "концы с помощью <newline>" напрямую, но откладывается до стандарта C.
Решение проблемы "no terminal newline"
Обратите внимание на ответ Гордона Дэвисона. Простой тест показывает, что его наблюдение является точным:
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
Поэтому его техника:
while read line || [ -n "$line" ]; do echo $line; done < y
или же:
cat y | while read line || [ -n "$line" ]; do echo $line; done
будет работать для файлов без новой строки в конце (по крайней мере, на моей машине).
Я все еще удивлен, обнаружив, что оболочки отбрасывают последний сегмент (его нельзя назвать линией, потому что он не заканчивается новой строкой), но в POSIX для этого может быть достаточно обоснования. И, безусловно, лучше всего, чтобы ваши текстовые файлы были текстовыми файлами, заканчивающимися новой строкой.