Como exibir uma linha aleatória de um arquivo de texto?

26

Estou tentando escrever um script de shell. A idéia é selecionar uma única linha aleatoriamente no arquivo de texto e exibi-la como uma notificação da área de trabalho do Ubuntu.

Mas quero que diferentes linhas sejam selecionadas cada vez que executo o script. Existe alguma solução para fazer isso? Eu não quero o script inteiro. Apenas essa coisa simples.

Anandu M Das
fonte

Respostas:

40

Você pode usar o shufutilitário para imprimir linhas aleatórias do arquivo

$ shuf -n 1 filename

-n : número de linhas para imprimir

Exemplos:

$ 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
aneeshep
fonte
Mas, usando isso, eu tenho que alterar o valor de n manualmente, certo? Eu quero que o shell escolha automaticamente outra linha aleatoriamente. Não era exatamente necessário ser aleatório. Mas alguma outra linha.
Anandu M Das
4
@AnanduMDas Não, você não precisa indicar no número de linhas para imprimir. (ou seja, se você deseja apenas uma linha ou duas linhas). Não é o número da linha (ou seja, primeira linha 2ª linha).
Aneeshep
@AnanduMDas: adicionei alguns exemplos à minha resposta. Espero que esteja claro agora.
Aneeshep 18/09/2014
1
Obrigado, está claro agora :) Eu também encontrei outro algoritmo, tipo, armazene o horário atual (apenas o segundo por date +%S) em uma variável xe selecione a décima linha usando os comandos heade taildo arquivo de texto. De qualquer forma, seu método é mais fácil. Obrigado
Anandu M Das
+1: shufestá no coreutils, portanto está disponível por padrão. Nota: carrega o arquivo de entrada na memória. Existe um algoritmo eficiente que não exige isso .
jfs
13

Você também pode usar o sortcomando para obter uma linha aleatória do arquivo.

sort -R filename | head -n1
g_p
fonte
Nota: sort -Rproduz resultados diferentes shuf -n1ou select-randomse existem linhas duplicadas na entrada. Veja o comentário de @ EliahKagan .
jfs
8

Apenas por diversão, aqui está uma solução festa pura que não usa shuf, sort, wc, sed, head, tailou quaisquer outras ferramentas externas.

A única vantagem sobre a shufvariante é que é um pouco mais rápida, já que é pura festa. Na minha máquina, para um arquivo de 1000 linhas, a shufvariante leva cerca de 0,1 segundos, enquanto o script a seguir leva cerca de 0,01 segundos;) Portanto, embora shufseja a variante mais fácil e mais curta, isso é mais rápido.

Com toda a honestidade, eu ainda procuraria a shufsolução, a menos que alta eficiência seja uma preocupação 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"
Malte Skoruppa
fonte
@EliahKagan Obrigado pelas sugestões e bons pontos. Admito que existem alguns casos de esquina que realmente não havia pensado muito. Eu escrevi isso muito mais por diversão. Usar shufé muito melhor de qualquer maneira. Pensando nisso, não acredito que o bash puro seja realmente mais eficiente do que usar shuf, como escrevi anteriormente. Pode haver a menor sobrecarga (constante) ao disparar uma ferramenta externa, mas ela executará o mach mais rápido que o bash interpretado. Então, shufcertamente dimensiona melhor. Então, digamos que o script serve a um propósito educacional: É bom vê-lo pode ser feito;)
Malte Skoruppa
O GNU / Linux / Un * x tem muitas rodas testadas na estrada que eu não gostaria de reinventar, a menos que fosse um exercício puramente acadêmico. A "carcaça" foi projetada para ser usada para montar muitas pequenas peças existentes que poderiam ser (re) montadas de várias maneiras através de opções de entrada / saída e muitas outras opções. Qualquer outra coisa é ruim, a menos que seja para esportes (por exemplo, codegolf.stackexchange.com/tour ); nesse caso, jogue em ...!
22614 Michael
2
@michael_n Embora uma maneira de "bash puro" seja principalmente útil para ensinar e modificar para outras tarefas, essa é uma implementação "razoável" mais razoável do que parece. O Bash está amplamente disponível, mas shufé específico para o GNU Coreutils (por exemplo, não no FreeBSD 10.0). sort -Ré portátil, mas resolve um problema diferente (relacionado): cadeias que aparecem como várias linhas têm probabilidade igual àquelas que aparecem apenas uma vez. (É claro, wce outros utilitários ainda podem ser usados.) Acho que a principal limitação aqui é que nunca escolhe nada após a linha 32768 (e se torna menos aleatória um pouco antes).
Eliah Kagan 19/09/14
2
Malte Skoruppa: Vejo que você mudou a pergunta do bash PRNG para U&L . Legal. Dica: $((RANDOM<<15|RANDOM))está em 0..2 ^ 30-1. @JFSebastian Não é shufisso sort -Rque distorce as entradas mais frequentes. Coloque shuf -n 1no lugar sort -R | head -n1e compare. (As iterações entre 10 ^ 3 são mais rápidas que 10 ^ 6 e ainda o suficiente para mostrar a diferença.) Veja também uma demonstração mais áspera e visual e esse pouco de bobagem mostrando que funciona em grandes entradas, onde todas as seqüências de caracteres são de alta frequência .
Eliah Kagan
1
@JFSebastian Nesse comando, a entrada para dieharderparece ser todos os zeros. Supondo que isso não seja apenas um erro estranho da minha parte, isso certamente explicaria por que não é aleatório! Você obtém dados de boa aparência se executar while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outpor um tempo e depois examinar o conteúdo outcom um editor hexadecimal? (Ou veja como quiser.) Recebo todos os zeros e RANDOMnão sou o culpado: também recebo todos os zeros quando substituo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))por 100.
Eliah Kagan 26/09
4

Digamos que você tenha um arquivo notifications.txt. Precisamos contar o número total de linhas, para determinar o intervalo do gerador aleatório:

$ cat notifications.txt | wc -l

Vamos escrever para a variável:

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

Agora, para gerar o número de 0para $LINE, usaremos a RANDOMvariável

$ echo $[ $RANDOM % LINES]

Vamos escrevê-lo na variável:

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

Agora só precisamos imprimir este número de linha:

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

Sobre a 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.

Verifique se o seu arquivo tem menos de 32767 números de linha. Veja isso se você precisar de um gerador aleatório maior que funcione imediatamente.

Exemplo:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
fonte
Uma alternativa estilística (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael
por exemplo, observe a última imagem no Teste PRNG usando bitmap cinza para entender por que não é uma boa ideia aplicar % na um número aleatório.
jfs
2

Aqui está um script Python que seleciona uma linha aleatória dos arquivos de entrada ou 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

O algoritmo é tempo O (n), espaço O (1). Ele funciona para arquivos maiores que 32767 linhas. Não carrega arquivos de entrada na memória. Ele lê cada linha de entrada exatamente uma vez, ou seja, você pode canalizar conteúdo arbitrário (mas finito). Aqui está uma explicação do algoritmo .

jfs
fonte
1

Estou impressionado com o trabalho que Malte Skoruppa e outros fizeram, mas aqui está uma maneira muito mais simples de "festança pura":

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

Como alguns observaram, $ RANDOM não é aleatório. No entanto, o limite de tamanho do arquivo de 32767 linhas é superado ao juntar $ RANDOMs conforme necessário.

Wastrel
fonte