"cat" 파일의 모든 줄을 쌍으로 확장하기 위한 명령줄 도구

"cat" 파일의 모든 줄을 쌍으로 확장하기 위한 명령줄 도구

다음과 같은 파일이 있다고 가정해 보겠습니다(sample.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

전체 파일을 배열에 저장할 필요가 없도록 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 만에 실행됩니다.

답변2

이것이 메모리에서 수행하는 것보다 더 나은지는 잘 모르겠지만 내부 파일의 모든 줄을 sed읽는 r내부 파일 과 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, dashbusybox모두 bash인쇄된 0만큼 많은 필드로 분할됩니다 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항상 가장 느립니다. 그러나 쉘이 수행하는 유일한 작업은 인수 문자열을 생성하는 것뿐입니다.(1000부 filename -)효과는 미미합니다. zsh위에서 언급했듯이 여기서 성능 차이는 bash100분의 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. while rints의 출력은 파일의 줄 수만큼 첫 번째 인수의 각 줄 cat에 저장되며 해당 스크립트도 여기에 파이프됩니다. 입력을 다시 병합 하지만 이번에는 표준 입력과 링크 이름에 두 개의 인수만 필요합니다 .<&3sed ppaste-/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

답변3

음, 쉘에서는 언제든지 이 작업을 수행할 수 있습니다.

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개의 행만 저장됩니다. 내 시스템에서는 1000개의 행을 처리하는 데 0.6몇 초밖에 걸리지 않았습니다.

답변4

이것을 컴파일 할 수 있습니다상당히 빠른 결과를 얻기 위한 코드입니다.
1000줄 파일의 경우 약 0.19 - 0.27초 내에 완료됩니다.

현재는 10000라인을 메모리로 읽어들이고(화면 인쇄 속도를 높이기 위해) 1000라인당 문자가 있는 경우 메모리보다 적은 메모리를 사용하므로 10mb문제가 되지 않을 것 같습니다. 하지만 해당 섹션을 완전히 제거하고 문제가 발생하는 경우 화면에 직접 인쇄할 수 있습니다.

다음을 사용하여 컴파일할 수 있습니다. 저장할 파일의 이름은 g++ -o "NAME" "NAME.cpp"
어디에 있고 이 코드가 저장되는 파일은 어디에 있습니까?NAMENAME.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

관련 정보