Comment afficher une ligne aléatoire à partir d'un fichier texte?

26

J'essaie d'écrire un script shell. L'idée est de sélectionner une seule ligne au hasard dans un fichier texte et de l'afficher en tant que notification de bureau Ubuntu.

Mais je veux que différentes lignes soient sélectionnées à chaque fois que j'exécute le script. Y a-t-il une solution pour ce faire? Je ne veux pas le script entier. Juste cette chose simple seulement.

Anandu M Das
la source
Visitez également: askubuntu.com/q/492572/256099
Pandya
stackoverflow.com/questions/448005/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Réponses:

40

Vous pouvez utiliser l' shufutilitaire pour imprimer des lignes aléatoires à partir d'un fichier

$ shuf -n 1 filename

-n : nombre de lignes à imprimer

Exemples:

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false
aneeshep
la source
Mais en utilisant cela, je dois changer la valeur de n manuellement non? Je veux que ce shell choisisse automatiquement une autre ligne au hasard. Pas exactement besoin d'être au hasard. Mais une autre ligne.
Anandu M Das
4
@AnanduMDas Non, vous n'avez pas à nindiquer le nombre de lignes à imprimer. (c'est-à-dire si vous ne voulez qu'une seule ligne ou deux lignes). Pas le numéro de ligne (c'est-à-dire la première ligne de la deuxième ligne)
aneeshep
@AnanduMDas: J'ai ajouté quelques exemples à ma réponse. J'espère que c'est clair maintenant.
aneeshep
1
Merci, c'est clair maintenant :) J'ai également trouvé un autre algorithme, son genre, stocker l'heure actuelle (seconde seulement, par date +%S) dans une variable x, puis sélectionner cette xième ligne en utilisant les commandes headet taildu fichier texte. Quoi qu'il en soit, votre méthode est plus simple. Merci
Anandu M Das
+1: shufest dans coreutils, il est donc disponible par défaut. Remarque: il charge le fichier d'entrée en mémoire. Il existe un algorithme efficace qui n'en a pas besoin .
jfs
13

Vous pouvez également utiliser la sortcommande pour obtenir une ligne aléatoire à partir du fichier.

sort -R filename | head -n1
g_p
la source
note: sort -Rproduit un résultat différent de shuf -n1ou select-randoms'il y a des lignes en double dans l'entrée. Voir le commentaire de @ EliahKagan .
jfs
8

Juste pour le plaisir, voici un solution pure bash qui n'utilise pas shuf, sort, wc, sed, head, tailou tout autre outils externes.

Le seul avantage par rapport à la shufvariante est qu'elle est légèrement plus rapide, car elle est pure bash. Sur ma machine, pour un fichier de 1000 lignes, la shufvariante prend environ 0,1 seconde, tandis que le script suivant prend environ 0,01 seconde;) Donc, alors que shufc'est la variante la plus simple et la plus courte, c'est plus rapide.

En toute honnêteté, j'irais toujours pour la shufsolution, à moins que la haute efficacité ne soit une préoccupation importante.

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"
Malte Skoruppa
la source
@EliahKagan Merci pour les suggestions et les bons points. J'admets qu'il y a pas mal de cas de coin auxquels je n'avais pas vraiment réfléchi. Je l'ai écrit vraiment plus pour le plaisir. L'utilisation shufest bien meilleure de toute façon. En y réfléchissant, je ne pense pas que le bash pur soit en fait plus efficace que l'utilisation shuf, comme je l'ai écrit précédemment. Il peut y avoir la moindre surcharge (constante) lors du lancement d'un outil externe, mais il s'exécutera plus rapidement que bash interprété. Alors, ça shufévolue certainement mieux. Alors disons que le script a un but éducatif: c'est agréable de voir que cela peut être fait;)
Malte Skoruppa
GNU / Linux / Un * x a beaucoup de roues très bien testées sur route que je ne voudrais pas réinventer, sauf si c'était un exercice purement académique. La "coque" était destinée à être utilisée pour assembler de nombreuses petites pièces existantes qui pouvaient être (ré) assemblées de diverses manières via des entrées / sorties et de nombreuses options. Tout le reste est de mauvaise forme, sauf pour le sport (par exemple, codegolf.stackexchange.com/tour ), auquel cas, jouez sur ...!
michael
2
@michael_n Bien qu'une méthode "pure bash" soit principalement utile pour l'enseignement et la modification pour d'autres tâches, c'est une implémentation "pour de vrai" plus raisonnable qu'il n'y paraît. Bash est largement disponible, mais shufspécifique à GNU Coreutils (par exemple, pas dans FreeBSD 10.0). sort -Rest portable, mais résout un problème différent (lié): les chaînes apparaissant comme plusieurs lignes ont une probabilité égale à celles n'apparaissant qu'une seule fois. (Bien sûr, wcet d'autres utilitaires pourraient encore être utilisés.) Je pense que la principale limitation ici est que cela ne choisit jamais rien après la 32768e ligne (et devient moins aléatoire un peu plus tôt).
Eliah Kagan
2
Malte Skoruppa: Je vois que vous avez déplacé la question bash PRNG vers U&L . Cool. Astuce: $((RANDOM<<15|RANDOM))est en 0..2 ^ 30-1. @JFSebastian Ce n'est shufpas le cas sort -R, ce qui biaise vers des entrées plus fréquentes. Mettre shuf -n 1en place sort -R | head -n1et comparer. (Btw 10 ^ 3 itérations est plus rapide que 10 ^ 6 et encore assez pour montrer la différence.) Voir aussi une démo plus grossière et plus visuelle et ce peu de bêtise montrant qu'il fonctionne sur de grandes entrées où toutes les cordes sont à haute fréquence .
Eliah Kagan du
1
@JFSebastian Dans cette commande, l'entrée de diehardersemble être tous des zéros. En supposant que ce n'est pas simplement une étrange erreur de ma part, cela expliquerait certainement pourquoi ce n'est pas aléatoire! Obtenez-vous de bonnes données si vous exécutez while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outpendant un certain temps, puis examinez le contenu d' outun éditeur hexadécimal? (Ou voir cependant autre que vous aimez.) Je reçois tous les zéros, et RANDOMn'est pas le coupable: je reçois tous les zéros quand je remplacerai $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))avec 100, aussi.
Eliah Kagan
4

Disons que vous avez un fichier notifications.txt. Nous devons compter le nombre total de lignes, pour déterminer la plage du générateur aléatoire:

$ cat notifications.txt | wc -l

Permet d'écrire dans une variable:

$ LINES=$(cat notifications.txt | wc -l)

Maintenant, pour générer un nombre de 0à $LINEnous utiliserons une RANDOMvariable.

$ echo $[ $RANDOM % LINES]

Permet de l'écrire dans une variable:

$  R_LINE=$(($RANDOM % LINES))

Il ne nous reste plus qu'à imprimer ce numéro de ligne:

$ sed -n "${R_LINE}p" notifications.txt

À propos de RANDOM:

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

Assurez-vous que votre fichier comporte moins de 32767 numéros de ligne. Voyez ceci si vous avez besoin d'un plus grand générateur aléatoire qui fonctionne hors de la boîte.

Exemple:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
la source
Une alternative stylistique (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael
par exemple, regardez la dernière image dans Test PRNG en utilisant un bitmap gris pour comprendre pourquoi ce n'est pas une bonne idée d'appliquer % nà un nombre aléatoire.
jfs
2

Voici un script Python qui sélectionne une ligne aléatoire dans les fichiers d'entrée ou stdin:

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from /programming//a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

L'algorithme est O (n) -heure, O (1) -espace. Il fonctionne pour les fichiers de plus de 32 767 lignes. Il ne charge pas les fichiers d'entrée en mémoire. Il lit chaque ligne d'entrée exactement une fois, c'est-à-dire que vous pouvez y canaliser un contenu arbitraire de grande taille (mais fini). Voici une explication de l'algorithme .

jfs
la source
1

Je suis impressionné par le travail que Malte Skoruppa et d'autres ont fait, mais voici une manière beaucoup plus simple de «pur bash»:

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

Comme certains l'ont noté, $ RANDOM n'est pas aléatoire. Cependant, la limite de taille de fichier de 32 767 lignes est dépassée en enchaînant $ RANDOM ensemble selon les besoins.

Gaspilleur
la source