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 piped?
ls | shuf -n 5
Source de Unix Stackexchange
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 piped?
ls | shuf -n 5
Source de Unix Stackexchange
Réponses:
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
"$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=COUNT
valeur 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
N
des fichiers aléatoires, donc l'utilisation 1
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 tableau randf
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.
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 N
cela 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 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<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 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.
"{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.
ls | shuf -n 10 # ten random files
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érerais find . -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:
shuf -ezn 5 * | xargs -0 -n1 echo
Remplacez echo
par la commande que vous souhaitez exécuter pour vos fichiers.
read
n'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
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 N
fichiers / lignes, utilisez (la note se N
trouve à 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
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 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 $a
tableau contient tous les fichiers de toute la hiérarchie.
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
.
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?
#!/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 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 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;
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?
$ ls | perl -MList :: Util = shuffle -e '@lines = shuffle (<>); print @lines [0..4] '