¿Cómo puedo repetir el contenido de un archivo n veces?

19

Estoy tratando de comparar para comparar dos formas diferentes de procesar un archivo. Tengo una pequeña cantidad de datos de entrada, pero para obtener buenas comparaciones, necesito repetir las pruebas varias veces.

En lugar de repetir las pruebas, me gustaría duplicar los datos de entrada varias veces (por ejemplo, 1000) para que un archivo de 3 líneas se convierta en 3000 líneas y pueda ejecutar una prueba mucho más satisfactoria.

Estoy pasando los datos de entrada a través de un nombre de archivo:

mycommand input-data.txt
Oli
fuente

Respuestas:

21

No es necesario input-duplicated.txt.

Tratar:

mycommand <(perl -0777pe '$_=$_ x 1000' input-data.txt)

Explicación

  • 0777: -0establece el separador de registro de entrada (variable especial perl $/que es una nueva línea por defecto). Establecer esto en un valor mayor que 0400hará que Perl absorba todo el archivo de entrada en la memoria.
  • pe: el -pmedio "imprime cada línea de entrada después de aplicar la secuencia de comandos dada por -eella".
  • $_=$_ x 1000: $_es la línea de entrada actual. Debido a que estamos leyendo todo el archivo a la vez debido a -0700esto, esto significa todo el archivo. El x 1000resultado será la impresión de 1000 copias de todo el archivo.
Cuonglm
fuente
Agradable. Esto es estúpidamente rápido. 0.785s para 1000 xargs, 0.006s para esto, así que sí, probablemente supere los problemas generales que estaba viendo con otros bucles.
Oli
Y aumentar eso a 100000 veces solo aumenta el tiempo de ejecución en .002s. Eso es bastante asombroso.
Oli
@Oli: con archivos pequeños y con suficiente memoria, perles tan eficiente que está diseñado para esto.
Cuonglm
11

Originalmente estaba pensando que tendría que generar un archivo secundario, pero podría simplemente repetir el archivo original en Bash y usar alguna redirección para que aparezca como un archivo.

Probablemente hay una docena de formas diferentes de hacer el ciclo, pero aquí hay cuatro:

mycommand <( seq 1000 | xargs -i -- cat input-data.txt )
mycommand <( for _ in {1..1000}; do cat input-data.txt; done )
mycommand <((for _ in {1..1000}; do echo input-data.txt; done) | xargs cat )
mycommand <(awk '{for(i=0; i<1000; i++)print}' input-data.txt)  #*

El tercer método allí se improvisa a partir del comentario de maru a continuación y crea una gran lista de nombres de archivos de entrada para cat. xargsdividirá esto en tantos argumentos como lo permita el sistema. Es mucho más rápido que n gatos separados.

La awkforma (inspirada en la respuesta de terdon ) es probablemente la más optimizada, pero duplica cada línea a la vez. Esto puede o no adaptarse a una aplicación en particular, pero es increíblemente rápido y eficiente.


Pero esto se está generando sobre la marcha. Es probable que la salida de Bash sea mucho más lenta de lo que algo puede leer, por lo que debe generar un nuevo archivo para probar. Afortunadamente, esa es solo una extensión muy simple:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt
Oli
fuente
3
Ambos comandos tienen gato corriendo N veces. ¿No sería más eficiente ejecutar cat una vez y alimentarlo un argumento N veces? Algo así como cat $(for i in {1..N}; do echo filename; done). Esto tiene la limitación del tamaño de arg, pero debería ser más rápido.
muru
@muru Buena idea también. Necesitaba algo de trabajo pero lo agregaré. La implementación actual está haciendo 1000 iteraciones de un archivo de 7 líneas en ~ 0.020s. Eso es realmente mucho mejor que mis versiones, pero no en el nivel Perl de Gnouc.
Oli
6

Aquí hay una awksolución:

awk '{a[NR]=$0}END{for (i=0; i<1000; i++){for(k in a){print a[k]}}}' file 

Es esencialmente tan rápido como Perl de @Gnuc (corrí 1000 veces y obtuve el tiempo promedio):

$ for i in {1..1000}; do 
 (time awk '{a[NR]=$0}END{for (i=0;i<1000;i++){for(k in a){print a[k]}}}' file > a) 2>&1 | 
    grep -oP 'real.*?m\K[\d\.]+'; done | awk '{k+=$1}END{print k/1000}'; 
0.00426

$ for i in {1..1000}; do 
  (time perl -0777pe '$_=$_ x 1000' file > a ) 2>&1 | 
    grep -oP 'real.*?m\K[\d\.]+'; done | awk '{k+=$1}END{print k/1000}'; 
0.004076
terdon
fuente
1
Para ser justos, probablemente podría simplificar esto para awk '{for(i=0; i<1000; i++)print}' input-data.txtque solo emita 1000 copias de cada línea a la vez. No se adapta a todas las ocasiones, pero incluso más rápido, menos demora y no necesita mantener todo el archivo en la RAM.
Oli
@Oli de hecho, supuse que querías mantener el orden de la línea, así que 123123123estaba bien, pero 111222333no lo estaba. Su versión es claramente más rápida que la de Gnouc, tiene un promedio de 0.00297 segundos. EDITAR: tacha eso, cometí un error, en realidad es equivalente a 0.004013 segundos.
terdon
5

Solo usaría un editor de texto.

vi input-data.txt
gg (move cursor to the beginning of the file)
yG (yank til the end of the file)
G (move the cursor to the last line of the file)
999p (paste the yanked text 999 times)
:wq (save the file and exit)

Si absolutamente necesita hacerlo a través de la línea de comandos (esto requiere que tenga viminstalado, ya vique no tiene el :normalcomando), puede usar:

vim -es -u NONE "+normal ggyGG999p" +wq input-data.txt

Aquí, -es(o -e -s) hace que vim funcione silenciosamente, por lo que no debería tomar el control de la ventana de su terminal, y -u NONEevita que vea su vimrc, lo que debería hacer que funcione un poco más rápido de lo que lo haría de otra manera (tal vez mucho más rápido, si usa muchos complementos vim).

maldad
fuente
Sí, pero todo esto es manual, lo que lo hace varios órdenes de magnitud más lento y complejo que las otras soluciones.
terdon
4

Aquí hay una línea simple, sin secuencias de comandos involucradas:

mycommand <(cat `yes input-data.txt | head -1000 | paste -s`)

Explicación

  • `yes input-data.txt | head -1000 | paste -s`produce el texto input-data.txt1000 veces separado por espacios en blanco
  • El texto se pasa a catuna lista de archivos.
roeeb
fuente
Esta solución no parece funcionar. ¿Necesitas usar xargs paste -s? Esto funciona, pero no conserva las nuevas líneas en el archivo de entrada.
JeremyKun
Asegúrese de estar usando el apóstrofe correcto.
roeeb
2

Mientras trabajaba en un script completamente diferente, aprendí que con 29 millones de líneas de texto, el uso seek()y la operación de byte de datos a menudo es más rápido que línea por línea. La misma idea se aplica en la secuencia de comandos a continuación: abrimos el archivo y, en lugar de recorrerlo abriendo y cerrando el archivo (lo que puede agregar una sobrecarga, incluso si no es significativo), mantenemos el archivo abierto y volvemos al principio.

#!/usr/bin/env python3
from __future__ import print_function
import sys,os

def error_out(string):
    sys.stderr.write(string+"\n")
    sys.exit(1)

def read_bytewise(fp):
    data = fp.read(1024)
    print(data.decode(),end="",flush=True)
    while data:
        data = fp.read(1024)
        print(data.decode(),end="",flush=True)
    #fp.seek(0,1)

def main():
    howmany = int(sys.argv[1]) + 1
    if not os.path.isfile(sys.argv[2]):
       error_out("Needs a valid file") 

    fp = open(sys.argv[2],'rb')
    for i in range(1,howmany):
        #print(i)
        fp.seek(0)
        read_bytewise(fp)
    fp.close()

if __name__ == '__main__': main()

El script en sí es bastante simple en uso:

./repeat_text.py <INT> <TEXT.txt>

Para un archivo de texto de 3 líneas y 1000 iteraciones, todo va bastante bien, aproximadamente 0.1 segundos:

$ /usr/bin/time ./repeat_text.py 1000 input.txt  > /dev/null                                                             
0.10user 0.00system 0:00.23elapsed 45%CPU (0avgtext+0avgdata 9172maxresident)k
0inputs+0outputs (0major+1033minor)pagefaults 0swaps

El script en sí no es muy elegante, probablemente podría acortarse, pero hace el trabajo. Por supuesto, agregué algunos bits adicionales aquí y allá, como la error_out()función, que no es necesaria, es solo un pequeño toque fácil de usar.

Sergiy Kolodyazhnyy
fuente
1

Podemos resolver esto sin un archivo adicional, ni programas especiales, puro Bash (bueno, cat es un comando estándar).

Basado en una característica de printf dentro de bash podemos generar una cadena repetida):

printf "test.file.txt %.0s\n" {1..1000}

Luego, podemos enviar dicha lista de 1000 nombres de archivo (repetidos) y llamar a cat:

printf "test.file.txt %.0s" {1..1000} | xargs cat 

Y finalmente, podemos dar el resultado al comando para ejecutar:

mycommand "$( printf "%.0sinput.txt\n" {1..1000} | xargs cat )"

O, si el comando necesita recibir la entrada en el stdin:

mycommand < <( printf "%.0sinput.txt\n" {1..1000} | xargs cat )

Sí, se necesita el doble <.


fuente
0

Generaría un nuevo archivo usando Unix para loop:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
SmallChess
fuente