J'ai un répertoire avec environ 2000 fichiers. Comment sélectionner un échantillon aléatoire de fichiers N
à l'aide d'un script bash ou d'une liste de commandes redirigées?
Voici un script qui utilise GNU sort:
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
Vous pouvez utiliser shuf
(à partir du paquet = GNU coreutils)]. Ajoutez-lui simplement une liste de noms de fichiers et demandez-lui de renvoyer la première ligne à partir d'une permutation aléatoire:
ls dirname | shuf -n 1
# probably faster and more flexible:
find dirname -type f | shuf -n 1
# etc..
Ajuste le -n, --head-count=COUNT
valeur pour renvoyer le nombre de lignes désirées. Par exemple, pour renvoyer 5 noms de fichiers aléatoires, vous utiliseriez:
find dirname -type f | shuf -n 5
Voici quelques possibilités qui n'analysent pas la sortie de ls
et qui sont 100% sûres en ce qui concerne les fichiers avec des espaces et des symboles amusants dans leur nom. Tous vont remplir un tableau randf
avec une liste de fichiers aléatoires. Ce tableau s’imprime facilement avec printf '%s\n' "${randf[@]}"
si besoin.
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 la possibilité précédente vous a vraiment plu, vous pouvez utiliser eval
. Mais c'est mauvais, et vous devez vraiment vous assurer que N
ne provient pas directement de la saisie de l'utilisateur sans avoir été minutieusement vérifié!
N=42
a=( * )
eval randf=( \"\${a[RANDOM%\${#a[@]}]\"\{1..$N\}\"}\" )
Personnellement, je n'aime pas eval
et par conséquent 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 voulez 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 . Ceci est une réponse tardive à un ancien message, mais la réponse acceptée renvoie à une page externe qui montre une pratique terrible bash , et l’autre réponse n’est pas bien meilleure 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 de toute évidence une bonne pratique, mais ne répond pas exactement au PO.
ls | shuf -n 10 # ten random files
Une solution simple pour sélectionner 5
fichiers aléatoires while évitant d'analyser ls . Il fonctionne également avec les fichiers contenant des espaces, des nouvelles lignes et d’autres caractères spéciaux:
shuf -ezn 5 * | xargs -0 -n1 echo
Remplacez echo
par la commande à exécuter pour vos fichiers.
Si vous avez Python installé (fonctionne avec soit Python 2 ou soit Python 3). 3).
Pour sélectionner un fichier (ou une ligne 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, utilisez (note N
est à la fin de la commande, remplacez-le 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 d’augmenter car elle est de loin la meilleure réponse, à deux reprises. (Une fois pour éviter eval
et une fois pour un traitement sûr du nom de fichier.)
Mais il m'a fallu quelques minutes pour démêler la ou les fonctionnalités "pas très bien documentées" utilisées par cette réponse. Si vos compétences en Bash sont suffisamment solides pour que vous voyiez immédiatement comment cela fonctionne, ignorez ce commentaire. Mais je ne l'ai pas fait et l'ayant démêlé, je pense que ça vaut la peine de l'expliquer.
La fonctionnalité n ° 1 est le fichier globbing 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, ce qui garantit que cette liste est correcte, que tous les fichiers sont protégés, etc. Inutile de s’inquiéter de l’analyse correcte des noms de fichiers textuels renvoyés par ls
.
La caractéristique n ° 2 est Bash extensions de paramètres pour tableaux , l'un imbriqué dans l'autre. Cela commence par ${#ARRAY[@]}
, qui s'étend à la longueur de $ARRAY
.
Cette extension est ensuite utilisée pour inscrire le tableau. La méthode standard pour trouver un nombre aléatoire compris entre 1 et N consiste à prendre la valeur du nombre aléatoire modulo N. Nous voulons un nombre aléatoire compris entre 0 et la longueur de notre tableau. Voici l'approche, divisée en deux lignes pour plus de clarté:
LENGTH=${#ARRAY[@]}
RANDOM=${a[RANDOM%$LENGTH]}
Mais cette solution le fait en une seule ligne, en supprimant l’affectation inutile des variables.
La fonctionnalité n ° 3 est expansion de l'accolade Bash , même si je dois avouer que je ne la comprends pas tout à fait. Le développement par accolade 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'extension d'accolade place un seul chiffre entre les ]
et le }
, ce que je pensais au départ souscrire le tableau, mais si tel était le cas, il serait précédé de deux points. (Cela aurait également renvoyé 42 éléments consécutifs à un emplacement aléatoire du tableau, ce qui n’est pas du tout la même chose que renvoyer 42 éléments aléatoires du tableau.) 42 éléments aléatoires du tableau. (Mais si quelqu'un peut l'expliquer plus en détail, j'aimerais l'entendre.)
La raison pour laquelle N doit être codé en dur (jusqu'à 42) est que l'expansion des accolades a lieu avant l'expansion des variables.
Enfin, voici la fonctionnalité n ° 4 , si vous souhaitez effectuer cette opération de manière récursive pour une hiérarchie de répertoires:
shopt -s globstar
a=( ** )
Ceci active un option Shell qui provoque **
pour correspondre récursivement. Maintenant votre $a
tableau contient tous les fichiers de la hiérarchie.
MacOS n'a pas les commandes triées -R et shuf , donc je avait besoin d’une solution uniquement bash qui randomise tous les fichiers sans doublons et n’a pas trouvé cela ici. Cette solution est similaire à la solution n ° 4 de gniourf_gniourf, mais elle espère ajouter de meilleurs commentaires.
Le script doit être facile à modifier pour s'arrêter après N échantillons en utilisant un compteur avec if, ou la boucle for gniourf_gniourf avec N. $ RANDOM est limité à environ 32 000 fichiers, mais cela devrait suffire 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
C’est le seul script que je puisse jouer avec Nice avec bash sur MacOS. J'ai combiné et modifié des extraits à partir des deux liens suivants:
commande ls: comment obtenir une liste récursive avec un chemin complet, 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
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 retourne.
# 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;
Si vous avez plus de fichiers dans votre dossier, vous pouvez utiliser la commande ci-dessous piped 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
.