ファイル内のすべての行のペアワイズ展開を「cat」するコマンドラインツール


13

次のようなファイル(sample.txtというファイル)があるとします。

Row1,10
Row2,20
Row3,30
Row4,40

基本的に4行すべてのペアの組み合わせであるこのファイルからのストリームで作業できるようにしたいので(合計で16になるはずです)。たとえば、出力が次のようなストリーミング(つまり効率的な)コマンドを探しています。

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40

私の使用例は、この出力を別のコマンド(awkなど)にストリーミングして、このペアごとの組み合わせに関するメトリックを計算することです。

私はawkでこれを行う方法がありますが、私の懸念は、END {}ブロックを使用すると、出力する前に基本的にファイル全体をメモリに保存することです。サンプルコード:

awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt 
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20

基本的にファイルをメモリに保存してからENDブロックに出力することなく、これを行う効率的なストリーミング方法はありますか?


1
他のファイルの2行目の出力の生成を開始する前に、常に1つのファイルを最後まで読み取る必要があります。ストリーミングできる他のファイル。
reinierpost 14年

回答:


12

ファイル全体を配列に保存する必要がないように、awkで行う方法を次に示します。これは基本的にterdonのアルゴリズムと同じです。

必要に応じて、コマンドラインで複数のファイル名を指定することもでき、各ファイルを個別に処理し、結果を連結します。

#!/usr/bin/awk -f

#Cartesian product of records

{
    file = FILENAME
    while ((getline line <file) > 0)
        print $0, line
    close(file)
}

私のシステムでは、これはterdonのperlソリューションの約2/3の時間で実行されます。


1
ありがとう!この問題に対するすべての解決策は素晴らしいものでしたが、私は1)シンプルさと2)awkにとどまったために、この問題を解決しました。ありがとう!
トムヘイデン14年

1
喜んでくれた、トム。私は最近、ほとんどPythonでプログラムする傾向がありますが、行とファイルに対する組み込みループのために、行ごとのテキスト処理にawkがまだ好きです。また、多くの場合、Pythonよりも高速です。
PM 2Ring 14年

7

私は確かにこれはメモリにそれを行うよりも良いですが、ではないよsedというrパイプの反対側にあるすべてのそのINFILEの行と他のためにそのINFILEアウトEADSが交互にH入力ラインと古いスペース...

cat <<\IN >/tmp/tmp
Row1,10
Row2,20
Row3,30
Row4,40
IN

</tmp/tmp sed -e 'i\
' -e 'r /tmp/tmp' | 
sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D'

出力

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

私はこれを別の方法でやった。メモリに一部を保存します-次のような文字列を保存します:

"$1" -

...ファイルの各行に対して。

pairs(){ [ -e "$1" ] || return
    set -- "$1" "$(IFS=0 n=
        case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac
        printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))"
    eval "cat -- $2 </dev/null | paste -d ' \n' -- $2"
}

とても速いです。これcatは、ファイル内の行と同じ数のファイルです|pipe。パイプの反対側では、入力はファイル内の行と同じ回数だけファイル自体とマージされます。

caseものは単なる移植性のためである- yashzsh、スプリットに一つの要素を追加しながら、両方mkshposhの両方を失う1。kshdashbusybox、およびbashによって印刷されたとしてゼロがあるとして多くの分野と正確にすべてのスプリットアウトprintf。書かれているように、上記は私のマシン上の上記のシェルのすべてに対して同じ結果をレンダリングします。

ファイルが非常に長い場合、$ARGMAX引数が多すぎる場合に問題が発生する可能性があり、その場合はxargs同様に導入する必要があります。

出力が同一になる前に使用したのと同じ入力が与えられた場合。しかし、もし私が大きくなるとしたら...

seq 10 10 10000 | nl -s, >/tmp/tmp

それは私が前に使用したものとほぼ同じファイルを生成します (sans 'Row')とが、1000行になります。あなたはそれがどれくらい速いか自分で見ることができます:

time pairs /tmp/tmp |wc -l

1000000
pairs /tmp/tmp  0.20s user 0.07s system 110% cpu 0.239 total
wc -l  0.05s user 0.03s system 32% cpu 0.238 total

1000行では、シェル間でパフォーマンスにわずかなばらつきがあります- bash常に最も遅いです-しかし、とにかく行うのはarg文字列を生成することだけであるため(の1000コピーfilename -)を、効果は最小限です。zsh上記のパフォーマンスの違いbashは、ここでは100分の1秒です。

任意の長さのファイルで機能する別のバージョンを次に示します。

pairs2()( [ -e "$1" ] || exit
    rpt() until [ "$((n+=1))" -gt "$1" ]
          do printf %s\\n "$2"
          done
    [ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit
    : & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")"
    ln -s "$PWD/${1##*/}" "$2" || exit
    n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0
    n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3
    }; rm "$2"
)

/tmp奇妙なファイル名でハングアップしないように、セミランダムな名前で最初の引数へのソフトリンクを作成します。これは重要です。なぜなら、catの引数はを介してパイプ経由で渡されるからxargsです。cat出力が保存される<&3一方、sed pそのファイル内の行が存在するように何回もrints最初の引数内のすべての行を-と、そのスクリプトは、パイプを介してそれに供給されます。再びpasteその入力をマージしますが、今回-はその標準入力とリンク名に対して再び2つの引数のみを取ります/dev/fd/3

最後に- /dev/fd/[num]リンクは、Linuxシステムやその他の多くのシステムで動作するはずですが、名前付きパイプが作成されない場合はmkfifo、代わりにそれを使用しても動作します。

最後に行うことはrm、終了する前に作成するソフトリンクです。

私のシステムでは、このバージョンは実際にはまだ高速です。私はそれがより多くのアプリケーションを実行するけれども、彼らにすぐに彼らの議論を手渡し始めたからであると思う-それがそれらをすべて最初にスタックする前に。

time pairs2 /tmp/tmp | wc -l

1000000
pairs2 /tmp/tmp  0.30s user 0.09s system 178% cpu 0.218 total
wc -l  0.03s user 0.02s system 26% cpu 0.218 total

ペア関数はファイル内にあると想定されていますが、そうでない場合はどのように宣言しますか?

@Jidder-どのように宣言しますか?端末にコピーして貼り付けるだけでいいのですか?
mikeserv 14年

1
関数を宣言します。私はあなたが改行をエスケープするだろうと思った、私はただコードを貼り付けることには警戒しているが、ありがとう:)また、それは非常に高速で、いい答えです!

@Jidder-私は通常、私と同じctrl+v; ctrl+jように改行を取得するためだけに使用するライブシェルでこれらを記述します。
mikeserv 14年

@Jidder-どうもありがとう。そして、警戒するのは賢明です-あなたのために。それらはファイルでも同様に機能します- . ./file; fn_nameその場合、それをコピーできます。
mikeserv 14年

5

まあ、あなたは常にあなたのシェルでそれを行うことができます:

while read i; do 
    while read k; do echo "$i $k"; done < sample.txt 
done < sample.txt 

それはあなたのawkソリューションよりもかなり遅いです(私のマシンでは、1000行で約11秒かかりましたが、で約0.3秒かかりましたawk)が、少なくともメモリに数行以上を保持することはありません。

上記のループは、例にある非常に単純なデータに対して機能します。バックスラッシュで窒息し、末尾と先頭のスペースを食べます。同じもののより堅牢なバージョンは次のとおりです。

while IFS= read -r i; do 
    while IFS= read -r k; do printf "%s %s\n" "$i" "$k"; done < sample.txt 
done < sample.txt 

別の選択肢は、perl代わりに使用することです:

perl -lne '$line1=$_; open(A,"sample.txt"); 
           while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt

上記のスクリプトは、入力ファイル(-ln)の各行を読み取り、名前を付けて保存し$lsample.txt再度開き、各行をとともに印刷し$lます。結果はすべてペアワイズの組み合わせですが、メモリに保存されるのは2行のみです。私のシステムでは、0.61000行で約数秒しかかかりませんでした。


わあ、ありがとう!perlソリューションがbash whileステートメントよりもはるかに高速である理由を疑問に思う
トムヘイデン14年

@TomHaydenは基本的に、perlはawkのようにbashよりもずっと速いためです。
テルドン

1
whileループにダウン投票しなければなりませんでした。そこには4つの異なる悪い習慣があります。あなたの方がよく分かっている。
ステファンシャゼル14年

1
@StéphaneChazelasよく、ここでのあなたの答えに基づいて、私はそれechoが問題になるかもしれないケースを考えることができませんでした。私が書いたもの(私はprintf今追加しました)はそれらのすべてで正しく動作するはずです?whileループについては、なぜですか?何が問題なのwhile read f; do ..; done < fileですか?きっとあなたはforループを提案していない!他の選択肢は何ですか?
テルドン

2
@cuonglm、それを避けるべき1つの考えられる理由を示唆しているだけです。うち、概念信頼性読みやすさパフォーマンスセキュリティの側面、それが唯一カバーし、信頼性を
ステファンシャゼル14年

4

zsh

a=(
Row1,10
Row2,20
Row3,30
Row4,40
)
printf '%s\n' $^a' '$^a

$^a配列上では、配列に対して中括弧のような展開(のような{elt1,elt2})をオンにします。


4

このコードをコンパイルして、非常に迅速な結果を得ることができます。
1000行のファイルで約0.19〜0.27秒で完了します。

現在10000、メモリに行を読み込み(画面への印刷を高速化するため)、10001行あたりの文字数が10mbメモリよりも少ない場合、これは問題ではないと思います。ただし、そのセクションを完全に削除し、問題が発生する場合は画面に直接印刷することができます。

g++ -o "NAME" "NAME.cpp"
Where を使用してコンパイルできます。Where NAMEはファイルの名前で、NAME.cppこのコードが保存されるファイルです

CTEST.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>
int main(int argc,char *argv[])
{

        if(argc != 2)
        {
                printf("You must provide at least one argument\n"); // Make                                                                                                                      sure only one arg
                exit(0);
   }
std::ifstream file(argv[1]),file2(argv[1]);
std::string line,line2;
std::stringstream ss;
int x=0;

while (file.good()){
    file2.clear();
    file2.seekg (0, file2.beg);
    getline(file, line);
    if(file.good()){
        while ( file2.good() ){
            getline(file2, line2);
            if(file2.good())
            ss << line <<" "<<line2 << "\n";
            x++;
            if(x==10000){
                    std::cout << ss.rdbuf();
                    ss.clear();
                    ss.str(std::string());
            }
    }
    }
}
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}

デモンストレーション

$ g++ -o "Stream.exe" "CTEST.cpp"
$ seq 10 10 10000 | nl -s, > testfile
$ time ./Stream.exe testfile | wc -l
1000000

real    0m0.243s
user    0m0.210s
sys     0m0.033s

3
join -j 2 file.txt file.txt | cut -c 2-
  • 存在しないフィールドで結合し、最初のスペースを削除します

フィールド2は空で、file.txtのすべての要素で等しいため、join各要素を他のすべての要素と連結します。実際には、デカルト積を計算しています。


2

Pythonのオプションの1つは、ファイルをメモリマップし、Python正規表現ライブラリがメモリマップファイルを直接操作できるという事実を利用することです。これはファイル上でネストされたループを実行しているように見えますが、メモリマッピングにより、OSが使用可能な物理RAMを最適な状態で再生できるようになります。

import mmap
import re
with open('test.file', 'rt') as f1, open('test.file') as f2:
    with mmap.mmap(f1.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m1,\
        mmap.mmap(f2.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m2:
        for line1 in re.finditer(b'.*?\n', m1):
            for line2 in re.finditer(b'.*?\n', m2):
                print('{} {}'.format(line1.group().decode().rstrip(),
                    line2.group().decode().rstrip()))
            m2.seek(0)

代わりにPythonでの迅速な解決策ですが、メモリの効率が依然として懸念される場合があります

from itertools import product
with open('test.file') as f:
    for a, b  in product(f, repeat=2):
        print('{} {}'.format(a.rstrip(), b.rstrip()))
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

定義上、ファイル全体をメモリに保持しませんか?私はPythonを知りませんが、あなたの言語は確かにPythonを示唆しています。
テルドン

1
@terdon、メモリマッピングソリューションについて言及している場合、OSは使用可能な物理RAMに基づいて余裕がある限り、ファイルをメモリに透過的に保持します。使用可能な物理RAMは、ファイルサイズを超える必要はありません(ただし、追加の物理RAMがあれば有利な状況になります)。最悪の場合、これはディスク上のファイルをループする速度に低下するか、さらに悪い場合があります。これは時間の経過とともに変動する可能性があり、何かであるように、このアプローチの重要な利点は、利用可能な物理RAMの透明用法である
iruvar

1

bashでは、シェル組み込みコマンドのみを使用してkshも動作するはずです。

#!/bin/bash
# we require array support
d=( $(< sample.txt) )
# quote arguments and
# build up brace expansion string
d=$(printf -- '%q,' "${d[@]}")
d=$(printf -- '%s' "{${d%,}}' '{${d%,}}")
eval printf -- '%s\\n' "$d"

これはメモリ内のファイル全体をシェル変数に保持しますが、必要なのは単一の読み取りアクセスのみです。


1
OPの重要なポイントは、ファイルをメモリに保持しないことです。それ以外の場合、現在のgawkアプローチは単純であり、はるかに高速です。これは、数ギガバイトのサイズのテキストファイルで動作する必要があると思います。
テルドン

ええ、それは正確です-私はこれを行う必要があり、メモリに保持したくないいくつかの巨大なデータファイルがあります
トムヘイデン14年

ケースでは、メモリによって制約下、私は@terdonからのソリューションのいずれかを使用することをお勧めしますということです
Franki

0

sed 解決。

line_num=$(wc -l < input.txt)
sed 'r input.txt' input.txt | sed -re "1~$((line_num + 1)){h;d}" -e 'G;s/(.*)\n(.*)/\2 \1/'

説明:

  • sed 'r file2' file1 -file1のすべての行について、file2のすべてのファイルの内容を読み取ります。
  • 構成と1~iは、1行目、1 + i行、1 + 2 * i、1 + 3 * iなどを1~$((line_num + 1)){h;d}意味します。したがって、hバッファへの古い先の行、dパターンスペースを削除し、新しいサイクルを開始します。
  • 'G;s/(.*)\n(.*)/\2 \1/'-前の手順で選択したものを除くすべての行について、次の操作をG行います。ホールドバッファーからet行を現在の行に追加します。次に、行の場所を交換します。だったcurrent_line\nbuffer_line\n、となりましたbuffer_line\ncurrent_line\n

出力

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.