GROUP BY under bash
关于这个SO线程,根据不同的需求有一些不同的答案。
1. 将IP计数为SO请求(按IP地址分组)。
由于IP很容易转换为单个整数,对于小串地址,如果您需要多次重复这种操作,使用纯bash函数可能会更有效!
纯粹的bash(没有fork!)
有一种方法,使用bash函数。这条路非常快,因为没有叉子!
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位无符号整型值,用作数组的索引。这使用简单的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
在我的主机上,这样做比使用fork快得多,最多可以使用大约1000个地址,但当我尝试排序并计数10,000个地址时,大约需要整整1秒钟。
2. GROUP BY duplicate(文件内容)
通过使用校验和,你可以在某个地方标识重复的文件:
find . -type f -exec sha1sum {} + |
sort |
sed '
:a;
$s/^[^ ]\+ \+//;
N;
s/^\([^ ]\+\) \+\([^ ].*\)\n\1 \+\([^ ].*\)$/\1 \2\o11\3/;
ta;
s/^[^ ]\+ \+//;
P;
D;
ba
'
这将打印所有副本,按行,以制表($'\t'或八进制011 ou可以更改/\1 \2\o11\3/;By /\1 \2|\3/;使用|作为分隔符)。
./b.txt ./e.txt
./a.txt ./c.txt ./d.txt
可以写成(以|为分隔符):
find . -type f -exec sha1sum {} + | sort | sed ':a;$s/^[^ ]\+ \+//;N;
s/^\([^ ]\+\) \+\([^ ].*\)\n\1 \+\([^ ].*\)$/\1 \2|\3/;ta;s/^[^ ]\+ \+//;P;D;ba'
纯粹的bash方式
通过使用nameref,你可以构建一个包含所有副本的bash数组:
declare -iA sums='()'
while IFS=' ' read -r sum file ;do
declare -n list=_LST_$sum
list+=("$file")
sums[$sum]+=1
done < <(
find . -type f -exec sha1sum {} +
)
从那里,你有一堆数组保存所有重复的文件名作为分离的元素:
for i in ${!sums[@]};do
declare -n list=_LST_$i
printf "%d %d %s\n" ${sums[$i]} ${#list[@]} "${list[*]}"
done
这可能会输出如下内容:
2 2 ./e.txt ./b.txt
3 3 ./c.txt ./a.txt ./d.txt
md5sum (${sum [$shasum]})匹配数组${_LST_ShAsUm[@]}中元素的计数。
for i in ${!sums[@]};do
declare -n list=_LST_$i
echo ${list[@]@A}
done
declare -a _LST_22596363b3de40b06f981fb85d82312e8c0ed511=([0]="./e.txt" [1]="./b.txt")
declare -a _LST_f572d396fae9206628714fb2ce00f72e94f2258f=([0]="./c.txt" [1]="./a.txt" [2]="./d.txt")
注意,这个方法可以处理文件名中的空格和特殊字符!
3.GROUP BY表中的列
由于匿名者提供了使用awk的有效示例,这里是一个纯bash解决方案。
所以你想要总结第3列到最后一列,并按第1列和第2列分组,table.txt看起来像这样
我们| 1000 | 2000 |
我们| 1000 | 2000 | B
我们C | 1000 | 2000 |
英国| 1 | 1000 | 2000
英国| 1 | 1000 | 2000 | 3000年
英国| 1 | 1000 | 2000 | 3000 | 4000
对于不太大的表格,你可以:
myfunc() {
local -iA restabl='()';
local IFS=+
while IFS=\| read -ra ar; do
restabl["${ar[0]}|${ar[1]}"]+="${ar[*]:2}"
done
for i in ${!restabl[@]} ;do
printf '%s|%s\n' "$i" "${restabl[$i]}"
done
}
可以输出如下内容:
myfunc <table.txt
UK|1|19000
US|A|3000
US|C|3000
US|B|3000
对表进行排序:
myfunc() {
local -iA restabl='()';
local IFS=+ sorted=()
while IFS=\| read -ra ar; do
sorted[64#${ar[0]}${ar[1]}]="${ar[0]}|${ar[1]}"
restabl["${ar[0]}|${ar[1]}"]+="${ar[*]:2}"
done
for i in ${sorted[@]} ;do
printf '%s|%s\n' "$i" "${restabl[$i]}"
done
}
必须返回:
myfunc <table
UK|1|19000
US|A|3000
US|B|3000
US|C|3000