Fungsi Bash untuk menemukan pola pencocokan file terbaru

141

Di Bash, saya ingin membuat fungsi yang mengembalikan nama file dari file terbaru yang cocok dengan pola tertentu. Sebagai contoh, saya memiliki direktori file seperti:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

Saya ingin file terbaru yang dimulai dengan 'b2'. Bagaimana saya melakukan ini di bash? Saya perlu memiliki ini di ~/.bash_profileskrip saya .

jlconlin
sumber
4
lihat superuser.com/questions/294161/… untuk petunjuk jawaban lebih lanjut. Penyortiran adalah langkah kunci untuk mendapatkan file terbaru Anda
Wolfgang Fahl

Jawaban:

229

The lsperintah memiliki parameter -tuntuk mengurutkan berdasarkan waktu. Anda kemudian dapat mengambil yang pertama (terbaru) dengan head -1.

ls -t b2* | head -1

Tetapi waspadalah: Mengapa Anda tidak harus menguraikan output ls

Pendapat pribadi saya: parsing lshanya berbahaya ketika nama file dapat berisi karakter lucu seperti spasi atau baris baru. Jika Anda dapat menjamin bahwa nama file tidak akan mengandung karakter lucu maka parsing lscukup aman.

Jika Anda sedang mengembangkan skrip yang dimaksudkan untuk dijalankan oleh banyak orang pada banyak sistem dalam banyak situasi yang berbeda maka saya sangat merekomendasikan untuk tidak melakukan parse ls.

Berikut ini cara melakukannya "benar": Bagaimana saya dapat menemukan file terbaru (terbaru, paling awal, terlama) dalam direktori?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done
lesmana
sumber
8
Catatan untuk orang lain: jika Anda melakukan ini untuk direktori, Anda akan menambahkan opsi -d ke ls, seperti ini 'ls -td <pattern> | kepala -1 '
ken.ganong
5
The LS parsing Link mengatakan tidak untuk melakukan ini dan merekomendasikan metode di BashFAQ 99 . Saya mencari 1-liner daripada sesuatu yang tahan peluru untuk dimasukkan ke dalam skrip, jadi saya akan terus menguraikan secara tidak aman seperti @lesmana.
Eponim
1
@Eponymous: Jika Anda mencari satu liner tanpa menggunakan rapuh ls, printf "%s\n" b2* | head -1akan melakukannya untuk Anda.
David Ongaro
2
@ DavidvidOngaro Pertanyaannya tidak mengatakan bahwa nama file adalah nomor versi. Ini tentang waktu modifikasi. Bahkan dengan asumsi nama file b2.10_5_2membunuh solusi ini.
Eponim
1
Satu liner Anda memberi saya jawaban yang benar, tetapi cara "benar" sebenarnya memberi saya file tertua . Ada yang tahu kenapa?
NewNameStat
15

Kombinasi finddan lsberfungsi dengan baik untuk

  • nama file tanpa baris baru
  • jumlah file tidak terlalu besar
  • nama file tidak terlalu panjang

Solusinya:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

Mari kita jabarkan:

Dengan findkita dapat mencocokkan semua file menarik seperti ini:

find . -name "my-pattern" ...

lalu menggunakan -print0kita bisa memberikan semua nama file dengan aman ke yang lsseperti ini:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

findparameter dan pola pencarian tambahan dapat ditambahkan di sini

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

ls -takan mengurutkan file berdasarkan waktu modifikasi (terbaru terlebih dahulu) dan mencetaknya satu per satu. Anda dapat menggunakannya -cuntuk mengurutkan berdasarkan waktu pembuatan. Catatan : ini akan pecah dengan nama file yang mengandung baris baru.

Akhirnya head -1memberi kami file pertama dalam daftar diurutkan.

Catatan: xargs gunakan batas sistem untuk ukuran daftar argumen. Jika ukuran ini melebihi, xargsakan memanggil lsbeberapa kali. Ini akan mematahkan penyortiran dan mungkin juga hasil akhir. Lari

xargs  --show-limits

untuk memeriksa batas pada sistem Anda.

Catatan 2: gunakan find . -maxdepth 1 -name "my-pattern" -print0jika Anda tidak ingin mencari file melalui subfolder.

Catatan 3: Seperti yang ditunjukkan oleh @starfry - -rargumen untuk xargsmencegah panggilan ls -1 -t, jika tidak ada file yang cocok dengan find. Terima kasih atas sarannya.

Boris Brodski
sumber
2
Ini lebih baik daripada solusi berbasis ls, karena berfungsi untuk direktori dengan file yang sangat banyak, di mana ls tersedak.
Marcin Zukowski
find . -name "my-pattern" ... -print0memberi sayafind: paths must precede expression: `...'
Jaakko
Oh! ...singkatan dari "lebih banyak parameter". Abaikan saja, jika Anda tidak membutuhkannya.
Boris Brodski
2
Saya menemukan bahwa ini dapat mengembalikan file yang tidak cocok dengan pola jika tidak ada file yang cocok dengan pola. Itu terjadi karena find melewatkan apa-apa ke xargs yang kemudian memanggil ls tanpa daftar file, menyebabkannya bekerja pada semua file. Solusinya adalah menambahkan -rbaris perintah xargs yang memberi tahu xargs untuk tidak menjalankan baris perintahnya jika tidak menerima apa pun pada input standarnya.
starfry
@ starfry terima kasih! Tangkapan bagus. Saya menambahkan -rjawaban.
Boris Brodski
7

Ini adalah implementasi yang mungkin dari fungsi Bash yang diperlukan:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

Hanya menggunakan Bash bawaan, dan harus menangani file yang namanya berisi baris baru atau karakter tidak biasa lainnya.

pjh
sumber
1
Anda dapat menggunakan nullglob_shopt=$(shopt -p nullglob)dan kemudian $nullglobuntuk mengembalikan nullglobbagaimana sebelumnya.
gniourf_gniourf
Saran dari @gniourf_gniourf untuk menggunakan $ (shopt -p nullglob) adalah saran yang bagus. Saya biasanya mencoba menghindari penggunaan substitusi perintah ( $()atau backticks) karena lambat, terutama di bawah Cygwin, bahkan ketika perintah hanya menggunakan builtin. Juga, konteks subkulit di mana perintah dijalankan kadang-kadang dapat menyebabkan mereka berperilaku dengan cara yang tidak terduga. Saya juga mencoba untuk menghindari menyimpan perintah dalam variabel (seperti nullglob_shopt) karena hal-hal yang sangat buruk dapat terjadi jika Anda mendapatkan nilai dari variabel yang salah.
pjh
Saya menghargai perhatian terhadap detail yang dapat menyebabkan kegagalan jelas ketika diabaikan. Terima kasih!
Ron Burk
Saya suka Anda menggunakan cara yang lebih unik untuk menyelesaikan masalah! Sudah pasti bahwa di Unix / Linux ada lebih dari satu cara untuk 'menguliti cat!'. Bahkan jika ini membutuhkan lebih banyak kerja, ia memiliki manfaat menunjukkan konsep orang. Punya +1!
Pryftan
3

Nama file yang tidak biasa (seperti file yang berisi \nkarakter yang valid dapat mendatangkan malapetaka dengan parsing semacam ini. Berikut cara untuk melakukannya di Perl:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

Itu transformasi Schwartzian yang digunakan di sana.

glenn jackman
sumber
1
Semoga schwartz bersamamu!
Nathan Monteleone
jawaban ini mungkin berhasil tetapi saya tidak akan mempercayainya mengingat dokumentasi yang buruk.
Wolfgang Fahl
1

Anda dapat menggunakan statglob file dan menghias-sort-undecorate dengan waktu file yang ditambahkan di bagian depan:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-
dawg
sumber
nggak. "stat: tidak dapat membaca informasi sistem file untuk '% m% t% N': Tidak ada file atau direktori seperti itu"
Ken Ingram
Saya pikir ini mungkin untuk versi Mac / FreeBSD stat, jika saya mengingat opsinya dengan benar. Untuk mendapatkan hasil serupa di platform lain, Anda bisa menggunakanstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash
1

Mantra fungsi ilmu hitam bagi mereka yang menginginkan find ... xargs ... head ...solusi di atas, tetapi dalam bentuk fungsi yang mudah digunakan sehingga Anda tidak perlu berpikir:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

Cetakan:

file2.txt

Yang mana:

Nama file dengan stempel waktu modifikasi tertua dari file di bawah direktori yang diberikan cocok dengan pola yang diberikan.

Eric Leschinski
sumber
1

Gunakan perintah find.

Dengan asumsi Anda menggunakan Bash 4.2+, gunakan -printf '%T+ %p\n'untuk nilai cap waktu file.

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Contoh:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Untuk skrip yang lebih bermanfaat, lihat skrip temukan-terbaru di sini: https://github.com/l3x/helpers

l3x
sumber
untuk bekerja dengan nama file yang berisi spasi ubah cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka
0

Ada cara yang jauh lebih efisien untuk mencapai ini. Pertimbangkan perintah berikut:

find . -cmin 1 -name "b2*"

Perintah ini menemukan file terbaru yang diproduksi tepat satu menit yang lalu dengan pencarian wildcard pada "b2 *". Jika Anda ingin file dari dua hari terakhir maka Anda akan lebih baik menggunakan perintah di bawah ini:

find . -mtime 2 -name "b2*"

"." mewakili direktori saat ini. Semoga ini membantu.

Naufal
sumber
9
Ini sebenarnya tidak menemukan "pola pencocokan file terbaru" ... itu hanya menemukan semua pola pencocokan file yang dibuat satu menit yang lalu, atau diubah dua hari yang lalu.
GnP
Jawaban ini didasarkan pada pertanyaan yang diajukan. Selain itu, Anda dapat mengubah perintah untuk melihat file terbaru yang datang sekitar satu hari yang lalu. Itu tergantung pada apa yang Anda coba lakukan.
Naufal
"tweaker" bukanlah jawabannya. itu seperti memposting ini sebagai jawaban: "Hanya mengubah perintah find dan menemukan jawabannya tergantung pada apa yang ingin Anda lakukan".
Kennet Celeste
Tidak yakin tentang komentar yang tidak perlu. Jika Anda merasa jawaban saya tidak mendukung, berikan alasan yang tepat mengapa jawaban saya tidak masuk akal dengan CONTOH. Jika tidak dapat melakukannya, maka tolong jangan berkomentar lebih lanjut.
Naufal
1
Solusi Anda mengharuskan Anda untuk mengetahui kapan file terbaru dibuat. Itu tidak ada dalam pertanyaan jadi tidak, jawaban Anda tidak didasarkan pada pertanyaan yang diajukan.
Bloke Down The Pub