Saya memiliki direktori dengan sekitar 2000 file. Bagaimana cara memilih sampel N
file secara acak dengan menggunakan skrip bash atau daftar perintah yang disalurkan?
ls | shuf -n 5
Sumber dari Unix Stackexchange
Saya memiliki direktori dengan sekitar 2000 file. Bagaimana cara memilih sampel N
file secara acak dengan menggunakan skrip bash atau daftar perintah yang disalurkan?
ls | shuf -n 5
Sumber dari Unix Stackexchange
Jawaban:
Berikut skrip yang menggunakan opsi acak jenis GNU:
ls |sort -R |tail -$N |while read file; do
# Something involving $file, or you can leave
# off the while to just get the filenames
done
"$file"
, tidak ditampilkan, yang peka terhadap spasi.
Anda dapat menggunakan shuf
(dari paket coreutils GNU) untuk itu. Cukup beri makan daftar nama file dan minta untuk mengembalikan baris pertama dari permutasi acak:
ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..
Sesuaikan -n, --head-count=COUNT
nilainya untuk mengembalikan jumlah baris yang diinginkan. Misalnya untuk mengembalikan 5 nama file acak yang akan Anda gunakan:
find dirname -type f | shuf -n 5
N
file secara acak, jadi penggunaannya 1
agak menyesatkan.
find dirname -type f -print0 | shuf -zn1
Berikut adalah beberapa kemungkinan yang tidak mengurai keluaran ls
dan yang 100% aman terkait file dengan spasi dan simbol lucu di namanya. Semuanya akan mengisi array randf
dengan daftar file acak. Larik ini mudah dicetak dengan printf '%s\n' "${randf[@]}"
jika diperlukan.
Yang ini mungkin akan mengeluarkan file yang sama beberapa kali, dan N
perlu diketahui sebelumnya. Di sini saya memilih N = 42.
a=( * )
randf=( "${a[RANDOM%${#a[@]}]"{1..42}"}" )
Fitur ini tidak didokumentasikan dengan baik.
Jika N tidak diketahui sebelumnya, tetapi Anda sangat menyukai kemungkinan sebelumnya, Anda dapat menggunakan eval
. Tapi itu jahat, dan Anda harus benar-benar memastikan itu N
tidak datang langsung dari input pengguna tanpa diperiksa secara menyeluruh!
N=42
a=( * )
eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
Saya pribadi tidak suka eval
dan karenanya jawaban ini!
Hal yang sama menggunakan metode yang lebih mudah (loop):
N=42
a=( * )
randf=()
for((i=0;i<N;++i)); do
randf+=( "${a[RANDOM%${#a[@]}]}" )
done
Jika Anda tidak ingin memiliki beberapa kali file yang sama:
N=42
a=( * )
randf=()
for((i=0;i<N && ${#a[@]};++i)); do
((j=RANDOM%${#a[@]}))
randf+=( "${a[j]}" )
a=( "${a[@]:0:j}" "${a[@]:j+1}" )
done
Catatan . Ini adalah jawaban terlambat untuk posting lama, tetapi jawaban yang diterima tertaut ke halaman eksternal yang menunjukkan burukpestalatihan, dan jawaban lainnya tidak jauh lebih baik karena juga mengurai keluaran ls
. Sebuah komentar atas jawaban yang diterima menunjukkan jawaban yang sangat baik oleh Lhunath yang jelas menunjukkan praktik yang baik, tetapi tidak secara tepat menjawab OP.
"{1..42}"
bagian meninggalkan jejak "1"
. Juga, $RANDOM
hanya 15 bit dan metode ini tidak akan bekerja dengan lebih dari 32767 file untuk dipilih.
ls | shuf -n 10 # ten random files
ls
. Ini tidak akan berfungsi jika misalnya nama file berisi baris baru.
ls
tidak dijamin memberi Anda nama file yang "bersih" jadi Anda tidak boleh mengandalkannya, titik. Fakta bahwa masalah ini jarang atau tidak biasa tidak mengubah masalah; terutama mengingat ada solusi yang lebih baik untuk ini.
ls
mungkin termasuk direktori dan baris kosong. Saya akan menyarankan sesuatu seperti itu find . -type f | shuf -n10
.
Solusi sederhana untuk memilih 5
file acak sambil menghindari parsing ls . Ini juga berfungsi dengan file yang berisi spasi, baris baru, dan karakter khusus lainnya:
shuf -ezn 5 * | xargs -0 -n1 echo
Ganti echo
dengan perintah yang ingin Anda jalankan untuk file Anda.
read
memiliki masalah yang sama dengan penguraian ls
? yaitu, membaca baris demi baris, jadi tidak berfungsi untuk file dengan baris baru dalam namanya
Jika Anda telah menginstal Python (bekerja dengan Python 2 atau Python 3):
Untuk memilih satu file (atau baris dari perintah arbitrer), gunakan
ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"
Untuk memilih N
file / baris, gunakan (catatan N
ada di akhir perintah, ganti ini dengan angka)
ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N
Ini adalah tanggapan yang lebih baru untuk jawaban terlambat @ gniourf_gniourf, yang baru saja saya beri suara positif karena sejauh ini merupakan jawaban terbaik, dua kali lipat. (Sekali untuk menghindari eval
dan sekali untuk penanganan nama file yang aman.)
Tetapi saya butuh beberapa menit untuk menguraikan fitur "tidak terdokumentasi dengan baik" yang digunakan jawaban ini. Jika keterampilan Bash Anda cukup kuat sehingga Anda langsung dapat melihat cara kerjanya, lewati komentar ini. Tapi saya tidak melakukannya, dan setelah melepaskannya saya pikir itu layak untuk dijelaskan.
Fitur # 1 adalah globbing file shell itu sendiri. a=(*)
membuat array, $a
yang anggotanya adalah file di direktori saat ini. Bash memahami semua keanehan nama file, sehingga daftar dijamin benar, dijamin lolos, dll. Tidak perlu khawatir tentang penguraian nama file tekstual yang dikembalikan oleh ls
.
Fitur # 2 adalah perluasan parameter Bash untuk array , satu bersarang di dalam yang lain. Ini dimulai dengan ${#ARRAY[@]}
, yang meluas ke panjang $ARRAY
.
Ekspansi itu kemudian digunakan untuk subskrip array. Cara standar untuk mencari bilangan acak antara 1 dan N adalah dengan mengambil nilai bilangan acak modulo N. Kita menginginkan bilangan acak antara 0 dan panjang larik kita. Inilah pendekatannya, dipecah menjadi dua baris demi kejelasan:
LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
Tetapi solusi ini melakukannya dalam satu baris, menghapus tugas variabel yang tidak perlu.
Fitur # 3 adalah perluasan brace Bash , meskipun saya harus mengakui bahwa saya tidak sepenuhnya memahaminya. Ekspansi brace digunakan, misalnya, untuk menghasilkan daftar 25 file bernama filename1.txt
, filename2.txt
, dll:echo "filename"{1..25}".txt"
.
Ekspresi di dalam subkulit di atas`` "${a[RANDOM%${#a[@]}]"{1..42}"}"
menggunakan trik itu untuk menghasilkan 42 ekspansi terpisah. Ekspansi tanda kurung menempatkan satu digit di antara ]
dan}
, yang pada awalnya saya pikir merupakan subskrip dari array, tetapi jika demikian itu akan didahului oleh titik dua. (Ini juga akan mengembalikan 42 item berturut-turut dari tempat acak dalam larik, yang sama sekali tidak sama dengan mengembalikan 42 item acak dari larik.) Saya pikir itu hanya membuat shell menjalankan ekspansi 42 kali, sehingga mengembalikan 42 item acak dari array. (Tetapi jika seseorang dapat menjelaskannya lebih lengkap, saya ingin mendengarnya.)
Alasan N harus di-hardcode (ke 42) adalah karena ekspansi brace terjadi sebelum ekspansi variabel.
Terakhir, inilah Fitur # 4 , jika Anda ingin melakukan ini secara rekursif untuk hierarki direktori:
shopt -s globstar
a=( ** )
Ini mengaktifkan opsi shell yang menyebabkan **
kecocokan secara rekursif. Sekarang $a
array Anda berisi setiap file di seluruh hierarki.
Jika Anda memiliki lebih banyak file di folder Anda, Anda dapat menggunakan perintah piped di bawah ini yang saya temukan di unix stackexchange .
find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/
Di sini saya ingin menyalin file, tetapi jika Anda ingin memindahkan file atau melakukan sesuatu yang lain, ubah saja perintah terakhir yang pernah saya gunakan cp
.
Ini adalah satu-satunya skrip yang saya bisa bermain bagus dengan bash di MacOS. Saya menggabungkan dan mengedit cuplikan dari dua tautan berikut:
Perintah ls: bagaimana saya bisa mendapatkan daftar jalur lengkap rekursif, satu baris per file?
#!/bin/bash
# Reads a given directory and picks a random file.
# The directory you want to use. You could use "$1" instead if you
# wanted to parametrize it.
DIR="/path/to/"
# DIR="$1"
# Internal Field Separator set to newline, so file names with
# spaces do not break our script.
IFS='
'
if [[ -d "${DIR}" ]]
then
# Runs ls on the given dir, and dumps the output into a matrix,
# it uses the new lines character as a field delimiter, as explained above.
# file_matrix=($(ls -LR "${DIR}"))
file_matrix=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }'))
num_files=${#file_matrix[*]}
# This is the command you want to run on a random file.
# Change "ls -l" by anything you want, it's just an example.
ls -l "${file_matrix[$((RANDOM%num_files))]}"
fi
exit 0
MacOS tidak memiliki perintah sort -R dan shuf , jadi saya memerlukan solusi khusus bash yang mengacak semua file tanpa duplikat dan tidak menemukannya di sini. Solusi ini mirip dengan solusi gniourf_gniourf # 4, tetapi semoga menambahkan komentar yang lebih baik.
Skrip harus mudah dimodifikasi untuk dihentikan setelah N sampel menggunakan penghitung dengan if, atau perulangan for gniourf_gniourf dengan N. $ RANDOM dibatasi hingga ~ 32000 file, tetapi itu harus dilakukan untuk kebanyakan kasus.
#!/bin/bash
array=(*) # this is the array of files to shuffle
# echo ${array[@]}
for dummy in "${array[@]}"; do # do loop length(array) times; once for each file
length=${#array[@]}
randomi=$(( $RANDOM % $length )) # select a random index
filename=${array[$randomi]}
echo "Processing: '$filename'" # do something with the file
unset -v "array[$randomi]" # set the element at index $randomi to NULL
array=("${array[@]}") # remove NULL elements introduced by unset; copy array
done
Saya menggunakan ini: ini menggunakan file sementara tetapi masuk jauh ke dalam direktori sampai menemukan file biasa dan mengembalikannya.
# find for a quasi-random file in a directory tree:
# directory to start search from:
ROOT="/";
tmp=/tmp/mytempfile
TARGET="$ROOT"
FILE="";
n=
r=
while [ -e "$TARGET" ]; do
TARGET="$(readlink -f "${TARGET}/$FILE")" ;
if [ -d "$TARGET" ]; then
ls -1 "$TARGET" 2> /dev/null > $tmp || break;
n=$(cat $tmp | wc -l);
if [ $n != 0 ]; then
FILE=$(shuf -n 1 $tmp)
# or if you dont have/want to use shuf:
# r=$(($RANDOM % $n)) ;
# FILE=$(tail -n +$(( $r + 1 )) $tmp | head -n 1);
fi ;
else
if [ -f "$TARGET" ] ; then
rm -f $tmp
echo $TARGET
break;
else
# is not a regular file, restart:
TARGET="$ROOT"
FILE=""
fi
fi
done;
Bagaimana dengan solusi Perl yang sedikit direkayasa dari Tuan Kang di sini:
Bagaimana saya dapat mengacak baris dari file teks pada baris perintah Unix atau dalam skrip shell?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); cetak @ baris [0..4] '