Parsing output ls
adalah tidak dapat diandalkan .
Sebagai gantinya, gunakan find
untuk mencari file dan sort
memesannya dengan stempel waktu. Sebagai contoh:
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
# do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
Apa yang sedang dilakukan semua ini?
Pertama, find
perintah menempatkan semua file dan direktori di direktori saat ini ( .
), tetapi tidak di subdirektori dari direktori saat ini ( -maxdepth 1
), kemudian mencetak:
- Stempel waktu
- Sebuah ruang
- Jalur relatif ke file
- Karakter NULL
Stempel waktu itu penting. Penentu %T@
format untuk -printf
dipecah menjadi T
, yang menunjukkan "Waktu modifikasi terakhir" dari file (mtime) dan @
, yang menunjukkan "Detik sejak 1970", termasuk detik pecahan.
Ruang hanyalah pembatas yang sewenang-wenang. Path lengkap ke file adalah agar kita dapat merujuknya nanti, dan karakter NULL adalah terminator karena itu adalah karakter ilegal dalam nama file dan dengan demikian memberi tahu kami bahwa kami mencapai ujung path ke mengajukan.
Saya telah menyertakan 2>/dev/null
agar file yang pengguna tidak memiliki izin untuk mengaksesnya dikecualikan, tetapi pesan kesalahan tentang mereka yang dikecualikan ditekan.
Hasil dari find
perintah adalah daftar semua direktori di direktori saat ini. Daftar ini disalurkan ke sort
yang diperintahkan untuk:
-z
Perlakukan NULL sebagai karakter terminator garis alih-alih baris baru.
-n
Sortir secara numerik
Karena detik-sejak-1970 selalu naik, kami ingin file yang cap waktu-nya adalah angka terkecil. Hasil pertama dari sort
akan menjadi garis yang berisi stempel waktu bernomor terkecil. Yang tersisa hanyalah mengekstrak nama file.
Hasil dari find
, sort
pipeline dilewatkan melalui proses substitusi ke while
tempat itu dibaca seolah-olah itu adalah file di stdin. while
pada gilirannya memanggil read
untuk memproses input.
Dalam konteks read
kami mengatur IFS
variabel menjadi tidak ada, yang berarti spasi tidak akan ditafsirkan secara tidak tepat sebagai pembatas. read
dikatakan -r
, yang menonaktifkan ekspansi, dan -d $'\0'
, yang membuat pembatas akhir-line NULL, cocok dengan output dari find
, sort
pipeline kami.
Potongan data pertama, yang mewakili jalur file tertua yang didahului oleh stempel waktu dan spasinya, dibaca ke dalam variabel line
. Selanjutnya, substitusi parameter digunakan dengan ekspresi #*
, yang hanya mengganti semua karakter dari awal string hingga spasi pertama, termasuk spasi, tanpa spasi. Ini menghilangkan tanda waktu modifikasi, hanya menyisakan path lengkap ke file.
Pada titik ini nama file disimpan $file
dan Anda dapat melakukan apa pun yang Anda suka dengannya. Setelah selesai melakukan sesuatu dengan $file
para while
pernyataan kehendak loop dan read
perintah akan dieksekusi lagi, penggalian bongkahan berikutnya dan nama file selanjutnya.
Apakah tidak ada cara yang lebih sederhana?
Tidak. Cara-cara yang lebih sederhana itu buggy.
Jika Anda menggunakan ls -t
dan mem-pipe ke head
atau tail
(atau apa saja ) Anda akan merusak file dengan baris baru dalam nama file. Jika Anda mv $(anything)
kemudian file dengan spasi putih dalam nama akan menyebabkan kerusakan. Jika Anda mv "$(anything)"
kemudian file dengan trailing baris baru pada nama akan menyebabkan kerusakan. Jika Anda read
tanpa -d $'\0'
maka Anda akan merusak file dengan spasi putih di nama mereka.
Mungkin dalam kasus-kasus tertentu Anda tahu pasti bahwa cara yang lebih sederhana sudah cukup, tetapi Anda tidak boleh menulis asumsi seperti itu di skrip jika Anda dapat menghindari melakukannya.
Larutan
#!/usr/bin/env bash
# move to the first argument
dest="$1"
# move from the second argument or .
source="${2-.}"
# move the file count in the third argument or 20
limit="${3-20}"
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
echo mv "$file" "$dest"
let limit-=1
[[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
Sebut seperti:
move-oldest /mnt/backup/ /var/log/foo/ 20
Untuk memindahkan 20 file terlama dari /var/log/foo/
ke /mnt/backup/
.
Perhatikan bahwa saya termasuk file dan direktori. Untuk file hanya menambah -type f
ke find
doa.
Terima kasih
Terima kasih kepada enzotib dan Павел Танков untuk peningkatan pada jawaban ini.