Come visualizzare una linea casuale da un file di testo?


26

Sto cercando di scrivere uno script di shell. L'idea è quella di selezionare una singola riga a caso dal file di testo e visualizzarla come notifica desktop Ubuntu.

Ma desidero selezionare righe diverse ogni volta che eseguo lo script. C'è qualche soluzione per farlo? Non voglio l'intero script. Solo quella cosa semplice.



Risposte:


40

È possibile utilizzare l' shufutilità per stampare righe casuali dal file

$ shuf -n 1 filename

-n : numero di righe da stampare

Esempi:

$ 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

Ma usando questo, devo cambiare il valore di n manualmente giusto? Voglio che la shell scelga automaticamente un'altra riga in modo casuale. Non è necessario essere casuali. Ma qualche altra linea.
Anandu M Das,

4
@AnanduMDas No, non è necessario indicare nil numero di righe da stampare. (ovvero se si desidera solo una o due righe). Non il numero di riga (ovvero prima riga seconda riga).
aneeshep,

@AnanduMDas: ho aggiunto alcuni esempi alla mia risposta. Spero sia chiaro ora.
aneeshep,

1
Grazie è chiaro ora :) Ho anche trovato un altro algoritmo, come il suo, memorizzare l'ora corrente (solo secondo, da date +%S) in una variabile x, quindi selezionare quella riga X usando i comandi heade taildal file di testo. Comunque il tuo metodo è più semplice. Grazie
Anandu M Das il

+1: shufè in coreutils quindi è disponibile per impostazione predefinita. Nota: carica il file di input in memoria. Esiste un algoritmo efficiente che non lo richiede .
jfs,


8

Solo per divertimento, ecco un soluzione di bash pura che non usa shuf, sort, wc, sed, head, tailo altri strumenti esterni.

L'unico vantaggio rispetto alla shufvariante è che è leggermente più veloce, poiché è puro bash. Sulla mia macchina, per un file di 1000 righe la shufvariante richiede circa 0,1 secondi, mentre il seguente script impiega circa 0,01 secondi;) Quindi, mentre shufè la variante più semplice e più breve, questa è più veloce.

In tutta onestà, continuerei a cercare la shufsoluzione, a meno che l'alta efficienza non sia una preoccupazione 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"

@EliahKagan Grazie per i suggerimenti e buoni punti. Devo ammettere che ci sono alcuni casi angolari a cui non avevo davvero pensato troppo. L'ho scritto molto di più per il gusto di farlo. L'uso shufè molto meglio comunque. Pensandoci, non credo che il puro bash sia effettivamente più efficiente dell'uso shuf, come ho scritto in precedenza. Ci può essere il più piccolo (costante) overhead quando si accende uno strumento esterno, ma poi eseguirà mach più velocemente di quanto interpretato bash. Quindi shufsicuramente scala meglio. Quindi diciamo che la sceneggiatura ha uno scopo educativo: è bello vedere che può essere fatta;)
Malte Skoruppa,

GNU / Linux / Un * x ha molte ruote testate su strada molto bene che non vorrei reinventare, a meno che non fosse un esercizio puramente accademico. La "shell" doveva essere utilizzata per assemblare molte piccole parti esistenti che potevano essere (ri) assemblate in vari modi tramite input / output e molte opzioni. Qualcos'altro ha una cattiva forma, a meno che non sia per lo sport (es. Codegolf.stackexchange.com/tour ), nel qual caso, gioca su ...!
michael,

2
@michael_n Sebbene un modo "puro bash" sia utile principalmente per insegnare e modificare per altri compiti, questa è un'implementazione "per reale" più ragionevole di quanto possa sembrare. Bash è ampiamente disponibile, ma shufè specifico per GNU Coreutils (ad esempio, non in FreeBSD 10.0). sort -Rè portatile, ma risolve un diverso problema (correlato): le stringhe che appaiono come più righe hanno probabilità pari a quelle che appaiono una sola volta. (Certo, wce altre utilità potrebbero ancora essere usate.) Penso che il limite principale qui sia che non prende mai niente dopo la 32768esima riga (e diventa meno casuale un po 'prima).
Eliah Kagan,

2
Malte Skoruppa: Vedo che hai spostato la domanda bash PRNG in U&L . Freddo. Suggerimento: $((RANDOM<<15|RANDOM))è in 0..2 ^ 30-1. @JFSebastian E ' shuf, non è sort -R, che inclina verso gli ingressi più frequenti. Metti shuf -n 1al posto di sort -R | head -n1e confronta. (Tra 10 ^ 3 iterazioni è più veloce di 10 ^ 6 e ancora abbastanza per mostrare la differenza.) Vedi anche una demo più ruvida, più visiva e questo bit di stupidità che mostra che funziona su grandi input in cui tutte le stringhe sono ad alta frequenza .
Eliah Kagan,

1
@JFSebastian In quel comando, l'input a diehardersembra essere tutto zeri. Supponendo che questo non sia semplicemente uno strano errore da parte mia, ciò spiegherebbe sicuramente perché non è casuale! Ricevi dati di bell'aspetto se corri while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outper un po 'e poi esamini il contenuto outcon un editor esadecimale? (O vederlo comunque altra cosa ti piace.) Ottengo tutti zero, e RANDOMnon è il colpevole: ricevo tutti gli zeri quando sostituisco $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))con 100, anche.
Eliah Kagan,

4

Di 'che hai un file notifications.txt. Dobbiamo contare il numero totale di linee, per determinare l'intervallo del generatore casuale:

$ cat notifications.txt | wc -l

Consente di scrivere nella variabile:

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

Ora per generare il numero da 0a $LINEuseremo la RANDOMvariabile.

$ echo $[ $RANDOM % LINES]

Scriviamolo nella variabile:

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

Ora dobbiamo solo stampare questo numero di riga:

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

Informazioni su 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.

Assicurati che il tuo file contenga meno di 32767 numeri di riga. Vedi questo se hai bisogno di un generatore casuale più grande che funzioni immediatamente.

Esempio:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '

Un'alternativa stilistica (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael,


ad esempio, guarda l'ultima immagine in Test PRNG usando bitmap grigia per capire perché non è una buona idea applicare % na un numero casuale.
jfs,

2

Ecco uno script Python che seleziona una linea casuale da file di input o 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'algoritmo è O (n) -time, O (1) -space. Funziona con file di dimensioni superiori a 32767 linee. Non carica i file di input in memoria. Legge ogni riga di input esattamente una volta, ovvero è possibile reindirizzare arbitrariamente contenuto di grandi dimensioni (ma finito). Ecco una spiegazione dell'algoritmo .


1

Sono impressionato dal lavoro svolto da Malte Skoruppa e altri, ma ecco un modo "semplice bash" molto più semplice per farlo:

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

Come alcuni hanno notato, $ RANDOM non è casuale. Tuttavia, il limite della dimensione del file di 32767 righe viene superato mettendo insieme $ RANDOM secondo necessità.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.