Bash içindeki bir dizinden rastgele dosyaları nasıl seçebilirim?


Yanıtlar:


180

İşte GNU sort'in rastgele seçeneğini kullanan bir script:

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

Güzel, sıra bilmiyordum -R; Daha önce bogosort kullandım :-p
alex

5
sort: geçersiz seçenek - R Daha fazla bilgi için `` sort --help '' komutunu deneyin.

2
İçinde boşluk olan dosyalar için çalışmıyor gibi görünüyor.
Houshalter

Bu, boşluklu dosyalar için çalışmalıdır (ardışık düzen satırları işler). İçinde satırsonu olan isimler için çalışmaz. Sadece "$file"gösterilmemesi, kullanımı boşluklara karşı hassas olacaktır.
Yann Vernier


108

Bunun için shuf(GNU coreutils paketinden) kullanabilirsiniz. Sadece dosya adlarının bir listesini verin ve ilk satırı rastgele bir permütasyondan döndürmesini isteyin:

ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..

İstenen -n, --head-count=COUNTsatır sayısını döndürmek için değeri ayarlayın . Örneğin, kullanacağınız 5 rastgele dosya adı döndürmek için:

find dirname -type f | shuf -n 5

4
OP Nrastgele dosyaları seçmek istedi , bu yüzden kullanmak 1biraz yanıltıcı.
aioobe

4
Yeni satırlarla dosya adlarınız varsa:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek

5
bu rastgele seçilen dosyaları başka bir klasöre kopyalamam gerekirse ne olur? rastgele seçilen bu dosyalar üzerinde işlemler nasıl yapılır?
Rishabh Agrahari

18

İşte çıktılarını ayrıştırmayan ve lsadlarında boşluk ve komik sembollere sahip dosyalar için% 100 güvenli olan birkaç olasılık . Hepsi bir dizi randfrastgele dosya listesiyle doldurulur . Bu dizi printf '%s\n' "${randf[@]}"gerektiğinde kolayca yazdırılır .

  • Bu dosya aynı dosyayı birkaç kez Nçıkarır ve önceden bilinmesi gerekir. Burada N = 42'yi seçtim.

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

    Bu özellik çok iyi belgelenmemiştir.

  • N önceden bilinmiyorsa, ancak önceki olasılığı gerçekten beğendiyseniz, kullanabilirsiniz eval. Ama bu kötü ve gerçekten Nkontrol edilmeden doğrudan kullanıcı girişinden gelmediğinden emin olmalısınız !

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

    Ben şahsen beğenmedim evalve dolayısıyla bu cevabı!

  • Aynı şey daha basit bir yöntem (bir döngü) kullanarak:

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Aynı dosyanın birkaç katına sahip olmak istemiyorsanız:

    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

Not . Bu eski bir gönderiye geç bir cevaptır, ancak kabul edilen yanıt korkunç gösteren harici bir sayfaya bağlanırve diğer yanıtı da çıktısını ayrıştırdığı için çok daha iyi değildir ls. Kabul edilen cevaba yapılan bir yorum, Lhunath'ın iyi bir uygulama gösteren, ancak OP'ye tam olarak cevap vermeyen mükemmel bir cevaba işaret ediyor.


Birinci ve ikinci üretilen "kötü ikame"; "{1..42}"bir iz bırakan kısmı beğenmedi "1". Ayrıca, $RANDOMsadece 15 bit ve yöntem 32767'den fazla dosya ile çalışmaz.
Yann Vernier

13
ls | shuf -n 10 # ten random files

1
Çıktısına güvenmemelisiniz ls. Örneğin, bir dosya adı yeni satır içeriyorsa bu çalışmaz.
bfontaine

3
@bfontaine dosya adlarında yeni satırlarla perili görünüyorsunuz :). Gerçekten bu kadar yaygın mı? Başka bir deyişle, adlarında yeni satır içeren dosyalar oluşturan bazı araçlar var mı? Bir kullanıcı olarak böyle bir dosya adı oluşturmak çok zordur. İnternetten gelen dosyalar için aynı
Ciprian Tomoiagă

3
@CiprianTomoiaga Bu, karşılaşabileceğiniz sorunlara bir örnektir. lssize "temiz" dosya adları vermeniz garanti edilmez, bu yüzden ona güvenmemelisiniz, nokta. Bu sorunların nadir veya olağandışı olması sorunu değiştirmez; özellikle bunun için daha iyi çözümler var.
bfontaine

lsdizinler ve boş satırlar içerebilir. Bunun find . -type f | shuf -n10yerine böyle bir şey öneririm .
cherdt

9

Ls ayrıştırmaktan kaçınırken5 rastgele dosyaları seçmek için basit bir çözüm . Ayrıca boşluklar, yeni satırlar ve diğer özel karakterler içeren dosyalarla da çalışır:

shuf -ezn 5 * | xargs -0 -n1 echo

echoDosyalarınız için yürütmek istediğiniz komutla değiştirin .


1
boru + readayrıştırma ile aynı sorunlara sahip değil lsmi? yani satır satır okur, bu yüzden
adlarında

3
Haklısın. Önceki çözümüm, yeni satırlar içeren dosya adları için işe yaramadı ve muhtemelen belirli özel karakterlere sahip diğerlerini de kırdı. Yeni satır yerine null sonlandırma kullanmak için cevabımı güncelledim.
scai

4

Python yüklüyse (Python 2 veya Python 3 ile çalışır):

Bir dosya (veya rastgele bir komuttan satır) seçmek için

ls -1 | python -c "import sys; import random; print(random.choice(sys.stdin.readlines()).rstrip())"

NDosyaları / satırları seçmek için şunu kullanın (not Nkomutun sonundadır, bunu bir sayı ile değiştirin)

ls -1 | python -c "import sys; import random; print(''.join(random.sample(sys.stdin.readlines(), int(sys.argv[1]))).rstrip())" N

Dosya adınız yeni satır içeriyorsa bu çalışmaz.
bfontaine

4

Bu, @ gniourf_gniourf'un geç cevabına daha da sonra gelen bir cevaptır, ki bu sadece en iyi cevaptır çünkü iki kez bitti. (Bir kez kaçınmak evalve bir kez güvenli dosya adı işlemek için.)

Ancak bu cevabın kullandığı "çok iyi belgelenmemiş" özellik (ler) i çözmek birkaç dakika sürdü. Bash becerileriniz nasıl çalıştığını hemen görebilecek kadar sağlamsa, bu yorumu atlayın. Ama ben yapmadım ve çözdüğümde açıklamaya değer olduğunu düşünüyorum.

Özellik # 1 , kabuğun kendi dosya globbingidir. üyeleri geçerli dizindeki dosyalar olan a=(*)bir dizi oluşturur $a. Bash, dosya adlarının tüm tuhaflıklarını anlar, böylece liste doğru, garantili kaçış vb. Garanti edilir ls. Döndürülen metin dosyası adlarını düzgün ayrıştırmak konusunda endişelenmenize gerek yoktur .

Özellik # 2 , biri diğerinin içine yerleştirilmiş diziler için Bash parametresi genişletmeleridir . Bu , uzunluğu ile genişleyen ile başlar .${#ARRAY[@]}$ARRAY

Bu genişletme daha sonra diziye abone olmak için kullanılır. 1 ve N arasında rastgele bir sayı bulmanın standart yolu, rastgele sayı modulo N değerini almaktır. 0 ile dizimizin uzunluğu arasında rastgele bir sayı istiyoruz. İşte netlik için iki çizgiye ayrılmış yaklaşım:

LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}

Ancak bu çözüm, gereksiz değişken atamasını kaldırarak tek bir satırda yapar.

Özellik # 3 , Bash brace genişlemesi , ancak itiraf etmeliyim, ancak tamamen anlamıyorum. Ayraç genişleme adında 25 dosyaların bir listesini oluşturmak için, örneğin, kullanılan filename1.txt, filename2.txtvb: echo "filename"{1..25}".txt".

Yukarıdaki alt kabuk içindeki ifade, "${a[RANDOM%${#a[@]}]"{1..42}"}"42 ayrı genişletme üretmek için bu numarayı kullanır. Küme ayracı genişletme , ilk önce diziye abone olduğunu düşündüğüm ]ve arasında tek bir basamak yerleştirir }, ancak öyleyse önünde bir iki nokta üst üste olur. (Aynı zamanda dizideki 42 rastgele öğeyi döndürmekle aynı şey olmayan, dizideki rastgele bir noktadan arka arkaya 42 öğe döndürürdü.) Bence sadece kabuk genişletmeyi 42 kez çalıştırıyor, böylece geri dönüyor Diziden 42 rastgele öğe. (Ama birisi daha eksiksiz açıklayabilirse, duymak isterim.)

N'nin kodlanmasının (42'ye kadar) nedeni, küme genişlemesinin değişken genişlemeden önce gerçekleşmesidir.

Son olarak, bir dizin hiyerarşisi için bunu tekrar tekrar yapmak istiyorsanız Özellik # 4'ü aşağıda bulabilirsiniz :

shopt -s globstar
a=( ** )

Bu, özyinelemeli olarak eşleşmesine neden olan bir kabuk seçeneğini açar **. Şimdi $adiziniz tüm hiyerarşideki her dosyayı içeriyor.


2

Klasörünüzde daha fazla dosya varsa, unix stackexchange'te bulduğum aşağıdaki borulu komutu kullanabilirsiniz .

find /some/dir/ -type f -print0 | xargs -0 shuf -e -n 8 -z | xargs -0 cp -vt /target/dir/

Burada dosyaları kopyalamak istedim, ancak dosyaları taşımak veya başka bir şey yapmak istiyorsanız, kullandığım son komutu değiştirin cp.


1

MacOS'ta bash ile güzel oynayabileceğim tek komut dosyası bu. Snippet'leri aşağıdaki iki bağlantıdan birleştirdim ve düzenledim:

ls komut: özyinelemeli tam yol listesi, dosya başına bir satır nasıl alabilirim?

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 sıralama -R ve shuf komutları yok, bu yüzden tüm dosyaları çoğaltmadan rastgele bir bash çözümüne ihtiyacım vardı ve burada bulamadık gerekiyordu. Bu çözüm gniourf_gniourf'un # 4 numaralı çözümüne benzer, ancak umarım daha iyi yorumlar ekler.

Komut dosyası, if ile bir sayaç kullanarak N örnekleri sonra durdurmak için kolayca değiştirilebilmelidir veya N ile döngü için gniourf_gniourf '$ $ RANDOM ~ 32000 dosya ile sınırlıdır, ancak bu çoğu durumda yapılmalıdır.

#!/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

Bunu kullanıyorum: geçici dosya kullanıyor, ancak normal bir dosya bulana ve geri dönene kadar bir dizinde derinlemesine gidiyor.

# 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;

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.