2009-01-05 10 views
76

J'ai un répertoire avec environ 2000 fichiers. Comment puis-je sélectionner un échantillon aléatoire de fichiers N en utilisant un script bash ou une liste de commandes canalisées?Comment puis-je sélectionner des fichiers aléatoires à partir d'un répertoire dans bash?

+1

également une bonne réponse à Unix et Linux: http://unix.stackexchange.com/a/38344/24170 –

+7

'ls | shuf -n 5' [Source de Unix Stackexchange] (http://unix.stackexchange.com/a/48477/14993) – jgomo3

+0

Similaire: https://stackoverflow.com/questions/2153882/how-can-i-shuffle -les-lignes-d'un-fichier-texte-sur-l'unix-command-line-or-in-a-shel – AAAfarmclub

Répondre

107

Voici un script qui utilise l'option GNU est un peu aléatoire:

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 
+0

Cool, ne connaissait pas le sort -R; J'ai déjà utilisé bogosort :-p – alex

+4

sort: option invalide - R Essayez 'sort --help 'pour plus d'informations. –

+1

Ne semble pas fonctionner pour les fichiers contenant des espaces. – Houshalter

15

Voici quelques possibilités qui ne compilent pas la sortie de ls et qui sont 100% sûr en ce qui concerne les fichiers avec des espaces et des symboles amusants dans leur prénom. 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 sera peut-être sortie plusieurs fois le même fichier, et N est nécessaire de connaître à 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, et vous devez vraiment vous assurer que N ne vient pas directement de la saisie de l'utilisateur sans être soigneusement vérifié!

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

    Je n'aime pas personnellement eval et donc cette réponse!

  • Le même 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 voulez pas avoir peut-être 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 
    

Note. Ceci est une réponse tardive à un ancien message, mais la réponse acceptée renvoie à une page externe qui montre une pratique terrible, et l'autre réponse n'est pas beaucoup mieux car elle analyse également la sortie de ls. Un commentaire à la réponse acceptée indique une excellente réponse de Lhunath qui montre évidemment de bonnes pratiques, mais ne répond pas exactement au PO.

+0

Première et deuxième "mauvaise substitution" produite; il n'aimait pas la partie '" {1..42} "' laissant un "1 En outre, '$ RANDOM' est seulement 15 bits et la méthode ne fonctionnera pas avec plus de 32767 fichiers à choisir. –

62

Vous pouvez utiliser shuf (à partir du package GNU coreutils) pour cela. Il suffit de nourrir une liste de noms de fichiers et de lui demander de retourner 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.. 

Régler la valeur -n, --head-count=COUNT pour retourner le nombre de lignes voulu. Par exemple, pour revenir 5 noms de fichiers aléatoires que vous utilisez:

find dirname -type f | shuf -n 5 
+3

OP voulait sélectionner «N» des fichiers aléatoires, donc en utilisant «1» est un peu trompeur. – aioobe

+2

Mais 'N' peut être' 1' – CousinCocaine

+4

Si vous avez des noms de fichiers avec newlines: 'find dirname -type f -print0 | shuf -zn1' – Hitechcomputergeek

1

C'est le seul script que je peux apprendre à jouer agréable avec bash sur MacOS.Je combiné et des extraits édités des deux liens suivants:

ls command: how can I get a recursive full-path listing, one line 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 
0

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 retour il.

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

Si vous avez installé Python (fonctionne soit avec Python 2 ou Python 3):

Pour sélectionner un fichier (ou d'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 N fichiers/lignes, l'utilisation (note N est à la fin de la commande, remplacer par un numéro)

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

Cela ne fonctionne pas si votre nom de fichier contient des retours à la ligne. – bfontaine

3

Ceci est une réponse encore plus tardive à la réponse tardive de @ gniourf_gniourf, que je viens de mettre à jour parce que c'est de loin la meilleure réponse, deux fois plus. (Une fois pour éviter eval et une fois pour la gestion sûre des noms de fichier.)

Mais il m'a fallu quelques minutes pour démêler la (les) caractéristique (s) "pas très bien documentée (s)" que cette réponse utilise. Si vos compétences Bash sont suffisamment solides pour que vous voyiez immédiatement comment cela fonctionne, alors passez ce commentaire. Mais je ne l'ai pas fait, et après l'avoir démêlé, je pense que ça vaut la peine d'expliquer.

Fonctionnalité # 1 est la propre globalisation de fichier du shell. a=(*) crée un tableau, $a, dont les membres sont les fichiers du répertoire en cours. Bash comprend toutes les bizarreries des noms de fichiers, de sorte que la liste est garantie correcte, garanti échappé, etc Pas besoin de s'inquiéter de l'analyse correcte des noms de fichiers textuels retournés par ls.

La caractéristique # 2 est Bash parameter expansions pour arrays, une imbriquée dans une autre. Cela commence par ${#ARRAY[@]}, qui s'étend à la longueur de $ARRAY. Cette extension est ensuite utilisée pour l'indice du tableau. La façon 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, coupée en deux par souci de clarté:

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

Mais cette solution, il le fait dans une seule ligne, en supprimant l'affectation des variables inutiles.

La caractéristique # 3 est Bash brace expansion, bien que je dois avouer que je ne comprends pas entièrement. L'expansion de l'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 de la sous-couche ci-dessus, "${a[RANDOM%${#a[@]}]"{1..42}"}", utilise cette astuce pour produire 42 expansions séparées. L'expansion de l'accolade place un seul chiffre entre le ] et le }, qui, au début, je pensais que l'indice était le 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 d'un endroit 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 c'est juste que le shell exécute l'expansion 42 fois, retournant 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 de l'accolade se produit avant l'expansion de la variable.

Enfin, voici Feature # 4, si vous voulez faire récursive pour une hiérarchie de répertoires:

shopt -s globstar 
a=(**) 

Cela tourne sur un shell option qui provoque ** pour correspondre récursive. Votre tableau $a contient maintenant tous les fichiers de la hiérarchie entière.

1

Une solution simple pour sélectionner 5 fichiers aléatoires en avoiding to parse ls. Il fonctionne également avec les fichiers contenant des espaces, des sauts de ligne et autres caractères spéciaux:

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

Remplacer echo avec la commande que vous souhaitez exécuter pour vos fichiers.

+1

bien, le tube + 'read' n'a-t-il pas les mêmes problèmes que l'analyse de' ls'? à savoir, il lit ligne par ligne, de sorte qu'il ne fonctionne pas pour les fichiers avec des nouvelles lignes dans leur nom –

+0

Vous avez raison. Ma solution précédente ne fonctionnait pas pour les noms de fichiers contenant des retours à la ligne et se brisait probablement sur d'autres avec certains caractères spéciaux. J'ai mis à jour ma réponse pour utiliser null-termination au lieu de newlines. – scai

2
ls | shuf -n 10 # ten random files 
+0

Vous ne devriez pas compter sur la sortie de 'ls'. Cela ne fonctionnera pas si, par ex. un nom de fichier contient des nouvelles lignes. – bfontaine

+1

@bfontaine vous semblez hanté par les nouvelles lignes dans les noms de fichiers :). Sont-ils vraiment si commun? En d'autres termes, existe-t-il un outil qui crée des fichiers avec des nouvelles lignes 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 –

+1

@CiprianTomoiaga Voilà un exemple des problèmes que vous pourriez rencontrer. 'ls' n'est pas garanti pour vous donner des noms de fichiers" propres "donc vous ne devriez pas compter dessus, point final. Le fait que ces problèmes soient rares ou inhabituels ne change pas le problème; surtout étant donné qu'il existe de meilleures solutions pour cela. – bfontaine

1

MacOS ne pas les tri-R et SHUF commandes, donc je besoin d'une bash seule solution qui randomizes tous les fichiers sans doublons et n'a pas trouvé ici. Cette solution est similaire à la solution # 4 de gniourf_gniourf, mais j'espère ajouter de meilleurs commentaires.

Le script devrait être facile à modifier pour arrêter après N échantillons en utilisant un compteur avec if, ou gniourf_gniourf pour la boucle avec N. $ RANDOM est limité à ~ 32000 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 
Questions connexes