ファイルの内容をn回繰り返すにはどうすればよいですか?


19

ファイルを処理する2つの異なる方法を比較するためのベンチマークを試みています。少量の入力データがありますが、適切な比較を行うには、テストを何度も繰り返す必要があります。

テストを繰り返すのではなく、入力データを何度も(たとえば1000)複製して、3行のファイルが3000行になり、より充実したテストを実行できるようにします。

入力データをファイル名で渡します:

mycommand input-data.txt

回答:


21

必要ありませんinput-duplicated.txt

試してください:

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

説明

  • 0777-0setsは、入力レコードの区切り文字($/デフォルトでは改行であるperl特殊変数)を設定します。これをより大きな値に設定すると、0400Perlは入力ファイル全体をメモリに丸lurみします。
  • pe:は、-p「与えられたスクリプトを適用した後に各入力行を印刷する」という意味-eです。
  • $_=$_ x 1000$_現在の入力行です。のためにファイル全体を一度に読んでいるので-0700、これはファイル全体を意味します。これx 1000により、ファイル全体の1000コピーが印刷されます。

いいね これは愚かです。1000 xargsで0.785秒、これで0.006秒であるため、他のループで発生していたオーバーヘッドの問題をおそらく克服できます。
オリ

そして、それを100000回に上げても、実行時間は.002秒だけ増加します。すごいですね。
オリ

@Oli:小さなファイルで十分なメモリがある場合、これperlは非常に効率的であり、このために設計されています。
クオンルム14

11

元々、セカンダリファイルを生成する必要があると考えていましたが、元のファイルをBashでループして、リダイレクトを使用してファイルとして表示することができました。

ループを実行する方法はおそらく12種類ありますが、次の4つがあります。

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

3番目の方法は、以下のmaruのコメントから即興で作成され、catの入力ファイル名の大きなリストを作成します。xargsこれは、システムが許可する数の引数に分割します。それはだずっと速くよりn個の別々の猫。

awk(に触発方法terdonの答えは)おそらく最も最適化されているが、それは一度にそれぞれの行を複製します。これは特定のアプリケーションに適する場合と適さない場合がありますが、高速で効率的です。


しかし、これはその場で生成されます。Bashの出力は、読むことができるものよりも非常に遅い可能性が高いため、テスト用に新しいファイルを生成する必要があります。ありがたいことに、これは非常に単純な拡張機能です。

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

3
両方のコマンドで、catがN回実行されています。catを1回実行して、1つの引数をN回フィードする方が効率的ではないでしょうか?のようなものcat $(for i in {1..N}; do echo filename; done)。これにはargサイズの制限がありますが、もっと速いはずです。
ムル14

@muruいいアイデアも。いくつかの作業が必要でしたが、追加します。現在の実装では、7行のファイルを約0.020秒で1000回繰り返し処理しています。これは私のバージョンよりもはるかに優れていますが、GnoucのPerlレベルではそうではありません。
オリ

6

ここだawk解決策は:

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

それは本質的に@GnucのPerlと同じくらい速いです(私は両方とも1000回実行し、平均時間を得ました):

$ 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
公平には、おそらくこれを単純化awk '{for(i=0; i<1000; i++)print}' input-data.txtして、一度に各行のコピーを1000個だけ発行することができます。すべての状況に適しているわけではありませんが、より高速で遅延が少なく、ファイル全体をRAMに保持する必要はありません。
オリ

@Oli確かに、私はあなたが行順を維持したいと思っていたので、それ123123123はうまくいった111222333がそうではなかった。お使いのバージョンは、Gnoucのバージョンよりも明らかに高速で、平均は0.00297秒です。編集:スクラッチ、私は間違いを犯した、それは実際には0.004013秒で同等です。
テルドン14

5

テキストエディタを使用するだけです。

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)

コマンドラインで絶対に実行する必要がある場合(コマンドがないため、vimインストールする必要があります)、次を使用できます。vi:normal

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

ここでは、-es(または-e -s)vimがサイレントに動作するため、ターミナルウィンドウを引き継ぐべきではなく-u NONE、vimrcを見るのを止めます。多くのvimプラグイン)。


はい。ただし、これはすべてマニュアルであるため、他のソリューションよりも数桁遅く、複雑になります。
テルドン14

4

スクリプトを使用しないシンプルなワンライナーを次に示します。

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

説明

  • `yes input-data.txt | head -1000 | paste -s`input-data.txt空白で区切られたテキストを1000回生成します
  • テキストはcatファイルリストとして渡されます

この解決策は機能しないようです。使用する必要がありますxargs paste -sか?これは機能しますが、入力ファイルの改行を保持しません。
ジェレミークン

正しいアポストロフィを使用していることを確認してください。
roeeb

2

完全に異なるスクリプトで作業している間、2900万行のテキストを使用seek()して、データをバイト単位で使用および操作することは、行単位で行うよりも高速であることが多いことを学びました。以下のスクリプトにも同じ考えが適用されます。ファイルを開き、ファイルを開いたり閉じたりするループ(これは重要ではないにしてもオーバーヘッドを追加する可能性があります)ではなく、ファイルを開いたまま先頭にシークします。

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

スクリプト自体の使い方は非常に簡単です。

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

3行のテキストファイルと1000回の反復では、約0.1秒で問題ありません。

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

スクリプト自体は最もエレガントではなく、おそらく短縮される可能性がありますが、仕事はします。もちろん、error_out()必要ではない機能のように、あちこちにいくつかの余分なビットを追加しました-それは単にユーザーフレンドリーな小さなタッチです。


1

追加のファイルや特別なプログラム、純粋なBashなしでこれを解決できます(まあ、catは標準コマンドです)。

bash内のprintfの機能に基づいて、繰り返し文字列を生成できます)。

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

次に、1000個のファイル名のリスト(繰り返し)を送信し、catを呼び出します。

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

最後に、実行するコマンドに出力を与えることができます。

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

または、コマンドが標準入力で入力を受信する必要がある場合:

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

はい、二重<が必要です。


0

Unix forループを使用して新しいファイルを生成します。

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.