Comment puis-je sélectionner des fichiers aléatoires à partir d’un répertoire dans bash?

J’ai un répertoire avec environ 2000 fichiers. Comment puis-je sélectionner un échantillon aléatoire de N fichiers en utilisant un script bash ou une liste de commandes rediffusées?

Voici un script qui utilise l’option aléatoire de GNU sort:

 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 

Vous pouvez utiliser shuf (à partir du paquet GNU coreutils) pour cela. Il suffit de lui fournir une liste de noms de fichiers et de lui demander de renvoyer la première ligne d’une permutation aléatoire:

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

Ajustez la valeur -n, --head-count=COUNT pour renvoyer le nombre de lignes voulues. Par exemple, pour renvoyer 5 noms de fichiers aléatoires que vous utiliseriez:

 find dirname -type f | shuf -n 5 

Voici quelques possibilités qui n’parsingnt pas la sortie de ls et qui sont 100% sûres en ce qui concerne les fichiers avec des espaces et des symboles amusants. Tous vont remplir un tableau randf avec une liste de fichiers aléatoires. Ce tableau est facilement imprimé avec printf '%s\n' "${randf[@]}" si nécessaire.

  • Celui-ci produira éventuellement le même fichier plusieurs fois, et N doit être connu à l’avance. Ici j’ai choisi N = 42.

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

    Cette fonctionnalité n’est pas très bien documentée.

  • Si N n’est pas connu à l’avance, mais vous avez vraiment aimé la possibilité précédente, vous pouvez utiliser eval . Mais c’est diabolique, et vous devez vraiment vous assurer que N ne provient pas directement des entrées de l’utilisateur sans être minutieusement vérifié!

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

    eval je n’aime pas eval et donc cette réponse!

  • La même chose en utilisant une méthode plus simple (une boucle):

     N=42 a=( * ) randf=() for((i=0;i 
  • Si vous ne voulez pas avoir plusieurs fois le même fichier:

     N=42 a=( * ) randf=() for((i=0;i 

Note Ceci est une réponse tardive à un ancien message, mais la réponse acceptée est liée à une page externe qui montre une pratique bash terrible, et l'autre réponse n'est pas meilleure car elle parsing également la sortie de ls . Un commentaire sur la réponse acceptée indique une excellente réponse de Lhunath, qui montre clairement les bonnes pratiques, mais ne répond pas exactement à la question.

 ls | shuf -n 10 # ten random files 

Si Python est installé (fonctionne avec Python 2 ou Python 3):

Pour sélectionner un fichier (ou une ligne d’une commande arbitraire), utilisez

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

Pour sélectionner N fichiers / lignes, utilisez (la note N est à la fin de la commande, remplacez-la par un nombre)

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

Ceci est une réponse encore plus tardive à la réponse tardive de @gniourf_gniourf, que je viens de relever, car c’est de loin la meilleure réponse, deux fois. (Une fois pour éviter eval et une fois pour une gestion sécurisée des noms de fichiers.)

Mais il m’a fallu quelques minutes pour démêler les fonctionnalités “pas très bien documentées” utilisées dans cette réponse. Si vos compétences Bash sont suffisamment solides pour que vous puissiez voir immédiatement comment cela fonctionne, ignorez ce commentaire. Mais je ne l’ai pas fait et après l’avoir démêlé, je pense que ça vaut la peine de l’expliquer.

La fonctionnalité n ° 1 correspond à la globalisation du fichier du shell. a=(*) crée un tableau, $a , dont les membres sont les fichiers du répertoire en cours. Bash comprend toutes les étrangetés des noms de fichiers, de sorte que cette liste est garantie correcte, garantie d’évasion, etc. Inutile de vous soucier d’parsingr correctement les noms de fichiers textuels renvoyés par ls .

La fonctionnalité n ° 2 est une extension des parameters Bash pour les tableaux , l’un étant nested dans un autre. Cela commence par ${#ARRAY[@]} , qui s’étend à la longueur de $ARRAY .

Cette extension est ensuite utilisée pour indexer le tableau. La méthode standard pour trouver un nombre aléatoire entre 1 et N consiste à prendre la valeur du nombre aléatoire modulo N. Nous voulons un nombre aléatoire compris entre 0 et la longueur de notre tableau. Voici l’approche, divisée en deux lignes pour des raisons de clarté:

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

Mais cette solution le fait en une seule ligne, éliminant ainsi l’atsortingbution inutile des variables.

Le trait n ° 3 est l’ expansion de l’accolade Bash , bien que je dois avouer que je ne le comprends pas entièrement. L’extension d’accolade est utilisée, par exemple, pour générer une liste de 25 fichiers nommés filename1.txt , filename2.txt , etc.: echo "filename"{1..25}".txt" .

L’expression à l’intérieur du sous-shell ci-dessus, "${a[RANDOM%${#a[@]}]"{1..42}"}" , utilise cette astuce pour produire 42 extensions séparées. L’expansion des accolades place un seul chiffre entre le ] et le } , ce qui, au début, je pensais que c’était l’indice du tableau, mais si c’était le cas, il serait précédé d’un deux-points. (Il aurait également renvoyé 42 éléments consécutifs à partir d’un point aléatoire du tableau, ce qui n’est pas la même chose que le retour de 42 éléments aléatoires du tableau.) 42 éléments aléatoires du tableau. (Mais si quelqu’un peut l’expliquer davantage, j’aimerais l’entendre.)

La raison pour laquelle N doit être codé en dur (à 42) est que l’expansion des accolades se produit avant l’expansion des variables.

Enfin, voici la fonctionnalité n ° 4 , si vous voulez faire cela récursivement pour une hiérarchie de répertoires:

 shopt -s globstar a=( ** ) 

Cela active une option de shell qui fait correspondre ** récursivement. Maintenant, votre tableau $a contient tous les fichiers de toute la hiérarchie.

Une solution simple pour sélectionner 5 fichiers aléatoires tout en évitant d’parsingr ls . Il fonctionne également avec des fichiers contenant des espaces, des nouvelles lignes et d’autres caractères spéciaux:

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

Remplacez echo par la commande que vous souhaitez exécuter pour vos fichiers.

C’est le seul script que je peux faire jouer gentiment avec bash sur MacOS. J’ai combiné et édité des extraits à partir des deux liens suivants:

Commande ls: comment puis-je obtenir une liste de chemin complet récursive, une ligne par fichier?

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 paramesortingze 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 masortingx, # it uses the new lines character as a field delimiter, as explained above. # file_masortingx=($(ls -LR "${DIR}")) file_masortingx=($(ls -R $DIR | awk '; /:$/&&f{s=$0;f=0}; /:$/&&!f{sub(/:$/,"");s=$0;f=1;next}; NF&&f{ print s"/"$0 }')) num_files=${#file_masortingx[*]} # 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_masortingx[$((RANDOM%num_files))]}" fi exit 0 

MacOS n’a pas les commandes -R et shuf , donc j’avais besoin d’une solution bash qui randomise tous les fichiers sans doublons et ne les trouve pas ici. Cette solution est similaire à la solution n ° 4 de gniourf_gniourf, mais ajoute, espérons-le, de meilleurs commentaires.

Le script devrait être facile à modifier pour s’arrêter après N échantillons en utilisant un compteur avec if, ou en boucle avec gniourf_gniourf avec N. $ RANDOM est limité à ~ 32000 fichiers, mais cela devrait être le cas dans la plupart des cas.

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

J’utilise ceci: il utilise un fichier temporaire mais va profondément dans un répertoire jusqu’à ce qu’il trouve un fichier régulier et le retourne.

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

Qu’en est-il d’une solution Perl légèrement faussée par M. Kang ici:
Comment puis-je mélanger les lignes d’un fichier texte sur la ligne de commande Unix ou dans un script shell?

$ ls | perl -MList :: Util = shuffle -e ‘@lines = shuffle (<>); print @lines [0..4] ‘