파일의 모든 행을 쌍으로 확장하는 "고양이"명령 줄 도구


13

다음과 같은 파일 (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
다른 파일의 두 번째 줄에 대한 출력을 생성하려면 항상 한 파일을 끝까지 읽어야합니다. 스트리밍 할 수있는 다른 파일입니다.
reinierpost

답변:


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에 머물러 있기 때문에이 문제를 해결했습니다. 감사!
Tom Hayden

1
당신이 좋아서 기뻐요, 톰 요즘에는 주로 Python으로 프로그래밍하는 경향이 있지만 줄과 파일에 대한 내장 루프로 인해 줄 단위 텍스트 처리를 위해 awk를 여전히 좋아합니다. 그리고 종종 파이썬보다 빠릅니다.
PM 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- 물건은 휴대 성입니다 yashzsh동안, 분할에 모두 추가 한 요소 mkshposh모두 잃게 한. ksh, dash, busybox, 및 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항상 느리지 만-어쨌든 그들이하는 유일한 작업은 인수 문자열 (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이상한 파일 이름에 걸리지 않도록 세미 임의 이름 으로 첫 번째 인수에 대한 소프트 링크를 만듭니다 . cat의 인수는를 통해 파이프를 통해 공급 되기 때문에 중요 xargs합니다. cat의 출력은 파일에 행이있는 횟수만큼 첫 번째 인수의 모든 행 <&3sed p헹구는 동안 저장되며 스크립트는 파이프를 통해 해당 행에 공급됩니다. 다시 paste입력을 병합하지만 이번에 -는 표준 입력과 링크 이름에 대해 두 개의 인수 만 다시 사용합니다 /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

pairs 함수는 어떻게 선언하지 않더라도 파일에 있다고 가정합니까?

@Jidder-어떻게 선언합니까? 터미널에 복사해서 붙여 넣기 만하면됩니다.
mikeserv

1
기능을 선언하십시오. 그래서 당신은 할 수 있습니다! 나는 줄 바꿈을 피할 것이라고 생각했습니다.하지만 코드를 붙여 넣는 것을 조심합니다.

@Jidder-나는 보통 ctrl+v; ctrl+j개행을하기 위해 사용하는 라이브 쉘로 이것을 쓴다 .
mikeserv

@Jidder-대단히 감사합니다. 그리고 조심하는 것이 현명합니다-당신에게 좋습니다. 그것들은 파일에서도 잘 작동 할 것입니다-당신은 . ./file; fn_name그 경우에 복사 할 수 있습니다 .
mikeserv

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 줄에서 약 초가 걸렸습니다 .


와우 감사합니다! 펄 솔루션이 왜 bash 솔루션보다 훨씬 빠른지 궁금합니다.
Tom Hayden

@TomHayden은 기본적으로 awk와 같은 perl 이 bash보다 훨씬 빠르기 때문입니다.
terdon

1
while 루프에 대해 공감해야했습니다. 거기에 4 가지 나쁜 습관이 있습니다. 네가 더 잘 알 잖아.
Stéphane Chazelas

1
@ StéphaneChazelas 잘, 당신의 답변에 따라 여기에 , 나는 어떤 경우에 생각할 수없는 echo문제가 될 수 있습니다. 내가 쓴 내용 (지금 추가 한 내용 printf)이 모두 제대로 작동해야합니까? 에 관해서는 while루프, 왜? 무슨 일이야 while read f; do ..; done < file? 분명히 당신은 for루프를 제안하지 않습니다 ! 다른 대안은 무엇입니까?
terdon

2
@cuonglm, 그 이유는 한 가지 이유만으로 피해야하는 이유를 암시합니다. 의 아웃 개념 , 신뢰성 , 가독성 , 성능보안 측면, 즉만을 다루고 신뢰성을 .
Stéphane Chazelas

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"
어디에 있고이 코드가 저장된 파일은 어디에 사용하여 컴파일 할 수 있습니다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

3
join -j 2 file.txt file.txt | cut -c 2-
  • 존재하지 않는 필드로 연결하고 첫 번째 공백을 제거하십시오.

필드 2는 file.txt의 모든 요소에 대해 비어 있고 동일하므로 join각 요소를 다른 모든 요소와 연결합니다. 실제로 카티 전 곱을 계산합니다.


2

파이썬의 한 가지 옵션은 은 파일 메모리 매핑 하고 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

정의상 전체 파일을 메모리에 보관하지 않습니까? 나는 파이썬을 모른다.하지만 당신의 언어는 분명히 그것을 제안합니다.
terdon

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의 요점은 파일을 메모리에 보관하지 않는 것입니다. 그렇지 않으면, 현재의 괴짜 접근 방식이 더 간단하고 훨씬 빠릅니다. 나는 이것이 몇 기가 바이트 크기의 텍스트 파일로 작동해야한다고 생각합니다.
terdon

네, 정확히 맞습니다. 몇 가지 거대한 데이터 파일을 가지고 있으며 메모리에 저장하고 싶지 않습니다.
Tom Hayden

메모리에 의해 제약을받는 경우 @terdon의 솔루션 중 하나를 사용하는 것이 좋습니다.
프랭키

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.