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

Réponses:

180

Voici un script qui utilise l'option aléatoire du tri 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
Josh Lee
la source
Cool, je ne savais pas trier -R; J'ai utilisé bogosort précédemment :-p
alex
5
sort: option invalide - R Essayez `sort --help 'pour plus d'informations.
2
Cela ne semble pas fonctionner pour les fichiers contenant des espaces.
Houshalter
Cela devrait fonctionner pour les fichiers avec des espaces (le pipeline traite les lignes). Cela ne fonctionne pas pour les noms avec une nouvelle ligne. Seule l'utilisation de "$file", non représentée, serait sensible aux espaces.
Yann Vernier
108

Vous pouvez utiliser shuf(à partir du paquet GNU coreutils) pour cela. Donnez-lui simplement une liste de noms de fichiers et demandez-lui 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 -n, --head-count=COUNTvaleur pour renvoyer le nombre de lignes voulues. Par exemple, pour renvoyer 5 noms de fichiers aléatoires, vous utiliseriez:

find dirname -type f | shuf -n 5
Mainframe nordique
la source
4
OP voulait sélectionner Ndes fichiers aléatoires, donc l'utilisation 1est un peu trompeuse.
aioobe
4
Si vous avez des noms de fichiers avec des nouvelles lignes:find dirname -type f -print0 | shuf -zn1
Hitechcomputergeek
5
Que faire si je dois copier ces fichiers sélectionnés au hasard dans un autre dossier? comment effectuer des opérations sur ces fichiers sélectionnés au hasard?
Rishabh Agrahari
18

Voici quelques possibilités qui n'analysent pas la sortie de lset qui sont 100% sûres concernant les fichiers avec des espaces et des symboles amusants dans leur nom. Tous rempliront un tableau randfavec une liste de fichiers aléatoires. Ce tableau est facilement imprimé printf '%s\n' "${randf[@]}"si nécessaire.

  • Celui-ci produira éventuellement le même fichier plusieurs fois, et Ndoit ê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 que vous avez vraiment aimé la possibilité précédente, vous pouvez utiliser eval. Mais c'est maléfique, et vous devez vraiment vous assurer que Ncela ne vient pas directement de l'entrée de l'utilisateur sans être minutieusement vérifié!

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

    Personnellement, je n'aime pas evalet 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<N;++i)); do
        randf+=( "${a[RANDOM%${#a[@]}]}" )
    done
  • Si vous ne souhaitez pas avoir plusieurs fois le même fichier:

    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

Remarque . Il s'agit d'une réponse tardive à un ancien message, mais la réponse acceptée renvoie à une page externe qui montre despratique, et l'autre réponse n'est pas beaucoup mieux car elle analyse également la sortie de ls. Un commentaire sur la réponse acceptée indique une excellente réponse de Lhunath qui montre évidemment une bonne pratique, mais ne répond pas exactement au PO.

gniourf_gniourf
la source
Le premier et le second ont produit une «mauvaise substitution»; il n'aimait pas la "{1..42}"partie laissant une traînée "1". En outre, $RANDOMest seulement 15 bits et la méthode ne fonctionnera pas avec plus de 32767 fichiers à choisir.
Yann Vernier
13
ls | shuf -n 10 # ten random files
silgon
la source
1
Vous ne devriez pas vous fier à la sortie de ls. Cela ne fonctionnera pas si, par exemple, un nom de fichier contient des nouvelles lignes.
bfontaine
3
@bfontaine vous semblez hanté par les nouvelles lignes dans les noms de fichiers :). Sont-ils vraiment si courants? En d'autres termes, existe-t-il un outil qui crée des fichiers avec des retours à la ligne dans leur nom? En tant qu'utilisateur, il est très difficile de créer un tel nom de fichier. Idem pour les fichiers provenant d'Internet
Ciprian Tomoiagă
3
@CiprianTomoiaga C'est un exemple des problèmes que vous pourriez rencontrer. lsn'est pas garanti de vous donner des noms de fichiers "propres", vous ne devriez donc pas vous y fier, point final. Le fait que ces problèmes soient rares ou inhabituels ne change pas le problème; d'autant plus qu'il existe de meilleures solutions pour cela.
bfontaine
lspeut inclure des répertoires et des lignes vides. Je suggérerais find . -type f | shuf -n10plutôt quelque chose comme .
cherdt
9

Une solution simple pour sélectionner 5des fichiers aléatoires tout en évitant d'analyser les ls . Il fonctionne également avec des fichiers contenant des espaces, des retours à la ligne et d'autres caractères spéciaux:

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

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

scai
la source
1
eh bien, le pipe + readn'a-t-il pas les mêmes problèmes que l'analyse ls? à savoir, il lit ligne par ligne, donc cela ne fonctionne pas pour les fichiers avec des nouvelles lignes dans leur nom
Ciprian Tomoiagă
3
Vous avez raison. Ma solution précédente ne fonctionnait pas pour les noms de fichiers contenant des retours à la ligne et se cassait probablement sur d'autres avec certains caractères spéciaux également. J'ai mis à jour ma réponse pour utiliser la terminaison nulle au lieu de nouvelles lignes.
scai
4

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

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

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

Pour sélectionner des Nfichiers / lignes, utilisez (la note se Ntrouve à 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]))).rstrip())" N
marque
la source
Cela ne fonctionne pas si votre nom de fichier contient des nouvelles lignes.
bfontaine
4

C'est une réponse encore plus tardive à la réponse tardive de @ gniourf_gniourf, que je viens de voter parce que c'est de loin la meilleure réponse, deux fois. (Une fois pour éviter evalet une fois pour une gestion sûre 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 par cette réponse. Si vos compétences Bash sont suffisamment solides pour que vous voyiez 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 cela vaut la peine de l'expliquer.

La fonction n ° 1 est le globbing de fichiers du shell. a=(*)crée un tableau, $adont les membres sont les fichiers du répertoire courant. Bash comprend toutes les bizarreries des noms de fichiers, de sorte que la liste est garantie correcte, garantie échappée, etc. Pas besoin de s'inquiéter de l'analyse correcte des noms de fichiers textuels renvoyés par ls.

La fonction n ° 2 est l' expansion des paramètres Bash pour les tableaux , l'un imbriqué dans un autre. Cela commence par ${#ARRAY[@]}, qui s'étend jusqu'à la longueur de $ARRAY.

Cette extension est ensuite utilisée pour indiquer le tableau. La manière standard de trouver un nombre aléatoire entre 1 et N est de prendre la valeur du nombre aléatoire modulo N. Nous voulons un nombre aléatoire entre 0 et la longueur de notre tableau. Voici l'approche, divisée en deux lignes par souci de clarté:

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

Mais cette solution le fait en une seule ligne, supprimant l'affectation de variable inutile.

La fonctionnalité n ° 3 est l' expansion des accolades Bash , même si je dois avouer que je ne la comprends pas entièrement. L' expansion des accolades est utilisé, 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 distinctes. L'expansion d'accolades place un seul chiffre entre le ]et le }, ce qui, au début, je pensais indiquer l'indice du tableau, mais si c'est le cas, il serait précédé d'un deux-points. (Il aurait également renvoyé 42 éléments consécutifs à partir d'un emplacement aléatoire dans le tableau, ce qui n'est pas du tout la même chose que de renvoyer 42 éléments aléatoires du tableau.) Je pense que cela fait simplement exécuter le shell 42 fois l'expansion, retournant ainsi 42 éléments aléatoires du tableau. (Mais si quelqu'un peut l'expliquer plus complètement, 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 variable.

Enfin, voici la fonctionnalité n ° 4 , si vous souhaitez le faire de manière récursive pour une hiérarchie de répertoires:

shopt -s globstar
a=( ** )

Cela active une option de shell qui provoque **une correspondance récursive. Maintenant, votre $atableau contient tous les fichiers de toute la hiérarchie.

Ken
la source
2

Si vous avez plus de fichiers dans votre dossier, vous pouvez utiliser la commande canalisée ci-dessous que j'ai trouvée dans unix stackexchange .

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

Ici, je voulais copier les fichiers, mais si vous voulez déplacer des fichiers ou faire autre chose, changez simplement la dernière commande où j'ai utilisé cp.

Bhaskar Chakradhar
la source
1

C'est le seul script que je peux jouer gentiment avec bash sur MacOS. J'ai combiné et modifié des extraits des deux liens suivants:

Commande ls: comment puis-je obtenir une liste de chemins complets récursifs, 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 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
benmarbles
la source
1

MacOS n'a pas les commandes sort -R et shuf , donc j'avais besoin d'une solution bash seulement qui randomise tous les fichiers sans doublons et je n'ai pas trouvé cela ici. Cette solution est similaire à la solution n ° 4 de gniourf_gniourf, mais j'espère qu'elle ajoute de meilleurs commentaires.

Le script devrait être facile à modifier pour s'arrêter après N échantillons en utilisant un compteur avec if, ou la boucle for de gniourf_gniourf avec N. $ RANDOM est limité à ~ 32 000 fichiers, mais cela devrait le faire 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
chat
la source
0

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

# 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;
bzimage
la source