Bir dosyanın içeriğini n kez nasıl tekrarlayabilirim?


19

Bir dosyayı işlemenin iki farklı yolunu karşılaştırmak için karşılaştırma yapmaya çalışıyorum. Az miktarda girdi verim var, ancak iyi karşılaştırmalar elde etmek için testleri birkaç kez tekrarlamam gerekiyor.

Testleri tekrarlamak yerine, giriş verilerini birkaç kez (örneğin 1000) çoğaltmak istiyorum, böylece 3 satırlı bir dosya 3000 satır olur ve çok daha tatmin edici bir test çalıştırabilirim.

Girdi verilerini bir dosya adı üzerinden veriyorum:

mycommand input-data.txt

Yanıtlar:


21

İhtiyacınız yok input-duplicated.txt.

Deneyin:

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

açıklama

  • 0777: -0set giriş kayıt ayırıcısını ayarlar ( $/varsayılan olarak yeni satır olan perl özel değişkeni ). Bunu daha büyük bir değere ayarlamak 0400Perl'in tüm girdi dosyasını belleğe almasına neden olur.
  • pe: -p"her komut satırını kendisine verilen komut dosyasını uyguladıktan sonra yazdır" anlamına gelir -e.
  • $_=$_ x 1000: $_geçerli giriş satırıdır. Çünkü tüm dosyayı bir kerede okuduğumuz için -0700bu, tüm dosya anlamına gelir. Bu x 1000, tüm dosyanın 1000 kopyasının yazdırılmasına neden olur.

Güzel. Bu çok aptalca. 1000 xargs için 0.785s, bunun için 0.006s, bu yüzden evet, muhtemelen diğer döngülerle gördüğüm genel sorunların üstesinden gelir.
Oli

Ve bunu 100000 katına çıkarmak, çalışma zamanını yalnızca .002s artırır. Bu oldukça şaşırtıcı.
Oli

@Oli: Küçük dosyalarla ve yeterli belleğiniz varsa, perlçok verimlidir, bunun için tasarlanmıştır.
cuonglm

11

Başlangıçta ikincil bir dosya oluşturmak zorunda kalacağımı düşünüyordum ama sadece Bash'deki orijinal dosyayı döngüye sokabiliyor ve dosya olarak görünmesini sağlamak için bazı yeniden yönlendirmeler kullanabiliyordum.

Döngüyü yapmanın muhtemelen bir düzine farklı yolu var, ancak burada dört tane var:

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

Üçüncü yöntem, maru'nun aşağıdaki yorumundan doğaçlamadır ve kedi için büyük bir girdi dosya adları listesi oluşturur. xargsbunu sistemin izin verdiği sayıda bağımsız değişkene böler. Bu var çok daha hızlı n ayrı kediler.

awk(Esinlenerek yolu Terdon cevabı ) muhtemelen en iyi duruma ama bir anda her satırı kopyalar. Bu, belirli bir uygulamaya uygun olabilir veya olmayabilir, ancak yıldırım hızlı ve etkilidir.


Ama bu anında gerçekleşiyor. Bash çıktısının bir şeyin okuyabileceğinden çok daha yavaş olması muhtemeldir, bu nedenle test için yeni bir dosya oluşturmanız gerekir. Neyse ki bu sadece çok basit bir uzantı:

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

3
Her iki komutunuzda da kedi N kere çalışıyor. Kediyi bir kez çalıştırmak ve N kez bir argümanla beslemek daha verimli olmaz mıydı? Gibi bir şey cat $(for i in {1..N}; do echo filename; done). Bu, arg boyutu sınırlamasına sahiptir, ancak daha hızlı olmalıdır.
muru

@muru Çok iyi bir fikir. Biraz çalışmaya ihtiyacım vardı ama ekleyeceğim. Mevcut uygulama ~ 0.020s içinde 7 satırlı bir dosya 1000 yineleme yapıyor. Bu benim versiyonlarımdan çok daha iyi, ama Gnouc'un Perl seviyesinde değil.
Oli

6

İşte bir awkçözüm:

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

Temelde @ Gnuc Perl kadar hızlı (1000 kere koştum ve ortalama zamanı aldım):

$ 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
Adil olmak gerekirse, muhtemelen bunu basitleştirebilirsiniz, awk '{for(i=0; i<1000; i++)print}' input-data.txtböylece her seferinde her satırın 1000 kopyasını yayınlar. Tüm durumlara uymayacak, daha da hızlı, daha az gecikme olacak ve tüm dosyayı RAM'de tutmaya gerek yok.
Oli

@ Gerçekten, hat sırasını korumak istediğini varsaymıştım, bu yüzden 123123123iyiydi ama 111222333değildi. Versiyonunuz Gnouc'dan açıkça daha hızlı, ortalama 0.00297 saniyedir. EDIT: çizik, bir hata yaptım, aslında 0.004013 saniyede eşdeğer.
terdon

5

Sadece bir metin düzenleyici kullanırdım.

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)

Kesinlikle (bu adres gerektirir komut satırı üzerinden yapmak gerekirse vimolarak, yüklü viyok :normalkomutu), şunu kullanabilirsiniz:

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

Burada, -es(veya -e -s) vim'in sessizce çalışmasını sağlar, bu yüzden terminal pencerenizi ele geçirmemeli ve -u NONEvimrc'nize bakmasını engellemelidir, bu da aksi takdirde olduğundan biraz daha hızlı çalışmasını sağlayacaktır (belki de daha hızlı, bir sürü vim eklentisi).


Evet, ancak tüm bunlar manueldir, bu da onu diğer çözümlerden daha yavaş ve daha karmaşık birkaç sıraya sokar.
terdon

4

İşte basit bir astar, komut dosyası içermez:

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

açıklama

  • `yes input-data.txt | head -1000 | paste -s`metni input-data.txtboşlukla 1000 kez ayrılmış olarak üretir
  • Daha sonra metin catbir dosya listesi olarak iletilir

Bu çözüm işe yaramıyor gibi görünüyor. Kullanmanız mı gerekiyor xargs paste -s? Bu çalışır, ancak giriş dosyasındaki yeni satırları korumaz.
JeremyKun

Doğru kesme işaretini kullandığınızdan emin olun.
roeeb

2

Tamamen farklı bir betik üzerinde çalışırken, 29 milyon satırlık metinle, seek()veriyi zaman zaman kullanmanın ve kullanmanın genellikle satır satır bazında daha hızlı olduğunu öğrendim . Aşağıdaki senaryoda da aynı fikir geçerlidir: dosyayı açıyoruz ve dosyayı açıp kapatarak (önemli olmasa bile ek yük ekleyebilir) döngü yerine dosyayı açık tutuyoruz ve başlangıca geri dönüyoruz.

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

Betiğin kullanımı oldukça basittir:

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

3 satır metin dosyası ve 1000 yineleme için yaklaşık 0,1 saniye oldukça iyi gider:

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

Komut dosyasının kendisi en zarif değil, muhtemelen kısaltılabilir, ancak iş yapar. Tabii ki, buraya birkaç ekstra bit ekledim, error_out()fonksiyon gibi , gerekli değil - bu sadece küçük bir kullanıcı dostu dokunuş.


1

Bunu ek bir dosya veya özel programlar, saf Bash olmadan çözebiliriz (kedi standart bir komuttur).

Bash içindeki printf özelliğine dayanarak tekrarlanan bir dize oluşturabiliriz):

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

Ardından, 1000 dosya adı (tekrarlanan) listesini gönderebilir ve cat'i arayabiliriz:

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

Ve son olarak, çıktıyı yürütme komutuna verebiliriz:

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

Veya, komutun stdin'deki girişi alması gerekiyorsa:

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

Evet, çift <gereklidir.


0

Ben döngü için Unix kullanarak yeni bir dosya oluşturmak istiyorsunuz:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.