Bagaimana cara memilih file acak dari direktori di bash?


Jawaban:


185

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

1
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.
Yann Vernier


115

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:

find dirname -type f | shuf -n 5

4
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.

    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 Ntidak datang langsung dari input pengguna tanpa diperiksa secara menyeluruh!

    N=42
    a=( * )
    eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
    

    Saya pribadi tidak suka evaldan 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 buruklatihan, 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.
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
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.


1
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.


2

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.


1

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

1

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

0

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;

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.