¿Cuál es una manera fácil de leer una línea aleatoria de un archivo en la línea de comandos de Unix?

263

¿Cuál es una manera fácil de leer una línea aleatoria de un archivo en la línea de comandos de Unix?

codeforester
fuente
¿Cada línea está acolchada a una longitud fija?
Rastreador1
No, cada línea tiene un número variable de caracteres
archivo grande: stackoverflow.com/questions/29102589/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

383

Puedes usar shuf:

shuf -n 1 $FILE

También hay una utilidad llamada rl. En Debian está en el randomize-linespaquete que hace exactamente lo que quieres, aunque no está disponible en todas las distribuciones. En su página de inicio, en realidad recomienda el uso de en su shuflugar (que no existía cuando se creó, creo). shufes parte de los coreutils de GNU, rlno lo es.

rl -c 1 $FILE
rogerdpack
fuente
2
Gracias por el shufconsejo, está integrado en Fedora.
Cheng
55
Además, sort -Rdefinitivamente hará que uno espere mucho si se trata de archivos considerablemente grandes (líneas de 80kk), mientras que shuf -nactúa de manera bastante instantánea.
Rubens
23
Puede obtener shuf en OS X instalando coreutilsdesde Homebrew. Podría llamarse en gshuflugar de shuf.
Alyssa Ross
2
Del mismo modo, puede usar randomize-linesen OS X porbrew install randomize-lines; rl -c 1 $FILE
Jamie
44
Tenga en cuenta que shufes parte de GNU Coreutils y, por lo tanto, no estará necesariamente disponible (por defecto) en los sistemas * BSD (¿o Mac?). El perl one-liner de @ Tracker1 a continuación es más portátil (y según mis pruebas, es un poco más rápido).
Adam Katz
74

Otra alternativa:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
PolyThinker
fuente
28
$ {RANDOM} solo genera números menores que 32768, así que no lo use para archivos grandes (por ejemplo, el diccionario de inglés).
Ralf
3
Esto no le da la misma probabilidad precisa para cada línea, debido a la operación del módulo. Esto apenas importa si la longitud del archivo es << 32768 (y nada si divide ese número), pero tal vez valga la pena señalar.
Anaphory
10
Puede extender esto a números aleatorios de 30 bits utilizando (${RANDOM} << 15) + ${RANDOM}. Esto reduce significativamente el sesgo y le permite trabajar para archivos que contienen hasta mil millones de líneas.
nneonneo
@nneonneo: Truco muy bueno, aunque de acuerdo con este enlace debería ser O '' los $ {RANDOM} 'en lugar de PLUS' stackoverflow.com/a/19602060/293064
Jay Taylor
+y |son iguales ya que ${RANDOM}es 0..32767 por definición.
nneonneo
71
sort --random-sort $FILE | head -n 1

(Sin embargo, me gusta aún más el enfoque shuf anterior: ni siquiera sabía que existía y nunca habría encontrado esa herramienta por mi cuenta)

Thomas Vander Stichele
fuente
10
+1 Me gusta, pero es posible que necesites un muy reciente sort, no funcionó en ninguno de mis sistemas (CentOS 5.5, Mac OS 10.7.2). Además, el uso inútil del gato, podría reducirse asort --random-sort < $FILE | head -n 1
Steve Kehlet
sort -R <<< $'1\n1\n2' | head -1es tan probable que devuelva 1 y 2, porque sort -Rordena juntas líneas duplicadas. Lo mismo se aplica sort -Ru, porque elimina las líneas duplicadas.
Lri
55
Esto es relativamente lento, ya que se debe barajar todo el archivo sortantes de canalizarlo head. shufselecciona líneas aleatorias del archivo, y es mucho más rápido para mí.
Bengt el
1
@SteveKehlet mientras estamos en ello, sort --random-sort $FILE | headsería lo mejor, ya que le permite acceder al archivo directamente, posiblemente permitiendo una clasificación paralela eficiente
WaelJ
55
Las opciones --random-sorty -Rson específicas del ordenamiento GNU (por lo que no funcionarán con BSD o Mac OS sort). GNU sort aprendió esos indicadores en 2005, por lo que necesita GNU coreutils 6.0 o posterior (por ejemplo, CentOS 6).
RJHunter
31

Esto es simple.

cat file.txt | shuf -n 1

De acuerdo, esto es solo un poco más lento que el "shuf -n 1 file.txt" por sí solo.

Yokai
fuente
2
La mejor respuesta. No sabía sobre este comando. Tenga en cuenta que -n 1especifica 1 línea, y puede cambiarla a más de 1. también shufse puede usar para otras cosas; Acabo de canalizar ps auxy grepcon él para matar al azar procesos que coinciden parcialmente con un nombre.
sudo
18

perlfaq5: ¿Cómo selecciono una línea aleatoria de un archivo? Aquí hay un algoritmo de muestreo de yacimientos del Camel Book:

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

Esto tiene una ventaja significativa en el espacio sobre la lectura del archivo completo. Puede encontrar una prueba de este método en The Art of Computer Programming, Volumen 2, Sección 3.4.2, por Donald E. Knuth.

Rastreador1
fuente
1
Solo para propósitos de inclusión (en caso de que el sitio referido caiga), aquí está el código que Tracker1 señaló: "cat filename | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Anirvan el
3
Este es un uso inútil del gato. Aquí hay una ligera modificación del código que se encuentra en perlfaq5 (y cortesía del libro Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) mientras <>; print $ line; ' filename
Sr. Muskrat el
err ... el sitio vinculado, es decir
Nathan Fellman
Acabo de comparar una versión de N líneas de este código en contra shuf. El código perl es muy ligeramente más rápido (8% más rápido según el tiempo del usuario, 24% más rápido según el tiempo del sistema), aunque anecdóticamente he encontrado que el código perl "parece" menos aleatorio (escribí un jukebox usándolo).
Adam Katz
2
Más ideas para pensar: shufalmacena todo el archivo de entrada en la memoria , lo cual es una idea horrible, mientras que este código solo almacena una línea, por lo que el límite de este código es un recuento de líneas de INT_MAX (2 ^ 31 o 2 ^ 63 dependiendo de su arch), suponiendo que cualquiera de sus líneas potenciales seleccionadas se ajuste a la memoria.
Adam Katz
11

usando un 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
fuente
1
Aleatorio puede ser 0, sed necesita 1 para la primera línea. sed -n 0p devuelve un error.
asalamon74
mhm: ¿qué tal $ 1 para "tmp.txt" y $ 2 para NUM?
blabla999
pero incluso con el error vale la pena, ya que no necesita perl o python y es tan eficiente como puede obtener (leer el archivo exactamente dos veces pero no en la memoria, por lo que funcionaría incluso con archivos enormes).
blabla999
@ asalamon74: gracias @ blabla999: si hacemos una función, vale por $ 1, pero ¿por qué no calcular NUM?
Paolo Tedesco el
Cambiar la línea sed a: head - $ {X} $ {FILE} | tail -1 debería hacerlo
JeffK
4

Línea de bash simple:

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

Problema leve: nombre de archivo duplicado.

asalamon74
fuente
2
pequeño problema realizar esto en / usr / share / dict / words tiende a favorecer las palabras que comienzan con "A". Jugando con él, tengo aproximadamente 90% de palabras "A" a 10% de palabras "B". Ninguno que comience con números todavía, que constituyen el encabezado del archivo.
bibby
wc -l < test.txtevita tener que canalizar cut.
fedorqui 'SO deja de dañar'
3

Aquí hay un script simple de Python que hará el trabajo:

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
fuente
1
Esto no funciona del todo. Se detiene después de una sola línea. Para que funcione, hice esto: import random, sys lines = open(sys.argv[1]).readlines() para i en rango (len (líneas)): rand = random.randint (0, len (líneas) -1) print lines.pop (rand),
Jed Daniels
Estúpido sistema de comentarios con formato de mierda. ¿El formato en los comentarios no funcionó alguna vez?
Jed Daniels
randint es inclusivo, por len(lines)lo tanto, puede conducir a IndexError. Podrías usar print(random.choice(list(open(sys.argv[1])))). También hay un algoritmo de muestreo de depósito de memoria eficiente .
jfs
2
Bastante espacio hambriento; considere un archivo de 3TB.
Michael Campbell
@MichaelCampbell: el algoritmo de muestreo de yacimientos que he mencionado anteriormente puede funcionar con un archivo de 3TB (si el tamaño de la línea es limitado).
jfs
2

Otra forma de usar ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
Baskar
fuente
2
Eso usa awk y bash ( $RANDOMes un bashismo ). Aquí hay un método awk (mawk) puro que usa la misma lógica que el código perlfaq5 citado por @ Tracker1 anterior: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(¡guau, es incluso más corto que el código perl!)
Adam Katz
Ese código debe leer el archivo ( wc) para obtener un recuento de línea, luego debe leer (parte de) el archivo nuevamente ( awk) para obtener el contenido del número de línea aleatorio dado. La E / S será mucho más costosa que obtener un número aleatorio. Mi código lee el archivo solo una vez. El problema con awk rand()es que se basa en segundos, por lo que obtendrá duplicados si lo ejecuta consecutivamente demasiado rápido.
Adam Katz
1

Una solución que también funciona en MacOSX, y también debería funcionar en Linux (?):

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

Dónde:

  • N es la cantidad de líneas aleatorias que quieres

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> guardar los números de línea escritos file1y luego imprimir la línea correspondiente enfile2

  • jot -r $N 1 $(wc -l < $file)-> dibujar Nnúmeros al azar ( -r) en rango (1, number_of_line_in_file)con jot. La sustitución del proceso <()hará que parezca un archivo para el intérprete, así que file1en el ejemplo anterior.
jrjc
fuente
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]}
Conocido
fuente
Como $ RANDOM genera números menores que el número de palabras en / usr / share / dict / words, que tiene 235886 (en mi Mac de todos modos), solo genero 6 números aleatorios separados entre 0 y 9 y los encadené. Luego me aseguro de que ese número sea menor que 235886. Luego, elimine los ceros a la izquierda para indexar las palabras que almacené en la matriz. Dado que cada palabra es su propia línea, esto podría usarse fácilmente para que cualquier archivo elija una línea al azar.
Ken
0

Esto es lo que descubrí, ya que mi Mac OS no utiliza todas las respuestas fáciles. Usé el comando jot para generar un número ya que las soluciones variables $ RANDOM no parecen ser muy aleatorias en mi prueba. Al probar mi solución, tuve una amplia variación en las soluciones proporcionadas en la salida.

  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

El eco de la variable es obtener una representación visual del número aleatorio generado.

dreday13
fuente
0

Usando solo vainilla sed y awk, y sin usar $ RANDOM, un "trazador de líneas" simple, eficiente en espacio y razonablemente rápido para seleccionar una sola línea pseudoaleatoriamente de un archivo llamado FILENAME es el siguiente:

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

(Esto funciona incluso si FILENAME está vacío, en cuyo caso no se emite ninguna línea).

Una posible ventaja de este enfoque es que solo llama a rand () una vez.

Como señaló @AdamKatz en los comentarios, otra posibilidad sería llamar a rand () para cada línea:

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

(Se puede proporcionar una prueba simple de corrección basada en la inducción).

Advertencia sobre rand()

"En la mayoría de las implementaciones de awk, incluyendo gawk, rand () comienza a generar números a partir del mismo número inicial, o semilla, cada vez que ejecuta awk".

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

pico
fuente
Vea el comentario que publiqué un año antes de esta respuesta , que tiene una solución awk más simple que no requiere sed. También tenga en cuenta mi advertencia sobre el generador de números aleatorios de awk, que se siembra en segundos completos.
Adam Katz