При использовании систем контроля версий меня раздражает шум, когда говорит diff No newline at end of file
.
Поэтому мне было интересно: как добавить новую строку в конце файла, чтобы избавиться от этих сообщений?
При использовании систем контроля версий меня раздражает шум, когда говорит diff 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
не доступна в 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
надо делать свое дело. (Или вы хотели попросить идентифицировать эти файлы и исправить их?)
edit удалил ""
из echo "" >> foo
(см. комментарий @yuyichao )
edit2""
снова добавил ( но см. комментарий @Keith Thompson)
""
не нужно (по крайней мере , для Баш) и tail -1 | wc -l
может быть использован , чтобы выяснить файл без новой строки в конце
""
для bash это не обязательно, но я видел echo
реализации, которые ничего не печатают при вызове без аргументов (хотя ни одна из тех, что я могу найти сейчас, не делает этого). echo "" >> noeol-file
вероятно, немного более устойчивый. printf "\n" >> noeol-file
тем более.
csh
«s echo
это один известный выводить ничего , когда не передается никаких аргументов. Но тогда, если мы собираемся поддерживать не-подобные Борну оболочки, мы должны сделать это echo ''
вместо того, echo ""
как echo ""
было бы ""<newline>
с rc
или es
например.
tcsh
, в отличие от этого csh
, печатает новую строку, когда вызывается без аргументов - независимо от настройки $echo_style
.
Другое решение с использованием ed
. Это решение влияет только на последнюю строку и только если \n
отсутствует:
ed -s file <<< w
По сути, это работает, открывая файл для редактирования через скрипт, скрипт - это единственная w
команда, которая записывает файл обратно на диск. Это основано на этом предложении, найденном на ed(1)
странице руководства:
ОГРАНИЧЕНИЯ (...) Если текстовый (недвоичный) файл не заканчивается символом новой строки, затем Эд добавляет один на чтение / запись. В случае двоичного файл, ed не добавляет новую строку при чтении / записи.
Простой, переносимый, POSIX-совместимый способ добавить отсутствующий, последний символ новой строки в текстовый файл:
[ -n "$(tail -c1 file)" ] && echo >> file
Этот подход не должен читать весь файл; он может просто стремиться к EOF и работать оттуда.
Этот подход также не требует создания временных файлов за вашей спиной (например, sed -i), поэтому жесткие ссылки не затрагиваются.
echo добавляет новую строку в файл, только когда результатом подстановки команды является непустая строка. Обратите внимание, что это может произойти, только если файл не пустой и последний байт не является новой строкой.
Если последний байт файла является новой строкой, tail возвращает его, а подстановка команд удаляет его; Результатом является пустая строка. Тест -n не проходит и эхо не запускается.
Если файл пуст, результатом подстановки команды также является пустая строка, и опять эхо не запускается. Это желательно, поскольку пустой файл не является недопустимым текстовым файлом и не является эквивалентом непустого текстового файла с пустой строкой.
yash
если последний символ в файле является многобайтовым символом (например, в языковых стандартах UTF-8) или если языковым стандартом является C и для последнего байта в файле установлен 8-й бит. С другими оболочками (кроме 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
Работает в золах, баш, лкш, мкш, кш93, атш и зш, но не в яше.
Если вам нужно решение, переносимое на 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 есть od -An -tx1
шестнадцатеричное значение байта.
Вам лучше исправить редактор пользователя, который последний раз редактировал файл. Если вы последний, кто редактировал файл - какой редактор вы используете, я полагаю, textmate ..?
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 и для каждого файла читает последний байт, чтобы определить, заканчивается ли файл новой строкой или нет. Это очень быстро, потому что он избегает чтения всего содержимого каждого файла. Он выводит одну строку для каждого файла, который читает, с префиксом «error:», если возникает какая-либо ошибка, «empty:», если файл пустой (не заканчивается символом новой строки!), «EOL:» («конец line "), если файл заканчивается символом новой строки и" no 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
Чтобы исправить несколько файлов, проверьте: Как исправить «Нет новой строки в конце файла» для большого количества файлов? в СО
Почему это так важно? Чтобы наши файлы были совместимы с 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