cara `tail` file terbaru dalam direktori


20

Dalam shell, bagaimana saya bisa tailmembuat file terbaru dalam direktori?


1
ayolah penutup, programmer perlu membuntuti!
amit

Penutupan hanya untuk pindah ke superuser atau kesalahan server. Pertanyaannya akan tinggal di sana, dan lebih banyak orang yang mungkin tertarik akan menemukannya.
Mnementh

Masalah sebenarnya di sini adalah menemukan file pembaruan terbaru di direktori dan saya percaya itu sudah dijawab (baik di sini atau di Super User, saya tidak bisa mengingat).
dmckee

Jawaban:


24

Jangan tidak mengurai output dari ls! Mengurai output ls sulit dan tidak dapat diandalkan .

Jika Anda harus melakukan ini, saya sarankan menggunakan find. Awalnya saya punya contoh sederhana di sini hanya untuk memberi Anda inti dari solusi, tetapi karena jawaban ini tampaknya agak populer saya memutuskan untuk merevisi ini untuk menyediakan versi yang aman untuk menyalin / menempel dan digunakan dengan semua input. Apakah Anda duduk dengan nyaman? Kami akan mulai dengan oneliner yang akan memberi Anda file terbaru di direktori saat ini:

tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"

Tidak cukup oneliner sekarang, kan? Ini dia lagi sebagai fungsi shell dan diformat untuk lebih mudah dibaca:

latest-file-in-directory () {
    find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
            sort -znr -t. -k1,2 | \
            while IFS= read -r -d '' -r record ; do
                    printf '%s' "$record" | cut -d. -f3-
                    break
            done
}

Dan sekarang bahwa sebagai oneliner sebuah:

tail -- "$(latest-file-in-directory)"

Jika semuanya gagal, Anda dapat memasukkan fungsi di atas dalam .bashrcdan mempertimbangkan masalah terpecahkan, dengan satu peringatan. Jika Anda hanya ingin menyelesaikan pekerjaan, Anda tidak perlu membaca lebih lanjut.

Peringatan dengan ini adalah bahwa nama file yang diakhiri dengan satu atau lebih baris baru masih tidak akan diteruskan dengan tailbenar. Mengatasi masalah ini rumit dan saya menganggapnya cukup bahwa jika nama file berbahaya tersebut dijumpai, perilaku yang relatif aman untuk menemukan kesalahan "Tidak ada file seperti itu" akan terjadi alih-alih sesuatu yang lebih berbahaya.

Detail juicy

Bagi yang penasaran ini adalah penjelasan yang melelahkan tentang cara kerjanya, mengapa ini aman dan mengapa metode lain mungkin tidak.

Bahaya, Will Robinson

Pertama-tama, satu-satunya byte yang aman untuk membatasi jalur file adalah nol karena itu adalah satu-satunya byte yang secara universal dilarang di jalur file pada sistem Unix. Penting ketika menangani daftar jalur file apa saja untuk hanya menggunakan null sebagai pembatas dan, ketika menyerahkan bahkan satu jalur file dari satu program ke program lain, untuk melakukannya dengan cara yang tidak akan tersedak byte acak. Ada banyak cara yang tampaknya benar untuk menyelesaikan masalah ini dan masalah lain yang gagal dengan mengasumsikan (bahkan tidak sengaja) bahwa nama file tidak akan memiliki baris atau spasi baru di dalamnya. Tidak ada asumsi yang aman.

Untuk keperluan hari ini, langkah pertama adalah mendapatkan daftar file tanpa batas yang tidak ditemukan. Ini sangat mudah jika Anda memiliki findpendukung -print0seperti GNU:

find . -print0

Tetapi daftar ini masih tidak memberi tahu kami mana yang terbaru, jadi kami perlu memasukkan informasi itu. Saya memilih untuk menggunakan -printfswitch find yang memungkinkan saya menentukan data apa yang muncul di output. Tidak semua versi finddukungan -printf(ini bukan standar) tetapi GNU find tidak. Jika Anda menemukan diri -printfAnda tanpa Anda harus mengandalkan pada -exec stat {} \;titik mana Anda harus melepaskan semua harapan portabilitas karena stattidak standar juga. Untuk saat ini saya akan melanjutkan dengan asumsi Anda memiliki alat GNU.

find . -printf '%T@.%p\0'

Di sini saya meminta format printf %T@yang merupakan waktu modifikasi dalam hitungan detik sejak awal zaman Unix diikuti oleh suatu periode dan kemudian diikuti oleh angka yang menunjukkan pecahan detik. Saya menambahkan ini periode lain dan kemudian %p(yang merupakan path lengkap ke file) sebelum berakhir dengan byte nol.

Sekarang sudah

find . -maxdepth 1 \! -type d -printf '%T@.%p\0'

Itu mungkin pergi tanpa mengatakan tetapi demi menjadi lengkap -maxdepth 1mencegah finddari daftar isi sub direktori dan \! -type dmelewatkan direktori yang Anda tidak ingin tail. Sejauh ini saya memiliki file di direktori saat ini dengan informasi waktu modifikasi, jadi sekarang saya perlu mengurutkan berdasarkan waktu modifikasi.

Mendapatkannya dalam urutan yang benar

Secara default sortmengharapkan inputnya untuk menjadi catatan dibatasi-baris baru. Jika Anda memiliki GNU, sortAnda dapat memintanya untuk mengharapkan catatan yang dibatasi-nol alih-alih dengan menggunakan -zsakelar .; untuk standar sorttidak ada solusi. Saya hanya tertarik untuk mengurutkan berdasarkan dua angka pertama (detik dan fraksi kedua) dan tidak ingin mengurutkan berdasarkan nama file yang sebenarnya jadi saya memberi tahu sortdua hal: Pertama, harus mempertimbangkan periode ( .) pembatas bidang dan kedua bahwa itu hanya harus menggunakan bidang pertama dan kedua ketika mempertimbangkan cara menyortir catatan.

| sort -znr -t. -k1,2

Pertama-tama saya menggabungkan tiga opsi pendek yang tidak memiliki nilai bersama; -znrhanyalah cara singkat untuk mengatakan -z -n -r). Setelah itu -t .(spasi opsional) memberi tahu sortkarakter pembatas bidang dan -k 1,2menentukan nomor bidang: pertama dan kedua ( sortmenghitung bidang dari satu, bukan nol). Ingatlah bahwa catatan sampel untuk direktori saat ini akan terlihat seperti:

1000000000.0000000000../some-file-name

Ini berarti sortakan terlihat pada awalnya 1000000000dan kemudian 0000000000ketika memesan catatan ini. The -npilihan memberitahu sortmenggunakan perbandingan numerik ketika membandingkan nilai-nilai ini, karena kedua nilai adalah angka. Ini mungkin tidak penting karena angkanya panjang tetap tetapi tidak ada salahnya.

Switch lain yang diberikan sortadalah -runtuk "mundur." Secara default output dari jenis numerik akan menjadi angka terendah pertama, -rmengubahnya sehingga daftar angka terendah lalu dan angka tertinggi pertama. Karena angka-angka ini adalah cap waktu yang lebih tinggi berarti lebih baru dan ini menempatkan catatan terbaru di awal daftar.

Hanya bagian-bagian penting

Ketika daftar path file muncul dari sortsana sekarang memiliki jawaban yang diinginkan kami cari tepat di atas. Yang tersisa adalah menemukan cara untuk membuang catatan lain dan menghapus cap waktu. Sayangnya bahkan GNU headdan tailtidak menerima sakelar untuk membuatnya beroperasi pada input tanpa batas nol. Sebaliknya saya menggunakan loop sementara sebagai semacam orang miskin head.

| while IFS= read -r -d '' record

Pertama-tama saya menghapus IFSpengaturan sehingga daftar file tidak mengalami pemisahan kata. Berikutnya saya mengatakan readdua hal: Jangan menginterpretasikan urutan escape pada input ( -r) dan input dibatasi dengan byte nol ( -d); di sini string kosong ''digunakan untuk menunjukkan "no delimiter" alias dibatasi oleh null. Setiap catatan akan dibaca ke dalam variabel recordsehingga setiap kali whileloop berulang itu memiliki cap waktu tunggal dan nama file tunggal. Perhatikan bahwa itu -dadalah ekstensi GNU; jika Anda hanya memiliki standar readteknik ini tidak akan berhasil dan Anda memiliki sedikit bantuan.

Kita tahu bahwa recordvariabel memiliki tiga bagian untuk itu, semua dibatasi oleh karakter titik. Dengan menggunakan cututilitas, dimungkinkan untuk mengekstrak sebagian darinya.

printf '%s' "$record" | cut -d. -f3-

Di sini seluruh catatan dilewatkan ke printfdan dari sana disalurkan ke cut; di bash Anda bisa menyederhanakan ini lebih lanjut menggunakan disini tali untuk cut -d. -3f- <<<"$record"untuk kinerja yang lebih baik. Kami memberi tahu cutdua hal: Pertama dengan -ditu harus pembatas khusus untuk mengidentifikasi bidang (seperti dengan sortpembatas .digunakan). Kedua cutdiperintahkan -funtuk mencetak hanya nilai-nilai dari bidang tertentu; daftar bidang diberikan sebagai rentang 3-yang menunjukkan nilai dari bidang ketiga dan dari semua bidang berikut. Ini berarti bahwa cutakan membaca dan mengabaikan semuanya hingga dan termasuk yang kedua .yang ditemukan dalam catatan dan kemudian akan mencetak sisanya, yang merupakan bagian path file.

Setelah mencetak path file terbaru, Anda tidak perlu melanjutkan: breakkeluar dari loop tanpa membiarkannya pindah ke path file kedua.

Satu-satunya yang tersisa adalah berjalan taildi jalur file yang dikembalikan oleh pipa ini. Anda mungkin telah memperhatikan dalam contoh saya bahwa saya melakukan ini dengan melampirkan pipa dalam sebuah subkulit; apa yang Anda mungkin tidak perhatikan adalah bahwa saya melampirkan subkulit dalam tanda kutip ganda. Ini penting karena pada akhirnya bahkan dengan semua upaya ini agar aman untuk setiap nama file, perluasan subkulit yang tidak dikutip masih dapat merusak banyak hal. Penjelasan lebih rinci tersedia jika Anda tertarik. Aspek penting kedua tetapi mudah diabaikan untuk doa tailadalah bahwa saya memberikan pilihan --untuk itu sebelum memperluas nama file. Ini akan menginstruksikantailbahwa tidak ada lagi opsi yang ditentukan dan semuanya berikut adalah nama file, yang membuatnya aman untuk menangani nama file yang dimulai dengan -.


1
@AashashM: karena Anda mungkin mendapatkan hasil yang "mengejutkan", misalnya jika file memiliki karakter "tidak biasa" dalam namanya (hampir semua karakter legal).
John Zwinck

6
Orang-orang yang menggunakan karakter khusus dalam nama file mereka pantas mendapatkan semua yang mereka dapatkan :-)

6
Melihat paxdiablo membuat pernyataan itu cukup menyakitkan, tetapi kemudian dua orang memberikan suara! Orang yang menulis perangkat lunak buggy dengan sengaja pantas mendapatkan semua yang mereka dapatkan.
John Zwinck

4
Jadi solusi di atas tidak bekerja pada osx karena kurangnya opsi-printf di find, tetapi berikut ini hanya berfungsi pada osx karena perbedaan dalam perintah stat ... mungkin itu masih akan membantu seseorangtail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
audio.zoom

2
"Sayangnya bahkan GNU headdan tailtidak menerima sakelar untuk membuatnya beroperasi pada input tanpa batas nol." Pengganti saya untuk head: … | grep -zm <number> "".
Kamil Maciorowski

22
tail `ls -t | head -1`

Jika Anda khawatir tentang nama file dengan spasi,

tail "`ls -t | head -1`"

1
Tetapi apa yang terjadi ketika file terbaru Anda memiliki spasi atau karakter khusus? Gunakan $ () daripada `` dan kutip subkulit Anda untuk menghindari masalah ini.
phogg

Saya suka ini. Bersih dan sederhana. Seperti seharusnya.

6
Mudah bersih dan sederhana jika Anda berkorban yang kuat dan benar.
phogg

2
Yah, itu tergantung pada apa yang Anda lakukan, sungguh. Sebuah solusi yang selalu bekerja di mana-mana, untuk semua nama file yang mungkin, sangat bagus, tetapi dalam situasi terbatas (file log, misalnya, dengan nama-nama non-aneh yang dikenal) mungkin tidak diperlukan.

Ini adalah solusi terbersih sejauh ini. Terima kasih!
demisx

4

Kamu bisa memakai:

tail $(ls -1t | head -1)

The $()membangun dimulai sub-shell yang menjalankan perintah ls -1t(daftar semua file dalam urutan waktu, satu per baris) dan pipa yang melalui head -1untuk mendapatkan baris pertama (file).

Output dari perintah itu (file terbaru) kemudian diteruskan ke tailuntuk diproses.

Ingatlah bahwa ini berisiko mengambil direktori jika itu adalah entri direktori terbaru yang dibuat. Saya telah menggunakan trik itu dalam alias untuk mengedit file log terbaru (dari set yang berputar) di direktori yang hanya berisi file-file log tersebut.


Tidak -1perlu, lsapakah itu untuk Anda ketika ada di pipa. Bandingkan lsdan ls|cat, misalnya.
Dijeda sampai pemberitahuan lebih lanjut.

Itu mungkin terjadi di Linux. Dalam Unix "benar", proses tidak mengubah perilaku mereka berdasarkan ke mana hasil mereka akan pergi. Itu akan membuat debugging pipa benar-benar menyebalkan :-)

Hmmm, tidak yakin itu benar - ISTR harus mengeluarkan "ls -C" untuk mendapatkan output yang diformat kolom di bawah 4.2BSD ketika memipakan output melalui filter, dan saya cukup yakin apakah di bawah Solaris bekerja dengan cara yang sama. Apa itu "satu, Unix benar"?

Tanda kutip! Tanda kutip! Nama file memiliki spasi di dalamnya!
Norman Ramsey

@TMN: Satu-satunya cara Unix yang sejati adalah tidak bergantung pada ls untuk konsumen non-manusia. "Jika output ke terminal, formatnya ditentukan implementasi." - ini specnya. Jika Anda ingin memastikan Anda harus mengatakan ls -1 atau ls -C.
phogg

4

Pada sistem POSIX, tidak ada cara untuk mendapatkan entri direktori "yang terakhir dibuat". Setiap entri direktori memiliki atime, mtimedan ctime, tetapi bertentangan dengan Microsoft Windows, ctimebukan berarti CreationTime, tetapi "Waktu perubahan status terakhir".

Jadi yang terbaik yang bisa Anda dapatkan adalah "mengekor file yang terakhir diubah", yang dijelaskan dalam jawaban lain. Saya akan pergi untuk perintah ini:

tail -f "$ (ls -tr | sed 1q)"

Perhatikan kutipan di sekitar lsperintah. Ini membuat snippet bekerja dengan hampir semua nama file.


Kerja bagus. Langsung ke intinya. +1
Norman Ramsey

4

Saya hanya ingin melihat perubahan ukuran file yang dapat Anda gunakan menonton.

watch -d ls -l

3

Dalam zsh:

tail *(.om[1])

Lihat: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , di sini mmenunjukkan waktu modifikasi m[Mwhms][-|+]n, dan sebelumnya menunjukkan obahwa ia diurutkan dalam satu cara ( Omengurutkannya dengan cara lain). The .berarti hanya file biasa. Di dalam kurung [1]mengambil item pertama. Untuk memilih tiga digunakan [1,3], untuk mendapatkan penggunaan tertua [-1].

Itu bagus pendek dan tidak digunakan ls.


1

Mungkin ada sejuta cara untuk melakukan ini, tetapi cara saya akan melakukannya adalah ini:

tail `ls -t | head -n 1`

Bit antara backticks (kutipan seperti karakter) ditafsirkan dan hasilnya dikembalikan ke ekor.

ls -t #gets the list of files in time order
head -n 1 # returns the first line only

2
Backticks itu jahat. Gunakan $ () sebagai gantinya.
William Pursell

1

Sederhana:

tail -f /path/to/directory/*

bekerja dengan baik untuk saya.

Masalahnya adalah untuk mendapatkan file yang dihasilkan setelah Anda memulai perintah ekor. Tetapi jika Anda tidak membutuhkannya (karena semua solusi di atas tidak mempedulikannya), tanda bintang hanyalah solusi yang lebih sederhana, IMO.



0

Seseorang mempostingnya, dan kemudian menghapusnya karena suatu alasan, tetapi ini adalah satu-satunya yang berfungsi, jadi ...

tail -f `ls -tr | tail`

Anda harus mengecualikan direktori, bukan?
amit

1
Saya memposting ini awalnya tapi saya menghapusnya karena saya setuju dengan Sorpigal bahwa parsing output dari lsbukan hal yang paling cerdas untuk dilakukan ...
ChristopheD

Saya membutuhkannya cepat dan kotor, tidak ada direktori di dalamnya. Jadi, Jika Anda akan menambahkan jawaban Anda, saya akan menerimanya
Itay Moav -Malimovka

0
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`

Penjelasan:

  • ls -lt: Daftar semua file dan direktori yang diurutkan berdasarkan waktu modifikasi
  • grep -v ^ d: kecualikan direktori
  • head -2 dan seterusnya: parsing nama file yang dibutuhkan

1
+1 untuk pandai, -2 untuk parsing output ls, -1 untuk tidak mengutip subkulit, -1 untuk asumsi ajaib "bidang 8" (itu tidak portabel!) Dan akhirnya -1 untuk terlalu pintar . Skor keseluruhan: -4.
phogg

@Sorpigal Setuju. Senang menjadi contoh buruk.
amit

ya tidak membayangkan akan salah pada begitu banyak penting
amit

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.