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


144

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


1
Aussi une bonne réponse chez Unix et Linux: unix.stackexchange.com/a/38344/24170
Nikana Reklawyks


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

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

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.


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

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.


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

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.


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.


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

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

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;

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.