Come posso ripetere n volte il contenuto di un file?

17

Sto provando a 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, ho bisogno di ripetere le prove un certo numero di volte.

Piuttosto che ripetere i test, vorrei duplicare i dati di input un numero di volte (es. 1000) in modo che un file di 3 righe diventi 3000 linee e io possa eseguire un test molto più soddisfacente.

Sto passando i dati di input tramite un nome file:

mycommand input-data.txt
    
posta Oli 08.09.2014 - 12:39

8 risposte

21

Non è necessario input-duplicated.txt .

Prova:

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

Spiegazione

  • 0777 : -0 set imposta il separatore del record di input (variabile speciale perl $/ che è una nuova riga per impostazione predefinita). Impostando questo valore su un valore maggiore di 0400 , Perl slurperà l'intero file di input in memoria.
  • pe : il -p significa "stampa ogni riga di input dopo aver applicato lo script dato da -e ad esso".
  • $_=$_ x 1000 : $_ è la riga di input corrente. Poiché stiamo leggendo l'intero file in una sola volta a causa di -0700 , significa l'intero file. Il x 1000 comporterà 1000 copie dell'intero file stampato.
risposta data cuonglm 08.09.2014 - 13:36
9

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

Ci sono probabilmente una dozzina di modi diversi di fare il ciclo, ma qui ce ne 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 qui sotto e crea una grande lista di nomi di file di input per cat. xargs lo dividerà in tutti gli argomenti consentiti dal sistema. È molto più veloce di n gatti separati.

Il awk way (ispirato alla risposta di terdon ) è probabilmente il più ottimizzato ma duplica ogni riga in un tempo. Questo potrebbe non essere adatto ad una particolare applicazione, ma è veloce ed efficiente.

Ma questo sta generando al volo. È probabile che l'esecuzione di Bash sia molto più lenta di quanto si possa leggere in modo da generare un nuovo file da testare. 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
    
risposta data Oli 08.09.2014 - 12:39
6

Ecco una soluzione awk :

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

È essenzialmente veloce come @ Gnuc's Perl (ho eseguito entrambe le 1000 volte e 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
    
risposta data terdon 08.09.2014 - 15:37
4

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 di avere vim installato, poiché vi non ha il comando :normal ), potresti usare:

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

Qui, -es (o -e -s ) fa funzionare vim in modo silenzioso, quindi non dovrebbe occupare la finestra del terminale, e -u NONE gli impedisce di guardare il tuo vimrc, il che dovrebbe renderlo un po 'più veloce altrimenti sarebbe (forse molto più veloce, se usi molti plug-in di Vim).

    
risposta data evilsoup 08.09.2014 - 15:07
2

Ecco una semplice interfaccia, senza lo scripting coinvolto:

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

Spiegazione

  • 'yes input-data.txt | head -1000 | paste -s' produce il testo input-data.txt 1000 volte separati da uno spazio bianco
  • Il testo viene quindi passato a cat come elenco di file
risposta data roeeb 10.03.2016 - 19:19
2

Mentre lavoravo su uno script completamente diverso, ho imparato che con 29 milioni di righe di testo, usando seek() e operando su dati bytewise è spesso più veloce che linea per linea. La stessa idea viene applicata nello script seguente: apriamo il file e invece di eseguire il ciclo di apertura e chiusura del file (che può aggiungere un sovraccarico, anche se non significativo), manteniamo il file aperto e cerchiamo di tornare 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 è abbastanza semplice nell'uso:

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

Per file di testo a 3 righe e 1000 iterazioni va abbastanza 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 in sé non è molto elegante, probabilmente potrebbe essere abbreviato, ma fa il lavoro. Ovviamente, ho aggiunto qualche bit in più qua e là, come la funzione error_out() , che non è necessario - è solo un piccolo tocco user-friendly.

    
risposta data Sergiy Kolodyazhnyy 07.01.2017 - 10:40
0

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

Sulla base di una funzione di printf all'interno di bash possiamo generare una stringa ripetuta):

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

Quindi, possiamo inviare tale lista 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 da 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ì, il doppio & lt; è necessario.

    
risposta data user379914 30.07.2015 - 08:11
0

Vorrei generare un nuovo file usando Unix for loop:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
    
risposta data SmallChess 12.12.2016 - 04:47

Leggi altre domande sui tag