Wie kann ich den Inhalt einer Datei n-mal wiederholen?

19

Ich versuche ein Benchmarking durchzuführen, um zwei verschiedene Arten der Dateiverarbeitung zu vergleichen. Ich habe eine kleine Menge von Eingabedaten, aber um gute Vergleiche zu erhalten, muss ich die Tests einige Male wiederholen.

Anstatt nur die Tests zu wiederholen, möchte ich die Eingabedaten mehrmals duplizieren (z. B. 1000), damit eine Datei mit 3 Zeilen zu 3000 Zeilen wird und ich einen viel ausführlicheren Test ausführen kann.

Ich übergebe die Eingabedaten über einen Dateinamen:

mycommand input-data.txt
Oli
quelle

Antworten:

21

Das brauchst du nicht input-duplicated.txt.

Versuchen:

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

Erläuterung

  • 0777: -0sets setzt das Trennzeichen für den Eingabesatz (Perl-Spezialvariable)$/ die standardmäßig ein Zeilenumbruch ist). Wenn Sie diesen Wert auf einen höheren Wert setzen, 0400wird Perl die gesamte Eingabedatei in den Arbeitsspeicher verschieben.
  • pe: das -pbedeutet "drucke jede Eingabezeile nach dem Anwenden des Skripts aus-e ihr ".
  • $_=$_ x 1000: $_ist die aktuelle Eingabezeile. Da wir aus -0700diesem Grund die gesamte Datei auf einmal lesen , bedeutet dies die gesamte Datei. Das x 1000führt in 1000 Kopien der gesamten Datei gedruckt werden.
cuonglm
quelle
Nett. Das ist blöd-schnell. 0,785s für 1000 xargs, 0,006s dafür, also ja, überwindet wahrscheinlich die Overhead-Probleme, die ich bei anderen Loops gesehen habe.
Oli
Wenn Sie das auf 100000-mal erhöhen, wird die Laufzeit nur um 0,002 Sekunden erhöht. Das ist ziemlich erstaunlich.
Oli
@Oli: Mit kleinen Dateien und genügend Arbeitsspeicher perlist es so effizient, dass es dafür ausgelegt ist.
Cuonglm
11

Ich dachte ursprünglich, dass ich eine sekundäre Datei generieren müsste, aber ich könnte einfach die ursprüngliche Datei in Bash schleifen und eine Umleitung verwenden, um sie als Datei erscheinen zu lassen.

Es gibt wahrscheinlich ein Dutzend verschiedene Möglichkeiten, die Schleife auszuführen, aber hier sind vier:

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

Die dritte Methode, die es gibt, ist aus Marus Kommentar unten improvisiert und erstellt eine große Liste von Eingabedateinamen für cat. xargswird dies in so viele Argumente aufteilen, wie das System zulässt. Es ist viel schneller als n separate Katzen.

Der awkWeg (inspiriert von Terdons Antwort ) ist wahrscheinlich der optimierteste, aber er dupliziert jede Zeile gleichzeitig. Dies kann für eine bestimmte Anwendung geeignet sein oder auch nicht, ist jedoch blitzschnell und effizient.


Dies wird jedoch im laufenden Betrieb generiert. Die Bash-Ausgabe ist wahrscheinlich sehr viel langsamer als irgendetwas zu lesen ist. Sie sollten daher eine neue Datei zum Testen generieren. Zum Glück ist das nur eine sehr einfache Erweiterung:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt
Oli
quelle
3
Bei beiden Befehlen läuft die Katze N-mal. Wäre es nicht effizienter, cat einmal auszuführen und N-mal mit einem Argument zu füttern? So etwas wie cat $(for i in {1..N}; do echo filename; done). Dies hat die Begrenzung der arg-Größe, sollte aber schneller sein.
muru
@muru Gute Idee auch. Benötigte etwas Arbeit, aber ich werde es hinzufügen. Bei der aktuellen Implementierung werden 1000 Iterationen einer 7-zeiligen Datei in ~ 0,020 Sekunden ausgeführt. Das ist wirklich viel besser als meine Versionen, aber nicht auf Perl-Niveau von Gnouc.
Oli
6

Hier ist eine awkLösung:

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

Es ist im Wesentlichen so schnell wie @ Gnucs Perl (ich habe beide 1000-mal ausgeführt und die durchschnittliche Zeit erhalten):

$ 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
quelle
1
Aus Gründen der Fairness könnten Sie dies wahrscheinlich so vereinfachen, awk '{for(i=0; i<1000; i++)print}' input-data.txtdass nur 1000 Kopien jeder Zeile gleichzeitig ausgegeben werden. Nicht für alle Gelegenheiten geeignet, aber noch schneller, mit weniger Verzögerung und ohne dass die gesamte Datei im RAM gespeichert werden muss.
Oli
@Oli in der Tat, ich hatte angenommen, Sie wollten die Zeilenreihenfolge beibehalten, das war in Ordnung, 123123123war es aber 111222333nicht. Ihre Version ist deutlich schneller als die von Gnouc und liegt im Durchschnitt bei 0,00297 Sekunden. BEARBEITEN: Kratz das, ich habe einen Fehler gemacht, es ist eigentlich gleichbedeutend mit 0.004013 Sekunden.
Terdon
5

Ich würde nur einen Texteditor verwenden.

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)

Wenn Sie dies unbedingt über die Befehlszeile tun müssen (dies setzt voraus, dass Sie viminstalliert haben, da vider :normalBefehl nicht vorhanden ist), können Sie Folgendes verwenden:

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

Hier sorgt -es(oder -e -s) dafür, dass vim im Hintergrund arbeitet, sodass Ihr Terminalfenster nicht -u NONEüberlastet wird , und verhindert, dass Ihr vimrc angezeigt wird. Dies sollte dazu führen, dass vim etwas schneller ausgeführt wird als sonst (möglicherweise viel schneller, wenn Sie es verwenden) viele vim plugins).

Übelsuppe
quelle
Ja, aber das ist alles manuell, was es einige Größenordnungen langsamer und komplexer macht als die anderen Lösungen.
Terdon
4

Hier ist ein einfacher Einzeiler ohne Scripting:

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

Erläuterung

  • `yes input-data.txt | head -1000 | paste -s`Erzeugt den Text input-data.txt1000-mal durch Leerzeichen getrennt
  • Der Text wird dann catals Dateiliste übergeben
Reh
quelle
Diese Lösung scheint nicht zu funktionieren. Müssen Sie verwenden xargs paste -s? Dies funktioniert, behält jedoch keine Zeilenumbrüche in der Eingabedatei bei.
JeremyKun
Stellen Sie sicher, dass Sie den richtigen Apostroph verwenden.
Roeeb
2

Während ich an einem völlig anderen Skript arbeitete, habe ich gelernt, dass mit 29 Millionen Textzeilen das Verwenden seek()und Bearbeiten von Daten nacheinander oft schneller ist als nacheinander. Dieselbe Idee wird im folgenden Skript angewendet: Wir öffnen eine Datei, und anstatt die Datei in einer Schleife zu öffnen und zu schließen (was zu zusätzlichem Aufwand führen kann, auch wenn dieser nicht signifikant ist), lassen wir die Datei geöffnet und versuchen, zum Anfang zurückzukehren.

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

Das Skript selbst ist recht einfach zu bedienen:

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

Für eine 3-zeilige Textdatei und 1000 Iterationen geht es ganz gut, ungefähr 0,1 Sekunden:

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

Das Drehbuch selbst ist nicht besonders elegant, könnte wahrscheinlich gekürzt werden, macht aber den Job. Natürlich habe ich hier und da ein paar zusätzliche Elemente hinzugefügt, wie z. B. die error_out()Funktion, die nicht erforderlich ist - es ist nur eine kleine benutzerfreundliche Berührung.

Sergiy Kolodyazhnyy
quelle
1

Wir können dies ohne eine zusätzliche Datei oder spezielle Programme lösen, reine Bash (na ja, cat ist ein Standardbefehl).

Basierend auf einer Funktion von printf in bash können wir einen wiederholten String erzeugen.

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

Dann können wir eine solche Liste mit 1000 Dateinamen (wiederholt) senden und cat anrufen:

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

Und schließlich können wir die Ausgabe an den Befehl übergeben, der ausgeführt werden soll:

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

Oder, wenn der Befehl die Eingabe in der Standardeingabe erhalten muss:

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

Ja, das double <wird benötigt.


quelle
0

Ich würde eine neue Datei mit Unix for loop erzeugen:

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