¿Cómo mostrar una línea aleatoria de un archivo de texto?

26

Estoy tratando de escribir un script de shell. La idea es seleccionar una sola línea al azar del archivo de texto y mostrarla como una notificación de escritorio de Ubuntu.

Pero quiero que se seleccionen diferentes líneas cada vez que ejecuto el script. ¿Hay alguna solución para hacer esto? No quiero todo el guión. Solo esa cosa simple solamente.

Anandu M Das
fuente
También visite: askubuntu.com/q/492572/256099
Pandya
stackoverflow.com/questions/448005/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

40

Puede usar la shufutilidad para imprimir líneas aleatorias desde el archivo

$ shuf -n 1 filename

-n : número de líneas para imprimir

Ejemplos:

$ 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
fuente
Pero al usar esto, tengo que cambiar el valor de n manualmente, ¿verdad? Quiero que ese shell elija automáticamente otra línea al azar. No es exactamente necesario estar al azar. Pero alguna otra línea.
Anandu M Das
44
@AnanduMDas No, no tiene que nindicar el número de líneas para imprimir. (es decir, si solo desea una o dos líneas). No es el número de línea (es decir, primera línea, segunda línea).
aneeshep
@AnanduMDas: He agregado algunos ejemplos a mi respuesta. Espero que esté claro ahora.
aneeshep
1
Gracias, ahora está claro :) También encontré otro algoritmo, como, almacenar la hora actual (solo segundo, por date +%S) en una variable x, y luego seleccionar esa línea x usando los comandos heady taildel archivo de texto. De todos modos tu método es más fácil. Gracias
Anandu M Das
+1: shufestá en coreutils, por lo que está disponible de forma predeterminada. Nota: carga el archivo de entrada en la memoria. Hay un algoritmo eficiente que no lo requiere .
jfs
8

Sólo por diversión, aquí hay una solución pura fiesta que no utiliza shuf, sort, wc, sed, head, tailu otras herramientas externas.

La única ventaja sobre la shufvariante es que es un poco más rápido, ya que es puro golpe. En mi máquina, para un archivo de 1000 líneas, la shufvariante tarda aproximadamente 0.1 segundos, mientras que el siguiente script tarda aproximadamente 0.01 segundos;) Entonces, si bien shufes la variante más fácil y más corta, esta es más rápida.

Honestamente, seguiría buscando la shufsolución, a menos que la alta eficiencia sea una preocupación 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
fuente
@EliahKagan Gracias por las sugerencias y buenos puntos. Admito que hay bastantes casos de esquina en los que realmente no había pensado demasiado. Escribí esto realmente más por diversión. Usar shufes mucho mejor de todos modos. Pensando en ello, no creo que pure bash sea en realidad más eficiente que usar shuf, como escribí anteriormente. Puede haber una sobrecarga mínima (constante) al disparar una herramienta externa, pero luego ejecutará mach más rápido que bash interpretado. Así que shufciertamente escala mejor. Entonces, digamos que el guión tiene un propósito educativo: es agradable ver que se puede hacer;)
Malte Skoruppa
GNU / Linux / Un * x tiene muchas ruedas muy bien probadas en carretera que no quisiera reinventar, a menos que fuera un ejercicio puramente académico. El "caparazón" estaba destinado a usarse para ensamblar muchas pequeñas piezas existentes que podrían (re) ensamblarse de varias maneras a través de entradas / salidas y muchas opciones. Cualquier otra cosa es mala, a menos que sea por deporte (por ejemplo, codegolf.stackexchange.com/tour ), en cuyo caso, ¡juega en ...!
michael
2
@michael_n Aunque una forma de "puro golpe" es principalmente útil para enseñar y modificar para otras tareas, esta es una implementación "real" más razonable de lo que parece. Bash está ampliamente disponible, pero shufes específico de GNU Coreutils (por ejemplo, no en FreeBSD 10.0). sort -Res portátil, pero resuelve un problema diferente (relacionado): las cadenas que aparecen como líneas múltiples tienen una probabilidad igual a las que aparecen solo una vez. (Por supuesto, wcy otras utilidades aún podrían usarse). Creo que la principal limitación aquí es que esto nunca elige nada después de la línea 32768 (y se vuelve menos aleatorio algo antes).
Eliah Kagan
2
Malte Skoruppa: Veo que moviste la pregunta PRNG de bash a U&L . Guay. Sugerencia: $((RANDOM<<15|RANDOM))está en 0..2 ^ 30-1. @JFSebastian Es shuf, no sort -R, lo que sesga hacia entradas más frecuentes. Poner shuf -n 1en lugar de sort -R | head -n1y comparar. (Por cierto, las iteraciones de 10 ^ 3 son más rápidas que 10 ^ 6 y aún así son suficientes para mostrar la diferencia). Vea también una demostración más áspera y visual y este poco de tontería que muestra que funciona en entradas grandes donde todas las cadenas son de alta frecuencia .
Eliah Kagan
1
@JFSebastian En ese comando, la entrada dieharderparece ser todos ceros. Asumiendo que esto no es simplemente un error extraño de mi parte, ¡eso ciertamente explicaría por qué no es aleatorio! ¿Obtiene datos atractivos si ejecuta while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outdurante un tiempo y luego examina el contenido outcon un editor hexadecimal? (O visualizarla sin embargo otra cosa te gusta.) Consigo todos los ceros, y RANDOMno es el culpable: consigo todos los ceros cuando sustituyo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))con 100, también.
Eliah Kagan
4

Digamos que tienes archivo notifications.txt. Necesitamos contar el número total de líneas, para determinar el rango del generador aleatorio:

$ cat notifications.txt | wc -l

Vamos a escribir en la variable:

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

Ahora para generar un número de 0a $LINEusaremos RANDOMvariable.

$ echo $[ $RANDOM % LINES]

Vamos a escribirlo en la variable:

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

Ahora solo necesitamos imprimir este número de línea:

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

Acerca de ALEATORIO:

   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.

Asegúrese de que su archivo tenga menos de 32767 números de línea. Ver esto si necesita un generador aleatorio más grande que funcione de inmediato.

Ejemplo:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
fuente
Una alternativa estilística (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael
por ejemplo, mire la última imagen en PRNG de prueba usando un mapa de bits gris para comprender por qué no es una buena idea aplicar % nun número aleatorio.
jfs
2

Aquí hay un script de Python que selecciona una línea aleatoria de los archivos de entrada o 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

El algoritmo es O (n) -time, O (1) -space. Funciona para archivos de más de 32767 líneas. No carga archivos de entrada en la memoria. Lee cada línea de entrada exactamente una vez, es decir, puede canalizar contenido arbitrario de gran tamaño (pero finito). Aquí hay una explicación del algoritmo .

jfs
fuente
1

Estoy impresionado por el trabajo que hicieron Malte Skoruppa y otros, pero aquí hay una forma mucho más simple de hacerlo:

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 algunos han notado, $ RANDOM no es aleatorio. Sin embargo, el límite de tamaño de archivo de 32767 líneas se supera al unir $ RANDOM juntos según sea necesario.

Derrochador
fuente