Quel est un moyen facile de lire une ligne aléatoire à partir d'un fichier en ligne de commande Unix?

263

Quel est un moyen facile de lire une ligne aléatoire à partir d'un fichier en ligne de commande Unix?

codeforester
la source
Chaque ligne est-elle rembourrée à une longueur fixe?
Tracker1
non, chaque ligne a un nombre variable de caractères
gros fichier: stackoverflow.com/questions/29102589/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

383

Vous pouvez utiliser shuf:

shuf -n 1 $FILE

Il existe également un utilitaire appelé rl. Dans Debian, c'est dans le randomize-linespackage qui fait exactement ce que vous voulez, bien qu'il ne soit pas disponible dans toutes les distributions. Sur sa page d'accueil, il recommande en fait l'utilisation de shufplutôt (qui n'existait pas lors de sa création, je crois). shuffait partie des coreutils GNU, rln'est pas.

rl -c 1 $FILE
rogerdpack
la source
2
Merci pour le shufconseil, il est intégré à Fedora.
Cheng
5
Andalso, sort -Rva certainement faire attendre beaucoup s'il s'agit de fichiers considérablement volumineux - lignes de 80kk -, alors qu'il shuf -nagit assez instantanément.
Rubens
23
Vous pouvez obtenir shuf sur OS X en installant coreutilsdepuis Homebrew. Peut être appelé à la gshufplace de shuf.
Alyssa Ross
2
De même, vous pouvez utiliser randomize-linessur OS X parbrew install randomize-lines; rl -c 1 $FILE
Jamie
4
Notez que cela shuffait partie de GNU Coreutils et ne sera donc pas nécessairement disponible (par défaut) sur les systèmes * BSD (ou Mac?). Le perl one-liner de @ Tracker1 ci-dessous est plus portable (et d'après mes tests, est légèrement plus rapide).
Adam Katz
74

Une autre alternative:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
PolyThinker
la source
28
$ {RANDOM} ne génère que des nombres inférieurs à 32 768, donc ne l'utilisez pas pour les fichiers volumineux (par exemple le dictionnaire anglais).
Ralf
3
Cela ne vous donne pas la même probabilité précise pour chaque ligne, en raison de l'opération modulo. Cela n'a guère d'importance si la longueur du fichier est << 32768 (et pas du tout s'il divise ce nombre), mais cela vaut peut-être la peine d'être noté.
Anaphory
10
Vous pouvez l'étendre à des nombres aléatoires de 30 bits à l'aide de (${RANDOM} << 15) + ${RANDOM}. Cela réduit considérablement le biais et lui permet de fonctionner pour les fichiers contenant jusqu'à 1 milliard de lignes.
nneonneo
@nneonneo: Truc très cool, bien que selon ce lien, il devrait être OU sur les $ {RANDOM} au lieu de PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor
+et |sont les mêmes depuis ${RANDOM}0..32767 par définition.
nneonneo
71
sort --random-sort $FILE | head -n 1

(J'aime encore mieux l'approche shuf ci-dessus - je ne savais même pas qu'elle existait et je n'aurais jamais trouvé cet outil par moi-même)

Thomas Vander Stichele
la source
10
+1 Je l'aime bien, mais il se peut que vous ayez besoin d'un très récent sort, qui ne fonctionne sur aucun de mes systèmes (CentOS 5.5, Mac OS 10.7.2). En outre, l'utilisation inutile de chat pourrait être réduite àsort --random-sort < $FILE | head -n 1
Steve Kehlet
sort -R <<< $'1\n1\n2' | head -1est aussi susceptible de renvoyer 1 et 2, car sort -Rtrie les lignes en double ensemble. La même chose s'applique à sort -Ru, car elle supprime les lignes en double.
Lri
5
C'est relativement lent, car le fichier entier doit être mélangé sortavant de le rediriger head. shufsélectionne plutôt des lignes aléatoires dans le fichier et est beaucoup plus rapide pour moi.
Bengt
1
@SteveKehlet pendant que nous y sommes, sort --random-sort $FILE | headserait mieux, car il lui permet d'accéder directement au fichier, ce qui permet peut-être un tri parallèle efficace
WaelJ
5
Les options --random-sortet -Rsont spécifiques au tri GNU (elles ne fonctionneront donc pas avec BSD ou Mac OS sort). Le tri GNU a appris ces indicateurs en 2005, vous avez donc besoin de GNU coreutils 6.0 ou plus récent (par exemple CentOS 6).
RJHunter
31

C’est simple.

cat file.txt | shuf -n 1

Certes, c'est juste un peu plus lent que le "shuf -n 1 file.txt" seul.

Yokai
la source
2
Meilleure réponse. Je ne connaissais pas cette commande. Notez que -n 1spécifie 1 ligne, et vous pouvez la changer en plus de 1. shufpeut également être utilisée pour d'autres choses; Je viens de jouer avec ps auxet greppour tuer au hasard des processus correspondant partiellement à un nom.
sudo
18

perlfaq5: Comment sélectionner une ligne aléatoire dans un fichier? Voici un algorithme d'échantillonnage de réservoir du Camel Book:

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

Cela présente un avantage d'espace considérable par rapport à la lecture de l'intégralité du fichier. Vous pouvez trouver une preuve de cette méthode dans The Art of Computer Programming, Volume 2, Section 3.4.2, de Donald E. Knuth.

Tracker1
la source
1
Juste pour les besoins de l'inclusion (au cas où le site référé tombe en panne), voici le code que Tracker1 a pointé: "cat filename | perl -e 'while (<>) {push (@ _, $ _);} print @ _[rand()*@_];';"
Anirvan le
3
C'est une utilisation inutile du chat. Voici une légère modification du code trouvé dans perlfaq5 (et gracieuseté du livre Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) while <>; print $ line; ' nom de fichier
M. Muskrat
euh ... le site lié, c'est-à-dire
Nathan Fellman
Je viens de comparer une version N-lignes de ce code shuf. Le code Perl est très légèrement plus rapide (8% plus rapide par le temps de l'utilisateur, 24% plus rapide par le temps du système), bien que je trouve anecdotique que le code Perl "semble" moins aléatoire (j'ai écrit un juke-box en l'utilisant).
Adam Katz
2
Plus de matière à réflexion: shufstocke l'intégralité du fichier d'entrée en mémoire , ce qui est une idée horrible, tandis que ce code ne stocke qu'une seule ligne, donc la limite de ce code est un nombre de lignes de INT_MAX (2 ^ 31 ou 2 ^ 63 selon votre arc), en supposant que l'une de ses lignes potentielles sélectionnées tient en mémoire.
Adam Katz
11

en utilisant un script bash:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}
Paolo Tedesco
la source
1
Aléatoire peut être 0, sed a besoin de 1 pour la première ligne. sed -n 0p renvoie une erreur.
asalamon74
mhm - que diriez-vous de 1 $ pour "tmp.txt" et 2 $ pour NUM?
blabla999 le
mais même avec le bogue qui vaut un point, car il n'a pas besoin de perl ou de python et est aussi efficace que possible (lire le fichier exactement deux fois mais pas en mémoire - donc cela fonctionnerait même avec des fichiers énormes).
blabla999 le
@ asalamon74: merci @ blabla999: si nous en faisons une fonction, ok pour 1 $, mais pourquoi ne pas calculer NUM?
Paolo Tedesco
Changer la ligne sed en: head - $ {X} $ {FILE} | queue -1 devrait le faire
JeffK
4

Ligne de bash unique:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

Petit problème: nom de fichier en double.

asalamon74
la source
2
problème plus léger. effectuer cette opération sur / usr / share / dict / words a tendance à favoriser les mots commençant par "A". Jouer avec, je suis à environ 90% des mots «A» à 10% des mots «B». Aucun ne commençant par des chiffres pour l'instant, qui constituent la tête du fichier.
bibby
wc -l < test.txtévite d'avoir à canaliser cut.
fedorqui 'SO arrête de nuire'
3

Voici un simple script Python qui fera le travail:

import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

Usage:

python randline.py file_to_get_random_line_from
Adam Rosenfield
la source
1
Cela ne fonctionne pas vraiment. Il s'arrête après une seule ligne. Pour le faire fonctionner, j'ai fait ceci: import random, sys lines = open(sys.argv[1]).readlines() pour i dans la plage (len (lignes)): rand = random.randint (0, len (lignes) -1) print lines.pop (rand),
Jed Daniels
Système de commentaires stupide avec un formatage merdique. La mise en forme dans les commentaires ne fonctionnait-elle pas une fois?
Jed Daniels
randint est inclusif len(lines)peut donc conduire à IndexError. Vous pourriez utiliser print(random.choice(list(open(sys.argv[1])))). Il existe également un algorithme d'échantillonnage de réservoir efficace en mémoire .
jfs
2
Assez d'espace faim; considérez un fichier de 3 To.
Michael Campbell
@MichaelCampbell: l'algorithme d'échantillonnage de réservoir que j'ai mentionné ci-dessus peut fonctionner avec un fichier de 3 To (si la taille de la ligne est limitée).
jfs
2

Une autre façon d'utiliser ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
Baskar
la source
2
Cela utilise awk et bash ( $RANDOMest un bashisme ). Voici une méthode pure awk (mawk) utilisant la même logique que le code perlfaq5 cité par @ Tracker1 ci-dessus: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(wow, c'est encore plus court que le code perl!)
Adam Katz
Ce code doit lire le fichier ( wc) afin d'obtenir un nombre de lignes, puis doit relire (une partie du) fichier ( awk) pour obtenir le contenu du numéro de ligne aléatoire donné. Les E / S coûteront beaucoup plus cher que d'obtenir un nombre aléatoire. Mon code lit le fichier une seule fois. Le problème avec awk rand()est qu'il se base sur quelques secondes, vous obtiendrez donc des doublons si vous l'exécutez consécutivement trop rapidement.
Adam Katz
1

Une solution qui fonctionne également sur MacOSX, et devrait également fonctionner sur Linux (?):

N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

Où:

  • N est le nombre de lignes aléatoires que vous voulez

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> enregistrer les numéros de ligne écrits file1puis imprimer la ligne correspondantefile2

  • jot -r $N 1 $(wc -l < $file)-> dessiner des Nnombres au hasard ( -r) dans la plage (1, number_of_line_in_file)avec jot. La substitution de processus <()le fera ressembler à un fichier pour l'interpréteur, donc file1dans l'exemple précédent.
jrjc
la source
0
#!/bin/bash

IFS=$'\n' wordsArray=($(<$1))

numWords=${#wordsArray[@]}
sizeOfNumWords=${#numWords}

while [ True ]
do
    for ((i=0; i<$sizeOfNumWords; i++))
    do
        let ranNumArray[$i]=$(( ( $RANDOM % 10 )  + 1 ))-1
        ranNumStr="$ranNumStr${ranNumArray[$i]}"
    done
    if [ $ranNumStr -le $numWords ]
    then
        break
    fi
    ranNumStr=""
done

noLeadZeroStr=$((10#$ranNumStr))
echo ${wordsArray[$noLeadZeroStr]}
Ken
la source
Puisque $ RANDOM génère des nombres inférieurs au nombre de mots dans / usr / share / dict / words, qui a 235886 (sur mon Mac de toute façon), je génère juste 6 nombres aléatoires séparés entre 0 et 9 et les enchaîne ensemble. Ensuite, je m'assure que ce nombre est inférieur à 235886. Ensuite, supprimez les zéros de tête pour indexer les mots que j'ai stockés dans le tableau. Étant donné que chaque mot est sa propre ligne, cela pourrait facilement être utilisé pour n'importe quel fichier pour choisir une ligne au hasard.
Ken
0

Voici ce que je découvre puisque mon Mac OS n'utilise pas toutes les réponses faciles. J'ai utilisé la commande jot pour générer un nombre car les solutions de variables $ RANDOM ne semblent pas être très aléatoires dans mon test. Lors du test de ma solution, j'avais une grande variance dans les solutions fournies dans la sortie.

  RANDOM1=`jot -r 1 1 235886`
   #range of jot ( 1 235886 ) found from earlier wc -w /usr/share/dict/web2
   echo $RANDOM1
   head -n $RANDOM1 /usr/share/dict/web2 | tail -n 1

L'écho de la variable est d'obtenir un visuel du nombre aléatoire généré.

dreday13
la source
0

En utilisant uniquement vanilla sed et awk, et sans utiliser $ RANDOM, un "one-liner" simple, peu encombrant et relativement rapide pour sélectionner une seule ligne de manière pseudo-aléatoire dans un fichier nommé FILENAME est le suivant:

sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' FILENAME)p FILENAME

(Cela fonctionne même si FILENAME est vide, auquel cas aucune ligne n'est émise.)

Un avantage possible de cette approche est qu'elle n'appelle rand () qu'une seule fois.

Comme l'a souligné @AdamKatz dans les commentaires, une autre possibilité serait d'appeler rand () pour chaque ligne:

awk 'rand() * NR < 1 { line = $0 } END { print line }' FILENAME

(Une simple preuve d'exactitude peut être donnée sur la base de l'induction.)

Méfiez-vous de rand()

"Dans la plupart des implémentations awk, y compris gawk, rand () commence à générer des nombres à partir du même numéro de départ, ou graine, chaque fois que vous exécutez awk."

- https://www.gnu.org/software/gawk/manual/html_node/Numeric-Functions.html

de pointe
la source
Voir le commentaire que j'ai posté un an avant cette réponse , qui a une solution awk plus simple qui ne nécessite pas sed. Notez également ma mise en garde concernant le générateur de nombres aléatoires d'awk, qui se déclenche en secondes entières.
Adam Katz