¿Cuál es una manera fácil de leer una línea aleatoria de un archivo en la línea de comandos de Unix?
fuente
¿Cuál es una manera fácil de leer una línea aleatoria de un archivo en la línea de comandos de Unix?
Puedes usar shuf
:
shuf -n 1 $FILE
También hay una utilidad llamada rl
. En Debian está en el randomize-lines
paquete 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 shuf
lugar (que no existía cuando se creó, creo). shuf
es parte de los coreutils de GNU, rl
no lo es.
rl -c 1 $FILE
shuf
consejo, está integrado en Fedora.
sort -R
definitivamente hará que uno espere mucho si se trata de archivos considerablemente grandes (líneas de 80kk), mientras que shuf -n
actúa de manera bastante instantánea.
coreutils
desde Homebrew. Podría llamarse en gshuf
lugar de shuf
.
randomize-lines
en OS X porbrew install randomize-lines; rl -c 1 $FILE
shuf
es 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).
Otra alternativa:
head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
(${RANDOM} << 15) + ${RANDOM}
. Esto reduce significativamente el sesgo y le permite trabajar para archivos que contienen hasta mil millones de líneas.
+
y |
son iguales ya que ${RANDOM}
es 0..32767 por definición.
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)
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
sort -R <<< $'1\n1\n2' | head -1
es tan probable que devuelva 1 y 2, porque sort -R
ordena juntas líneas duplicadas. Lo mismo se aplica sort -Ru
, porque elimina las líneas duplicadas.
sort
antes de canalizarlo head
. shuf
selecciona líneas aleatorias del archivo, y es mucho más rápido para mí.
sort --random-sort $FILE | head
sería lo mejor, ya que le permite acceder al archivo directamente, posiblemente permitiendo una clasificación paralela eficiente
--random-sort
y -R
son 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).
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.
-n 1
especifica 1 línea, y puede cambiarla a más de 1. también shuf
se puede usar para otras cosas; Acabo de canalizar ps aux
y grep
con él para matar al azar procesos que coinciden parcialmente con un nombre.
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.
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).
shuf
almacena 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.
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}
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.
wc -l < test.txt
evita tener que canalizar cut
.
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
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),
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 .
Otra forma de usar ' awk '
awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
$RANDOM
es 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!)
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.
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 file1
y luego imprimir la línea correspondiente enfile2
jot -r $N 1 $(wc -l < $file)
-> dibujar N
nú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 file1
en el ejemplo 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]}
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.
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).
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