अन्य फ़ाइलों से नए कॉलम जोड़ें / जोड़ें

Nov 24 2020

मेरे पास एक कॉलम की name.txt फाइल है, जैसे

A
B
C
D
E
F

फिर मेरे पास कई फाइलें हैं, जैसे कि, एक्सटेक्स्ट, 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

क्या बैश के साथ इसे बनाना संभव है? (शायद जाग?)
बहुत धन्यवाद !!!


पहला संपादन - मेरे अस्थायी प्रयास
चूंकि मैं कोसने के लिए काफी नया हूं, इसलिए मेरे लिए जाग के साथ एक संभावित समाधान का पता लगाना वास्तव में कठिन है। मैं R से अधिक परिचित हूं, जिसमें यह पूरा किया जा सकता है

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

सब सब में, मैं वास्तव में नीचे दी गई मदद की सराहना करता हूं जिससे मुझे awkऔर अधिक जानने में मदद मिल सके join!


दूसरी बार संपादन - एक सुपर कुशल दृष्टिकोण लगा!

सौभाग्य से नीचे कुछ वास्तव में शानदार जवाब से प्रेरित होकर, मैंने नीचे के रूप में एक बहुत ही कम्प्यूटेशनल तरीके से हल किया है। यह अन्य लोगों को भी इसी तरह के सवालों का सामना करने में मददगार हो सकता है, विशेष रूप से यदि वे बहुत बड़ी संख्या में बहुत बड़े आकार की फाइलों से निपटते हैं।

सबसे पहले एक join_awk.bash को टच करें

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

उदाहरण के लिए, name.txt और x.txt के लिए इस बैश स्क्रिप्ट को निष्पादित करें

join_awk.bash name.txt x.txt

उत्पन्न होगा

1
0
3
2
0
0

ध्यान दें कि यहां मैं डिस्क स्थान को बचाने के लिए केवल दूसरा कॉलम रखता हूं, क्योंकि मेरे डेटासेट में पहले कॉलम बहुत लंबे नाम हैं जो जबरदस्त डिस्क स्थान लेते हैं।

फिर बस लागू करें

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

यह जीएनयू समानांतर और शामिल होने के नीचे दिए गए शानदार जवाब से प्रेरित है। अंतर यह है कि नीचे दिए गए उत्तर को अपने धारावाहिक के तर्क के कारण निर्दिष्ट j1करना parallelपड़ता है, जो इसे वास्तव में "समानांतर" नहीं बनाता है। इसके अलावा, धारावाहिक के जारी रहने से गति धीमी और धीमी हो जाएगी। इसके विपरीत, यहां हम समानांतर में प्रत्येक फ़ाइल को अलग से जोड़ते हैं। यह बहुत तेज हो सकता है जब हम बड़ी संख्या में बड़े आकार की फाइलों को कई सीपीयू के साथ सौदा करते हैं।

अंत में बस सभी एकल-स्तंभ आउटपुट फ़ाइलों को एक साथ मर्ज करें

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

इसे करने का एक और तरीका जोड़ना। क्या आप दिखाए गए नमूनों के साथ निम्नलिखित लिखित और परीक्षण करने की कोशिश कर सकते हैं। IMHO किसी भी काम करना चाहिए awk, हालांकि मेरे पास awkकेवल GNU का 3.1 संस्करण है । यह बहुत ही सरल और सामान्य तरीका है, पहले (प्रमुख) 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"