O que é uma maneira fácil de ler linhas aleatórias de um arquivo na linha de comando do Unix?
O que é uma maneira fácil de ler linhas aleatórias de um arquivo na linha de comando do Unix?
Respostas:
Você pode usar shuf
:
shuf -n 1 $FILE
Há também um utilitário chamado rl
. No Debian, está no randomize-lines
pacote 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). shuf
faz parte dos GNU coreutils, rl
não é.
rl -c 1 $FILE
shuf
dica, ele está embutido no Fedora.
sort -R
definitivamente fará com que se espere muito se lidar com arquivos consideravelmente grandes - linhas de 80 kkk -, embora shuf -n
atue instantaneamente.
coreutils
partir do Homebrew. Pode ser chamado em gshuf
vez de shuf
.
randomize-lines
no OS X porbrew install randomize-lines; rl -c 1 $FILE
shuf
faz 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).
Outra alternativa:
head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
(${RANDOM} << 15) + ${RANDOM}
. Isso reduz significativamente o viés e permite trabalhar com arquivos que contêm até 1 bilhão de linhas.
+
e |
são os mesmos, pois ${RANDOM}
é 0..32767 por definição.
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)
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
sort -R <<< $'1\n1\n2' | head -1
é provável que retorne 1 e 2, porque sort -R
classifica linhas duplicadas juntas. O mesmo se aplica a sort -Ru
, porque remove linhas duplicadas.
sort
antes de transmiti- lo head
. shuf
seleciona linhas aleatórias do arquivo e é muito mais rápido para mim.
sort --random-sort $FILE | head
seria melhor, pois permite-lo para acessar o arquivo diretamente, possivelmente permitindo paralelo eficiente de triagem
--random-sort
e -R
sã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).
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ó.
-n 1
especifica 1 linha e você pode alterá-la para mais de 1. também shuf
pode ser usada para outras coisas; Acabei de canalizar ps aux
e grep
com ele matar aleatoriamente processos que correspondem parcialmente a um nome.
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.
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).
shuf
armazena 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.
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}
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.
wc -l < test.txt
evita ter que canalizar para cut
.
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
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),
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 .
Outra maneira de usar ' awk '
awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
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.
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 file1
e 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, file1
no exemplo anterior.#!/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]}
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.
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.)
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