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 salida141
), 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 lahead
.
- Las funciones toman no solo entrada
Se realiza una comparación de rendimiento .
- Función compatible con POSIX basada en
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-; }
- 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 BSDawk
;2.388s
con GNUawk
(4.1.1);1.811s
conmawk
(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 deawk
que use es importante (mawk
es más rápido que GNUawk
, BSDawk
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:
La
unsort
launsort
. 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 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''.