Як додати новий рядок до кінця файлу?


190

Використовуючи системи управління версіями, я дратуюся від шуму, коли різниця каже No newline at end of file.

Тож мені було цікаво: як додати новий рядок в кінці файлу, щоб позбутися цих повідомлень?


1
дивіться також так / q / 10082204/155090
RubyTuesdayDONO

1
Приємне рішення внизу, яке рецидивує всі файли рекурсивно. Відповідь @Patrick Oscity
Qwerty


Вперед у текстових редакторах часто є варіанти, щоб упевнитись у новому рядку, який ви та ваші співробітники можете використати для чистоти.
Нік Т

Відповіді:


44

Для рекурсивної санітарії проекту я використовую цей oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

Пояснення:

  • git ls-files -zперелічує файли у сховищі. Він бере додатковий шаблон як додатковий параметр, який може бути корисним у деяких випадках, якщо ви хочете обмежити роботу певними файлами / каталогами. Як альтернативу, ви можете використовувати find -print0 ...або подібні програми для списку файлів, на які впливає - просто переконайтеся, що вони випускають NULрозділені записи.

  • while IFS= read -rd '' f; do ... done повторює записи, безпечно обробляючи імена файлів, що містять пробіли та / або нові рядки.

  • tail -c1 < "$f" читає останній знак із файлу.

  • read -r _ виходить із ненульовим статусом виходу, якщо відсутній новий рядок.

  • || echo >> "$f" додає до файлу новий рядок, якщо стан виходу попередньої команди був ненульовим.


Ви також можете зробити це так, якщо ви хочете просто очистити підмножину файлів:find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Пер Лундберг

@ StéphaneChazelas гарні пропозиції, спробую включити це у свою відповідь.
Патрік Осіті

@PerLundberg ви також можете передати шаблон, до git ls-filesякого ви все-таки врятуєте вас від редагування файлів, які не відслідковуються в контролі версій.
Патрік Осіті

@ StéphaneChazelas додавання IFS= значка для вимкнення роздільника добре зберігати навколишній пробіл. Потрібні завершені записи є актуальними лише у тому випадку, якщо у вас є файли або каталоги з новим рядком на їх ім’я, який здається надуманим, але це більш правильний спосіб обробки загального випадку, я згоден. Так само, як невеликий застереження: -dваріант readPOS недоступний у POSIX sh.
Патрік Осіті

Так, звідси мої zsh / bash's . Дивіться також моє використання, tail -n1 < "$f"щоб уникнути проблем із іменами файлів, які починаються з -( tail -n1 -- "$f"не працює для названого файлу -). Ви можете уточнити, що зараз відповідь zsh / bash.
Стефан Шазелас

202

Ось вам :

sed -i -e '$a\' file

І як варіант для OS X sed:

sed -i '' -e '$a\' file

Це додається \nв кінці файлу, лише якщо він ще не закінчується новим рядком. Тож якщо запустити його двічі, він не додасть іншого нового рядка:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0

1
@jwd: Від man sed: $ Match the last line.Але, можливо, це працює лише випадково. Ваше рішення також працює.
l0b0

1
Ваше рішення є більш елегантним, і я перевірив і зробив це, але як вона може працювати? Якщо $відповідає останньому рядку, чому він не додає інший новий рядок до рядка, який вже містить новий рядок?
l0b0

27
Існує два різних значення $. Всередині регулярного вираження, наприклад, у формі /<regex>/, воно має звичайне значення "збіг кінця рядка". В іншому випадку, використовується як адреса, sed надає їй спеціальне значення "останній рядок у файлі". Код працює, тому що sed за замовчуванням додає до виводу новий рядок, якщо його ще немає. Код "$ a \" просто говорить "відповідати останньому рядку файлу, і нічого до нього не додавати". Але неявно, sed додає новий рядок до кожного рядка, який він обробляє (наприклад, цього $рядка), якщо його ще немає.
jwd

1
Щодо сторінки сторінки: Цитата, яку ви посилаєтесь, знаходиться у розділі "Адреси". Поміщення його всередині /regex/надає йому іншого значення. Сторінки
jwd

2
Якщо файл вже закінчується в новому рядку, це не змінює його, але він переписує його та оновлює свою часову позначку. Це може чи не має значення.
Кіт Томпсон

39

Гляньте:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

тому echo "" >> noeol-fileслід робити трюк. (Або ви хотіли попросити визначити ці файли та виправити їх?)

редагування видалено ""з echo "" >> foo(див. коментар @ yuyichao) edit2"" знову додано ( але дивіться коментар @Keith Thompson)


4
the ""не потрібен (принаймні для bash) і tail -1 | wc -lможе бути використаний для пошуку файлу без нового рядка наприкінці
yuyichao

5
@yuyichao: ""Не потрібен для bash, але я бачив echoреалізацію, яка нічого не друкує, коли викликається без аргументів (хоча жоден із тих, кого я зараз можу знайти, не робить цього). echo "" >> noeol-fileце, мабуть, трохи надійніше. printf "\n" >> noeol-fileтим більше.
Кіт Томпсон,

2
@KeithThompson, cshs echo- це відомий, що нічого не виводить, коли не передається жоден аргумент. Але тоді, якщо ми будемо підтримувати не-Bourne-подібні оболонки, ми повинні зробити це echo ''замість того, echo ""як echo ""би виходило ""<newline>з rcабо, esнаприклад.
Стефан Шазелас

1
@ StéphaneChazelas: І tcsh, на відміну від цього csh, друкує новий рядок, коли викликається без аргументів - незалежно від налаштування $echo_style.
Кіт Томпсон,

16

Ще одне рішення з використанням ed. Це рішення впливає лише на останній рядок і лише у разі \nвідсутності:

ed -s file <<< w

Він по суті працює з відкриттям файлу для редагування через скрипт, скрипт - це єдина wкоманда, яка записує файл назад на диск. Він заснований на цьому реченні, знайденому на ed(1)сторінці man:

ОБМЕЖЕННЯ
       (...)

       Якщо текстовий (не двійковий) файл не закінчується символом нового рядка,
       потім Ед додає його при читанні / написанні. У разі двійкового
       Файл ed не додає новий рядок для читання / запису.

1
Це не додає для мене нового рядка.
Ольховський

4
Працює для мене; він навіть друкує "Доданий новий рядок" (ed-1.10-1 в Arch Linux).
Стефан Маєвський

12

Простий, портативний, сумісний з POSIX спосіб додати відсутній, остаточний новий рядок до текстового файлу:

[ -n "$(tail -c1 file)" ] && echo >> file

У цьому підході не потрібно читати весь файл; він може просто прагнути до EOF і працювати звідти.

Цей підхід також не потребує створення тимчасових файлів за спиною (наприклад, sed -i), тому жорсткі посилання не впливають.

echo додає новий рядок до файлу лише тоді, коли результатом підстановки команди є не порожній рядок. Зауважте, що це може статися лише в тому випадку, якщо файл не порожній і останній байт не є новим рядком.

Якщо останній байт файлу є новим рядком, хвіст повертає його, після чого заміна команди знімає його; результат - порожній рядок. Тест -n не вдається, і луна не запускається.

Якщо файл порожній, результатом заміни команди є також порожній рядок, і знову ехо не запускається. Це бажано, оскільки порожній файл не є недійсним текстовим файлом, а також не еквівалентний не порожньому текстовому файлу з порожнім рядком.


1
Зауважте, що це не працює, yashякщо останній символ у файлі є багатобайтовим символом (наприклад, у локалі UTF-8) або якщо у локалі є C, а останній байт у файлі має восьмий біт. З іншими оболонками (крім zsh), він не додасть новий рядок, якби файл закінчився байтом NUL (але знову ж таки, це означатиме, що вхід буде нетекстовим навіть після додавання нового рядка).
Стефан Шазелас


1
Чи можливо запустити це для кожного файлу в папці та вкладених папках?
Qwerty

12

Додати новий рядок незалежно:

echo >> filename

Ось спосіб перевірити, чи існує новий рядок в кінці перед тим, як додати його, використовуючи Python:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f

1
Я б не використовував версію python в будь-якому циклі через повільний час запуску python. Звичайно, ви можете зробити цикл у python, якщо хочете.
Кевін Кокс

2
Час запуску Python тут становить 0,03 секунди. Ви дійсно вважаєте це проблематичним?
Олександр

3
Час запуску має значення, якщо ви називаєте python у циклі, тому я сказав, що варто подумати про створення циклу в python. Тоді ви несете вартість запуску лише один раз. Для мене половина вартості запуску - це більше половини часу всього фрагменту, я вважаю, що це значні витрати. (Знову ж таки, не має значення, якщо ви робите лише невелику кількість файлів)
Кевін Кокс

2
echo ""здається більш надійним, ніж echo -n '\n'. Або ви могли скористатисяprintf '\n'
Кіт Томпсон,

2
Це добре спрацювало для мене
Даніель Гомес Ріко

8

Найшвидше рішення:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. Справді швидко.
    На файл середнього розміру seq 99999999 >fileце займає мілісекунди.
    Інші рішення потребують тривалого часу:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
  2. Працює в золі, bash, lksh, mksh, ksh93, attsh та zsh, але не yash.

  3. Не змінює часову позначку файлу, якщо немає необхідності додавати новий рядок.
    Всі інші рішення, представлені тут, змінюють часову позначку файлу.
  4. Усі рішення, вказані вище, є дійсними POSIX.

Якщо вам потрібно рішення, яке можна переносити на яш (та всі інші перераховані вище снаряди), воно може стати трохи складнішим:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi

7

Найшвидший спосіб перевірити, чи є останній байт файлу новим рядком - це прочитати лише той останній байт. Це можна було б зробити з tail -c1 file. Однак спрощений спосіб перевірити, чи байтове значення є новим рядком, залежно від оболонки, як правило, видалення останнього нового рядка всередині команди розширення не вдається (наприклад) у yash, коли останнім символом у файлі є UTF- 8 значення.

Правильний, сумісний з POSIX, всі (розумні) оболонки спосіб встановити, чи є останній байт файлу новим рядком - використовувати xxd або hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Тоді, порівнюючи вихідний показник з вище, ви 0Aотримаєте надійний тест.
Корисно уникати додавання нового рядка в інакше порожній файл.
Файл, який не зможе надати останній символ 0A, звичайно:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Короткий і солодкий. Це забирає дуже мало часу, оскільки він лише читає останній байт (звертайтеся до EOF). Не має значення, чи файл великий. Потім додайте лише один байт, якщо потрібно.

Ні тимчасових файлів не потрібно, ні використовувати. Ніякі жорсткі посилання не впливають.

Якщо цей тест запускається двічі, він не додасть іншого нового рядка.


1
@crw Я вірю, що вона додає корисну інформацію.
соронтар

2
Зауважте, що ні утиліти, xxdні hexdumpPOSIX не є. У інструментальній панелі POSIX od -An -tx1потрібно отримати шістнадцяткове значення байта.
Стефан Шазелас

@ StéphaneChazelas Будь ласка, опублікуйте це як відповідь; Я надто багато разів
завітав


Зауважте, що POSIX не гарантує значення LF 0x0a. Є ще POSIX-системи, де їх немає (засновані на EBCDIC), хоча в наші дні вони надзвичайно рідкісні.
Стефан Шазелас

4

Вам краще виправити редактор користувача, який востаннє редагував файл. Якщо ви є останньою особою, яка редагувала файл - який редактор ви використовуєте, я здогадуюсь, текстовий партнер ..?


2
Vim - це редактор, про який йде мова. Але взагалі ви маєте рацію, я повинен не тільки виправляти симфони;)
k0pernikus

6
для vim, ви повинні піти зі свого шляху і виконати танець бінарного файлу на збереження, щоб отримати vim, щоб не додати новий рядок в кінці файлу - просто не робіть цього танцю. АБО, щоб просто виправити наявні файли, відкрийте їх у vim та збережіть файл, а vim "виправить" пропущений новий рядок для вас (можна легко скриптувати для декількох файлів)
AD7six

3
Мої emacsне додають новий рядок у кінці файлу.
enzotib

2
Дякуємо за коментар @ AD7six, я продовжую отримувати фантомні звіти з розрізників, коли я виконую речі, про те, як у вихідному файлі немає нового рядка в кінці. Як би я не редагував файл з vim, я не можу отримати його, щоб не помістити туди новий рядок. Так що це просто vim робити це.
Стівен Лу

1
@enzotib: у мене є (setq require-final-newline 'ask)в моєму.emacs
Keith Thompson

3

Якщо ви просто хочете швидко додати нову лінію під час обробки певного конвеєра, скористайтеся цим:

outputting_program | { cat ; echo ; }

це також сумісно з POSIX.

Тоді, звичайно, ви можете перенаправити його у файл.


2
Те, що я можу використовувати це в конвеєрі, є корисним. Це дозволяє мені рахувати кількість рядків у файлі CSV, виключаючи заголовок. І це допомагає отримати точний підрахунок рядків для файлів Windows, які не закінчуються новим рядком або поверненням каретки. cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Кайл Толле

3

За умови, що введені нулі:

paste - <>infile >&0

... було б достатньо завжди додавати новий рядок до хвостового кінця інфіле, якщо його ще не було. І для цього потрібно лише прочитати вхідний файл лише один раз.


Це не працюватиме так, як stdin та stdout мають однаковий опис відкритого файлу (тому курсор у файлі). paste infile 1<> infileНатомість вам знадобиться .
Стефан Шазелас

2

Хоча це не відповідає безпосередньо на запитання, ось пов'язаний сценарій, який я написав для виявлення файлів, які не закінчуються в новому рядку. Це дуже швидко.

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Сценарій perl читає список (необов'язково відсортованих) імен файлів із stdin, і для кожного файла він читає останній байт, щоб визначити, закінчується файл у новому рядку чи ні. Це дуже швидко, оскільки дозволяє уникнути читання всього вмісту кожного файлу. Він виводить по одному рядку для кожного прочитаного файлу з префіксом "помилка:" якщо виникає якась помилка, "порожня": якщо файл порожній (не закінчується новим рядком!), "EOL:" ("кінець рядок "), якщо файл закінчується новим рядком та" немає EOL: ", якщо файл не закінчується новим рядком.

Примітка: сценарій не обробляє імена файлів, які містять нові рядки. Якщо ви користуєтесь системою GNU або BSD, ви можете обробити всі можливі імена файлів, додавши -print0 для пошуку, -z для сортування та -0 для perl, як це:

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Звичайно, вам все-таки доведеться придумати спосіб кодування назв файлів новими рядками у висновку (залишений як вправа для читача).

Вихід може бути відфільтрований за бажанням, щоб додати новий рядок до тих файлів, у яких його немає, найпростіше з

 echo >> "$filename"

Відсутність остаточного нового рядка може спричинити помилки в сценаріях, оскільки деякі версії оболонки та інші утиліти не будуть належним чином обробляти відсутній остаточний рядок під час читання такого файлу.

На мій досвід, відсутність остаточного нового рядка викликано використанням різних утиліт Windows для редагування файлів. Я ніколи не бачив, щоб vim викликав відсутність остаточного рядка під час редагування файлу, хоча він повідомляє про такі файли.

Нарешті, є набагато коротші (але повільніші) скрипти, які можуть перетинати вхідні дані свого файлу для друку тих файлів, які не закінчуються в новому рядку, наприклад:

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...

1

В vi/ vim/ exредактори автоматично додавати <EOL>в EOF , якщо файл вже не має його.

Тому спробуйте будь-що:

vi -ecwq foo.txt

що еквівалентно:

ex -cwq foo.txt

Тестування:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

Щоб виправити декілька файлів, перевірте: Як виправити "Немає нового рядка в кінці файлу" для багатьох файлів? при SO

Чому це так важливо? Щоб зберегти наші файли POSIX сумісні .


0

Щоб застосувати прийняту відповідь до всіх файлів у поточному каталозі (плюс підкаталогів):

$ find . -type f -exec sed -i -e '$a\' {} \;

Це працює на Linux (Ubuntu). В OS X вам, ймовірно, доведеться користуватися -i ''(не перевірено).


4
Зауважте, що find .перелічені всі файли, включаючи файли в .git. Щоб виключити:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
friederbluemle

Хотілося б, щоб я прочитав цей коментар / подумав про це, перш ніж запустити його. Ну добре.
Кстев

0

Принаймні, у версіях GNU просто grep ''абоawk 1 канонізує його введення, додавши остаточний новий рядок, якщо його ще немає. Вони копіюють файл у процесі, який вимагає часу, якщо великий (але джерело не повинен бути занадто великим, щоб прочитати?) Та оновлює модмейт, якщо ви не зробите щось подібне

 mv file old; grep '' <old >file; touch -r old file

(хоча це може бути нормально у файлі, який ви реєструєтесь, оскільки ви його змінили), і він втрачає жорсткі посилання, недозволені дозволи та ACL тощо, якщо ви ще не обережні.


Або просто grep '' file 1<> file, хоча це все-таки буде читати і записувати файл повністю.
Стефан Шазелас

-1

Це працює в AIX ksh:

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi

У моєму випадку, якщо у файлі відсутній новий рядок, wcкоманда повертає значення 2та пишемо новий рядок.


Зворотній зв'язок буде надходити у формі збільшення або знищення, або вас попросять у коментарях детальніше окреслити ваші відповіді / запитання, без сенсу запитувати їх у тілі відповіді. Будьте в курсі, ласкаво просимо в stackexchange!
k0pernikus

-1

Додавши до відповіді Патріка Осіті , якщо ви просто хочете застосувати його до певного каталогу, ви також можете використовувати:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Запустіть це всередині каталогу, до якого ви хочете додати нові рядки.


-1

echo $'' >> <FILE_NAME> додасть порожній рядок до кінця файлу.

echo $'\n\n' >> <FILE_NAME> додасть 3 пусті рядки до кінця файлу.


У StackExchange є кумедне форматування, я виправив це для вас :-)
peterh

-1

Якщо ваш файл закінчується закінченнями рядка Windows\r\n і ви перебуваєте в Linux, ви можете скористатися цією sedкомандою. Він додає \r\nдо останнього рядка лише якщо його ще немає:

sed -i -e '$s/\([^\r]\)$/\1\r\n/'

Пояснення:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\1\r\n    replace it with itself and add \r\n

Якщо останній рядок уже містить a, \r\nтоді пошуковий регекс не збігатиметься, тому нічого не відбудеться.


-1

Ви можете написати fix-non-delimited-lineсценарій на зразок:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  if sysopen -rwu0 -- "$file"; then
    if sysseek -w end -1; then
      read -r x || print -u0
    else
      syserror -p "Can't seek in $file before the last byte: "
      ret=1
    fi
  else
    ret=1
  fi
done
exit $ret

Всупереч деяким наведеним тут рішенням

  • має бути ефективним, оскільки він не розщеплює жодного процесу, читає лише один байт для кожного файлу і не переписує файл наново (лише додає новий рядок)
  • не порушує символьні посилання / жорсткі посилання та не вплине на метадані (також, ctime / mtime оновлюються лише тоді, коли буде доданий новий рядок)
  • повинен працювати добре, навіть якщо останній байт є NUL або є частиною багатобайтового символу.
  • має працювати нормально незалежно від того, які символи чи не символи можуть містити імена файлів
  • Потрібно обробляти файли з нечитабельними або непридатними для відвідування файлами (і повідомляти про помилки відповідно)
  • Не слід додавати новий рядок до порожніх файлів (але повідомляє про помилку щодо недійсного пошуку в цьому випадку)

Ви можете використовувати його, наприклад, як:

that-script *.txt

або:

git ls-files -z | xargs -0 that-script

POSIXly, ви можете зробити щось функціонально еквівалентне

export LC_ALL=C
ret=0
for file do
  [ -s "$file" ] || continue
  {
    c=$(tail -c 1 | od -An -vtc)
    case $c in
      (*'\n'*) ;;
      (*[![:space:]]*) printf '\n' >&0 || ret=$?;;
      (*) ret=1;; # tail likely failed
    esac
  } 0<> "$file" || ret=$? # record failure to open
done
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.