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> ~/
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> ~/
Jawaban:
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
).
a=(*)
Ini menciptakan array a
dengan 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.
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
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 cd
ke 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.
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 a
didefinisikan. Sebagai contoh:
LC_ALL=C a=(*)
Anda dapat mengetahui lokasi Anda saat ini dengan menjalankan locale
perintah.
Jika Anda menggunakan zsh
Anda 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 On
mengurutkan 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 cp
perintah untuk file memperlakukan yang namanya dimulai dengan -
benar, sehingga:
cp -- *(.On[1,4]) ~
Tambahkan D
kualifikasi jika Anda juga ingin mempertimbangkan file tersembunyi (dot-file):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) adalah perilaku default, jadi tidak perlu menentukan ini, dan -
di depan angka-angkanya menghitung nama file dari bawah.
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.
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 bash
solusi array-spesifik yang ditawarkan, tetapi harus portabel untuk setiap shell yang kompatibel dengan POSIX. Beberapa catatan tentang kedua metode:
Adalah penting bahwa Anda memimpin cp
argumen cp --
Anda tanpa mendapatkan salah satu dari .
titik atau /
di kepala setiap nama. Jika Anda gagal melakukan hal ini Anda berisiko terkemuka -
dasbor di cp
argumen pertama 's yang dapat diartikan sebagai pilihan dan menyebabkan operasi gagal atau sebaliknya membuat hasil yang tidak diinginkan.
set -- ./*
atau array=(./*)
.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- shift
satunya yang terjadi jika setidaknya ada 4 item dalam array arg - dan kemudian hanya menggeser argumen pertama yang menghasilkan surplus 4 ..
set 1 2 3 4 5
kasus itu akan menggeser 1
jauh, tetapi dalam sebuah set 1 2 3
kasus, itu tidak akan mengubah apa pun.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 sed
subtitusi-gaya ke semua nama file saat disalin.
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) ~/
a_
ke SEMUA empat file? Saya sudah mencoba echo "a_$(ls | tail -n 4)"
, tetapi hanya menambahkan file pertama.
ls
output 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".
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"
find
memberi Anda file dalam urutan yang diinginkan? Bagaimana Anda memastikan bahwa file yang mengandung karakter spasi putih (termasuk baris baru) dalam namanya ditangani dengan benar?
LC_ALL=C