O que é uma maneira fácil de ler linhas aleatórias de um arquivo na linha de comando do Unix?

263

O que é uma maneira fácil de ler linhas aleatórias de um arquivo na linha de comando do Unix?

codeforester
fonte
Cada linha é preenchida com um comprimento fixo?
Tracker1
não, cada linha tem um número variável de caracteres
arquivo grande: stackoverflow.com/questions/29102589/…
Ciro Santilli escreveu:

Respostas:

383

Você pode usar shuf:

shuf -n 1 $FILE

Há também um utilitário chamado rl. No Debian, está no randomize-linespacote que faz exatamente o que você deseja, embora não esteja disponível em todas as distros. Na sua página inicial, ele recomenda o uso shuf(em vez disso, que não existia quando foi criado). shuffaz parte dos GNU coreutils, rlnão é.

rl -c 1 $FILE
rogerdpack
fonte
2
Obrigado pela shufdica, ele está embutido no Fedora.
Cheng
5
Andalso, sort -Rdefinitivamente fará com que se espere muito se lidar com arquivos consideravelmente grandes - linhas de 80 kkk -, embora shuf -natue instantaneamente.
Rubens
23
Você pode obter shuf no OS X instalando a coreutilspartir do Homebrew. Pode ser chamado em gshufvez de shuf.
Alyssa Ross
2
Da mesma forma, você pode usar randomize-linesno OS X porbrew install randomize-lines; rl -c 1 $FILE
Jamie
4
Observe que shuffaz parte do GNU Coreutils e, portanto, não estará necessariamente disponível (por padrão) em sistemas * BSD (ou Mac?). O one-liner perl do @ Tracker1 abaixo é mais portátil (e, pelos meus testes, é um pouco mais rápido).
Adam Katz
74

Outra alternativa:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
PolyThinker
fonte
28
$ {RANDOM} gera apenas números inferiores a 32768, portanto, não use isso para arquivos grandes (por exemplo, o dicionário de inglês).
Ralf
3
Isso não fornece a mesma probabilidade exata para cada linha, devido à operação do módulo. Isso pouco importa se o tamanho do arquivo é << 32768 (e nem um pouco se ele divide esse número), mas talvez seja interessante notar.
Anaphory
10
Você pode estender isso para números aleatórios de 30 bits usando (${RANDOM} << 15) + ${RANDOM}. Isso reduz significativamente o viés e permite trabalhar com arquivos que contêm até 1 bilhão de linhas.
nneonneo
@nneonneo: Truque muito legal, embora, de acordo com este link, deva ser OR 'dos ​​$ {RANDOM}' em vez de PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor
+e |são os mesmos, pois ${RANDOM}é 0..32767 por definição.
Nneonneo 12/07/2015
71
sort --random-sort $FILE | head -n 1

(Eu gosto da abordagem shuf acima ainda melhor - eu nem sabia que existia e nunca teria encontrado essa ferramenta sozinha)

Thomas Vander Stichele
fonte
10
+1 Gostei, mas você pode precisar de um muito recente sort, não funcionou em nenhum dos meus sistemas (CentOS 5.5, Mac OS 10.7.2). Além disso, o uso inútil de um gato pode ser reduzido a #sort --random-sort < $FILE | head -n 1
Steve Kehlet 16/02/2012
sort -R <<< $'1\n1\n2' | head -1é provável que retorne 1 e 2, porque sort -Rclassifica linhas duplicadas juntas. O mesmo se aplica a sort -Ru, porque remove linhas duplicadas.
Lri 15/09/12
5
Isso é relativamente lento, pois o arquivo inteiro precisa ser embaralhado sortantes de transmiti- lo head. shufseleciona linhas aleatórias do arquivo e é muito mais rápido para mim.
Bengt
1
@SteveKehlet enquanto estamos no assunto, sort --random-sort $FILE | headseria melhor, pois permite-lo para acessar o arquivo diretamente, possivelmente permitindo paralelo eficiente de triagem
WaelJ
5
As opções --random-sorte -Rsão específicas para a classificação GNU (para que não funcionem com BSD ou Mac OS sort). O tipo GNU aprendeu esses sinalizadores em 2005, então você precisa do GNU coreutils 6.0 ou mais recente (por exemplo, CentOS 6).
RJHunter
31

Isto é simples.

cat file.txt | shuf -n 1

Concedido que isso é apenas um pouco mais lento que o "shuf -n 1 file.txt" por si só.

Yokai
fonte
2
Melhor resposta. Eu não sabia sobre esse comando. Observe que -n 1especifica 1 linha e você pode alterá-la para mais de 1. também shufpode ser usada para outras coisas; Acabei de canalizar ps auxe grepcom ele matar aleatoriamente processos que correspondem parcialmente a um nome.
Sudo
18

perlfaq5: Como seleciono uma linha aleatória de um arquivo? Aqui está um algoritmo de amostragem de reservatório do Camel Book:

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

Isso tem uma vantagem significativa no espaço em relação à leitura do arquivo inteiro. Você pode encontrar uma prova desse método em The Art of Computer Programming, Volume 2, Seção 3.4.2, de Donald E. Knuth.

Tracker1
fonte
1
Apenas para fins de inclusão (caso o site mencionado seja desativado), eis o código que Tracker1 apontou: "nome do arquivo cat | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Anirvan
3
Este é um uso inútil de gato. Aqui está uma pequena modificação do código encontrado no perlfaq5 (e cortesia do livro Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) enquanto <>; print $ line; ' nome do arquivo
Mr. Muskrat
err ... o site vinculado, isto é #
Nathan Fellman
Acabei de comparar uma versão N-lines deste código shuf. O código perl é um pouco mais rápido (8% mais rápido pelo tempo do usuário, 24% mais rápido pelo tempo do sistema), embora, anedoticamente, tenha encontrado o código perl "pareça" menos aleatório (escrevi uma jukebox usando-o).
Adam Katz
2
Mais informações: shufarmazena todo o arquivo de entrada na memória , o que é uma ideia horrível, enquanto esse código armazena apenas uma linha; portanto, o limite desse código é uma contagem de linhas de INT_MAX (2 ^ 31 ou 2 ^ 63, dependendo do seu arco), assumindo que qualquer uma de suas linhas de potencial selecionadas caiba na memória.
Adam Katz
11

usando um 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}
Paolo Tedesco
fonte
1
Aleatório pode ser 0, sed precisa de 1 para a primeira linha. sed -n 0p retorna erro.
Asalamon74
mhm - que tal $ 1 para "tmp.txt" e $ 2 para NUM?
blabla999
mas mesmo com o bug que vale um ponto, já que não precisa de perl ou python e é o mais eficiente possível (lendo o arquivo exatamente duas vezes, mas não na memória - por isso funcionaria mesmo com arquivos grandes).
blabla999
@ asalamon74: obrigado @ blabla999: se fizermos uma função dela, ok por US $ 1, mas por que não computar NUM?
Paolo Tedesco
Alterando a linha sed para: head - $ {X} $ {FILE} | tail -1 deve fazê-lo
JeffK
4

Linha de bash única:

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

Pequeno problema: nome de arquivo duplicado.

asalamon74
fonte
2
problema mais leve. executar isso em / usr / share / dict / words tende a favorecer as palavras que começam com "A". Brincando com isso, estou com cerca de 90% das palavras "A" a 10% das palavras "B". Nenhum começando com números ainda, que compõem o cabeçalho do arquivo.
Bibby
wc -l < test.txtevita ter que canalizar para cut.
fedorqui 'SO stop prejudying'
3

Aqui está um script Python simples que fará o trabalho:

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
Adam Rosenfield
fonte
1
Isso não funciona muito bem. Para após uma única linha. Para fazê-lo funcionar, fiz o seguinte: import random, sys lines = open(sys.argv[1]).readlines() para i no intervalo (len (linhas)): rand = random.randint (0, len (linhas) -1) print lines.pop (rand),
Jed Daniels
Sistema de comentários estúpido com formatação de baixa qualidade. A formatação de comentários não funcionou uma vez?
precisa saber é o seguinte
randint é inclusivo, portanto, len(lines)pode levar a IndexError. Você poderia usar print(random.choice(list(open(sys.argv[1])))). Também existe um algoritmo de amostragem de reservatório eficiente em memória .
jfs
2
Bastante espaço com fome; considere um arquivo de 3 TB.
Michael Campbell
@ MichaelCampbell: o algoritmo de amostragem de reservatório que mencionei acima pode funcionar com um arquivo de 3 TB (se o tamanho da linha for limitado).
jfs
2

Outra maneira de usar ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
Baskar
fonte
2
Isso usa awk e bash ( $RANDOMé um bashismo ). Aqui está um método awk puro (mawk) usando a mesma lógica do código perlfaq5 citado pelo @ Tracker1 acima: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(uau, é ainda mais curto que o código perl!)
Adam Katz
Esse código deve ler o arquivo ( wc) para obter uma contagem de linhas e, em seguida, ler (parte do) arquivo novamente ( awk) para obter o conteúdo do número de linha aleatório fornecido. A E / S será muito mais cara do que obter um número aleatório. Meu código lê o arquivo apenas uma vez. O problema do awk's rand()é que ele se espalha com base em segundos, para que você receba duplicados se executá-lo consecutivamente rápido demais.
Adam Katz
1

Uma solução que também funciona no MacOSX e também deve funcionar no Linux (?):

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

Onde:

  • N é o número de linhas aleatórias que você deseja

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> salve os números de linha escritos file1e imprima a linha correspondente emfile2

  • jot -r $N 1 $(wc -l < $file) -> desenhar N números aleatoriamente ( -r) no intervalo (1, number_of_line_in_file)com jot. A substituição do processo <()fará com que pareça um arquivo para o intérprete, portanto, file1no exemplo anterior.
jrjc
fonte
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]}
Ken
fonte
Como o $ RANDOM gera números menores que o número de palavras em / usr / share / dict / words, que possui 235886 (no meu Mac de qualquer maneira), eu apenas gero 6 números aleatórios separados entre 0 e 9 e os agrupamos. Então, verifique se esse número é menor que 235886. Em seguida, remova os zeros à esquerda para indexar as palavras que eu armazenei na matriz. Como cada palavra é sua própria linha, isso pode ser facilmente usado para qualquer arquivo escolher uma linha aleatoriamente.
Ken
0

Aqui está o que eu descobri, pois meu Mac OS não usa todas as respostas fáceis. Usei o comando jot para gerar um número, pois as soluções variáveis ​​$ RANDOM não parecem ser muito aleatórias no meu teste. Ao testar minha solução, tive uma grande variação nas soluções fornecidas na saída.

  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

O eco da variável é obter um visual do número aleatório gerado.

dreday13
fonte
0

Usando apenas vanilla sed e awk, e sem usar $ RANDOM, um "one-liner" simples, eficiente em termos de espaço e razoavelmente rápido para selecionar uma única linha pseudo-aleatoriamente de um arquivo chamado FILENAME é o seguinte:

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

(Isso funciona mesmo que FILENAME esteja vazio, caso em que nenhuma linha é emitida.)

Uma possível vantagem dessa abordagem é que ela chama apenas rand () uma vez.

Como apontado por @AdamKatz nos comentários, outra possibilidade seria chamar rand () para cada linha:

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

(Uma simples prova de correção pode ser dada com base na indução.)

Advertência sobre rand()

"Na maioria das implementações do awk, incluindo gawk, rand () começa a gerar números a partir do mesmo número inicial, ou semente, sempre que você executa o awk."

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

pico
fonte
Veja o comentário que eu publiquei um ano antes desta resposta , que possui uma solução awk mais simples que não requer sed. Observe também minha advertência sobre o gerador de números aleatórios do awk, que semeia em segundos inteiros.
Adam Katz