कमांड लाइन टूल एक फाइल में सभी पंक्तियों के "कैट" के लिए जोड़ीदार विस्तार


13

मान लीजिए कि मेरे पास एक फ़ाइल है (इसे नमूना कहिए। Txt) जो इस तरह दिखता है:

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

मैं इस फाइल से एक धारा पर काम करने में सक्षम होना चाहता हूं जो अनिवार्य रूप से सभी चार पंक्तियों का युग्म-संयोजन है (इसलिए हमें कुल 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
दूसरी फ़ाइल की दूसरी पंक्ति के लिए आउटपुट शुरू करने में सक्षम होने से पहले आपको हमेशा एक फ़ाइल को अंत तक पढ़ना होगा। दूसरी फाइल जिसे आप स्ट्रीम कर सकते हैं।
रिस्टोरियरपोस्ट

जवाबों:


12

यहां बताया गया है कि इसे awk में कैसे करें ताकि इसे पूरी फ़ाइल को किसी सरणी में संग्रहीत न करना पड़े। यह मूल रूप से टेर्डन के समान ही एल्गोरिदम है।

यदि आप चाहें, तो आप इसे कमांड लाइन पर कई फ़ाइलनाम भी दे सकते हैं और यह प्रत्येक फ़ाइल को स्वतंत्र रूप से संसाधित करेगा, परिणाम को एक साथ जोड़ देगा।

#!/usr/bin/awk -f

#Cartesian product of records

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

मेरे सिस्टम पर, यह टेर्डन के पर्ल समाधान के समय के बारे में 2/3 में चलता है।


1
धन्यवाद! इस समस्या के सभी समाधान शानदार थे लेकिन मैंने 1) सादगी और 2) जाग में रहने के कारण इसे समाप्त कर दिया। धन्यवाद!
टॉम हेडन

1
खुशी है कि आप इसे पसंद करते हैं, टॉम। मैं इन दिनों ज्यादातर पायथन में कार्यक्रम करता हूं, लेकिन मैं अभी भी लाइन टेक्स्ट प्रोसेसिंग द्वारा लाइन के लिए awk को पसंद करता हूं क्योंकि इसके बिल्ट-इन लूप्स इन लाइन्स और फाइल्स की वजह से हैं। और यह अक्सर पायथन की तुलना में तेज़ होता है।
पीएम 2Ring

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- सामान बस पोर्टेबिलिटी के लिए है yashऔर zshजबकि विभाजन करने के लिए दोनों ऐड एक तत्व, mkshऔर poshदोनों एक खो। ksh, dash, busybox, और bashवहाँ के रूप में शून्य के रूप में द्वारा मुद्रित कर रहे हैं कई क्षेत्रों के रूप में वास्तव में करने के लिए बाहर सभी विभाजित हो printf। जैसा कि ऊपर लिखा गया है मेरी मशीन पर उपर्युक्त गोले में से हर एक के लिए समान परिणाम प्रस्तुत करता है।

यदि फ़ाइल बहुत लंबी है, तो $ARGMAXबहुत सारे तर्कों के साथ समस्याएँ हो सकती हैं, जिस स्थिति में आपको परिचय xargsया इसी तरह की आवश्यकता होगी ।

एक ही इनपुट को देखते हुए मैंने आउटपुट से पहले इस्तेमाल किया था। लेकिन, अगर मैं बड़ा होता ...

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

इससे पहले जो मैंने इस्तेमाल किया था ( लगभग 'पंक्ति') - लेकिन 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 string उत्पन्न करता है (1000 प्रतियाँ filename -) प्रभाव कम से कम होता है। प्रदर्शन के बीच का अंतर zsh- जैसा कि ऊपर है - और bashयहां एक सेकंड का 100 वां है।

यहाँ एक और संस्करण है जो किसी भी लम्बाई की फ़ाइल के लिए काम करना चाहिए:

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अर्ध-यादृच्छिक नाम के साथ अपने पहले arg के लिए एक सॉफ्ट-लिंक बनाता है ताकि यह अजीब फ़ाइलनाम पर लटका हुआ न हो। यह महत्वपूर्ण है क्योंकि catएक पाइप पर इसके माध्यम से आर्ग को खिलाया जाता है xargs। पहले आर्ग में हर लाइन को चिन्हित करते हुए catआउटपुट को सेव किया जाता है क्योंकि उस फाइल में कई बार लाइनें होती हैं - और इसकी स्क्रिप्ट को पाइप के जरिए भी फीड किया जाता है। फिर से इसके इनपुट को मिलाता है, लेकिन इस बार इसके मानक इनपुट और लिंक नाम के लिए केवल दो तर्क हैं ।<&3sed ppaste-/dev/fd/3

वह आखिरी - /dev/fd/[num]लिंक - किसी भी लिनक्स सिस्टम पर काम करना चाहिए और इसके अलावा भी बहुत से काम करना चाहिए, लेकिन अगर वह नामांकित पाइप नहीं बनाता है 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

क्या जोड़े का कार्य किसी फाइल में होना माना जाता है, यदि नहीं तो आप इसे कैसे घोषित करेंगे?

@ जैडर - मैं कैसे घोषित करूंगा? आप इसे एक टर्मिनल में कॉपी + पेस्ट कर सकते हैं, नहीं?
15

1
फ़ंक्शन की घोषणा करें। तो आप कर सकते हैं! मुझे लगा कि आप नई कहानियों से बच गए होंगे, मैं सिर्फ चिपकाने वाले कोड से सावधान हूं, धन्यवाद हालांकि :) इसके अलावा यह बहुत तेज़ है, अच्छा जवाब है!

@ जैडर - मैं आमतौर पर एक लाइव शेल में लिखता हूं, ctrl+v; ctrl+jजैसा कि मैं करता हूं, वैसे ही नए अंक प्राप्त करने के लिए।
15

@ जीडर - बहुत बहुत धन्यवाद। और समझदार होना आपके लिए अच्छा है। वे एक फ़ाइल में भी काम करेंगे - आप इसे . ./file; fn_nameउस स्थिति में कॉपी कर सकते हैं ।
15

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) को पढ़ेगी, इसे सहेजें $l, sample.txtफिर से खोलें , और प्रत्येक पंक्ति को प्रिंट करें $l। परिणाम सभी जोड़ीदार संयोजनों है, जबकि केवल 2 लाइनें कभी मेमोरी में संग्रहीत होती हैं। मेरे सिस्टम पर, कि केवल 0.61000 लाइनों पर सेकंड के बारे में लिया ।


वाह धन्यवाद! मुझे आश्चर्य है कि कथन के अनुसार पर्ल का समाधान बैश की तुलना में बहुत अधिक तेज़ क्यों है
टॉम हेडन

@TomHayden मूल रूप से क्योंकि perl, awk की तरह, बैश की तुलना में बहुत तेज़ है।
terdon

1
अपने समय के पाश के लिए नीचे जाना था। वहाँ 4 अलग-अलग बुरे व्यवहार। आप बेहतर जानते हैं।
स्टीफन चेजलस

1
@ StéphaneChazelas अच्छी तरह से, अपने जवाब के आधार पर यहाँ , मैं किसी भी मामले हैं, जहां के बारे में सोच नहीं सकता था echoएक समस्या हो सकती है। मैंने जो लिखा था (मैंने printfअब जोड़ा ) उन सभी के साथ काम करना चाहिए? के रूप में whileपाश, क्यों? इसमें गलत क्या है while read f; do ..; done < file? निश्चित रूप से आप एक forलूप का सुझाव नहीं दे रहे हैं ! दूसरा विकल्प क्या है?
terdon

2
@ क्यूंग्लम, कि केवल एक संभावित कारण पर संकेत देता है कि किसी को इससे क्यों बचना चाहिए। में से वैचारिक , विश्वसनीयता , स्पष्टता , प्रदर्शन और सुरक्षा पहलुओं, कि केवल शामिल किया गया है विश्वसनीयता
स्टीफन चेज़लस 15

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में मेमोरी में लाइनों को पढ़ता है (स्क्रीन पर प्रिंटिंग को गति देने के लिए) जो अगर आपके पास 1000प्रति पंक्ति वर्ण है तो 10mbमेमोरी की तुलना में कम उपयोग करेगा जो मुझे नहीं लगता कि एक समस्या होगी। आप उस अनुभाग को पूरी तरह से हटा सकते हैं और स्क्रीन पर सीधे प्रिंट कर सकते हैं यदि यह किसी समस्या का कारण बनता है।

आप यह संकलित करने के लिए फ़ाइल का नाम g++ -o "NAME" "NAME.cpp"
कहां 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 में सभी तत्व के लिए फ़ील्ड 2 खाली और बराबर है। इसलिए joinप्रत्येक तत्व को अन्य सभी के साथ अलग करेगा: यह वास्तव में कार्टेशियन उत्पाद की गणना कर रहा है।


2

पायथन के साथ एक विकल्प फ़ाइल को मेमोरी-मैप करना और इस तथ्य का लाभ उठाना है कि पायथन नियमित अभिव्यक्ति लाइब्रेरी मेमोरी-मैप्ड फ़ाइलों के साथ सीधे काम कर सकती है। हालाँकि, यह फ़ाइल पर नेस्टेड लूप चलाने की उपस्थिति है, मेमोरी मैपिंग सुनिश्चित करता है कि ओएस भौतिक रूप से उपलब्ध रैम को खेलने के लिए उपलब्ध कराता है

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)

वैकल्पिक रूप से पायथन में एक त्वरित समाधान, हालांकि स्मृति दक्षता अभी भी एक चिंता का विषय हो सकती है

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

क्या यह नहीं है कि परिभाषा के अनुसार, पूरी फ़ाइल को स्मृति में रखें? मैं अजगर को नहीं जानता लेकिन आपकी भाषा निश्चित रूप से यह बताती है कि यह होगा।
terdon

1
@terdon, यदि आप मेमोरी-मैपिंग समाधान की बात कर रहे हैं, तो OS पारदर्शी रूप से मेमोरी में केवल उतनी ही फाइल रखेगा, जितना कि उपलब्ध फिजिकल रैम पर आधारित हो सकता है। उपलब्ध भौतिक रैम में फ़ाइल आकार से अधिक नहीं है (हालांकि अतिरिक्त भौतिक रैम होना स्पष्ट रूप से एक लाभप्रद स्थिति होगी)। सबसे खराब स्थिति में यह डिस्क या फ़ाइल पर फ़ाइल के माध्यम से लूपिंग की गति को कम कर सकता है। इस दृष्टिकोण के साथ प्रमुख लाभ उपलब्ध भौतिक रैम का पारदर्शी उपयोग है क्योंकि यह कुछ ऐसा है जो समय के साथ उतार-चढ़ाव कर सकता है
iruvar

1

बाश में, 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
मुझे लगता है कि ओपी के लिए पूरे बिंदु फ़ाइल को मेमोरी में नहीं रखना है। अन्यथा, उनका वर्तमान गॉक दृष्टिकोण सरल और बहुत तेज दोनों है । मैं यह अनुमान लगा रहा हूं कि पाठ फ़ाइलों के साथ काम करने की आवश्यकता है जो आकार में कई गीगाबाइट हैं।
terdon

हाँ, यह बिल्कुल सही है - मेरे पास एक बड़ी डेटा फ़ाइल है जिसे मुझे इसके साथ करने की आवश्यकता है और स्मृति में नहीं रखना चाहते हैं
टॉम हेडेन

मामले में आप कि स्मृति से constrainted, मैं @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होल्ड बफर से एट लाइन और इसे वर्तमान लाइन में जोड़ें। फिर लाइनों के स्थानों को स्वैप करें। था 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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.