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?
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?
Respuestas:
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
realmente 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 .]
shuf
y sort -R
difieren ligeramente, porque sort -R
ordena aleatoriamente los elementos según el hash de ellos, es decir, sort -R
juntará los elementos repetidos, mientras shuf
baraja todos los elementos aleatoriamente.
brew install coreutils
luego use gshuf ...
(:
sort -R
y shuf
debería ser visto como completamente diferente. sort -R
Es 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.
Perl one-liner sería una versión simple de la solución de Maxim
perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
\n
; sí, eso \n
debe estar presente, y generalmente lo está , de lo contrario obtendrá lo que describe.
<STDIN>
con <>
, por lo que la solución también funciona con la entrada de archivos .
Esta respuesta complementa las muchas excelentes respuestas existentes de las siguientes maneras:
Las respuestas existentes se agrupan en funciones de shell flexibles :
stdin
entrada, sino también argumentos de nombre de archivoSIGPIPE
de 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 .
awk
, sort
ycut
, 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 awk
implementación utilizada (por ejemplo, la awk
versión BSD utilizada en OSX suele ser más lenta que GNU awk
y 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
0.289s
0.589s
1.342s
con Python 2.7.6; 2.407s
(!) con Python 3.4.2awk
+ sort
+cut
3.003s
con BSD awk
; 2.388s
con GNU awk
(4.1.1); 1.811s
con 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 diferencia24.229s
bash
bucles + sort
32.593s
Conclusiones :
shuf
, si puede , es el más rápido con diferencia.awk
+ compatible con POSIX como último recursosort
cut
; el que awk
la aplicación utiliza asuntos ( mawk
es más rápido que GNU awk
, BSD awk
es más lento).sort -R
, bash
bucles, 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):
$OutputEncoding
si 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-Random
cmdlet (aunque el rendimiento puede ser un problema); p.ej:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)
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))" %*
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]));"
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.
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'.
<STDIN>
con <>
para que la solución funcione con la entrada de archivos también.
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
head myfile | awk ...
. Entonces solo lo cambio a gato; por eso lo dejaron allí.
-k1 -n
ordenar, 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. -k1
podrí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.
cat filename |
(o < filename |
) que recordar cómo cada programa toma la entrada del archivo (o no).
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
awk
con sort
y 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 awk
implementación utilizada). Una ligera simplificación sería reemplazar líneas while (1){
y if (e==d) {break}
con while (e<d)
.
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.
/dev/urandom
hace. Para utilizarlo desde Python: random.SystemRandom().shuffle(L)
.
.readLines()
devuelve las líneas con una nueva línea final.
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 sort
no se entienden los números. Se puede ordenar por orden lexicográfico (también conocido como comparación de cadena normal).
"$@"
, también funcionará con archivos como entrada. No hay razón para multiplicar rand()
, porque sort -n
es capaz de ordenar fracciones decimales. Sin embargo, es una buena idea controlar awk
el 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
.
sort
debería poder manejar fracciones decimales (incluso con miles de separadores, como acabo de notar).
Ruby FTW:
ls | ruby -e 'puts STDIN.readlines.shuffle'
puts ARGF.readlines.shuffle
, puede hacer que funcione con entrada de stdin y argumentos de nombre de archivo.
ruby -e 'puts $<.sort_by{rand}'
: ARGF ya es enumerable, por lo que podemos barajar las líneas clasificándolas por valores aleatorios.
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
Una forma simple e intuitiva sería usar shuf
.
Ejemplo:
Asumir words.txt
como:
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
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, puede alias esto en su bash_profile como:
alias shuf='python rand.py'
Si, como yo, viniste aquí para buscar una alternativa shuf
para macOS, úsala randomize-lines
.
Instale el randomize-lines
paquete (homebrew), que tiene un rl
comando 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
brew install coreutils
proporciona el shuf
binario como gshuf
.
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)'
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
}
awk
solución asistida por el OP , pero el rendimiento será un problema con una entrada más grande; el uso de un solo $RANDOM
valor 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 awk
solución asistida por el OP . Si puede confiar en sort
estar presente, también awk
debería estar allí.
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 mencionado hasta el momento:
El unsort
util. 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 ...]
msort
puede barajarse por línea, pero generalmente es exagerado:
seq 10 | msort -jq -b -l -n 1 -c r