bash他のファイルから新しい列を追加/追加します

Nov 24 2020

1列のname.txtファイルがあります。例:

A
B
C
D
E
F

次に、egxtxt、y.txt、z.txtなどの多くのファイルがあります

x.txtには

A 1
C 3
D 2

y.txtには

A 1
B 4
E 3

z.txtには

B 2
D 2
F 1

望ましい出力は次のとおりです(マッピングがない場合は0を入力します)

A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

bashで作成することは可能ですか?(おそらくawk?)
どうもありがとう!!!


最初の編集-私の暫定的な努力
私はbashにまったく慣れていないので、awkで可能な解決策を見つけるのは本当に難しいです。私はRに精通しており、これは次の方法で実現できます。

namematrix[namematrix[,1]==xmatrix[,1],]

すべてのすべてで、私は本当にについてもっと学ぶ私を助けて下の種類の助けに感謝awkしてjoin


2回目の編集-非常に効率的なアプローチがわかりました!

幸いなことに、以下のいくつかの非常に優れた回答に触発されて、私は以下のように非常に計算効率の高い方法を整理しました。これは、特に非常に大きなサイズの非常に多数のファイルを処理する場合に、同様の質問に遭遇する他の人々に役立つ可能性があります。

まず、join_awk.bashに触れます

#!/bin/bash
join -oauto -e0 -a1 $1 $2 | awk '{print $2}'

たとえば、name.txtとx.txtに対してこのbashスクリプトを実行します

join_awk.bash name.txt x.txt

生成します

1
0
3
2
0
0

私のデータセットでは最初の列は非常に長い名前であり、膨大なディスク容量を必要とするため、ここではディスク領域を節約するために2番目の列のみを保持していることに注意してください。

次に、単に実装します

parallel join_awk.bash name.txt {} \> outdir/output.{} ::: {a,b,c}.txt

これは、GNU並列および結合を使用した以下の素晴らしい答えに触発されています。違いは、シリアル追加ロジックのために以下の答えを指定する必要j1があるparallelことです。これにより、実際には「並列」ではなくなります。また、シリアル追加が続くと、速度はどんどん遅くなります。対照的に、ここでは各ファイルを個別に並行して操作します。複数のCPUを搭載した多数の大きなサイズのファイルを処理する場合、非常に高速になる可能性があります。

最後に、すべての単一列の出力ファイルをマージするだけです。

cd outdir
paste output* > merged.txt

paste本質的に並列であるため、これも非常に高速になります。

回答

12 anubhava Nov 24 2020 at 13:42

あなたはこれを使うことができますawk

awk 'NF == 2 {
   map[FILENAME,$1] = $2
   next
}
{
   printf "%s", $1 for (f=1; f<ARGC-1; ++f) printf "%s", OFS map[ARGV[f],$1]+0
   print ""
}' {x,y,z}.txt name.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1
9 RavinderSingh13 Nov 24 2020 at 14:15

それを行うもう1つの方法を追加します。示されているサンプルを使用して、以下を作成し、テストしてみてください。awk私はGNUの3.1バージョンしか持っていませんが、IMHOはどのでも動作するはずawkです。これは非常に単純で通常の方法です。最初の(メジャー)Input_fileの読み取りで配列を作成し、その後、各ファイル0でその配列の要素が特定のInput_fileで見つからない場合は、小さなサンプルのみでテストして追加します。

awk '
function checkArray(array){
  for(i in array){
    if(!(i in found)){ array[i]=array[i] OFS "0" }
  }
}
FNR==NR{
  arr[$0] next } foundCheck && FNR==1{ checkArray(arr) delete found foundCheck="" } { if($1 in arr){
    arr[$1]=(arr[$1] OFS $2) found[$1]
    foundCheck=1
    next
  }
}
END{
  checkArray(arr)
  for(key in arr){
    print key,arr[key]
  }
}
' name.txt x.txt y.txt  z.txt

説明:上記の詳細な説明を追加します。

awk '                               ##Starting awk program from here.
function checkArray(array){         ##Creating a function named checkArray from here.
  for(i in array){                  ##CTraversing through array here.
    if(!(i in found)){ array[i]=array[i] OFS "0" }   ##Checking condition if key is NOT in found then append a 0 in that specific value.
  }
}
FNR==NR{                            ##Checking condition if FNR==NR which will be TRUE when names.txt is being read.
  arr[$0] ##Creating array with name arr with index of current line. next ##next will skip all further statements from here. } foundCheck && FNR==1{ ##Checking condition if foundCheck is SET and this is first line of Input_file. checkArray(arr) ##Calling function checkArray by passing arr array name in it. delete found ##Deleting found array to get rid of previous values. foundCheck="" ##Nullifying foundCheck here. } { if($1 in arr){                    ##Checking condition if 1st field is present in arr.
    arr[$1]=(arr[$1] OFS $2) ##Appening 2nd field value to arr with index of $1.
    found[$1]                       ##Adding 1st field to found as an index here.
    foundCheck=1                    ##Setting foundCheck here.
    next                            ##next will skip all further statements from here.
  }
}
END{                                ##Starting END block of this program from here.
  checkArray(arr)                   ##Calling function checkArray by passing arr array name in it.
  for(key in arr){                  ##Traversing thorugh arr here.
    print key,arr[key]              ##Printing index and its value here.
  }
}
' name.txt x.txt y.txt z.txt        ##Mentioning Input_file names here.
6 DavidC.Rankin Nov 24 2020 at 13:35

はい、あなたはそれを行うことができます、そしてはい、それawkはツールです。配列と通常のファイル行番号(FNR レコードのファイル番号)および合計行(NR レコード)を使用しnames.txtて、すべての文字をa[]配列に読み込み、変数のファイル番号を追跡して、fnoからのすべての追加を追加してからx.txt、次のファイル(y.txt)の最初の行を処理する前に、最後のファイルにあるすべての文字をループし、表示されていない0場合は、を配置して、通常どおり処理を続行します。追加のファイルごとに繰り返します。

詳細な行ごとの説明はコメントに示されています:

awk '
    FNR==NR {                           # first file
        a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a {                           # if line begins with letter in array
        a[$1] = a[$1]" "$2 # append second field seen[$1]++                      # add letter to seen array
    }
END {
    for (i in a)                        # place zeros for last column
        if (!(i in seen))
            a[i] = a[i]" "0
    for (i in a)                        # print results
        print i a[i]
}' name.txt x.txt y.txt z.txt

使用例/出力

上記をコピーし、ファイルが含まれている現在のディレクトリを使用してxtermにマウスをミドルペーストすると、次のメッセージが表示されます。

A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

自己完結型スクリプトの作成

コマンドラインで貼り付ける代わりに実行するスクリプトを作成する場合は、内容を(一重引用符で囲まずに)含めてから、ファイルを実行可能にします。たとえば、最初の行としてインタプリタを含め、次のように内容を含めます。

#!/usr/bin/awk -f

FNR==NR {                           # first file
    a[$1] = "" # fill array with letters as index fno = 1 # set file number counter next # get next record (line) } FNR == 1 { fno++ } # first line in file, increment file count fno > 2 && FNR == 1 { # file no. 3+ (not run on x.txt) for (i in a) # loop over letters if (!(i in seen)) # if not in seen array a[i] = a[i]" "0 # append 0 delete seen # delete seen array } $1 in a {                           # if line begins with letter in array
    a[$1] = a[$1]" "$2 # append second field seen[$1]++                      # add letter to seen array
}
END {
    for (i in a)                    # place zeros for last column
        if (!(i in seen))
            a[i] = a[i]" "0
    for (i in a)                    # print results
        print i a[i]
}

awk 引数として指定されたファイル名を指定された順序で処理します。

使用例/出力

スクリプトファイルを使用して(私はそれを入れて実行可能にするためにnames.awk使用chmod +x names.awkしました)、次のようにします。

$ ./names.awk name.txt x.txt y.txt z.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

ご不明な点がございましたら、お気軽にお問い合わせください。

4 Sundeep Nov 24 2020 at 14:40

別のアプローチ GNU awk

$ cat script.awk NF == 1 { name[$1] = $1 for (i = 1; i < ARGC - 1; i++) { name[$1] = name[$1] " 0" } next } { name[$1] = gensub(/ ./, " " $2, ARGIND - 1, name[$1])
}

END {
    for (k in name) {
        print name[k]
    }
}

スクリプトの呼び出し:

$ awk -f script.awk name.txt {x,y,z}.txt
A 1 1 0
B 0 4 2
C 3 0 0
D 2 0 2
E 0 3 0
F 0 0 1

出力はと同じ順序を示しname.txtますが、すべての種類の入力に当てはまるとは思いません。

3 potong Nov 24 2020 at 19:47

これはあなたのために働くかもしれません(GNU並列と結合):

cp name.txt out && t=$(mktemp) && parallel -j1 join -oauto -e0 -a1 out {} \> $t \&\& mv $t out ::: {x,y,z}.txt

出力はファイルにありますout

2 DiegoTorresMilano Nov 24 2020 at 15:12

使用できます join

join -a1 -e0 -o '0,2.2' name.txt x.txt | join -a1 -e0 -o '0,1.2,2.2' - y.txt | join -a1 -e0 -o '0,1.2,1.3,2.2' - z.txt
1 tshiono Nov 24 2020 at 13:48

bash方法について:

#!/bin/bash

declare -A hash                                 # use an associative array
for f in "x.txt" "y.txt" "z.txt"; do            # loop over these files
    while read -r key val; do                   # read key and val pairs
        hash[$f,$key]=$val # assign the hash to val done < "$f"
done

while read -r key; do
    echo -n "$key" # print the 1st column for f in "x.txt" "y.txt" "z.txt"; do # loop over the filenames echo -n " ${hash[$f,$key]:-0}"          # print the associated value or "0" if undefined
    done
    echo                                        # put a newline
done < "name.txt"