Menggunakan sistem kontrol versi, saya merasa terganggu pada suara ketika diff mengatakan No newline at end of file
.
Jadi saya bertanya-tanya: Bagaimana cara menambahkan baris baru di akhir file untuk menghilangkan pesan-pesan itu?
Menggunakan sistem kontrol versi, saya merasa terganggu pada suara ketika diff mengatakan No newline at end of file
.
Jadi saya bertanya-tanya: Bagaimana cara menambahkan baris baru di akhir file untuk menghilangkan pesan-pesan itu?
Jawaban:
Untuk membersihkan proyek secara rekursif, saya menggunakan oneliner ini:
git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done
Penjelasan:
git ls-files -z
daftar file dalam repositori. Dibutuhkan pola opsional sebagai parameter tambahan yang mungkin berguna dalam beberapa kasus jika Anda ingin membatasi operasi ke file / direktori tertentu. Sebagai alternatif, Anda bisa menggunakan find -print0 ...
atau program serupa untuk membuat daftar file yang terkena dampak - pastikan saja ia memancarkan NUL
entri yang telah direvisi.
while IFS= read -rd '' f; do ... done
iterates melalui entri, aman menangani nama file yang menyertakan spasi dan / atau baris baru.
tail -c1 < "$f"
membaca char terakhir dari sebuah file.
read -r _
keluar dengan status keluar bukan nol jika baris baru tertinggal tidak ada.
|| echo >> "$f"
menambahkan baris baru ke file jika status keluar dari perintah sebelumnya adalah nol.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
yang masih akan menyelamatkan Anda dari mengedit file yang tidak dilacak dalam kontrol versi.
IFS=
untuk mengeset separator baik untuk melestarikan spasi putih sekitarnya. Entri yang dihentikan nol hanya relevan jika Anda memiliki file atau direktori dengan baris baru di namanya, yang sepertinya agak dibuat-buat, tetapi cara yang lebih tepat untuk menangani kasus generik, saya setuju. Sama seperti peringatan kecil: -d
opsi untuk read
tidak tersedia di POSIX sh.
tail -n1 < "$f"
untuk menghindari masalah dengan nama file yang dimulai dengan -
( tail -n1 -- "$f"
tidak berfungsi untuk file yang dipanggil -
). Anda mungkin ingin mengklarifikasi bahwa jawabannya sekarang khusus zsh / bash.
Ini dia :
sed -i -e '$a\' file
Dan sebagai alternatif untuk OS X sed
:
sed -i '' -e '$a\' file
Ini menambahkan \n
di akhir file hanya jika belum diakhiri dengan baris baru. Jadi, jika Anda menjalankannya dua kali, itu tidak akan menambah baris baru:
$ 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.
Tapi mungkin itu hanya bekerja secara tidak sengaja. Solusi Anda juga berfungsi.
$
cocok dengan baris terakhir, mengapa tidak menambahkan baris baru ke string yang sudah berisi baris baru?
$
. Di dalam regex, seperti dengan formulir /<regex>/
, itu memiliki makna "cocok ujung garis" yang biasa. Kalau tidak, digunakan sebagai alamat, sed memberikan makna "baris terakhir dalam file" khusus. Kode berfungsi karena sed secara default menambahkan baris baru ke outputnya jika belum ada. Kode "$ a \" hanya mengatakan "cocok dengan baris terakhir file, dan tidak menambahkan apa pun ke dalamnya." Tetapi secara implisit, sed menambahkan baris baru ke setiap baris yang diprosesnya (seperti $
baris ini ) jika belum ada di sana.
/regex/
memberinya makna yang berbeda. Halaman manual FreeBSD sedikit lebih informatif, saya pikir: freebsd.org/cgi/man.cgi?query=sed
Lihat:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
jadi echo "" >> noeol-file
sebaiknya lakukan triknya. (Atau apakah Anda bermaksud meminta untuk mengidentifikasi file-file ini dan memperbaikinya?)
edit menghapus ""
dari echo "" >> foo
(lihat komentar @ yuyichao)
edit2 menambahkan ""
lagi ( tapi lihat komentar @Keith Thompson)
""
tidak diperlukan (setidaknya untuk bash) dan tail -1 | wc -l
dapat digunakan untuk mengetahui file tanpa baris baru di akhir
""
perlu untuk bash, tapi saya telah melihat echo
implementasi yang tidak mencetak apa-apa ketika dipanggil tanpa argumen (meskipun tidak ada yang bisa saya temukan sekarang melakukan ini). echo "" >> noeol-file
mungkin sedikit lebih kuat. printf "\n" >> noeol-file
bahkan lebih dari itu.
csh
's echo
adalah salah satu dikenal untuk output apa-apa bila tidak lulus argumen. Tapi kemudian jika kita akan mendukung kerang non-Bourne-seperti, kita harus membuat echo ''
bukan echo ""
sebagai echo ""
akan ouput ""<newline>
dengan rc
atau es
misalnya.
tcsh
, tidak seperti csh
, mencetak baris baru ketika dipanggil tanpa argumen - terlepas dari pengaturan $echo_style
.
Solusi lain menggunakan ed
. Solusi ini hanya memengaruhi baris terakhir dan hanya jika \n
tidak ada:
ed -s file <<< w
Ini pada dasarnya berfungsi membuka file untuk diedit melalui skrip, skrip adalah w
perintah tunggal , yang menulis file kembali ke disk. Ini didasarkan pada kalimat yang ditemukan di ed(1)
halaman manual ini:
BATASAN (...) Jika file teks (non-biner) tidak diakhiri oleh karakter baris baru, kemudian ed menambahkan satu pada membaca / menulisnya. Dalam kasus biner file, ed tidak menambahkan baris baru pada membaca / menulis.
Cara sederhana, portabel, dan sesuai dengan POSIX untuk menambahkan baris akhir yang tidak ada ke file teks:
[ -n "$(tail -c1 file)" ] && echo >> file
Pendekatan ini tidak perlu membaca seluruh file; itu hanya dapat mencari EOF dan bekerja dari sana.
Pendekatan ini juga tidak perlu membuat file temp di belakang Anda (mis. Sed -i), sehingga hardlink tidak terpengaruh.
echo menambahkan baris baru ke file hanya ketika hasil substitusi perintah adalah string yang tidak kosong. Perhatikan bahwa ini hanya dapat terjadi jika file tidak kosong dan byte terakhir bukan baris baru.
Jika byte terakhir dari file adalah baris baru, tail mengembalikannya, lalu perintahkan strip penggantinya; hasilnya adalah string kosong. Tes -n gagal dan gema tidak berjalan.
Jika file kosong, hasil substitusi perintah juga merupakan string kosong, dan sekali lagi gema tidak berjalan. Ini diinginkan, karena file kosong bukan file teks tidak valid, juga tidak setara dengan file teks tidak kosong dengan baris kosong.
yash
jika karakter terakhir dalam file adalah karakter multi-byte (misalnya di UTF-8 lokal), atau jika lokal adalah C dan byte terakhir dalam file memiliki bit set ke-8. Dengan shell lain (kecuali zsh), itu tidak akan menambahkan baris baru jika file berakhir dengan byte NUL (tapi sekali lagi, itu berarti input akan menjadi non-teks bahkan setelah baris baru ditambahkan).
Tambahkan baris baru terlepas dari:
echo >> filename
Berikut adalah cara untuk memeriksa apakah ada baris baru di bagian akhir sebelum menambahkannya, dengan menggunakan Python:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
echo ""
tampaknya lebih kuat daripada echo -n '\n'
. Atau Anda bisa menggunakanprintf '\n'
Solusi tercepat adalah:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
Sangat cepat.
Pada file ukuran sedang seq 99999999 >file
ini membutuhkan milidetik.
Solusi lain membutuhkan waktu lama:
[ -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
Bekerja di abu, bash, lksh, mksh, ksh93, attsh dan zsh tetapi tidak yash.
Jika Anda membutuhkan solusi portabel untuk yash (dan semua cangkang lain yang tercantum di atas), mungkin akan sedikit lebih rumit:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
Cara tercepat untuk menguji apakah byte terakhir file adalah baris baru adalah dengan hanya membaca byte terakhir. Itu bisa dilakukan dengan tail -c1 file
. Namun, cara sederhana untuk menguji apakah nilai byte adalah baris baru, tergantung pada penghapusan shell biasa dari garis baru di dalam ekspansi perintah gagal (misalnya) di yash, ketika karakter terakhir dalam file adalah UTF- 8 nilai.
Cara shell yang benar, sesuai POSIX, semua (wajar) untuk menemukan apakah byte terakhir dari file adalah baris baru adalah dengan menggunakan xxd atau hexdump:
tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'
Kemudian, membandingkan output di atas 0A
akan memberikan tes yang kuat.
Berguna untuk menghindari menambahkan baris baru ke file yang kosong.
File yang gagal memberikan karakter terakhir 0A
, tentu saja:
f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"
Pendek dan manis. Ini membutuhkan sedikit waktu karena hanya membaca byte terakhir (seek to EOF). Tidak masalah jika file itu besar. Maka hanya tambahkan satu byte jika diperlukan.
Tidak perlu file temp atau digunakan. Tidak ada hardlink yang terpengaruh.
Jika tes ini dijalankan dua kali, itu tidak akan menambah baris baru.
xxd
atau hexdump
utilitas POSIX. Di toolchest POSIX, ada od -An -tx1
untuk mendapatkan nilai hex byte.
Anda lebih baik memperbaiki editor pengguna yang terakhir mengedit file. Jika Anda adalah orang terakhir yang mengedit file - editor apa yang Anda gunakan, saya menebak teman teks ..?
emacs
tidak menambahkan baris baru di akhir file.
(setq require-final-newline 'ask)
di dalam saya.emacs
Jika Anda hanya ingin dengan cepat menambahkan baris baru saat memproses beberapa jalur pipa, gunakan ini:
outputting_program | { cat ; echo ; }
itu juga sesuai dengan POSIX.
Kemudian, tentu saja, Anda dapat mengarahkannya ke file.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Asalkan tidak ada null dalam input:
paste - <>infile >&0
... akan cukup untuk selalu hanya menambahkan baris baru ke ujung ekor infile jika belum memilikinya. Dan itu hanya perlu membaca file input melalui satu waktu untuk memperbaikinya.
paste infile 1<> infile
malah perlu .
Meskipun tidak langsung menjawab pertanyaan, berikut adalah skrip terkait yang saya tulis untuk mendeteksi file yang tidak diakhiri dengan baris baru. Ini sangat cepat.
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 }
'
Skrip perl membaca daftar nama file (secara opsional diurutkan) dari stdin dan untuk setiap file membaca byte terakhir untuk menentukan apakah file berakhir pada baris baru atau tidak. Ini sangat cepat karena menghindari membaca seluruh isi setiap file. Ini menghasilkan satu baris untuk setiap file yang dibacanya, diawali dengan "kesalahan:" jika beberapa jenis kesalahan terjadi, "kosong:" jika file kosong (tidak berakhir dengan baris baru!), "EOL:" ("akhir dari baris ") jika file berakhir dengan baris baru dan" tidak EOL: "jika file tidak berakhir dengan baris baru.
Catatan: skrip tidak menangani nama file yang berisi baris baru. Jika Anda menggunakan sistem GNU atau BSD, Anda dapat menangani semua nama file yang mungkin dengan menambahkan -print0 untuk menemukan, -z untuk mengurutkan, dan -0 ke perl, seperti ini:
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 }
'
Tentu saja, Anda masih harus menemukan cara untuk menyandikan nama file dengan baris baru di output (dibiarkan sebagai latihan untuk pembaca).
Keluaran dapat difilter, jika diinginkan, untuk menambahkan baris baru ke file-file yang tidak memilikinya, paling sederhana dengan
echo >> "$filename"
Kurangnya baris baru final dapat menyebabkan bug dalam skrip karena beberapa versi shell dan utilitas lain tidak akan menangani baris baru akhir yang hilang saat membaca file tersebut.
Dalam pengalaman saya, tidak adanya baris baru final disebabkan oleh penggunaan berbagai utilitas Windows untuk mengedit file. Saya belum pernah melihat vim menyebabkan baris baru yang hilang saat mengedit file, meskipun akan melaporkan file tersebut.
Akhirnya, ada skrip yang jauh lebih pendek (tetapi lebih lambat) yang dapat mengulangi input nama file mereka untuk mencetak file-file yang tidak berakhir pada baris baru, seperti:
/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
The vi
/ vim
/ ex
editor secara otomatis menambahkan <EOL>
di EOF kecuali file sudah memiliki itu.
Jadi cobalah:
vi -ecwq foo.txt
yang setara dengan:
ex -cwq foo.txt
Pengujian:
$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt
Untuk memperbaiki beberapa file, periksa: Bagaimana cara memperbaiki 'Tidak ada baris baru di akhir file' untuk banyak file? di SO
Mengapa ini sangat penting? Untuk menjaga agar file kami POSIX kompatibel .
Untuk menerapkan jawaban yang diterima ke semua file di direktori saat ini (plus subdirektori):
$ find . -type f -exec sed -i -e '$a\' {} \;
Ini berfungsi di Linux (Ubuntu). Pada OS X Anda mungkin harus menggunakan -i ''
(belum diuji).
find .
mencantumkan semua file, termasuk file dalam .git
. Untuk mengecualikan:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Setidaknya dalam versi GNU, sederhanakan grep ''
atauawk 1
dikanonikan inputnya, tambahkan baris baru akhir jika belum ada. Mereka memang menyalin file dalam proses, yang membutuhkan waktu jika besar (tapi sumbernya tidak boleh terlalu besar untuk dibaca?) Dan memperbarui modtime kecuali jika Anda melakukan sesuatu seperti
mv file old; grep '' <old >file; touch -r old file
(walaupun itu mungkin baik-baik saja pada file yang Anda periksa karena Anda memodifikasinya) dan kehilangan tautan, izin nondefault dan ACL dll kecuali Anda bahkan lebih berhati-hati.
grep '' file 1<> file
, meskipun itu masih akan membaca dan menulis file sepenuhnya.
Ini bekerja di AIX ksh:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
Dalam kasus saya, jika file tidak ada baris baru, wc
perintah mengembalikan nilai 2
dan kami menulis baris baru.
Menambahkan ke jawaban Patrick Oscity , jika Anda hanya ingin menerapkannya ke direktori tertentu, Anda juga dapat menggunakan:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Jalankan ini di dalam direktori yang Anda ingin tambahkan baris baru.
echo $'' >> <FILE_NAME>
akan menambahkan baris kosong ke akhir file.
echo $'\n\n' >> <FILE_NAME>
akan menambahkan 3 baris kosong ke akhir file.
Jika file Anda diakhiri dengan ujung jalur Windows\r\n
dan Anda berada di Linux, Anda dapat menggunakan sed
perintah ini . Itu hanya menambah \r\n
baris terakhir jika belum ada di sana:
sed -i -e '$s/\([^\r]\)$/\1\r\n/'
Penjelasan:
-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
Jika baris terakhir sudah berisi \r\n
maka pencarian regexp tidak akan cocok, maka tidak akan terjadi apa-apa.
Anda dapat menulis fix-non-delimited-line
skrip seperti:
#! /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
Bertentangan dengan beberapa solusi yang diberikan di sini, itu
Anda dapat menggunakannya misalnya sebagai:
that-script *.txt
atau:
git ls-files -z | xargs -0 that-script
POSIXly, Anda bisa melakukan sesuatu yang fungsionaly setara dengannya
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