Quel est un moyen facile de lire une ligne aléatoire à partir d'un fichier en ligne de commande Unix?
Quel est un moyen facile de lire une ligne aléatoire à partir d'un fichier en ligne de commande Unix?
Réponses:
Vous pouvez utiliser shuf
:
shuf -n 1 $FILE
Il existe également un utilitaire appelé rl
. Dans Debian, c'est dans le randomize-lines
package 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 shuf
plutôt (qui n'existait pas lors de sa création, je crois). shuf
fait partie des coreutils GNU, rl
n'est pas.
rl -c 1 $FILE
shuf
conseil, il est intégré à Fedora.
sort -R
va certainement faire attendre beaucoup s'il s'agit de fichiers considérablement volumineux - lignes de 80kk -, alors qu'il shuf -n
agit assez instantanément.
coreutils
depuis Homebrew. Peut être appelé à la gshuf
place de shuf
.
randomize-lines
sur OS X parbrew install randomize-lines; rl -c 1 $FILE
shuf
fait 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).
Une autre alternative:
head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
(${RANDOM} << 15) + ${RANDOM}
. Cela réduit considérablement le biais et lui permet de fonctionner pour les fichiers contenant jusqu'à 1 milliard de lignes.
+
et |
sont les mêmes depuis ${RANDOM}
0..32767 par définition.
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)
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
sort -R <<< $'1\n1\n2' | head -1
est aussi susceptible de renvoyer 1 et 2, car sort -R
trie les lignes en double ensemble. La même chose s'applique à sort -Ru
, car elle supprime les lignes en double.
sort
avant de le rediriger head
. shuf
sélectionne plutôt des lignes aléatoires dans le fichier et est beaucoup plus rapide pour moi.
sort --random-sort $FILE | head
serait mieux, car il lui permet d'accéder directement au fichier, ce qui permet peut-être un tri parallèle efficace
--random-sort
et -R
sont 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).
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.
-n 1
spécifie 1 ligne, et vous pouvez la changer en plus de 1. shuf
peut également être utilisée pour d'autres choses; Je viens de jouer avec ps aux
et grep
pour tuer au hasard des processus correspondant partiellement à un nom.
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.
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).
shuf
stocke 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.
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}
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.
wc -l < test.txt
évite d'avoir à canaliser cut
.
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
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),
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 .
Une autre façon d'utiliser ' awk '
awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
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.
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 file1
puis imprimer la ligne correspondantefile2
jot -r $N 1 $(wc -l < $file)
-> dessiner des N
nombres 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 file1
dans l'exemple précédent.#!/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]}
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é.
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.)
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