Jak mogę powtórzyć zawartość pliku n razy?

19

Próbuję przeprowadzić test porównawczy, aby porównać dwa różne sposoby przetwarzania pliku. Mam niewielką ilość danych wejściowych, ale aby uzyskać dobre porównania, muszę kilkakrotnie powtórzyć testy.

Zamiast po prostu powtarzać testy, chciałbym zduplikować dane wejściowe kilka razy (np. 1000), aby plik z 3 liniami zamienił się w 3000 linii i mógłbym przeprowadzić test o wiele bardziej satysfakcjonujący.

Przesyłam dane wejściowe przez nazwę pliku:

mycommand input-data.txt
Oli
źródło

Odpowiedzi:

21

Nie trzeba input-duplicated.txt.

Próbować:

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

Wyjaśnienie

  • 0777: -0ustawia separator rekordów wejściowych (specjalna zmienna perla, $/domyślnie nowa linia). Ustawienie tej wartości na większą niż 0400spowoduje, że Perl zatopi cały plik wejściowy do pamięci.
  • pe: -poznacza „wydrukuj każdy wiersz wejściowy po zastosowaniu podanego mu skryptu -e”.
  • $_=$_ x 1000: $_jest bieżącą linią wejściową. Ponieważ czytamy cały plik naraz -0700, oznacza to cały plik. x 1000Spowoduje 1000 kopie całej dokumentacji, która jest drukowana.
Cuonglm
źródło
Ładny. To jest głupie szybkie. 0,785 dla 1000 xarg, 0,006 dla tego, więc tak, prawdopodobnie przezwyciężyłem ogólne problemy, które widziałem przy innych pętlach.
Oli
Zwiększenie tego do 100000 razy zwiększa tylko czas działania o 0,002 s. To całkiem niesamowite.
Oli
@Oli: Dzięki małym plikom i wystarczającej ilości pamięci perljest tak wydajny, że jest przeznaczony do tego.
cuonglm
11

Początkowo myślałem, że będę musiał wygenerować plik pomocniczy, ale mogę po prostu zapętlić oryginalny plik w Bash i użyć przekierowania, aby pojawił się jako plik.

Prawdopodobnie istnieje kilkanaście różnych sposobów wykonania pętli, ale oto cztery:

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

Trzecia metoda jest improwizowana na podstawie komentarza maru poniżej i buduje dużą listę nazw plików wejściowych dla cat. xargspodzieli to na tyle argumentów, na ile pozwoli system. Jest znacznie szybszy niż n osobnych kotów.

awkSposób (zainspirowany odpowiedź terdon za ) jest prawdopodobnie najbardziej zoptymalizowane, ale powiela każdą linię naraz. To może, ale nie musi pasować do konkretnego zastosowania, ale jest błyskawiczne i wydajne.


Ale to generuje w locie. Wyprowadzanie basha prawdopodobnie będzie znacznie wolniejsze niż coś, co można odczytać, więc powinieneś wygenerować nowy plik do testowania. Na szczęście to tylko bardzo proste rozszerzenie:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt
Oli
źródło
3
Oba polecenia mają kota uruchomionego N razy. Czy nie byłoby bardziej efektywne uruchomić kota raz i podać mu jeden argument N razy? Coś jak cat $(for i in {1..N}; do echo filename; done). Ma to ograniczenie wielkości arg, ale powinno być szybsze.
muru
@muru Również fajny pomysł. Potrzebowałem trochę pracy, ale dodam ją. Obecna implementacja wykonuje 1000 iteracji pliku 7-liniowego w ~ 0,020 s. To naprawdę dużo lepsze niż moje wersje, ale nie na poziomie Perla Gnouca.
Oli
6

Oto awkrozwiązanie:

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

Jest w zasadzie tak szybki jak Perl @ Gnuca (biegnąłem zarówno 1000 razy, jak i dostałem średni czas):

$ 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
źródło
1
Mówiąc uczciwie, prawdopodobnie można to uprościć, awk '{for(i=0; i<1000; i++)print}' input-data.txttak aby po prostu wydawało 1000 kopii każdej linii na raz. Nie będzie pasować na wszystkie okazje, ale nawet szybciej, mniej opóźnień i nie trzeba przechowywać całego pliku w pamięci RAM.
Oli
@Oli rzeczywiście założyłem, że chcesz zachować kolejność linii, więc 123123123było dobrze, ale 111222333nie było. Twoja wersja jest wyraźnie szybsza niż Gnouc, średnio 0,00297 sekundy. EDYCJA: podrap, że popełniłem błąd, w rzeczywistości jest to równowartość 0,004013 sekund.
terdon
5

Chciałbym po prostu użyć edytora tekstu.

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)

Jeśli absolutnie musisz to zrobić za pomocą wiersza polecenia (wymaga to vimzainstalowania, ponieważ vinie ma :normalpolecenia), możesz użyć:

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

Tutaj -es(lub -e -s) sprawia, że ​​vim działa cicho, więc nie powinien przejmować okna terminala i -u NONEpowstrzymuje go od patrzenia na vimrc, co powinno sprawić, że będzie działał trochę szybciej niż w innym przypadku (może znacznie szybciej, jeśli użyjesz dużo wtyczek vima).

zła
źródło
Tak, ale jest to cała instrukcja, dzięki czemu jest kilka rzędów wielkości wolniejsza i bardziej złożona niż inne rozwiązania.
terdon
4

Oto prosty linijka, bez skryptów:

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

Wyjaśnienie

  • `yes input-data.txt | head -1000 | paste -s`tworzy tekst input-data.txt1000 razy oddzielony białym odstępem
  • Tekst jest następnie przekazywany do catpostaci listy plików
sarna
źródło
To rozwiązanie nie działa. Czy potrzebujesz użyć xargs paste -s? Działa to, ale nie zachowuje nowych linii w pliku wejściowym.
JeremyKun
Upewnij się, że używasz prawidłowego apostrofu.
roeeb
2

Pracując nad zupełnie innym skryptem, dowiedziałem się, że przy 29 milionach wierszy tekstu używanie seek()i działanie na danych jest często szybsze niż w przypadku poszczególnych linii. Ten sam pomysł zastosowano w skrypcie poniżej: otwieramy plik i zamiast przechodzić przez pętlę poprzez otwieranie i zamykanie pliku (co może zwiększać obciążenie, nawet jeśli nie jest znaczące), utrzymujemy plik otwarty i wracamy do początku.

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

Sam skrypt jest dość prosty w użyciu:

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

W przypadku 3-wierszowego pliku tekstowego i iteracji 1000 idzie całkiem dobrze, około 0,1 sekundy:

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

Sam skrypt nie jest najbardziej elegancki, prawdopodobnie można go skrócić, ale spełnia swoje zadanie. Oczywiście dodałem tu i tam kilka dodatkowych bitów, takich jak error_out()funkcja, która nie jest konieczna - to tylko mały, przyjazny dla użytkownika dotyk.

Sergiy Kolodyazhnyy
źródło
1

Możemy to rozwiązać bez dodatkowego pliku, ani specjalnych programów, czysto Bash (cóż, cat to standardowe polecenie).

W oparciu o funkcję printf w bash możemy wygenerować powtarzający się ciąg):

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

Następnie możemy wysłać taką listę 1000 nazw plików (powtórzonych) i wywołać cat:

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

Na koniec możemy podać dane wyjściowe polecenia, aby wykonać:

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

Lub, jeśli polecenie musi otrzymać dane wejściowe w standardowym wejściu:

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

Tak, potrzebne jest podwójne <.


źródło
0

Wygenerowałbym nowy plik przy użyciu Uniksa dla pętli:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
SmallChess
źródło