scripts script programas paso manejo hacer ejemplos como comandos comando cadenas shell random command-line awk shuffle

shell - programas - script unix



¿Cómo puedo mezclar las líneas de un archivo de texto en la línea de comandos de Unix o en un script de shell? (19)

Quiero cambiar aleatoriamente las líneas de un archivo de texto y crear un nuevo archivo. El archivo puede tener varios miles de líneas.

¿Cómo puedo hacer eso con cat , awk , cut , etc?


Aquí hay un guión awk

awk ''BEGIN{srand() } { lines[++d]=$0 } END{ while (1){ if (e==d) {break} RANDOM = int(1 + rand() * d) if ( RANDOM in lines ){ print lines[RANDOM] delete lines[RANDOM] ++e } } }'' file

salida

$ cat file 1 2 3 4 5 6 7 8 9 10 $ ./shell.sh 7 5 10 9 6 8 2 1 3 4


Aquí hay un primer intento que es fácil para el programador, pero para la CPU que antepone un número aleatorio a cada línea, las ordena y luego elimina el número aleatorio de cada línea. En efecto, las líneas se ordenan aleatoriamente:

cat myfile | awk ''BEGIN{srand();}{print rand()"/t"$0}'' | sort -k1 -n | cut -f2- > myfile.shuffled


En Windows, puede probar este archivo por lotes para ayudarlo a mezclar sus datos.txt. El uso del código de lote es

C:/> type list.txt | shuffle.bat > maclist_temp.txt

Después de emitir este comando, maclist_temp.txt contendrá una lista aleatoria de líneas.

Espero que esto ayude.


Esta función bash tiene la dependencia mínima (solo ordenar y bash):

shuf() { while read -r x;do echo $RANDOM$''/x1f''$x done | sort | while IFS=$''/x1f'' read -r x y;do echo $y done }


Esta respuesta complementa las muchas grandes respuestas existentes de las siguientes maneras:

  • Las respuestas existentes se empaquetan en funciones de shell flexibles :

    • Las funciones toman no solo entrada stdin , sino también argumentos de nombre de archivo
    • Las funciones toman pasos adicionales para manejar SIGPIPE de la manera habitual (terminación silenciosa con el código de salida 141 ), en lugar de romperse ruidosamente. Esto es importante al canalizar la salida de la función a un tubo que se cierra antes, como cuando se canaliza a la head .
  • Se realiza una comparación de rendimiento .

  • Función compatible con POSIX basada en awk , sort y cut , adaptada de la propia respuesta del OP :

shuf() { awk ''BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}'' "$@" | sort -k1,1n | cut -d '' '' -f2-; }

  • Función basada en Perl - adaptada de la respuesta de Moonyoung Kang :

shuf() { perl -MList::Util=shuffle -e ''print shuffle(<>);'' "$@"; }

  • Función basada en Python , adaptada de la respuesta del scai :

shuf() { python -c '' import sys, random, fileinput; from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL); lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write("".join(lines)) '' "$@"; }

  • Función basada en Ruby , adaptada de la respuesta de hoffmanc :

shuf() { ruby -e ''Signal.trap("SIGPIPE", "SYSTEM_DEFAULT"); puts ARGF.readlines.shuffle'' "$@"; }

Comparación de rendimiento:

Nota: Estos números se obtuvieron en un iMac de finales de 2012 con Intel GHz Core i5 a 3.2 GHz y una unidad Fusion, con OSX 10.10.3. Si bien los tiempos varían según el sistema operativo utilizado, las especificaciones de la máquina, la implementación awk utilizada (por ejemplo, la versión awk BSD utilizada en OSX es generalmente más lenta que awk GNU y especialmente mawk ), esto debería proporcionar un sentido general de rendimiento relativo .

El archivo de entrada es un archivo de 1 millón de líneas producido con seq -f ''line %.0f'' 1000000 .
Los tiempos se enumeran en orden ascendente (el más rápido primero):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Pitón
    • 1.342s con Python 2.7.6; 2.407s (!) Con Python 3.4.2
  • awk + sort + cut
    • 3.003s con BSD awk ; 2.388s con GNU awk (4.1.1); 1.811s con mawk (1.3.4);

Para una comparación adicional, las soluciones no están empaquetadas como funciones anteriores:

  • sort -R (no es un verdadero shuffle si hay líneas de entrada duplicadas)
    • 10.661s : asignar más memoria no parece hacer una diferencia
  • Scala
    • 24.229s
  • bucles de bash + sort
    • 32.593s

Conclusiones :

  • Usa shuf , si puedes , es el más rápido por mucho.
  • Ruby lo hace bien, seguido por Perl .
  • Python es notablemente más lento que Ruby y Perl, y, comparando las versiones de Python, 2.7.6 es un poco más rápido que 3.4.1
  • Utilice el combo awk + sort + cut cumple con POSIX como último recurso ; la implementación de awk que use es importante ( mawk es más rápido que GNU awk , BSD awk es el más lento).
  • Manténgase alejado de sort -R , bash loops y Scala.

Este es un script de python que guardé como rand.py en mi carpeta de inicio:

#!/bin/python import sys import random if __name__ == ''__main__'': with open(sys.argv[1], ''r'') as f: flist = f.readlines() random.shuffle(flist) for line in flist: print line.strip()

En Mac OSX sort -R y shuf no están disponibles, por lo que puede crear un alias en su perfil bash como:

alias shuf=''python rand.py''


La función simple basada en awk hará el trabajo:

shuffle() { awk ''BEGIN{srand();} {printf "%06d %s/n", rand()*1000000, $0;}'' | sort -n | cut -c8- }

uso:

any_command | shuffle

Esto debería funcionar en casi cualquier UNIX. Probado en Linux, Solaris y HP-UX.

Actualizar:

Tenga en cuenta que la multiplicación de ceros %06d ( %06d ) y rand() hace que funcione correctamente también en sistemas donde la sort no comprende los números. Puede ordenarse por orden lexicográfico (también conocido como cadena normal).


No mencionado hasta el momento:

  1. La unsort la unsort . Sintaxis (algo orientada a la lista de reproducción):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic] [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] [--linefeed] [file ...]

  2. msort puede barajar por línea, pero generalmente es excesivo:

    seq 10 | msort -jq -b -l -n 1 -c r


Otra variante de awk :

#!/usr/bin/awk -f # usage: # awk -f randomize_lines.awk lines.txt # usage after "chmod +x randomize_lines.awk": # randomize_lines.awk lines.txt BEGIN { FS = "/n"; srand(); } { lines[ rand()] = $0; } END { for( k in lines ){ print lines[k]; } }


Perl one-liner sería una versión simple de la solución de Maxim

perl -MList::Util=shuffle -e ''print shuffle(<STDIN>);'' < myfile


Puedes usar shuf . Al menos en algunos sistemas (no parece estar en POSIX).

Como señaló jleedev: sort -R también podría ser una opción. En algunos sistemas al menos; Bueno, te haces una idea. Se ha señalado que sort -R no se baraja realmente, sino que clasifica los elementos según su valor hash.

[Nota del editor: sort -R casi se baraja, excepto que las líneas duplicadas / claves de clasificación siempre terminan una al lado de la otra . En otras palabras: solo con líneas / teclas de entrada únicas es un verdadero barajado. Si bien es cierto que el orden de salida está determinado por los valores de hash , la aleatoriedad proviene de la elección de una función de hash aleatoria (ver manual .


Ruby FTW:

ls | ruby -e ''puts STDIN.readlines.shuffle''


Si tienes Scala instalado, aquí hay una sola línea para mezclar la entrada:

ls -1 | scala -e ''for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)''


Si, como yo, has venido aquí para buscar una alternativa a shuf para macOS, usa randomize-lines .

Instale el paquete randomize-lines (homebrew), que tiene un comando rl que tiene una funcionalidad similar a la de shuf .

brew install randomize-lines

Usage: rl [OPTION]... [FILE]... Randomize the lines of a file (or stdin). -c, --count=N select N lines from the file -r, --reselect lines may be selected multiple times -o, --output=FILE send output to file -d, --delimiter=DELIM specify line delimiter (one character) -0, --null set line delimiter to null character (useful with find -print0) -n, --line-number print line number with output lines -q, --quiet, --silent do not output any errors or warnings -h, --help display this help and exit -V, --version output version information and exit


Tenemos un paquete para hacer el trabajo:

sudo apt-get install randomize-lines

Ejemplo:

Cree una lista ordenada de números y guárdela en 1000.txt:

seq 1000 > 1000.txt

Para barajarlo, simplemente usa

rl 1000.txt


Un forro para Python basado en la respuesta de Scai , pero a) toma stdin, b) hace que el resultado sea repetible con semilla, c) solo selecciona 200 de todas las líneas.

$ cat file | python -c "import random, sys; random.seed(100); print ''''.join(random.sample(sys.stdin.readlines(), 200))," / > 200lines.txt


Una forma simple e intuitiva sería usar shuf .

Ejemplo:

Asume words.txt como:

the an linux ubuntu life good breeze

Para barajar las líneas, haz:

$ shuf words.txt

lo que arrojaría las líneas barajadas a la salida estándar ; Entonces, debes canalizarlo a un archivo de salida como:

$ shuf words.txt > shuffled_words.txt

Una carrera de este tipo podría producir:

breeze the linux an ubuntu good life


Una sola línea para python:

python -c "import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''''.join(lines)," myFile

Y para imprimir solo una línea al azar:

python -c "import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile

Pero vea esta publicación para los inconvenientes de random.shuffle() de python. No funcionará bien con muchos (más de 2080) elementos.


Utilizo un pequeño script de Perl, al que llamo "unsort":

#!/usr/bin/perl use List::Util ''shuffle''; @list = <STDIN>; print shuffle(@list);

También tengo una versión delimitada por NULL, llamada "unsort0" ... útil para usar con find -print0 y así sucesivamente.

PD: también voté por ''shuf'', no tenía idea de que existía en coreutils en estos días ... lo anterior podría ser útil si sus sistemas no tienen ''shuf''.