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
일 때문에 sortuniq계산 작업을 수행하는 가장 쉬운 방법입니다,하지만 당신은 / 합계 필드 값을 계산하기 위해 필요로 할 때 도움을하지 않습니다. 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.