Dalam shell, bagaimana saya bisa tail
membuat file terbaru dalam direktori?
Dalam shell, bagaimana saya bisa tail
membuat file terbaru dalam direktori?
Jawaban:
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 .bashrc
dan 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 tail
benar. 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.
Bagi yang penasaran ini adalah penjelasan yang melelahkan tentang cara kerjanya, mengapa ini aman dan mengapa metode lain mungkin tidak.
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 find
pendukung -print0
seperti GNU:
find . -print0
Tetapi daftar ini masih tidak memberi tahu kami mana yang terbaru, jadi kami perlu memasukkan informasi itu. Saya memilih untuk menggunakan -printf
switch find yang memungkinkan saya menentukan data apa yang muncul di output. Tidak semua versi find
dukungan -printf
(ini bukan standar) tetapi GNU find tidak. Jika Anda menemukan diri -printf
Anda tanpa Anda harus mengandalkan pada -exec stat {} \;
titik mana Anda harus melepaskan semua harapan portabilitas karena stat
tidak 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 1
mencegah find
dari daftar isi sub direktori dan \! -type d
melewatkan 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.
Secara default sort
mengharapkan inputnya untuk menjadi catatan dibatasi-baris baru. Jika Anda memiliki GNU, sort
Anda dapat memintanya untuk mengharapkan catatan yang dibatasi-nol alih-alih dengan menggunakan -z
sakelar .; untuk standar sort
tidak 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 sort
dua 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; -znr
hanyalah cara singkat untuk mengatakan -z -n -r
). Setelah itu -t .
(spasi opsional) memberi tahu sort
karakter pembatas bidang dan -k 1,2
menentukan nomor bidang: pertama dan kedua ( sort
menghitung bidang dari satu, bukan nol). Ingatlah bahwa catatan sampel untuk direktori saat ini akan terlihat seperti:
1000000000.0000000000../some-file-name
Ini berarti sort
akan terlihat pada awalnya 1000000000
dan kemudian 0000000000
ketika memesan catatan ini. The -n
pilihan memberitahu sort
menggunakan 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 sort
adalah -r
untuk "mundur." Secara default output dari jenis numerik akan menjadi angka terendah pertama, -r
mengubahnya 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.
Ketika daftar path file muncul dari sort
sana 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 head
dan tail
tidak 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 IFS
pengaturan sehingga daftar file tidak mengalami pemisahan kata. Berikutnya saya mengatakan read
dua 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 record
sehingga setiap kali while
loop berulang itu memiliki cap waktu tunggal dan nama file tunggal. Perhatikan bahwa itu -d
adalah ekstensi GNU; jika Anda hanya memiliki standar read
teknik ini tidak akan berhasil dan Anda memiliki sedikit bantuan.
Kita tahu bahwa record
variabel memiliki tiga bagian untuk itu, semua dibatasi oleh karakter titik. Dengan menggunakan cut
utilitas, dimungkinkan untuk mengekstrak sebagian darinya.
printf '%s' "$record" | cut -d. -f3-
Di sini seluruh catatan dilewatkan ke printf
dan 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 cut
dua hal: Pertama dengan -d
itu harus pembatas khusus untuk mengidentifikasi bidang (seperti dengan sort
pembatas .
digunakan). Kedua cut
diperintahkan -f
untuk 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 cut
akan 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: break
keluar dari loop tanpa membiarkannya pindah ke path file kedua.
Satu-satunya yang tersisa adalah berjalan tail
di 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 tail
adalah bahwa saya memberikan pilihan --
untuk itu sebelum memperluas nama file. Ini akan menginstruksikantail
bahwa tidak ada lagi opsi yang ditentukan dan semuanya berikut adalah nama file, yang membuatnya aman untuk menangani nama file yang dimulai dengan -
.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
head
dan tail
tidak menerima sakelar untuk membuatnya beroperasi pada input tanpa batas nol." Pengganti saya untuk head
: … | grep -zm <number> ""
.
tail `ls -t | head -1`
Jika Anda khawatir tentang nama file dengan spasi,
tail "`ls -t | head -1`"
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 -1
untuk mendapatkan baris pertama (file).
Output dari perintah itu (file terbaru) kemudian diteruskan ke tail
untuk 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.
-1
perlu, ls
apakah itu untuk Anda ketika ada di pipa. Bandingkan ls
dan ls|cat
, misalnya.
Pada sistem POSIX, tidak ada cara untuk mendapatkan entri direktori "yang terakhir dibuat". Setiap entri direktori memiliki atime
, mtime
dan ctime
, tetapi bertentangan dengan Microsoft Windows, ctime
bukan 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 ls
perintah. Ini membuat snippet bekerja dengan hampir semua nama file.
Dalam zsh
:
tail *(.om[1])
Lihat: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , di sini m
menunjukkan waktu modifikasi m[Mwhms][-|+]n
, dan sebelumnya menunjukkan o
bahwa ia diurutkan dalam satu cara ( O
mengurutkannya 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
.
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
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.
Seseorang mempostingnya, dan kemudian menghapusnya karena suatu alasan, tetapi ini adalah satu-satunya yang berfungsi, jadi ...
tail -f `ls -tr | tail`
ls
bukan hal yang paling cerdas untuk dilakukan ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Penjelasan:
tail "$(ls -1tr|tail -1)"