Bagaimana cara memilih file acak dari direktori di bash?
154
Saya memiliki direktori dengan sekitar 2000 file. Bagaimana cara memilih sampel Nfile secara acak dengan menggunakan skrip bash atau daftar perintah yang disalurkan?
Keren, tidak tahu sort -R; Saya menggunakan bogosort sebelumnya :-p
alex
5
sort: opsi tidak valid - R Coba `sort --help 'untuk informasi selengkapnya.
2
Sepertinya tidak berfungsi untuk file yang memiliki spasi di dalamnya.
Houshalter
Ini harus berfungsi untuk file dengan spasi (jalur proses pipa). Ini tidak berfungsi untuk nama dengan baris baru di dalamnya. Hanya penggunaan "$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=COUNTnilainya untuk mengembalikan jumlah baris yang diinginkan. Misalnya untuk mengembalikan 5 nama file acak yang akan Anda gunakan:
OP ingin memilih Nfile secara acak, jadi penggunaannya 1agak menyesatkan.
aioobe
4
Jika Anda memiliki nama file dengan baris baru:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek
5
bagaimana jika saya harus menyalin file yang dipilih secara acak ini ke folder lain? bagaimana cara melakukan operasi pada file yang dipilih secara acak ini?
Rishabh Agrahari
18
Berikut adalah beberapa kemungkinan yang tidak mengurai keluaran lsdan yang 100% aman terkait file dengan spasi dan simbol lucu di namanya. Semuanya akan mengisi array randfdengan 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 Nperlu diketahui sebelumnya. Di sini saya memilih N = 42.
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 Ntidak datang langsung dari input pengguna tanpa diperiksa secara menyeluruh!
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.
Pertama dan kedua menghasilkan "substitusi yang buruk"; itu tidak suka "{1..42}"bagian meninggalkan jejak "1". Juga, $RANDOMhanya 15 bit dan metode ini tidak akan bekerja dengan lebih dari 32767 file untuk dipilih.
Anda tidak boleh mengandalkan keluaran dari ls. Ini tidak akan berfungsi jika misalnya nama file berisi baris baru.
bfontaine
3
@bfontaine Anda tampak dihantui oleh baris baru dalam nama file :). Apakah mereka benar-benar biasa? Dengan kata lain, apakah ada alat yang membuat file dengan baris baru di namanya? Karena sebagai pengguna sangat sulit untuk membuat nama file seperti itu. Sama untuk file yang berasal dari internet
Ciprian Tomoiagă
3
@CiprianTomoiaga Itulah contoh masalah yang mungkin Anda dapatkan. lstidak 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.
bfontaine
1
lsmungkin termasuk direktori dan baris kosong. Saya akan menyarankan sesuatu seperti itu find . -type f | shuf -n10.
cherdt
9
Solusi sederhana untuk memilih 5file 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 echodengan perintah yang ingin Anda jalankan untuk file Anda.
Nah, bukankah pipa + readmemiliki masalah yang sama dengan penguraian ls? yaitu, membaca baris demi baris, jadi tidak berfungsi untuk file dengan baris baru dalam namanya
Ciprian Tomoiagă
3
Kamu benar. Solusi saya sebelumnya tidak berfungsi untuk nama file yang berisi baris baru dan mungkin merusak orang lain dengan karakter khusus tertentu juga. Saya telah memperbarui jawaban saya untuk menggunakan penghentian nol alih-alih baris baru.
scai
4
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 Nfile / baris, gunakan (catatan Nada 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 tidak berfungsi jika nama file Anda berisi baris baru.
bfontaine
4
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 evaldan 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, $ayang 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 $aarray Anda berisi setiap file di seluruh hierarki.
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.
#!/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))]}"fiexit 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 fileunset -v "array[$randomi]"# set the element at index $randomi to NULL
array=("${array[@]}") # remove NULL elements introduced by unset; copy arraydone
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 ;
elseif [ -f "$TARGET" ] ; then
rm -f $tmpecho$TARGETbreak;
else# is not a regular file, restart:
TARGET="$ROOT"
FILE=""fifidone;
ls | shuf -n 5
Sumber dari Unix StackexchangeJawaban:
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
sumber
"$file"
, tidak ditampilkan, yang peka terhadap spasi.ls
?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
sumber
N
file secara acak, jadi penggunaannya1
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 arrayrandf
dengan daftar file acak. Larik ini mudah dicetak denganprintf '%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 ituN
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.sumber
"{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
sumber
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 itufind . -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.sumber
read
memiliki masalah yang sama dengan penguraianls
? yaitu, membaca baris demi baris, jadi tidak berfungsi untuk file dengan baris baru dalam namanyaJika 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 (catatanN
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
sumber
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 olehls
.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.sumber
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
.sumber
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?
http://www.linuxquestions.org/questions/linux-general-1/is-there-a-bash-command-for-picking-a-random-file-678687/
#!/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
sumber
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
sumber
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;
sumber
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?
sumber