Come posso ripetere il contenuto di un file n volte?


19

Sto cercando di fare un benchmark per confrontare due diversi modi di elaborare un file. Ho una piccola quantità di dati di input ma per ottenere buoni confronti, devo ripetere i test più volte.

Piuttosto che ripetere semplicemente i test vorrei duplicare i dati di input un numero di volte (ad es. 1000) in modo che un file a 3 righe diventi 3000 righe e posso eseguire un test molto più soddisfacente.

Sto passando i dati di input tramite un nome file:

mycommand input-data.txt

Risposte:


21

Non hai bisogno input-duplicated.txt.

Provare:

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

Spiegazione

  • 0777: -0sets imposta il separatore del record di input (variabile speciale perl $/che è una nuova riga di default). Impostandolo su un valore maggiore di 0400, Perl farà slurpare l'intero file di input in memoria.
  • pe: -psignifica "stampa ogni riga di input dopo aver applicato lo script assegnato -ead essa".
  • $_=$_ x 1000: $_è la riga di input corrente. Poiché stiamo leggendo l'intero file in una sola volta a causa di -0700ciò, ciò significa che l'intero file. La x 1000si tradurrà in 1000 copie di tutto il file in fase di stampa.

Bello. Questo è stupidamente veloce. 0.785s per 1000 xargs, 0.006s per questo, quindi sì, probabilmente supera i problemi generali che stavo vedendo con altri loop.
Oli

E aumentarlo a 100000 volte aumenta il tempo di esecuzione di 0,002 s. È piuttosto sorprendente.
Oli

@Oli: con file di piccole dimensioni e memoria sufficiente, perlè così efficiente, progettato per questo.
cuonglm,

11

Inizialmente pensavo che avrei dovuto generare un file secondario, ma potevo semplicemente eseguire il loop del file originale in Bash e utilizzare un reindirizzamento per farlo apparire come un file.

Esistono probabilmente una dozzina di modi diversi di eseguire il ciclo, ma qui ci sono quattro:

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)  #*

Il terzo metodo è stato improvvisato dal commento di Maru di seguito e crea un grande elenco di nomi di file di input per cat. xargslo dividerà in tutti gli argomenti consentiti dal sistema. È molto più veloce di n gatti separati.

Il awkmodo (ispirato alla risposta di Terdon ) è probabilmente il più ottimizzato, ma duplica ogni riga alla volta. Questo può o meno soddisfare una particolare applicazione, ma è veloce ed efficiente.


Ma questo si sta generando al volo. È probabile che l'output di bash sia molto più lento di quanto qualcosa possa leggere, quindi è necessario generare un nuovo file per il test. Per fortuna questa è solo un'estensione molto semplice:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt

3
Entrambi i tuoi comandi hanno cat in esecuzione N volte. Non sarebbe più efficiente eseguire cat una volta e dargli un argomento N volte? Qualcosa del genere cat $(for i in {1..N}; do echo filename; done). Ciò ha la limitazione della dimensione dell'arg, ma dovrebbe essere più veloce.
Muru,

@muru Bella idea anche. Ho bisogno di un po 'di lavoro ma lo aggiungerò. L'attuale implementazione sta eseguendo 1000 iterazioni di un file a 7 righe in ~ 0,020 secondi. È davvero molto meglio delle mie versioni, ma non a livello di Perl di Gnouc.
Oli

6

Ecco una awksoluzione:

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

È essenzialmente veloce come il Perl di @ Gnuc (ho corso entrambe le volte 1000 e ho ottenuto il tempo medio):

$ 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

1
In tutta onestà, potresti probabilmente semplificarlo in awk '{for(i=0; i<1000; i++)print}' input-data.txtmodo da emettere solo 1000 copie di ogni riga alla volta. Non andrà bene per tutte le occasioni ma anche più veloce, meno ritardi e non è necessario conservare l'intero file nella RAM.
Oli

@Oli davvero, avevo pensato che volessi mantenere l'ordine delle righe in modo che andasse 123123123bene, ma 111222333non lo era. La tua versione è chiaramente più veloce di quella di Gnouc, ha una media di 0,00297 secondi. EDIT: grattalo, ho fatto un errore, in realtà è equivalente a 0,004013 secondi.
terdon,

5

Vorrei solo usare un editor di testo.

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)

Se hai assolutamente bisogno di farlo tramite la riga di comando (questo richiede che tu abbia viminstallato, poiché vinon ha il :normalcomando), puoi usare:

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

Qui, -es(o -e -s) fa in modo che vim funzioni silenziosamente, quindi non dovrebbe prendere il controllo della finestra del terminale e -u NONEimpedisce che guardi il tuo vimrc, il che dovrebbe farlo funzionare un po 'più velocemente di quanto altrimenti farebbe (forse molto più veloce, se usi molti plugin di Vim).


Sì, ma questo è tutto manuale che rende diversi ordini di grandezza più lenti e complessi rispetto alle altre soluzioni.
terdon,

4

Ecco un semplice one-liner, senza script coinvolti:

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

Spiegazione

  • `yes input-data.txt | head -1000 | paste -s`produce il testo input-data.txt1000 volte separato da uno spazio bianco
  • Il testo viene quindi passato catcome un elenco di file

Questa soluzione non sembra funzionare. Hai bisogno di usare xargs paste -s? Funziona, ma non preserva le nuove righe nel file di input.
JeremyKun,

Assicurati di utilizzare l'apostrofo corretto.
Roeeb,

2

Mentre lavoravo su uno script completamente diverso, ho imparato che con 29 milioni di righe di testo, l'utilizzo seek()e il funzionamento su dati bytewise è spesso più veloce rispetto a riga per riga. La stessa idea viene applicata nello script seguente: apriamo il file e invece di scorrere ciclicamente l'apertura e la chiusura del file (che può aggiungere overhead, anche se non significativo), manteniamo il file aperto e cerchiamo all'inizio.

#!/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()

Lo script stesso è piuttosto semplice in uso:

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

Per file di testo a 3 righe e 1000 iterazioni, va tutto bene, circa 0,1 secondi:

$ /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

Lo script stesso non è molto elegante, probabilmente potrebbe essere abbreviato, ma fa il lavoro. Naturalmente, ho aggiunto alcuni bit extra qua e là, come la error_out()funzione, che non è necessaria - è solo un piccolo tocco user-friendly.


1

Possiamo risolverlo senza un file aggiuntivo, né programmi speciali, puro Bash (beh, cat è un comando standard).

Sulla base di una funzione di printf dentro bash possiamo generare una stringa ripetuta):

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

Quindi, possiamo inviare tale elenco di 1000 nomi di file (ripetuti) e chiamare cat:

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

E infine, possiamo dare l'output al comando per eseguire:

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

Oppure, se il comando deve ricevere l'input nello stdin:

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

Sì, è necessario il doppio <.


0

Genererei un nuovo file usando Unix per loop:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.