¿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?


285

Quiero mezclar 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?



Sí, hay algunas otras buenas respuestas en esa pregunta original también.
Ruggiero Spearman

entonces, ¿estabas haciendo una lista de palabras de wpa? (solo una suposición aleatoria)
thahgr

Respuestas:


360

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

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

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


31
shufy sort -Rdifieren ligeramente, porque sort -Rordena aleatoriamente los elementos según el hash de ellos, es decir, sort -Rjuntará los elementos repetidos, mientras shufbaraja todos los elementos aleatoriamente.
SeMeKh

146
Para usuarios de OS X:, brew install coreutilsluego use gshuf ...(:
ELLIOTTCABLE

15
sort -Ry shufdebería ser visto como completamente diferente. sort -REs determinista. Si lo llama dos veces en diferentes momentos en la misma entrada, obtendrá la misma respuesta. shuf, por otro lado, produce una salida aleatoria, por lo que probablemente dará una salida diferente en la misma entrada.
EfForEffort

18
Eso no es correcto. "sort -R" usa una clave aleatoria diferente cada vez que la invoca, por lo que produce una salida diferente cada vez.
Mark Pettit

3
Nota sobre la aleatoriedad: según los documentos de GNU, "Por defecto, estos comandos usan un generador pseudoaleatorio interno inicializado por una pequeña cantidad de entropía, pero pueden dirigirse a usar una fuente externa con la opción --random-source = file".
Royce Williams

85

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

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

66
Alias ​​esto para barajar en OS X. ¡Gracias!
The Unfun Cat

Este fue el único script en esta página que devolvió líneas REALES aleatorias. Otras soluciones awk a menudo imprimen resultados duplicados.
Felipe Alvarez

1
Pero tenga cuidado porque en el exterior perderá una línea :) Simplemente se unirá con otra línea :)
JavaRunner

@JavaRunner: supongo que estás hablando de entradas sin un seguimiento \n; sí, eso \ndebe estar presente, y generalmente lo está , de lo contrario obtendrá lo que describe.
mklement0

1
Maravillosamente conciso. Sugiero reemplazar <STDIN>con <>, por lo que la solución también funciona con la entrada de archivos .
mklement0

60

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

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

    • Las funciones toman no solo stdinentrada, sino también argumentos de nombre de archivo
    • Las funciones toman pasos adicionales para manejar SIGPIPEde la manera habitual (terminación silenciosa con código de salida 141), en lugar de romperse ruidosamente. Esto es importante cuando se canaliza la salida de la función a una tubería que se cierra temprano, como cuando se canaliza a head.
  • Se realiza una comparación de rendimiento .


  • Función compatible con POSIX basada en awk, sortycut , adaptada de la propia respuesta del OP :
shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
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))
' "$@"; }

Consulte la sección inferior para obtener una versión de Windows de esta función.

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 Core i5 de 3.2 GHz y una unidad Fusion, con OSX 10.10.3. Si bien los tiempos variarán con el sistema operativo utilizado, las especificaciones de la máquina, la awkimplementación utilizada (por ejemplo, la awkversión BSD utilizada en OSX suele ser más lenta que GNU awky especialmente mawk), esto debería proporcionar una sensación 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.342scon Python 2.7.6; 2.407s(!) con Python 3.4.2
  • awk+ sort+cut
    • 3.003scon BSD awk; 2.388scon GNU awk(4.1.1); 1.811scon mawk(1.3.4);

Para una mayor comparación, las soluciones no se empaquetan como las funciones anteriores:

  • sort -R (no es una combinación aleatoria verdadera si hay líneas de entrada duplicadas)
    • 10.661s - asignar más memoria no parece hacer la diferencia
  • Scala
    • 24.229s
  • bash bucles + sort
    • 32.593s

Conclusiones :

  • Úselo shuf, si puede , es el más rápido con diferencia.
  • A Ruby le va bien, seguido de Perl .
  • Python es notablemente más lento que Ruby y Perl, y, comparando las versiones de Python, 2.7.6 es bastante más rápido que 3.4.1
  • Use el combo + awk+ compatible con POSIX como último recursosortcut ; el que awkla aplicación utiliza asuntos ( mawkes más rápido que GNU awk, BSD awkes más lento).
  • Mantenerse alejado de sort -R, bashbucles, y Scala.

Versiones de Windows de la solución Python (el código de Python es idéntico, excepto por las variaciones en las citas y la eliminación de las declaraciones relacionadas con la señal, que no son compatibles con Windows):

  • Para PowerShell (en Windows PowerShell, tendrá que ajustar $OutputEncodingsi desea enviar caracteres no ASCII a través de la canalización):
# Call as `shuf someFile.txt` or `Get-Content someFile.txt | shuf`
function shuf {
  $Input | python -c @'
import sys, random, fileinput;
lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write(''.join(lines))
'@ $args  
}

Tenga en cuenta que PowerShell puede barajar de forma nativa a través de su Get-Randomcmdlet (aunque el rendimiento puede ser un problema); p.ej:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Para cmd.exe(un archivo por lotes):

Guardar en archivo shuf.cmd, por ejemplo:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*

SIGPIPE no existe en Windows, así que utilicé esta línea simple:python -c "import sys, random; lines = [x for x in sys.stdin.read().splitlines()] ; random.shuffle(lines); print(\"\n\".join([line for line in lines]));"
eleg

@elig: Gracias, pero omitir from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);de la solución original es suficiente y conserva la flexibilidad de poder también pasar argumentos de nombre de archivo ; no es necesario cambiar nada más (excepto las citas). Consulte la nueva sección que he agregado en fondo.
mklement0

27

Utilizo un pequeño script en perl, al que llamo "sin ordenar":

#!/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: Votado como 'shuf' también, no tenía idea de que estaba allí en coreutils en estos días ... lo anterior aún puede ser útil si sus sistemas no tienen 'shuf'.


agradable, RHEL 5.6 no tiene shuf (
Maxim Egorushkin

1
Bien hecho; Sugiero reemplazar <STDIN>con <>para que la solución funcione con la entrada de archivos también.
mklement0

20

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

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

8
UUOC. pasar el archivo a awk mismo.
ghostdog74

1
Bien, depuro con head myfile | awk .... Entonces solo lo cambio a gato; por eso lo dejaron allí.
Ruggiero Spearman

No es necesario -k1 -nordenar, ya que la salida de awk rand()es un decimal entre 0 y 1 y porque lo único que importa es que se reordena de alguna manera. -k1podría ayudar a acelerarlo ignorando el resto de la línea, aunque la salida de rand () debería ser lo suficientemente única como para hacer un cortocircuito en la comparación.
bonsaiviking

@ ghostdog74: La mayoría de los llamados usos inútiles de cat son realmente útiles para ser coherentes entre los comandos canalizados y no. Es mejor mantener el cat filename |(o < filename |) que recordar cómo cada programa toma la entrada del archivo (o no).
ShreevatsaR

2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | ordenar | cut -f2-;}
Miau

16

aquí hay un script 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

Bien hecho, pero en la práctica mucho más lento que la propia respuesta del OP , que se combina awkcon sorty cut. Para no más de varios miles de líneas, no hace mucha diferencia, pero con un mayor número de líneas es importante (el umbral depende de la awkimplementación utilizada). Una ligera simplificación sería reemplazar líneas while (1){y if (e==d) {break}con while (e<d).
mklement0

11

Una 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 sola línea aleatoria:

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

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


2
El "inconveniente" no es específico de Python. Los períodos de PRNG finitos podrían solucionarse volviendo a sembrar PRNG con entropía del sistema como lo /dev/urandomhace. Para utilizarlo desde Python: random.SystemRandom().shuffle(L).
jfs

¿la unión () no necesita estar en '\ n' para que las líneas se impriman cada una por su cuenta?
eleg

@elig: No, porque .readLines()devuelve las líneas con una nueva línea final.
mklement0

9

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 los ceros a la izquierda ( %06d) y la rand()multiplicación hacen que funcione correctamente también en sistemas donde sortno se entienden los números. Se puede ordenar por orden lexicográfico (también conocido como comparación de cadena normal).


Buena idea empaquetar la propia respuesta del OP como una función; si agrega "$@", también funcionará con archivos como entrada. No hay razón para multiplicar rand(), porque sort -nes capaz de ordenar fracciones decimales. Sin embargo, es una buena idea controlar awkel formato de salida, ya que con el formato predeterminado %.6g, rand()generará un número ocasional en notación exponencial . Si bien barajar hasta 1 millón de líneas es suficiente en la práctica, es fácil admitir más líneas sin pagar una gran penalización de rendimiento; por ej %.17f.
mklement0

1
@ mklement0 No noté que los OP respondieron mientras escribía el mío. rand () se multiplica por 10e6 para que funcione con Solaris o hpux, por lo que recuerdo. Buena idea con "$ @"
Michał Šrajer

1
Gracias; quizás podría agregar esta justificación de la multiplicación a la respuesta misma; generalmente, según POSIX, sortdebería poder manejar fracciones decimales (incluso con miles de separadores, como acabo de notar).
mklement0

7

Ruby FTW:

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

1
Buena cosa; Si lo usa puts ARGF.readlines.shuffle, puede hacer que funcione con entrada de stdin y argumentos de nombre de archivo.
mklement0

Aún más corto ruby -e 'puts $<.sort_by{rand}': ARGF ya es enumerable, por lo que podemos barajar las líneas clasificándolas por valores aleatorios.
akuhn

6

Un delineador para Python basado en la respuesta de scai , pero a) toma stdin, b) hace que el resultado sea repetible con seed, c) selecciona solo 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

6

Una forma simple e intuitiva sería usar shuf.

Ejemplo:

Asumir words.txtcomo:

the
an
linux
ubuntu
life
good
breeze

Para barajar las líneas, haga:

$ shuf words.txt

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

$ shuf words.txt > shuffled_words.txt

Una ejecución de este tipo podría producir:

breeze
the
linux
an
ubuntu
good
life

4

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 use

rl 1000.txt

3

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 -Ry shufno están disponibles, puede alias esto en su bash_profile como:

alias shuf='python rand.py'

3

Si, como yo, viniste aquí para buscar una alternativa shufpara macOS, úsala randomize-lines.

Instale el randomize-linespaquete (homebrew), que tiene un rlcomando que tiene una funcionalidad similar a 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

1
Instalar Coreutils con brew install coreutilsproporciona el shufbinario como gshuf.
shadowtalker

2

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

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

Atractivamente simple, pero a menos que Java VM deba iniciarse de todos modos, ese costo de inicio es considerable; tampoco funciona bien con recuentos de líneas grandes.
mklement0

1

Esta función bash tiene una dependencia mínima (solo sort 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
}

Buena solución bash que es paralela a la awksolución asistida por el OP , pero el rendimiento será un problema con una entrada más grande; el uso de un solo $RANDOMvalor baraja correctamente solo hasta 32.768 líneas de entrada; Si bien podría extender ese rango, probablemente no valga la pena: por ejemplo, en mi máquina, ejecutar su script en 32.768 líneas de entrada cortas toma aproximadamente 1 segundo, que es aproximadamente 150 veces más que la ejecución shuf, y aproximadamente 10-15 veces mientras dure la awksolución asistida por el OP . Si puede confiar en sortestar presente, también awkdebería estar allí.
mklement0

0

En Windows, puede probar este archivo por lotes para ayudarlo a barajar 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.


No funciona para archivos grandes. Me rendí después de 2 horas por un archivo de más de 1 millón de líneas
Stefan Haberl

0

No mencionado hasta el momento:

  1. El unsortutil. 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 barajarse por línea, pero generalmente es exagerado:

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

0

Otra awkvariante:

#!/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];
  }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.