Bagaimana cara mendapatkan file N terakhir dalam direktori?


15

Saya memiliki banyak file yang dipesan berdasarkan nama file dalam direktori. Saya ingin menyalin file N akhir (katakanlah, N = 4) ke direktori rumah saya. Bagaimana saya harus melakukannya?

cp ./<the final 4 files> ~/


1
Pada semua jawaban di bawah, Anda mungkin perlu menambahkan "LC_ALL = ..." di depan perintah menggunakan rentang, sehingga rentang mereka menggunakan lokal yang tepat untuk Anda (lokal dapat berubah, dan urutan karakter kemudian dapat berubah banyak, mis. di beberapa tempat, [az] dapat berisi semua huruf alfabet kecuali untuk "Z", karena huruf-hurufnya dipesan: aAbBcC .... zZ. Variasi lain ada (huruf aksentuasi, dan susunan yang lebih eksotis) ) Pilihan yang biasa adalah:LC_ALL=C
Olivier Dulac

Jawaban:


23

Ini dapat dengan mudah dilakukan dengan array bash / ksh93 / zsh:

a=(*)
cp -- "${a[@]: -4}" ~/

Ini berfungsi untuk semua nama file yang tidak tersembunyi walaupun mengandung spasi, tab, baris baru, atau karakter sulit lainnya (dengan asumsi setidaknya ada 4 file yang tidak disembunyikan di direktori saat ini dengan bash ).

Bagaimana itu bekerja

  • a=(*)

    Ini menciptakan array adengan semua nama file. Nama file yang dikembalikan oleh bash diurutkan berdasarkan abjad. (Saya berasumsi bahwa ini adalah apa yang Anda maksud dengan "dipesan dengan nama file." )

  • ${a[@]: -4}

    Ini mengembalikan empat elemen terakhir array a(asalkan array mengandung setidaknya 4 elemen bash).

  • cp -- "${a[@]: -4}" ~/

    Ini menyalin empat nama file terakhir ke direktori home Anda.

Untuk menyalin dan mengganti nama secara bersamaan

Ini akan menyalin empat file terakhir hanya ke direktori home dan, pada saat yang sama, menambahkan string a_ke nama file yang disalin:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done

Salin dari direktori yang berbeda dan ganti nama juga

Jika kita menggunakan a=(./some_dir/*)alih-alih a=(*), maka kita memiliki masalah direktori yang dilampirkan ke nama file. Salah satu solusinya adalah:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done

Solusi lain adalah dengan menggunakan subkulit dan cdke direktori dalam subkulit:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 

Ketika subkulit selesai, shell mengembalikan kita ke direktori asli.

Memastikan bahwa pemesanan konsisten

Pertanyaannya menanyakan file "dipesan berdasarkan nama file". Pesanan itu, Olivier Dulac tunjukkan dalam komentar, akan bervariasi dari satu lokal ke yang lain. Jika penting untuk mendapatkan hasil yang independen dari pengaturan mesin, maka yang terbaik adalah menentukan lokal secara eksplisit ketika array adidefinisikan. Sebagai contoh:

LC_ALL=C a=(*)

Anda dapat mengetahui lokasi Anda saat ini dengan menjalankan localeperintah.


9

Jika Anda menggunakan zshAnda dapat melampirkan dalam tanda kurung ()daftar yang disebut kualifikasi glob yang memilih file yang diinginkan. Dalam kasus Anda, itu akan menjadi

cp *(On[1,4]) ~/

Di sini Onmengurutkan nama file secara alfabet dengan urutan terbalik dan [1,4]hanya mengambil 4 pertama dari mereka.

Anda dapat membuat ini lebih kuat dengan memilih hanya file biasa (tidak termasuk direktori, pipa dll) dengan ., dan juga dengan menambahkan --ke cpperintah untuk file memperlakukan yang namanya dimulai dengan -benar, sehingga:

cp -- *(.On[1,4]) ~

Tambahkan Dkualifikasi jika Anda juga ingin mempertimbangkan file tersembunyi (dot-file):

cp -- *(D.On[1,4]) ~

2
Atau: *([-4,-1]).
Stéphane Chazelas

2
@ StéphaneChazelas ya, mari kita perjelas untuk pembaca masa depan yang mengurutkan menurut abjad ke arah normal ( on) adalah perilaku default, jadi tidak perlu menentukan ini, dan -di depan angka-angkanya menghitung nama file dari bawah.
jimmij

7

Berikut adalah solusi menggunakan perintah bash yang sangat sederhana.

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

Penjelasan:

find . -maxdepth 1 -type f

Temukan semua file di direktori saat ini.

sort

Mengurutkan menurut abjad.

tail -n 4

Hanya tampilkan 4 baris terakhir.

while read -r file; do cp "$file" ~/; done

Mengulang setiap baris melakukan perintah salin.


6

Selama Anda menemukan shell sortable, Anda bisa melakukan:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir

Ini sangat mirip dengan bashsolusi array-spesifik yang ditawarkan, tetapi harus portabel untuk setiap shell yang kompatibel dengan POSIX. Beberapa catatan tentang kedua metode:

  1. Adalah penting bahwa Anda memimpin cpargumen cp --Anda tanpa mendapatkan salah satu dari .titik atau /di kepala setiap nama. Jika Anda gagal melakukan hal ini Anda berisiko terkemuka -dasbor di cpargumen pertama 's yang dapat diartikan sebagai pilihan dan menyebabkan operasi gagal atau sebaliknya membuat hasil yang tidak diinginkan.

    • Bahkan jika bekerja dengan direktori saat ini sebagai direktori sumber ini dengan mudah dilakukan seperti ... set -- ./*atau array=(./*).
  2. Adalah penting ketika menggunakan kedua metode untuk memastikan Anda memiliki setidaknya banyak item dalam array arg Anda saat Anda mencoba untuk menghapus - Saya melakukannya di sini dengan ekspansi matematika. Satu- shiftsatunya yang terjadi jika setidaknya ada 4 item dalam array arg - dan kemudian hanya menggeser argumen pertama yang menghasilkan surplus 4 ..

    • Jadi dalam suatu set 1 2 3 4 5kasus itu akan menggeser 1jauh, tetapi dalam sebuah set 1 2 3kasus, itu tidak akan mengubah apa pun.
    • Misalnya: a=(1 2 3); echo "${a[@]: -4}"mencetak garis kosong.

Jika Anda menyalin dari satu direktori ke direktori lain, Anda dapat menggunakan pax:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir

... yang akan menerapkan sedsubtitusi-gaya ke semua nama file saat disalin.


4

Jika hanya ada file dan namanya tidak mengandung spasi atau baris baru (dan Anda belum dimodifikasi $IFS) atau karakter glob (atau beberapa karakter yang tidak dapat dicetak dengan beberapa implementasi ls), dan jangan mulai dengan ., maka Anda dapat melakukan ini :

cp -- $(ls | tail -n 4) ~/

Ini disebut substitusi perintah dan sangat berguna. tldp.org/LDP/abs/html/commandsub.html#CSPARENS
iyrin

Terima kasih! Berhasil. Apakah ada jauh untuk dengan cepat menambahkan awalan a_ke SEMUA empat file? Saya sudah mencoba echo "a_$(ls | tail -n 4)", tetapi hanya menambahkan file pertama.
Sibbs Gambling

@SibbsGambling Pendekatan saya tidak cocok untuk itu tetapi jawaban John1024 dapat dengan mudah disesuaikan dengan itu.
Hauke ​​Laging

5
Secara umum parsing lsoutput dianggap sebagai bentuk buruk karena biasanya gagal mengerikan pada nama file yang tidak hanya berisi spasi, tetapi juga tab, baris baru, atau karakter lain yang valid tetapi "sulit".
HBruijn

2
@ HaukeLaging Sekalipun saat ini tidak menjadi masalah (karena saya tidak memiliki nama samaran "rumit" di direktori saya), saya mungkin punya dalam 2 tahun, dan tiba-tiba segalanya jatuh pada kaki saya ...
glglgl

1

Ini adalah solusi bash sederhana tanpa kode loop apa pun. Salin 4 file terakhir dari direktori saat ini ke tujuan:

$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"

1
Bagaimana Anda memastikan itu findmemberi Anda file dalam urutan yang diinginkan? Bagaimana Anda memastikan bahwa file yang mengandung karakter spasi putih (termasuk baris baru) dalam namanya ditangani dengan benar?
Kusalananda
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.