Використовуючи системи управління версіями, я дратуюся від шуму, коли різниця каже No newline at end of file
.
Тож мені було цікаво: як додати новий рядок в кінці файлу, щоб позбутися цих повідомлень?
Використовуючи системи управління версіями, я дратуюся від шуму, коли різниця каже No newline at end of file
.
Тож мені було цікаво: як додати новий рядок в кінці файлу, щоб позбутися цих повідомлень?
Відповіді:
Для рекурсивної санітарії проекту я використовую цей 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
git ls-files
якого ви все-таки врятуєте вас від редагування файлів, які не відслідковуються в контролі версій.
IFS=
значка для вимкнення роздільника добре зберігати навколишній пробіл. Потрібні завершені записи є актуальними лише у тому випадку, якщо у вас є файли або каталоги з новим рядком на їх ім’я, який здається надуманим, але це більш правильний спосіб обробки загального випадку, я згоден. Так само, як невеликий застереження: -d
варіант read
POS недоступний у POSIX sh.
tail -n1 < "$f"
щоб уникнути проблем із іменами файлів, які починаються з -
( tail -n1 -- "$f"
не працює для названого файлу -
). Ви можете уточнити, що зараз відповідь zsh / bash.
Ось вам :
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
man sed
: $ Match the last line.
Але, можливо, це працює лише випадково. Ваше рішення також працює.
$
. Всередині регулярного вираження, наприклад, у формі /<regex>/
, воно має звичайне значення "збіг кінця рядка". В іншому випадку, використовується як адреса, sed надає їй спеціальне значення "останній рядок у файлі". Код працює, тому що sed за замовчуванням додає до виводу новий рядок, якщо його ще немає. Код "$ a \" просто говорить "відповідати останньому рядку файлу, і нічого до нього не додавати". Але неявно, sed додає новий рядок до кожного рядка, який він обробляє (наприклад, цього $
рядка), якщо його ще немає.
Гляньте:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
тому echo "" >> noeol-file
слід робити трюк. (Або ви хотіли попросити визначити ці файли та виправити їх?)
редагування видалено ""
з echo "" >> foo
(див. коментар @ yuyichao)
edit2""
знову додано ( але дивіться коментар @Keith Thompson)
""
не потрібен (принаймні для bash) і tail -1 | wc -l
може бути використаний для пошуку файлу без нового рядка наприкінці
""
Не потрібен для bash, але я бачив echo
реалізацію, яка нічого не друкує, коли викликається без аргументів (хоча жоден із тих, кого я зараз можу знайти, не робить цього). echo "" >> noeol-file
це, мабуть, трохи надійніше. printf "\n" >> noeol-file
тим більше.
csh
s echo
- це відомий, що нічого не виводить, коли не передається жоден аргумент. Але тоді, якщо ми будемо підтримувати не-Bourne-подібні оболонки, ми повинні зробити це echo ''
замість того, echo ""
як echo ""
би виходило ""<newline>
з rc
або, es
наприклад.
tcsh
, на відміну від цього csh
, друкує новий рядок, коли викликається без аргументів - незалежно від налаштування $echo_style
.
Ще одне рішення з використанням ed
. Це рішення впливає лише на останній рядок і лише у разі \n
відсутності:
ed -s file <<< w
Він по суті працює з відкриттям файлу для редагування через скрипт, скрипт - це єдина w
команда, яка записує файл назад на диск. Він заснований на цьому реченні, знайденому на ed(1)
сторінці man:
ОБМЕЖЕННЯ (...) Якщо текстовий (не двійковий) файл не закінчується символом нового рядка, потім Ед додає його при читанні / написанні. У разі двійкового Файл ed не додає новий рядок для читання / запису.
Простий, портативний, сумісний з POSIX спосіб додати відсутній, остаточний новий рядок до текстового файлу:
[ -n "$(tail -c1 file)" ] && echo >> file
У цьому підході не потрібно читати весь файл; він може просто прагнути до EOF і працювати звідти.
Цей підхід також не потребує створення тимчасових файлів за спиною (наприклад, sed -i), тому жорсткі посилання не впливають.
echo додає новий рядок до файлу лише тоді, коли результатом підстановки команди є не порожній рядок. Зауважте, що це може статися лише в тому випадку, якщо файл не порожній і останній байт не є новим рядком.
Якщо останній байт файлу є новим рядком, хвіст повертає його, після чого заміна команди знімає його; результат - порожній рядок. Тест -n не вдається, і луна не запускається.
Якщо файл порожній, результатом заміни команди є також порожній рядок, і знову ехо не запускається. Це бажано, оскільки порожній файл не є недійсним текстовим файлом, а також не еквівалентний не порожньому текстовому файлу з порожнім рядком.
yash
якщо останній символ у файлі є багатобайтовим символом (наприклад, у локалі UTF-8) або якщо у локалі є C, а останній байт у файлі має восьмий біт. З іншими оболонками (крім zsh), він не додасть новий рядок, якби файл закінчився байтом NUL (але знову ж таки, це означатиме, що вхід буде нетекстовим навіть після додавання нового рядка).
Додати новий рядок незалежно:
echo >> filename
Ось спосіб перевірити, чи існує новий рядок в кінці перед тим, як додати його, використовуючи Python:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
echo ""
здається більш надійним, ніж echo -n '\n'
. Або ви могли скористатисяprintf '\n'
Найшвидше рішення:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
Справді швидко.
На файл середнього розміру 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
Працює в золі, bash, lksh, mksh, ksh93, attsh та zsh, але не yash.
Якщо вам потрібно рішення, яке можна переносити на яш (та всі інші перераховані вище снаряди), воно може стати трохи складнішим:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
Найшвидший спосіб перевірити, чи є останній байт файлу новим рядком - це прочитати лише той останній байт. Це можна було б зробити з 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). Не має значення, чи файл великий. Потім додайте лише один байт, якщо потрібно.
Ні тимчасових файлів не потрібно, ні використовувати. Ніякі жорсткі посилання не впливають.
Якщо цей тест запускається двічі, він не додасть іншого нового рядка.
xxd
ні hexdump
POSIX не є. У інструментальній панелі POSIX od -An -tx1
потрібно отримати шістнадцяткове значення байта.
Вам краще виправити редактор користувача, який востаннє редагував файл. Якщо ви є останньою особою, яка редагувала файл - який редактор ви використовуєте, я здогадуюсь, текстовий партнер ..?
emacs
не додають новий рядок у кінці файлу.
(setq require-final-newline 'ask)
в моєму.emacs
Якщо ви просто хочете швидко додати нову лінію під час обробки певного конвеєра, скористайтеся цим:
outputting_program | { cat ; echo ; }
це також сумісно з POSIX.
Тоді, звичайно, ви можете перенаправити його у файл.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
За умови, що введені нулі:
paste - <>infile >&0
... було б достатньо завжди додавати новий рядок до хвостового кінця інфіле, якщо його ще не було. І для цього потрібно лише прочитати вхідний файл лише один раз.
paste infile 1<> infile
Натомість вам знадобиться .
Хоча це не відповідає безпосередньо на запитання, ось пов'язаний сценарій, який я написав для виявлення файлів, які не закінчуються в новому рядку. Це дуже швидко.
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 ...
В 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 сумісні .
Щоб застосувати прийняту відповідь до всіх файлів у поточному каталозі (плюс підкаталогів):
$ find . -type f -exec sed -i -e '$a\' {} \;
Це працює на Linux (Ubuntu). В OS X вам, ймовірно, доведеться користуватися -i ''
(не перевірено).
find .
перелічені всі файли, включаючи файли в .git
. Щоб виключити:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Принаймні, у версіях GNU просто grep ''
абоawk 1
канонізує його введення, додавши остаточний новий рядок, якщо його ще немає. Вони копіюють файл у процесі, який вимагає часу, якщо великий (але джерело не повинен бути занадто великим, щоб прочитати?) Та оновлює модмейт, якщо ви не зробите щось подібне
mv file old; grep '' <old >file; touch -r old file
(хоча це може бути нормально у файлі, який ви реєструєтесь, оскільки ви його змінили), і він втрачає жорсткі посилання, недозволені дозволи та ACL тощо, якщо ви ще не обережні.
grep '' file 1<> file
, хоча це все-таки буде читати і записувати файл повністю.
Це працює в AIX ksh:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
У моєму випадку, якщо у файлі відсутній новий рядок, wc
команда повертає значення 2
та пишемо новий рядок.
Додавши до відповіді Патріка Осіті , якщо ви просто хочете застосувати його до певного каталогу, ви також можете використовувати:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Запустіть це всередині каталогу, до якого ви хочете додати нові рядки.
echo $'' >> <FILE_NAME>
додасть порожній рядок до кінця файлу.
echo $'\n\n' >> <FILE_NAME>
додасть 3 пусті рядки до кінця файлу.
Якщо ваш файл закінчується закінченнями рядка 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
тоді пошуковий регекс не збігатиметься, тому нічого не відбудеться.
Ви можете написати 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
Всупереч деяким наведеним тут рішенням
Ви можете використовувати його, наприклад, як:
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