Come posso selezionare file casuali da una directory in bash?


Risposte:


180

Ecco uno script che utilizza l'opzione casuale dell'ordinamento 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

Fantastico, non conoscevo l'ordinamento -R; Ho usato bogosort in precedenza :-p
alex

5
sort: opzione non valida - R Prova `sort --help 'per ulteriori informazioni.

2
Non sembra funzionare per i file che contengono spazi.
Houshalter,

Questo dovrebbe funzionare per i file con spazi (le linee dei processi della pipeline). Non funziona per i nomi con newline in essi. Solo l'uso di "$file", non mostrato, sarebbe sensibile agli spazi.
Yann Vernier,


108

Puoi usare shuf(dal pacchetto GNU coreutils) per questo. Basta inserire un elenco di nomi di file e chiedergli di restituire la prima riga da una permutazione casuale:

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

Regola il -n, --head-count=COUNTvalore per restituire il numero di righe desiderate. Ad esempio, per restituire 5 nomi di file casuali dovresti usare:

find dirname -type f | shuf -n 5

4
OP voleva selezionare Nfile casuali, quindi l'utilizzo 1è un po 'fuorviante.
aioobe,

4
Se hai nomi di file con nuove righe:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek il

5
cosa succede se devo copiare questi file selezionati casualmente in un'altra cartella? come eseguire operazioni su questi file selezionati casualmente?
Rishabh Agrahari,

18

Ecco alcune possibilità che non analizzano l'output di lse che sono sicure al 100% per quanto riguarda i file con spazi e simboli divertenti nel loro nome. Tutti loro popoleranno un array randfcon un elenco di file casuali. Questo array può essere facilmente stampato con printf '%s\n' "${randf[@]}"se necessario.

  • Questo probabilmente produrrà lo stesso file più volte e Ndeve essere conosciuto in anticipo. Qui ho scelto N = 42.

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

    Questa funzione non è ben documentata.

  • Se N non è noto in anticipo, ma ti è davvero piaciuta la possibilità precedente, puoi usarla eval. Ma è malvagio e devi davvero assicurarti che Nnon provenga direttamente dall'input dell'utente senza essere accuratamente controllato!

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

    Personalmente non mi piace evale quindi questa risposta!

  • Lo stesso usando un metodo più semplice (un ciclo):

    N=42
    a=( * )
    randf=()
    for((i=0;i<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Se non vuoi avere più volte lo stesso file:

    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

Nota . Questa è una risposta tardiva a un vecchio post, ma la risposta accettata si collega a una pagina esterna che mostra terribilepratica, e l'altra risposta non è molto migliore in quanto analizza anche l'output di ls. Un commento alla risposta accettata indica una risposta eccellente di Lhunath che ovviamente mostra buone pratiche, ma non risponde esattamente al PO.


Il primo e il secondo prodotto "sostituzione errata"; non gli piaceva che la "{1..42}"parte lasciasse un finale "1". Inoltre, $RANDOMè solo 15 bit e il metodo non funzionerà con oltre 32767 file tra cui scegliere.
Yann Vernier,

13
ls | shuf -n 10 # ten random files

1
Non dovresti fare affidamento sull'output di ls. Questo non funzionerà se, ad esempio, un nome file contiene nuove righe.
bfontaine,

3
@bfontaine sembra ossessionato dalle nuove righe nei nomi dei file :). Sono davvero così comuni? In altre parole, c'è qualche strumento che crea file con newline nel loro nome? Dal momento che come utente è molto difficile creare un tale nome di file. Lo stesso
vale

3
@CiprianTomoiaga Questo è un esempio dei problemi che potresti riscontrare. lsnon è garantito che ti dia nomi di file "puliti" quindi non dovresti fare affidamento su di esso, punto. Il fatto che questi problemi siano rari o insoliti non cambia il problema; soprattutto dato che ci sono soluzioni migliori per questo.
bfontaine,

lspuò includere directory e righe vuote. Suggerirei qualcosa di simile find . -type f | shuf -n10invece.
cherdt

9

Una soluzione semplice per selezionare 5file casuali evitando di analizzare ls . Funziona anche con file contenenti spazi, newline e altri caratteri speciali:

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

Sostituisci echocon il comando che desideri eseguire per i tuoi file.


1
bene, il pipe + non readha gli stessi problemi dell'analisi ls? vale a dire, legge riga per riga, quindi non funziona per i file con nuove righe nel loro nome
Ciprian Tomoiagă

3
Hai ragione. La mia precedente soluzione non funzionava per i nomi di file contenenti newline e probabilmente si interrompe anche su altri con determinati caratteri speciali. Ho aggiornato la mia risposta per utilizzare la terminazione null anziché le nuove righe.
scai

4

Se hai installato Python (funziona con Python 2 o Python 3):

Per selezionare un file (o una riga da un comando arbitrario), utilizzare

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

Per selezionare Nfile / linee, utilizzare (la nota Nè alla fine del comando, sostituirla con un numero)

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

Questo non funziona se il tuo nome file contiene nuove righe.
bfontaine,

4

Questa è una risposta anche più tardi alla risposta tardiva di @ gniourf_gniourf, che ho appena votato perché è di gran lunga la risposta migliore, due volte. (Una volta per evitare evale una volta per una gestione sicura del nome file.)

Ma mi ci sono voluti alcuni minuti per districare le funzioni "non molto ben documentate" utilizzate da questa risposta. Se le tue abilità di Bash sono abbastanza solide da vedere immediatamente come funziona, salta questo commento. Ma non l'ho fatto e, dopo averlo districato, penso che valga la pena spiegarlo.

La funzione n. 1 è il globbing del file della shell. a=(*)crea un array, i $acui membri sono i file nella directory corrente. Bash comprende tutte le stranezze dei nomi dei file, quindi l'elenco è garantito in modo corretto, è garantito il escape, ecc. Non è necessario preoccuparsi di analizzare correttamente i nomi di file testuali restituiti da ls.

La funzione # 2 è espansioni di parametri Bash per array , uno nidificato all'interno di un altro. Questo inizia con ${#ARRAY[@]}, che si espande fino alla lunghezza di $ARRAY.

Tale espansione viene quindi utilizzata per sottoscrivere l'array. Il modo standard per trovare un numero casuale compreso tra 1 e N è prendere il valore di un numero casuale modulo N. Vogliamo un numero casuale compreso tra 0 e la lunghezza del nostro array. Ecco l'approccio, suddiviso in due righe per motivi di chiarezza:

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

Ma questa soluzione lo fa in una sola riga, rimuovendo l'assegnazione variabile non necessaria.

La funzione # 3 è l' espansione di Bash Brace , anche se devo confessare che non lo capisco del tutto. Espansione delle parentesi graffe viene utilizzato, ad esempio, per generare un elenco di 25 file di nome filename1.txt, filename2.txtecc: echo "filename"{1..25}".txt".

L'espressione all'interno della subshell sopra, "${a[RANDOM%${#a[@]}]"{1..42}"}"usa quel trucco per produrre 42 espansioni separate. L'espansione del controvento posiziona una singola cifra tra il ]e il }, che all'inizio pensavo stesse sottoscrivendo l'array, ma in tal caso sarebbe preceduto da due punti. (Avrebbe anche restituito 42 oggetti consecutivi da un punto casuale dell'array, il che non è affatto la stessa cosa di restituire 42 oggetti casuali dall'array.) Penso che stia solo facendo eseguire l'espansione della shell 42 volte, restituendo così 42 oggetti casuali dall'array. (Ma se qualcuno può spiegarlo in modo più completo, mi piacerebbe ascoltarlo.)

Il motivo per cui N deve essere hardcoded (a 42) è che l'espansione del controvento avviene prima dell'espansione variabile.

Infine, ecco la funzione # 4 , se vuoi farlo in modo ricorsivo per una gerarchia di directory:

shopt -s globstar
a=( ** )

Questo attiva un'opzione shell che fa **corrispondere ricorsivamente. Ora il tuo $aarray contiene tutti i file nell'intera gerarchia.


2

Se nella cartella sono presenti più file, è possibile utilizzare il comando di piping riportato di seguito che ho trovato in unix stackexchange .

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

Qui volevo copiare i file, ma se si desidera spostare i file o fare qualcos'altro, basta cambiare l'ultimo comando dove ho usato cp.


1

Questo è l'unico script che riesco a interpretare bene con bash su MacOS. Ho combinato e modificato frammenti dai seguenti due collegamenti:

Comando ls: come posso ottenere un elenco ricorsivo a percorso completo, una riga 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 non ha i comandi sort -R e shuf , quindi avevo bisogno di una soluzione solo bash che randomizza tutti i file senza duplicati e non li ho trovati qui. Questa soluzione è simile alla soluzione n. 4 di gniourf_gniourf, ma si spera che aggiunga commenti migliori.

Lo script dovrebbe essere facile da modificare per interrompere dopo N campioni usando un contatore con if, oppure gniourf_gniourf per il ciclo con N. $ RANDOM è limitato a ~ 32000 file, ma dovrebbe succedere nella maggior parte dei casi.

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

Lo uso: usa un file temporaneo ma entra profondamente in una directory fino a quando non trova un file normale e lo restituisce.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.