Qual è un modo semplice per leggere una riga casuale da un file nella riga di comando Unix?


Risposte:


383

Puoi usare shuf:

shuf -n 1 $FILE

C'è anche un'utilità chiamata rl. In Debian è nel randomize-linespacchetto che fa esattamente quello che vuoi, anche se non disponibile in tutte le distribuzioni. Sulla sua home page in realtà raccomanda l'uso di shuf(che non esisteva al momento della sua creazione, credo). shuffa parte dei coreutils GNU, rlnon lo è.

rl -c 1 $FILE

2
Grazie per la shufpunta, è integrato in Fedora.
Cheng,

5
Andalso, sort -Rsicuramente farà aspettare molto se si ha a che fare con file considerevolmente enormi - linee 80kk -, mentre shuf -nagisce abbastanza istantaneamente.
Rubens,

23
Puoi ottenere shuf su OS X installando coreutilsda Homebrew. Potrebbe essere chiamato gshufinvece di shuf.
Alyssa Ross,

2
Allo stesso modo, puoi usare randomize-linessu OS X dibrew install randomize-lines; rl -c 1 $FILE
Jamie il

4
Nota che shuffa parte di GNU Coreutils e quindi non sarà necessariamente disponibile (per impostazione predefinita) su sistemi * BSD (o Mac?). Il perl one-liner di @ Tracker1 di seguito è più portatile (e dai miei test, è leggermente più veloce).
Adam Katz,

74

Un'altra alternativa:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1

28
$ {RANDOM} genera solo numeri inferiori a 32768, quindi non utilizzarlo per file di grandi dimensioni (ad esempio il dizionario inglese).
Ralf

3
Questo non ti dà la stessa probabilità precisa per ogni linea, a causa dell'operazione modulo. Questo non importa se la lunghezza del file è << 32768 (e non lo è affatto se divide quel numero), ma forse vale la pena notare.
Anaphory

10
Puoi estenderlo a numeri casuali a 30 bit usando (${RANDOM} << 15) + ${RANDOM}. Ciò riduce significativamente la distorsione e gli consente di lavorare per file contenenti fino a 1 miliardo di righe.
nneonneo,

@nneonneo: Very Cool trucco, anche se in base a questo link dovrebbe essere OR delle $ {} RANDOM 's invece di PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor

+e |sono uguali poiché ${RANDOM}è 0..32767 per definizione.
nneonneo,

71
sort --random-sort $FILE | head -n 1

(Mi piace l'approccio shuf sopra ancora meglio - non sapevo nemmeno che esistesse e non avrei mai trovato quello strumento da solo)


10
+1 Mi piace, ma potresti averne bisogno molto recente sort, non ha funzionato su nessuno dei miei sistemi (CentOS 5.5, Mac OS 10.7.2). Inoltre, l'uso inutile del gatto potrebbe essere ridotto asort --random-sort < $FILE | head -n 1
Steve Kehlet,

sort -R <<< $'1\n1\n2' | head -1è probabile che restituisca 1 e 2, perché sort -Rordina le linee duplicate insieme. Lo stesso vale per sort -Ru, poiché rimuove le linee duplicate.
Lri,

5
Questo è relativamente lento, poiché l'intero file deve essere mischiato sortprima di eseguirne il piping head. shufseleziona invece righe casuali dal file ed è molto più veloce per me.
Bengt,

1
@SteveKehlet mentre ci siamo, sort --random-sort $FILE | headsarebbe meglio, in quanto consente di accedere direttamente al file, eventualmente abilitando l'ordinamento parallelo efficiente
WaelJ

5
Le opzioni --random-sorte -Rsono specifiche per l'ordinamento GNU (quindi non funzionano con BSD o Mac OS sort). L'ordinamento GNU ha appreso quelle bandiere nel 2005, quindi è necessario GNU coreutils 6.0 o versioni successive (ad esempio CentOS 6).
RJHunter

31

Questo è semplice

cat file.txt | shuf -n 1

Concesso, questo è solo un po 'più lento del "shuf -n 1 file.txt" da solo.


2
Migliore risposta. Non sapevo di questo comando. Nota che -n 1specifica 1 riga, e puoi cambiarla in più di 1. shufpuò essere usata anche per altre cose; Ho appena convogliato ps auxe grepcon esso per uccidere casualmente i processi che corrispondono in parte a un nome.
sudo,

18

perlfaq5: come posso selezionare una linea casuale da un file? Ecco un algoritmo di campionamento del serbatoio dal Camel Book:

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

Questo ha un vantaggio significativo nello spazio rispetto alla lettura dell'intero file. Puoi trovare una prova di questo metodo in The Art of Computer Programming, Volume 2, Sezione 3.4.2, di Donald E. Knuth.


1
Solo ai fini dell'inclusione (nel caso in cui il sito indicato non funzioni), ecco il codice che Tracker1 indicava: "nomefile cat | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; ';"
Anirvan,

3
Questo è un uso inutile del gatto. Ecco una leggera modifica del codice trovato in perlfaq5 (e per gentile concessione del libro Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) mentre <>; stampa $ line; ' nome file
Mr. Muskrat,

err ... il sito collegato, cioè
Nathan Fellman,

Ho appena confrontato una versione N-lines di questo codice con shuf. Il codice perl è leggermente più veloce (8% più veloce per ora dell'utente, 24% più veloce per ora del sistema), anche se aneddoticamente ho trovato il codice perl "sembra" meno casuale (ho scritto un jukebox usando).
Adam Katz,

2
Più spunti di riflessione: shufmemorizza l'intero file di input in memoria , il che è un'idea orribile, mentre questo codice memorizza solo una riga, quindi il limite di questo codice è un conteggio di riga di INT_MAX (2 ^ 31 o 2 ^ 63 a seconda del tuo arco), supponendo che una qualsiasi delle sue potenziali linee selezionate si adatti alla memoria.
Adam Katz,

11

usando uno 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}

1
Casuale può essere 0, sed ha bisogno di 1 per la prima riga. sed -n 0p restituisce errore.
asalamon74,

mhm - che ne dici di $ 1 per "tmp.txt" e $ 2 per NUM?
blabla999,

ma anche con il bug vale un punto, poiché non ha bisogno di perl o python ed è il più efficiente possibile (leggere il file esattamente due volte ma non in memoria - quindi funzionerebbe anche con file di grandi dimensioni).
blabla999,

@ asalamon74: grazie @ blabla999: se ne facciamo una funzione, ok per $ 1, ma perché non calcolare NUM?
Paolo Tedesco,

Modifica della linea sed in: head - $ {X} $ {FILE} | la coda -1 dovrebbe farlo
JeffK,

4

Linea singola bash:

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

Leggero problema: nome file duplicato.


2
problema più leggero. eseguire questo su / usr / share / dict / words tende a favorire le parole che iniziano con "A". Giocando con esso, sono circa al 90% di parole "A" e al 10% di parole "B". Nessuno che inizia ancora con i numeri, che costituiscono la testa del file.
bibby,

wc -l < test.txtevita di dover convogliare cut.
fedorqui "SO smettere di danneggiare"

3

Ecco un semplice script Python che farà il lavoro:

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

Uso:

python randline.py file_to_get_random_line_from

1
Questo non funziona del tutto. Si ferma dopo una sola riga. Per farlo funzionare, ho fatto questo: import random, sys lines = open(sys.argv[1]).readlines() for i in range (len (lines)): rand = random.randint (0, len (lines) -1) print lines.pop (rand),
Jed Daniels

Sistema di commenti stupidi con formattazione scadente. La formattazione nei commenti non ha funzionato una volta?
Jed Daniels,

randint è inclusivo quindi len(lines)può portare a IndexError. Puoi usare print(random.choice(list(open(sys.argv[1])))). Esiste anche un algoritmo di campionamento del serbatoio efficiente in termini di memoria .
jfs,

2
Abbastanza spazio affamato; considera un file da 3 TB.
Michael Campbell,

@MichaelCampbell: l' algoritmo di campionamento del serbatoio che ho menzionato sopra potrebbe funzionare con file da 3 TB (se la dimensione della linea è limitata).
jfs,

2

Un altro modo di usare ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name

2
Questo usa awk e bash ( $RANDOMè un bashismo ). Ecco un metodo pure awk (mawk) che usa la stessa logica del codice perlfaq5 citato da @ Tracker1 sopra: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(wow, è persino più corto del codice perl!)
Adam Katz,

Tale codice deve leggere il file ( wc) per ottenere un conteggio di riga, quindi deve leggere (parte di) di nuovo il file ( awk) per ottenere il contenuto di un determinato numero di riga casuale. L'I / O sarà molto più costoso rispetto all'ottenimento di un numero casuale. Il mio codice legge il file solo una volta. Il problema con awk's rand()è che si basa su secondi, quindi otterrai duplicati se lo esegui consecutivamente troppo velocemente.
Adam Katz,

1

Una soluzione che funziona anche su MacOSX e dovrebbe funzionare anche su Linux (?):

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

Dove:

  • N è il numero di linee casuali che desideri

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> salva i numeri di riga scritti file1e quindi stampa la riga corrispondentefile2

  • jot -r $N 1 $(wc -l < $file)-> disegna Nnumeri a caso ( -r) nell'intervallo (1, number_of_line_in_file)con jot. La sostituzione del processo <()lo farà apparire come un file per l'interprete, quindi file1nell'esempio precedente.

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]}

Dato che $ RANDOM genera numeri inferiori al numero di parole in / usr / share / dict / words, che ha 235886 (sul mio Mac comunque), ho appena generato 6 numeri casuali separati tra 0 e 9 e li stringo insieme. Quindi mi assicuro che quel numero sia inferiore a 235886. Quindi rimuovo gli zeri iniziali per indicizzare le parole che ho archiviato nell'array. Poiché ogni parola è la sua riga, questa può essere facilmente utilizzata per qualsiasi file per selezionare casualmente una riga.
Ken,

0

Ecco cosa scopro poiché il mio Mac OS non usa tutte le risposte facili. Ho usato il comando jot per generare un numero poiché le soluzioni della variabile $ RANDOM non sembrano essere molto casuali nel mio test. Durante il test della mia soluzione, ho riscontrato un'ampia variazione nelle soluzioni fornite nell'output.

  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'eco della variabile è di ottenere una visione del numero casuale generato.


0

Utilizzando solo vanilla sed e awk, e senza usare $ RANDOM, un "one-liner" semplice, efficiente in termini di spazio e ragionevolmente veloce per selezionare una singola riga in modo pseudo-casuale da un file chiamato FILENAME è il seguente:

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

(Funziona anche se FILENAME è vuoto, nel qual caso non viene emessa alcuna riga.)

Un possibile vantaggio di questo approccio è che chiama rand () solo una volta.

Come sottolineato da @AdamKatz nei commenti, un'altra possibilità sarebbe quella di chiamare rand () per ogni linea:

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

(Una semplice prova di correttezza può essere fornita sulla base dell'induzione.)

Avvertenza circa rand()

"Nella maggior parte delle implementazioni di awk, tra cui gawk, rand () inizia a generare numeri dallo stesso numero iniziale o seme, ogni volta che si esegue awk."

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


Vedi il commento che ho pubblicato un anno prima di questa risposta , che ha una soluzione awk più semplice che non richiede sed. Nota anche il mio avvertimento sul generatore di numeri casuali di awk, che semina in interi secondi.
Adam Katz,
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.