Qual è un modo semplice per leggere una riga casuale da un file nella riga di comando Unix?
Qual è un modo semplice per leggere una riga casuale da un file nella riga di comando Unix?
Risposte:
Puoi usare shuf
:
shuf -n 1 $FILE
C'è anche un'utilità chiamata rl
. In Debian è nel randomize-lines
pacchetto 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). shuf
fa parte dei coreutils GNU, rl
non lo è.
rl -c 1 $FILE
shuf
punta, è integrato in Fedora.
sort -R
sicuramente farà aspettare molto se si ha a che fare con file considerevolmente enormi - linee 80kk -, mentre shuf -n
agisce abbastanza istantaneamente.
coreutils
da Homebrew. Potrebbe essere chiamato gshuf
invece di shuf
.
randomize-lines
su OS X dibrew install randomize-lines; rl -c 1 $FILE
shuf
fa 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).
Un'altra alternativa:
head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
(${RANDOM} << 15) + ${RANDOM}
. Ciò riduce significativamente la distorsione e gli consente di lavorare per file contenenti fino a 1 miliardo di righe.
+
e |
sono uguali poiché ${RANDOM}
è 0..32767 per definizione.
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)
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
sort -R <<< $'1\n1\n2' | head -1
è probabile che restituisca 1 e 2, perché sort -R
ordina le linee duplicate insieme. Lo stesso vale per sort -Ru
, poiché rimuove le linee duplicate.
sort
prima di eseguirne il piping head
. shuf
seleziona invece righe casuali dal file ed è molto più veloce per me.
sort --random-sort $FILE | head
sarebbe meglio, in quanto consente di accedere direttamente al file, eventualmente abilitando l'ordinamento parallelo efficiente
--random-sort
e -R
sono 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).
Questo è semplice
cat file.txt | shuf -n 1
Concesso, questo è solo un po 'più lento del "shuf -n 1 file.txt" da solo.
-n 1
specifica 1 riga, e puoi cambiarla in più di 1. shuf
può essere usata anche per altre cose; Ho appena convogliato ps aux
e grep
con esso per uccidere casualmente i processi che corrispondono in parte a un nome.
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.
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).
shuf
memorizza 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.
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}
Linea singola bash:
sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt
Leggero problema: nome file duplicato.
wc -l < test.txt
evita di dover convogliare cut
.
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
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),
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 .
Un altro modo di usare ' awk '
awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
$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!)
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.
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 file1
e quindi stampa la riga corrispondentefile2
jot -r $N 1 $(wc -l < $file)
-> disegna N
numeri 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 file1
nell'esempio precedente.#!/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]}
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.
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.)
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