bash에서 "group by"를 시뮬레이션하는 가장 좋은 방법은 무엇입니까?
각 줄에 하나씩 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에서 왜 귀찮게 할 것인지 물었습니다. 그 이유는 기계 에서이 펄을 사용해야했기 때문에 사용할 수 없었기 때문입니다. 내가 익숙한 도구가없는 맞춤형 리눅스 시스템이었다. 그리고 나는 그것이 흥미로운 문제라고 생각합니다.
따라서 질문을 비난하지 말고 마음에 들지 않으면 무시하십시오. :-)
sort ip_addresses | uniq -c
카운트가 먼저 인쇄되지만 그 이외의 숫자는 원하는 것이어야합니다.
빠르고 더러운 방법은 다음과 같습니다.
cat ip_addresses | sort -n | uniq -c
bash의 값을 사용해야하는 경우 전체 명령을 bash 변수에 할당 한 다음 결과를 반복 할 수 있습니다.
추신
sort 명령이 생략되면 uniq은 연속적인 동일한 행만 보므로 올바른 결과를 얻지 못합니다.
기존 필드 그룹을 기반으로 여러 필드를 합산하려면 아래 예를 사용하십시오 (요구 사항에 따라 $ 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
표준 솔루션은 다른 응답자가 언급 한 솔루션입니다.
sort | uniq -c
Perl이나 awk로 쓸 수있는 것보다 짧고 간결합니다.
You write that you don't want to use sort, because the data's size is larger than the machine's main memory size. Don't underestimate the implementation quality of the Unix sort command. Sort was used to handle very large volumes of data (think the original AT&T's billing data) on machines with 128k (that's 131,072 bytes) of memory (PDP-11). When sort encounters more data than a preset limit (often tuned close to the size of the machine's main memory) it sorts the data it has read in main memory and writes it into a temporary file. It then repeats the action with the next chunks of data. Finally, it performs a merge sort on those intermediate files. This allows sort to work on data many times larger than the machine's main memory.
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'
this command would give you desired output
It seems that you have to either use a big amount of code to simulate hashes in bash to get linear behavior or stick to the
quadratic
superlinear versions.
Among those versions, saua's solution is the best (and simplest):
sort -n ip_addresses.txt | uniq -c
I found http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html. But it's ugly as hell...
Solution ( group by like mysql)
grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n
Result
3249 googleplus
4211 linkedin
5212 xing
7928 facebook
You probably can use the file system itself as a hash table. Pseudo-code as follows:
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
In the end, all you need to do is to traverse all the files and print the file names and numbers in them. Alternatively, instead of keeping a count, you could append a space or a newline each time to the file, and in the end just look at the file size in bytes.
I feel awk associative array is also handy in this case
$ awk '{count[$1]++}END{for(j in count) print j,count[j]}' ips.txt
A group by post here
Pure bash (no fork!)
There is a way, using a bash function. This way is very quick as there is no fork!...
... While bunch of ip addresses stay small!
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
}
Note: IP addresses are converted to 32bits unsigned integer value, used as index for array. This use simple bash arrays, not associative array (wich is more expensive)!
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
On my host, doing so is a lot quicker than using forks, upto approx 1'000 addresses, but take approx 1 entire second when I'll try to sort'n count 10'000 addresses.
I'd have done it like this:
perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}\n";}' ip_addresses
but uniq might work for you.
I understand you are looking for something in Bash, but in case someone else might be looking for something in Python, you might want to consider this:
mySet = set()
for line in open("ip_address_file.txt"):
line = line.rstrip()
mySet.add(line)
As values in the set are unique by default and Python is pretty good at this stuff, you might win something here. I haven't tested the code, so it might be bugged, but this might get you there. And if you want to count occurrences, using a dict instead of a set is easy to implement.
Edit: I'm a lousy reader, so I answered wrong. Here's a snippet with a dict that would count occurences.
mydict = {}
for line in open("ip_address_file.txt"):
line = line.rstrip()
if line in mydict:
mydict[line] += 1
else:
mydict[line] = 1
The dictionary mydict now holds a list of unique IP's as keys and the amount of times they occurred as their values.
Most of the other solutions count duplicates. If you really need to group key value pairs, try this:
Here is my example data:
find . | xargs md5sum
fe4ab8e15432161f452e345ff30c68b0 a.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt
This will print the key value pairs grouped by the md5 checksum.
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
Sort may be omitted if order is not significant
uniq -c <source_file>
or
echo "$list" | uniq -c
if the source list is a variable
참고URL : https://stackoverflow.com/questions/380817/best-way-to-simulate-group-by-from-bash
'Programing' 카테고리의 다른 글
널을 뷰 루트로 전달하지 마십시오 (팽창 된 레이아웃의 루트 요소에서 레이아웃 매개 변수를 해결해야 함) (0) | 2020.04.28 |
---|---|
주어진 이름과 일치하는 리소스를 찾을 수 없습니다 : attr 'android : keyboardNavigationCluster'. (0) | 2020.04.28 |
일반 사전에 대소 문자를 구분하지 않는 액세스 (0) | 2020.04.28 |
Heroku에서 Rails 서버를 다시 시작하는 방법? (0) | 2020.04.28 |
Anaconda를 어떻게 업데이트합니까? (0) | 2020.04.28 |