Я припускаю, що всі тут знайомі з приказкою про те, що всі текстові файли повинні закінчуватися новим рядком. Я знав про це «правило» роками, але завжди цікавився - чому?
Я припускаю, що всі тут знайомі з приказкою про те, що всі текстові файли повинні закінчуватися новим рядком. Я знав про це «правило» роками, але завжди цікавився - чому?
Відповіді:
Тому що ось стандарт 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 попереджає про це не тому, що він не може обробити файл, а тому, що він повинен бути частиною стандарту.
Стандарт мови мови говорить, що вихідний файл, який не є порожнім, повинен закінчуватися символом нового рядка, якому не повинно негайно передувати символ зворотної косої риски.
Оскільки це пункт "повинен", ми повинні вислати діагностичне повідомлення про порушення цього правила.
Про це йдеться в розділі 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
також буде виводити не <newline> завершений рядок (або неповний рядок ) як рядок - оскільки кількість починається з 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 (Томпсон, Річі та ін.) Або їх попередники Multics зрозуміли, що існує теоретична причина використовувати термінатори, а не роздільники ліній: With line термінатори, ви можете кодувати всі можливі файли рядків. У роздільниках рядків немає різниці між файлом нульових рядків і файлом, що містить один порожній рядок; обидва вони кодуються як файл, що містить нульові символи.
Отже, причини:
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.
Однак я вважаю, що правило походить від компіляторів С, що вимагають нового рядка. І як зазначено у попередженні компілятора "Немає нового рядка в кінці файлу" , #include не додасть новий рядок.
Уявіть, що файл обробляється, поки файл ще створюється іншим процесом.
Це може мати відношення до цього? Прапор, який вказує, що файл готовий до обробки.
Мені особисто подобаються нові рядки в кінці файлів вихідного коду.
Він може мати своє походження з Linux або всіма системами UNIX з цього питання. Я пам’ятаю, є помилки компіляції (якщо я не помиляюся), оскільки файли вихідного коду не закінчувались порожнім новим рядком. Чому це було зроблено таким чином, залишається дивуватися.
ІМХО, це питання особистого стилю та думки.
У старі часи я не ставив цей новий рядок. Збережений персонаж означає більшу швидкість через цей 14,4K модем.
Пізніше я помістив цей новий рядок, щоб було легше вибрати остаточний рядок, використовуючи shift + спадання.