Bagaimana cara menambahkan baris baru ke akhir file?

190

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?

k0pernikus
sumber
1
lihat juga so / q / 10082204/155090
RubyTuesdayDONO
1
Solusi bagus di bawah ini yang membersihkan semua file secara rekursif. Jawaban oleh @ Patrick Oscity
Qwerty
Ke depan, editor teks sering memiliki opsi untuk memastikan ada baris baru yang dapat Anda dan kolaborator gunakan untuk menjaga kebersihan.
Nick T

Jawaban:

44

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 -zdaftar 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 NULentri 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.

Patrick Oscity
sumber
Anda juga dapat melakukannya seperti ini jika Anda ingin membersihkan sebagian dari file Anda:find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Per Lundberg
@ StéphaneChazelas saran bagus, akan mencoba memasukkan ini ke dalam jawaban saya.
Patrick Oscity
@ PerLundberg Anda juga dapat meneruskan pola git ls-filesyang masih akan menyelamatkan Anda dari mengedit file yang tidak dilacak dalam kontrol versi.
Patrick Oscity
@ StéphaneChazelas menambahkan 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: -dopsi untuk readtidak tersedia di POSIX sh.
Patrick Oscity
Ya, karenanya zsh / bash saya . Lihat juga penggunaan saya 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.
Stéphane Chazelas
203

Ini dia :

sed -i -e '$a\' file

Dan sebagai alternatif untuk OS X sed:

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

Ini menambahkan \ndi 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
l0b0
sumber
1
@ jwd: Dari man sed: $ Match the last line.Tapi mungkin itu hanya bekerja secara tidak sengaja. Solusi Anda juga berfungsi.
l0b0
1
Solusi Anda juga lebih elegan, dan saya telah menguji & berkomitmen , tetapi bagaimana cara kerjanya? Jika $cocok dengan baris terakhir, mengapa tidak menambahkan baris baru ke string yang sudah berisi baris baru?
l0b0
27
Ada dua arti berbeda $. 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.
jwd
1
Mengenai halaman manual: Kutipan yang Anda maksud ada di bawah bagian "Alamat". Menempatkannya di dalam /regex/memberinya makna yang berbeda. Halaman manual FreeBSD sedikit lebih informatif, saya pikir: freebsd.org/cgi/man.cgi?query=sed
jwd
2
Jika file sudah berakhir di baris baru, ini tidak mengubahnya, tetapi ia menulis ulang dan memperbarui stempel waktunya. Itu mungkin atau mungkin tidak masalah.
Keith Thompson
39

Lihat:

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

jadi echo "" >> noeol-filesebaiknya 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)

sr_
sumber
4
yang ""tidak diperlukan (setidaknya untuk bash) dan tail -1 | wc -ldapat digunakan untuk mengetahui file tanpa baris baru di akhir
yuyichao
5
@ yuyichao: Tidak ""perlu untuk bash, tapi saya telah melihat echoimplementasi yang tidak mencetak apa-apa ketika dipanggil tanpa argumen (meskipun tidak ada yang bisa saya temukan sekarang melakukan ini). echo "" >> noeol-filemungkin sedikit lebih kuat. printf "\n" >> noeol-filebahkan lebih dari itu.
Keith Thompson
2
@KeithThompson, csh's echoadalah 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 rcatau esmisalnya.
Stéphane Chazelas
1
@ StéphaneChazelas: Dan tcsh, tidak seperti csh, mencetak baris baru ketika dipanggil tanpa argumen - terlepas dari pengaturan $echo_style.
Keith Thompson
16

Solusi lain menggunakan ed. Solusi ini hanya memengaruhi baris terakhir dan hanya jika \ntidak ada:

ed -s file <<< w

Ini pada dasarnya berfungsi membuka file untuk diedit melalui skrip, skrip adalah wperintah 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.
enzotib
sumber
1
Ini tidak menambah baris baru untuk saya.
Olhovsky
4
Bekerja untuk saya; bahkan mencetak "Newline appended" (ed-1.10-1 di Arch Linux).
Stefan Majewsky
12

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.

Barefoot IO
sumber
1
Perhatikan bahwa ini tidak berfungsi yashjika 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).
Stéphane Chazelas
1
@ StéphaneChazelas Solusi untuk yash telah ditambahkan .
sorontar
1
Apakah mungkin untuk menjalankan ini untuk setiap file dalam folder dan subfolder?
Qwerty
12

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
Alexander
sumber
1
Saya tidak akan menggunakan versi python dalam bentuk loop apa pun karena waktu startup python yang lambat. Tentu saja Anda bisa melakukan loop dengan python jika Anda mau.
Kevin Cox
2
Waktu startup untuk Python adalah 0,03 detik di sini. Apakah Anda benar-benar menganggapnya bermasalah?
Alexander
3
Waktu mulai tidak masalah jika Anda memanggil python dalam satu lingkaran, itu sebabnya saya katakan pertimbangkan untuk melakukan loop dalam python. Maka Anda hanya dikenakan biaya startup sekali. Bagi saya, setengah dari biaya startup lebih dari setengah dari keseluruhan snipit, saya akan mempertimbangkan biaya overhead yang substansial. (Sekali lagi, tidak relevan jika hanya melakukan sejumlah kecil file)
Kevin Cox
2
echo ""tampaknya lebih kuat daripada echo -n '\n'. Atau Anda bisa menggunakanprintf '\n'
Keith Thompson
2
Ini bekerja dengan baik untuk saya
Daniel Gomez Rico
8

Solusi tercepat adalah:

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

  1. Sangat cepat.
    Pada file ukuran sedang seq 99999999 >fileini 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
  2. Bekerja di abu, bash, lksh, mksh, ksh93, attsh dan zsh tetapi tidak yash.

  3. Tidak mengubah stempel waktu file jika tidak perlu menambahkan baris baru.
    Semua solusi lain yang disajikan di sini mengubah timestamp file.
  4. Semua solusi di atas adalah POSIX yang valid.

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
Ishak
sumber
7

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 0Aakan 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.

sorontar
sumber
1
@ Crw, saya yakin itu menambah informasi yang bermanfaat.
sorontar
2
Perhatikan bahwa baik xxdatau hexdumputilitas POSIX. Di toolchest POSIX, ada od -An -tx1untuk mendapatkan nilai hex byte.
Stéphane Chazelas
@ StéphaneChazelas Silakan posting itu sebagai jawaban; Saya datang ke sini untuk mencari komentar ini berkali-kali :)
kelvin
@kelvin, saya telah memperbarui jawaban saya
Stéphane Chazelas
Perhatikan bahwa POSIX tidak menjamin nilai LF menjadi 0x0a. Masih ada sistem POSIX di mana tidak (yang berbasis EBCDIC) meskipun mereka sangat langka hari ini.
Stéphane Chazelas
4

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 ..?

AD7six
sumber
2
Vim adalah editor yang dimaksud. Tetapi secara umum, Anda benar, saya seharusnya tidak hanya memperbaiki
gejala
6
untuk vim, Anda harus keluar dari jalan dan melakukan tarian binary-file-on-save untuk mendapatkan vim untuk tidak menambahkan baris baru di akhir file - jangan lakukan tarian itu. ATAU, untuk sekadar memperbaiki file yang sudah ada, buka file tersebut dalam vim dan simpan file tersebut dan vim akan 'memperbaiki' baris baru yang hilang untuk Anda (dapat dengan mudah dituliskan untuk beberapa file)
AD7six
3
Saya emacstidak menambahkan baris baru di akhir file.
enzotib
2
Terima kasih atas komentar @ AD7six, saya terus mendapatkan laporan hantu dari diffs ketika saya melakukan sesuatu, tentang bagaimana file asli tidak memiliki baris baru di akhir. Tidak peduli bagaimana saya mengedit file dengan vim saya tidak bisa mendapatkannya untuk tidak meletakkan baris baru di sana. Jadi itu hanya vim melakukannya.
Steven Lu
1
@enzotib: Ada (setq require-final-newline 'ask)di dalam saya.emacs
Keith Thompson
3

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.

Mikhal
sumber
2
Fakta bahwa saya dapat menggunakan ini dalam sebuah pipa sangat membantu. Ini memungkinkan saya untuk menghitung jumlah baris dalam file CSV, tidak termasuk header. Dan itu membantu mendapatkan jumlah baris yang akurat pada file windows yang tidak berakhir dengan baris baru atau carriage return. cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Kyle Tolle
3

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.

Toby Speight
sumber
Itu tidak akan berfungsi seperti itu karena stdin dan stdout berbagi deskripsi file terbuka yang sama (jadi kursor di dalam file). Anda paste infile 1<> infilemalah perlu .
Stéphane Chazelas
2

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 ...
jrw32982
sumber
0

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).

friederbluemle
sumber
4
Catatan yang find .mencantumkan semua file, termasuk file dalam .git. Untuk mengecualikan:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
friederbluemle
Seandainya saya membaca komentar ini / memikirkannya sebelum saya menjalankannya. Baiklah.
kstev
0

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.

dave_thompson_085
sumber
Atau hanya grep '' file 1<> file, meskipun itu masih akan membaca dan menulis file sepenuhnya.
Stéphane Chazelas
-1

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, wcperintah mengembalikan nilai 2dan kami menulis baris baru.

Daemoncan
sumber
Umpan balik akan datang dalam bentuk naik atau turunnya suara, atau Anda akan diminta komentar untuk menguraikan jawaban / pertanyaan Anda lebih lanjut, tidak ada gunanya memintanya dalam tubuh jawaban. Terus to the point, selamat datang di stackexchange!
k0pernikus
-1

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.

sandi
sumber
-1

echo $'' >> <FILE_NAME> akan menambahkan baris kosong ke akhir file.

echo $'\n\n' >> <FILE_NAME> akan menambahkan 3 baris kosong ke akhir file.

pengguna247137
sumber
StackExchange memiliki format lucu, saya memperbaikinya untuk Anda :-)
peterh
-1

Jika file Anda diakhiri dengan ujung jalur Windows\r\n dan Anda berada di Linux, Anda dapat menggunakan sedperintah ini . Itu hanya menambah \r\nbaris 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\nmaka pencarian regexp tidak akan cocok, maka tidak akan terjadi apa-apa.

masgo
sumber
-1

Anda dapat menulis fix-non-delimited-lineskrip 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

  • harus efisien karena tidak melakukan proses apa pun, hanya membaca satu byte untuk setiap file, dan tidak menulis ulang file tersebut (hanya menambahkan baris baru)
  • tidak akan merusak symlink / hardlink atau memengaruhi metadata (juga, waktu / mtime hanya diperbarui ketika baris baru ditambahkan)
  • harus berfungsi dengan baik meskipun byte terakhir adalah NUL atau merupakan bagian dari karakter multi-byte.
  • harus berfungsi dengan baik terlepas dari karakter apa atau non-karakter nama file yang mungkin berisi
  • Seharusnya menangani file yang tidak dapat dibaca atau tidak dapat ditulis atau tidak dicari dengan benar (dan melaporkan kesalahan yang sesuai)
  • Seharusnya tidak menambahkan baris baru ke file kosong (tetapi melaporkan kesalahan tentang pencarian yang tidak valid dalam kasus 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
Stéphane Chazelas
sumber