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?
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.
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:
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é!
Remarque . Il s'agit d'une réponse tardive à un ancien message, mais la réponse acceptée renvoie à une page externe qui montre desfrapperpratique, 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.
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.
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.
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.
#!/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
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 arraydone
ls | shuf -n 5
Source de Unix StackexchangeRéponses:
Voici un script qui utilise l'option aléatoire du tri GNU:
la source
"$file"
, non représentée, serait sensible aux espaces.ls
?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:Ajustez la
-n, --head-count=COUNT
valeur pour renvoyer le nombre de lignes voulues. Par exemple, pour renvoyer 5 noms de fichiers aléatoires, vous utiliseriez:la source
N
des fichiers aléatoires, donc l'utilisation1
est un peu trompeuse.find dirname -type f -print0 | shuf -zn1
Voici quelques possibilités qui n'analysent pas la sortie de
ls
et qui sont 100% sûres concernant les fichiers avec des espaces et des symboles amusants dans leur nom. Tous rempliront un tableaurandf
avec 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
N
doit être connu à l'avance. Ici, j'ai choisi N = 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 queN
cela ne vient pas directement de l'entrée de l'utilisateur sans être minutieusement vérifié!Personnellement, je n'aime pas
eval
et donc cette réponse!La même chose en utilisant une méthode plus simple (une boucle):
Si vous ne souhaitez pas avoir plusieurs fois le même fichier:
Remarque . Il s'agit d'une réponse tardive à un ancien message, mais la réponse acceptée renvoie à une page externe qui montre desfrapperpratique, 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.la source
"{1..42}"
partie laissant une traînée"1"
. En outre,$RANDOM
est seulement 15 bits et la méthode ne fonctionnera pas avec plus de 32767 fichiers à choisir.la source
ls
. Cela ne fonctionnera pas si, par exemple, un nom de fichier contient des nouvelles lignes.ls
n'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.ls
peut inclure des répertoires et des lignes vides. Je suggéreraisfind . -type f | shuf -n10
plutôt quelque chose comme .Une solution simple pour sélectionner
5
des 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:Remplacez
echo
par la commande que vous souhaitez exécuter pour vos fichiers.la source
read
n'a-t-il pas les mêmes problèmes que l'analysels
? à savoir, il lit ligne par ligne, donc cela ne fonctionne pas pour les fichiers avec des nouvelles lignes dans leur nomSi Python est installé (fonctionne avec Python 2 ou Python 3):
Pour sélectionner un fichier (ou une ligne à partir d'une commande arbitraire), utilisez
Pour sélectionner des
N
fichiers / lignes, utilisez (la note seN
trouve à la fin de la commande, remplacez-la par un nombre)la source
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
eval
et 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,$a
dont 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 parls
.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é:
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:
Cela active une option de shell qui provoque
**
une correspondance récursive. Maintenant, votre$a
tableau contient tous les fichiers de toute la hiérarchie.la source
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 .
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
.la source
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/
la source
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.
la source
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.
la source
Que diriez-vous d'une solution Perl légèrement trafiqué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?
la source