bash에서 "group by"를 시뮬레이션하는 가장 좋은 방법은 무엇입니까?


231

각 줄에 하나씩 IP 주소를 포함하는 파일이 있다고 가정합니다.

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

각 IP 주소에 대해 파일에 나타나는 횟수를 세는 셸 스크립트가 필요합니다. 이전 입력의 경우 다음 출력이 필요합니다.

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

이를 수행하는 한 가지 방법은 다음과 같습니다.

cat ip_addresses |uniq |while read ip
do
    echo -n $ip" "
    grep -c $ip ip_addresses
done

그러나 실제로 효율적이지는 않습니다.

bash를 사용 하여이 문제를 어떻게보다 효율적으로 해결할 수 있습니까?

(추가해야 할 사항 : perl 또는 awk에서 해결할 수 있다는 것을 알고 있습니다. 해당 언어가 아닌 bash의 더 나은 솔루션에 관심이 있습니다.)

추가 정보:

소스 파일이 5GB이고 알고리즘을 실행하는 시스템에 4GB가 있다고 가정하십시오. 따라서 정렬은 효율적인 솔루션이 아니며 파일을 두 번 이상 읽지 않습니다.

해시 테이블과 같은 솔루션이 마음에 들었습니다. 해당 솔루션을 개선 할 수있는 사람이 있습니까?

추가 정보 # 2 :

어떤 사람들은 왜 펄에서 더 쉬울 때 bash에서 왜 귀찮게 할 것인지 물었습니다. 그 이유는 기계 에서이 펄을 사용해야했기 때문에 사용할 수 없었기 때문입니다. 내가 익숙한 도구가없는 맞춤형 리눅스 시스템이었다. 그리고 나는 그것이 흥미로운 문제라고 생각합니다.

따라서 질문을 비난하지 말고 마음에 들지 않으면 무시하십시오. :-)


bash는 작업에 잘못된 도구라고 생각합니다. 펄은 아마도 더 나은 솔루션 일 것입니다.
— Francois Wolmarans

답변:


412
sort ip_addresses | uniq -c

카운트가 먼저 인쇄되지만 그 이외의 숫자는 원하는 것이어야합니다.


71
그런 다음 "sort -nr"로 파이프하여 가장 높은 수에서 가장 낮은 수까지 내림차순으로 정렬 할 수 있습니다. 즉,sort ip_addresses | uniq -c | sort -nr
— 브래드 공원

15
그리고 sort ip_addresses | uniq -c | sort -nr | awk '{ print $2, $1 }'첫 번째 열에서 IP 주소를 얻고 두 번째 열에서 계산하십시오.
— Raghu Dodda

정렬 부분을 한 번 더 비틀기 :sort -nr -k1,1
— 의 Andrzej 마르티나

50

빠르고 더러운 방법은 다음과 같습니다.

cat ip_addresses | sort -n | uniq -c

bash의 값을 사용해야하는 경우 전체 명령을 bash 변수에 지정한 다음 결과를 반복 할 수 있습니다.

추신

sort 명령이 생략되면 uniq은 연속적인 동일한 행만 보므로 올바른 결과를 얻지 못합니다.


효율성면에서 매우 비슷하지만, 여전히 이차적 인 행동이 있습니다
— Vinko Vrsalovic

이차적 의미 O (n ^ 2) ?? 그것은 분명히 정렬 알고리즘에 달려 있으며, 그런 종류의보고 정렬을 사용하지 않을 것입니다.
— paxdiablo

가장 좋은 경우는 O (n log (n))이며 두 번의 패스보다 나쁩니다 (사소한 해시 기반 구현으로 얻는 것입니다). 나는 이차 대신 '수퍼 선형'이라고 말 했어야했다.
— Vinko Vrsalovic '12

그리고 ... 영업 현명 효율성을 개선하기 위해 무엇을 요구하는 것과 같은 바인딩에 여전히
— Vinko Vrsalovic

11
uuoc, 쓸모없는 고양이 사용

22

기존 필드 그룹을 기반으로 여러 필드를 합산하려면 아래 예를 사용하십시오 (요구 사항에 따라 $ 1, $ 2, $ 3, $ 4 교체)

cat file

US|A|1000|2000
US|B|1000|2000
US|C|1000|2000
UK|1|1000|2000
UK|1|1000|2000
UK|1|1000|2000

awk 'BEGIN { FS=OFS=SUBSEP="|"}{arr[$1,$2]+=$3+$4 }END {for (i in arr) print i,arr[i]}' file

US|A|3000
US|B|3000
US|C|3000
UK|1|9000

2
카운트가 필요할뿐만 아니라 수행 할 작업을 보여주기 때문에 +1
— user829755

1
일 때문에 sort와 uniq계산 작업을 수행하는 가장 쉬운 방법입니다,하지만 당신은 / 합계 필드 값을 계산하기 위해 필요로 할 때 도움을하지 않습니다. awk의 배열 구문은 매우 강력하고 그룹화의 핵심입니다. 감사!
— odony

1
한 가지 더, awk의 print함수는 64 비트 정수를 32 비트로 다운 스케일하는 것 같습니다. 따라서 2 ^ 31을 초과하는 int 값 의 경우에는 그 대신 형식 printf과 함께 사용 하는 것이 %.0f좋습니다print
— odony

1
숫자를 추가하는 대신 문자열 연결을 사용하여 "group by"를 찾는 사람들은 arr [$ 1] = (arr [$ 1] $ 2)`로 대체 arr[$1,$2]+=$3+$4됩니다 arr[$1,$2]=(arr[$1,$2] $3 "," $4). I needed this to provide a grouped-by-package list of files (two columns only) and used: .
— Stéphane Gourichon

20

표준 솔루션은 다른 응답자가 언급 한 솔루션입니다.

sort | uniq -c

Perl이나 awk로 쓸 수있는 것보다 짧고 간결합니다.

데이터 크기가 시스템의 기본 메모리 크기보다 크기 때문에 정렬을 사용하지 않겠다고 씁니다. 유닉스 정렬 명령의 구현 품질을 과소 평가하지 마십시오. 정렬은 128k (131,072 바이트)의 메모리 (PDP-11)가있는 머신에서 대량의 데이터 (원래 AT & T의 청구 데이터를 생각하십시오)를 처리하는 데 사용되었습니다. 정렬이 사전 설정 제한보다 많은 데이터를 발견하면 (종종 기계의 주 메모리 크기에 가깝게 조정 됨) 주 메모리에서 읽은 데이터를 정렬하여 임시 파일에 씁니다. 그런 다음 다음 데이터 청크로 작업을 반복합니다. 마지막으로 중간 파일에 대해 병합 정렬을 수행합니다. 이를 통해 정렬은 머신의 주 메모리보다 여러 배 큰 데이터에서 작동 할 수 있습니다.


글쎄, 여전히 해시 카운트보다 나쁘지 않습니까? 데이터가 메모리에 맞는 경우 정렬 알고리즘이 어떤 정렬 알고리즘을 사용하는지 알고 있습니까? 숫자 데이터 대소 문자가 다양합니까 (-n 옵션)?
— Vinko Vrsalovic

sort (1) 구현 방법에 따라 다릅니다. GNU 배포본 (Linux 배포판에서 사용)과 BSD 정렬은 가장 적절한 알고리즘을 사용하기 위해 많은 시간을 소비합니다.
— Diomidis Spinellis

9
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'

이 명령은 원하는 출력을 제공합니다


4

선형 동작을 얻기 위해 bash에서 해시를 시뮬레이션하기 위해 많은 양의 코드를 사용해야하거나 2 차 초 선형 버전을 고수해야합니다 .

그 중에서도 saua 솔루션은 가장 훌륭하고 간단합니다.

sort -n ip_addresses.txt | uniq -c

http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html을 찾았습니다 . 근데 못 생겼어 ...


나는 동의한다. 이것은 지금까지 가장 좋은 솔루션이며 perl과 awk에서도 비슷한 솔루션이 가능합니다. 아무도 bash에서 더 깨끗한 구현을 제공 할 수 있습니까?
— Zizzencs

내가 아는 한에서는 아니다. 내 $ ip (@ips) {$ hash {$ ip} = $ hash {$ ip} + 1; } 그런 다음 키와 값을 인쇄하십시오.
— Vinko Vrsalovic

4

솔루션 (mysql과 같은 그룹화)

grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n

결과

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

3

파일 시스템 자체를 해시 테이블로 사용할 수 있습니다. 다음과 같은 의사 코드 :

for every entry in the ip address file; do
  let addr denote the ip address;

  if file "addr" does not exist; then
    create file "addr";
    write a number "0" in the file;
  else 
    read the number from "addr";
    increase the number by 1 and write it back;
  fi
done

결국, 당신이해야 할 모든 파일을 순회하고 파일 이름과 번호를 인쇄하는 것입니다. 또는 카운트를 유지하는 대신 매번 파일에 공백이나 줄 바꿈을 추가하고 결국 파일 크기를 바이트 단위로 볼 수 있습니다.


3

이 경우 awk 연관 배열도 편리하다고 생각합니다.

$ awk '{count[$1]++}END{for(j in count) print j,count[j]}' ips.txt

우편으로 그룹 여기


pp, 훌륭한 awk 솔루션이지만 awk는 내가하고있는 머신에서 사용할 수 없었습니다.
— Zizzencs

1

다른 솔루션의 대부분은 중복을 계산합니다. 키 값 쌍을 실제로 그룹화해야하는 경우 다음을 시도하십시오.

내 예제 데이터는 다음과 같습니다.

find . | xargs md5sum
fe4ab8e15432161f452e345ff30c68b0 a.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

md5 체크섬별로 그룹화 된 키 값 쌍을 인쇄합니다.

cat table.txt | awk '{print $1}' | sort | uniq  | xargs -i grep {} table.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 a.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

1

순수한 (포크 없음!)

방법을 사용하여 기능 . 이 방법은 포크가 없어 매우 빠릅니다! ...

... 많은 IP 주소 가 작게 유지 되는 동안 !

countIp () { 
    local -a _ips=(); local _a
    while IFS=. read -a _a ;do
        ((_ips[_a<<24|${_a[1]}<<16|${_a[2]}<<8|${_a[3]}]++))
    done
    for _a in ${!_ips[@]} ;do
        printf "%.16s %4d\n" \
          $(($_a>>24)).$(($_a>>16&255)).$(($_a>>8&255)).$(($_a&255)) ${_ips[_a]}
    done
}

참고 : IP 주소는 32 비트 부호없는 정수 값으로 변환되어 array의 색인으로 사용됩니다 . 이것은 연관 배열이 아닌 간단한 bash 배열을 사용합니다 (더 비싸다)!

time countIp < ip_addresses 
10.0.10.1    3
10.0.10.2    1
10.0.10.3    1
real    0m0.001s
user    0m0.004s
sys     0m0.000s

time sort ip_addresses | uniq -c
      3 10.0.10.1
      1 10.0.10.2
      1 10.0.10.3
real    0m0.010s
user    0m0.000s
sys     0m0.000s

내 호스트에서는 포크를 사용하는 것보다 최대 1,000 개의 주소보다 훨씬 빠르지 만 10,000 개의 주소 를 정렬 하려고 할 때 약 1 초가 걸립니다 .


0

나는 다음과 같이했었다.

perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}\n";}' ip_addresses

그러나 uniq가 당신을 위해 일할 수 있습니다.


원래 게시물에서 말했듯이 펄은 옵션이 아닙니다. 나는, 펄에서 쉽게 그 :-) 아무 문제가 없다 알
— Zizzencs

0

Bash에서 무언가를 찾고 있음을 이해하지만 다른 사람이 Python에서 무언가를 찾고있는 경우 다음을 고려할 수 있습니다.

mySet = set()
for line in open("ip_address_file.txt"):
     line = line.rstrip()
     mySet.add(line)

세트의 값은 기본적으로 고유하고 Python 은이 물건에 매우 적합하므로 여기에서 무언가를 얻을 수 있습니다. 나는 코드를 테스트하지 않았기 때문에 버그가있을 수 있지만 이것이 당신을 데려 갈 수 있습니다. 그리고 발생 횟수를 계산하려면 세트 대신 dict를 사용하는 것이 구현하기 쉽습니다.

편집 : 나는 형편없는 독자이므로 잘못 대답했습니다. 다음은 발생 횟수를 계산할 수있는 스 니펫이 포함 된 스 니펫입니다.

mydict = {}
for line in open("ip_address_file.txt"):
    line = line.rstrip()
    if line in mydict:
        mydict[line] += 1
    else:
        mydict[line] = 1

사전 mydict는 이제 고유 한 IP 목록을 키로 보유하고 값으로 발생한 횟수를 보유합니다.


이것은 아무것도 계산하지 않습니다. 점수를 유지하는 전략이 필요합니다.

도 질문을 잘못 읽었습니다. 죄송합니다. 처음에는 각 IP 주소가 발생한 횟수를 저장하기 위해 dict를 사용하는 것에 대해 약간의 정보가 있었지만 문제를 잘 읽지 못했기 때문에 제거했습니다. * 제대로 깨우려고 시도
— wzzrd

2
이 itertools.groupby()결합 된이 sorted()영업 이익은 요청 정확히 않습니다.
— jfs

그것은 파이썬에서 훌륭한 솔루션입니다. 이것은 사용할 수 없었습니다 :-)
— Zizzencs

-8

순서가 중요하지 않으면 정렬을 생략 할 수 있습니다.

uniq -c <source_file>

또는

echo "$list" | uniq -c

소스리스트가 변수 인 경우


1
더 명확하게 설명하기 위해, uniq 매뉴얼 페이지에서 : 참고 : 'uniq'는 인접하지 않으면 반복되는 행을 감지하지 않습니다. 입력을 먼저 정렬하거나 'uniq'없이 'sort -u'를 사용할 수 있습니다.
— converter42
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.