2009-12-16 5 views
0

J'ai un script bash qui traite tous les fichiers dans un répertoire en utilisant une boucle commeRandomisation ordre arg pour une fête pour la déclaration

for i in * txt faire ops ..... done

Il existe des milliers de fichiers qui sont toujours traités dans l'ordre alphanumérique en raison de l'extension '* .txt'.

Existe-t-il un moyen simple de randomiser la commande tout en assurant que je traite tous les fichiers une seule fois?

+0

pourquoi voulez-vous faire cela? – ennuikiller

Répondre

3

En supposant que les noms de fichiers n'ont pas des espaces, remplacez juste la sortie de List::Util::shuffle.

for i in `perl -MList::Util=shuffle -e'$,=$";print shuffle<*.txt>'`; do 
    .... 
done 

Si les noms de fichiers contiennent des espaces mais n'ont pas de saut de ligne ou de barre oblique inverse, lisez une ligne à la fois.

perl -MList::Util=shuffle -le'$,=$\;print shuffle<*.txt>' | while read i; do 
    .... 
done 

Pour être complètement sûrs chez Bash, utilisez des chaînes à terminaison NUL.

perl -MList::Util=shuffle -0 -le'$,=$\;print shuffle<*.txt>' | 
while read -r -d '' i; do 
    .... 
done 

Pas très efficace, mais il est possible de le faire dans Bash pur si on le souhaite. sort -R fait quelque chose comme ça, en interne.

declare -a a      # create an integer-indexed associative array 
for i in *.txt; do 
    j=$RANDOM     # find an unused slot 
    while [[ -n ${a[$j]} ]]; do 
     j=$RANDOM 
    done 
    a[$j]=$i      # fill that slot 
done 
for i in "${a[@]}"; do   # iterate in index order (which is random) 
    .... 
done 

Ou utilisez un shuffle traditionnel Fisher-Yates.

a=(*.txt) 
for ((i=${#a[*]}; i>1; i--)); do 
    j=$[RANDOM%i] 
    tmp=${a[$j]} 
    a[$j]=${a[$[i-1]]} 
    a[$[i-1]]=$tmp 
done 
for i in "${a[@]}"; do 
    .... 
done 
+1

Il n'est pas nécessaire d'utiliser des signes dollar pour les variables d'indice de tableau ou les expressions d'indice de tableau: 'a [j] = $ {a [i-1]}'. 'Man bash' dit aussi "L'ancien format $ [expression] est obsolète et sera supprimé dans les prochaines versions de bash." (en faveur de $ (()) 'par exemple dans votre' j = $ [RANDOM% i] ') –

3

Vous pourriez conduite par vos noms de fichiers de la commande de tri:

ls | sort --random-sort | xargs .... 
+0

quelle commande de tri utilisez-vous?/bin/sort n'a pas une telle option – ennuikiller

+0

J'utilise 'sort (GNU coreutils) 6.10' – tangens

+0

GNU coreutils le possède, bien que certaines versions aient des bogues dont l'efficacité varie selon les paramètres régionaux. – ephemient

1

est ici une réponse qui repose sur des fonctions très basiques au sein awk il devrait donc être portable entre unix.

ls -1 | awk '{print rand()*100, $0}' | sort -n | awk '{print $2}' 

EDIT:

ephemient fait un bon point que ce qui précède n'est pas un espace de sécurité. Voici une version qui est:

ls -1 | awk '{print rand()*100, $0}' | sort -n | sed 's/[0-9\.]* //' 
+0

Rompre si les noms de fichiers contiennent des espaces. – ephemient

+0

J'espère que personne ne les créera jamais, mais les nouvelles lignes incorporées dans les noms de fichiers vont quand même trébucher. – ephemient

1

Voici une solution avec commandes standard:

for i in $(ls); do echo $RANDOM-$i; done | sort | cut -d- -f 2- 
+0

votre solution ne fonctionne pas si les noms de fichiers contiennent des espaces –

+0

changer' $ (ls) 'en' * ' laissez le travailler avec des espaces. – dustmachine

+2

'ls',' sort', et 'cut' ne sont pas de simples commandes Bash. Échoue également dans le cas horrible de noms de fichiers contenant des nouvelles lignes incorporées. – ephemient

0

Voici une solution Python, si elle est disponible sur votre système

import glob 
import random 
files = glob.glob("*.txt") 
if files: 
    for file in random.shuffle(files): 
     print file 
+0

Pour répondre à la question d'origine, cela veut dire 'if file.endswith ('. Txt')'. Ou peut-être que vous pourriez le transformer en quelque chose de plus générique comme «shuf» ... – ephemient

1

Si vous avez coreutils GNU, vous pouvez utiliser shuf :

while read -d '' f 
do 
    # some stuff with $f 
done < <(shuf -ze *) 

Cela fonctionnera avec les fichiers avec des espaces ou des nouvelles lignes dans leurs noms.

hors sujet Edit:

Pour illustrer points de SiegeX dans le commentaire:

$ a=42; echo "Don't Panic" | while read line; do echo $line; echo $a; a=0; echo $a; done; echo $a 
Don't Panic 
42 
0 
42 
$ a=42; while read line; do echo $line; echo $a; a=0; echo $a; done < <(echo "Don't Panic"); echo $a 
Don't Panic 
42 
0 
0 

Le tuyau provoque la while à exécuter dans un sous-shell et ainsi des changements aux variables dans la l'enfant ne retourne pas au parent.

+0

Plus précisément, coreutils≥6.1, je crois. Personnellement, je préférerais «shuf -ze * | while read' sur 'done <<(shuf -ze *)' mais effectivement ils sont identiques. – ephemient

+0

La raison pour laquelle j'aime 'done <<()' est qu'elle est parallèle à 'done

+0

@ephemient La méthode de substitution de processus '<<(foo)' a l'avantage supplémentaire de ne pas créer de sous-shell comme le fait la méthode pipe. – SiegeX

Questions connexes