我有一个包含数千行的文件,每行包含一个数字,然后是一行文本。我想将文本相似的行的数字相加。我也希望输出独特的行。
例如:
25 cup of coffee
75 sign on the dotted
28 take a test
2 take a test
12 cup of coffee
输出为:
37 cup of coffee
75 sign on the dotted
30 take a test
有人建议如何在unix shell中实现这一目标吗?
我查看了Shell command to sum integers, one per line?,但这是关于在文件的所有行中汇总一列数字,而不是仅在相似的文本行中汇总。
答案 0 :(得分:3)
不需要多个进程和管道。单独使用awk
的能力远远超过了处理整个作业的能力(在大型文件上,速度要快几个数量级)。使用awk
,只需将每个字段2-NF
附加为字符串,然后将其用作索引,即可对数组中字段1中的数字求和。然后在END
部分中,只需输出数组的内容,例如假设您的数据存储在file
中,则可以执行以下操作:
awk '{
for (i=2; i<=NF; i++)
str = str " " $i
a[str] += $1
str=""
}
END {
for (i in a) print a[i], i
}' file
上面,第一个for
循环只是将2-NF
中str
的所有字段附加起来,a[str] += $1
使用{将字段1中的值求和到数组a
中{1}}作为索引。这样可以确保对相似行的值求和。在str
部分中,您只需循环遍历数组的每个元素,依次输出元素值(总和)和索引(字段END
的原始str
)。
使用/输出示例
仅需选择上面的内容,然后将其鼠标中键粘贴到2-NF
所在目录的命令行中即可(将file
的名称更改为数据文件名)
file
如果要以不同的顺序对行进行排序,只需在文件名后添加$ awk '{
> for (i=2; i<=NF; i++)
> str = str " " $i
> a[str] += $1
> str=""
> }
> END {
> for (i in a) print a[i], i
> }' file
30 take a test
37 cup of coffee
75 sign on the dotted
,即可将输出通过管道传输到| sort [options]
。例如,对于按照您显示的顺序进行的输出,您将使用sort
,而输出将是:
| sort -k 2
保留字符串的原始顺序
根据有关如何保持输入文件中看到的文本行的原始顺序的评论,您可以保留第二个数组,其中使用顺序索引将字符串按其出现的顺序存储在其中,以将它们保留在其中订购。例如,下面使用37 cup of coffee
75 sign on the dotted
30 take a test
数组(顺序数组)存储唯一的字符串(字段o
),而变量2-NF
用作计数器。使用数组上的循环检查字符串是否已包含,如果是,则使用n
避免存储字符串并跳转到下一条输入记录。然后在next
中,循环使用END
形式从两个数组中输出信息,顺序是在原始文件中看到字符串的顺序,例如
for (i = 0; i < n; i++)
输出
awk -v n=0 '{
for (i=2; i<=NF; i++)
str = str " " $i
a[str] += $1
for (i = 0; i < n; i++)
if (o[i] == str) {
str=""
next;
}
o[n++] = str;
str=""
}
END {
for (i = 0; i < n; i++) print a[o[i]], o[i]
}' file
答案 1 :(得分:0)
您可以执行以下操作(假设文件名为file.txt):
for key in $(sort -k2 -u file.txt | cut -d ' ' -f2)
do
cat file.txt|grep $key | awk '{s+=$1} END {print $2 "\t" s}'
done
说明: 1.获取所有唯一键(喝咖啡,在虚线上签名,进行测试):
sort -k2 -u file.txt | cut -d ' ' -f2
2。 grep使用文件中唯一键的所有行:
cat file.txt | grep $key
3。使用awk对行求和,其中$ 1 =数字列,$ 2 =键
awk '{s+=$1} END {print $2 "\t" s}'
注意:如果一个键可以是另一个键的子字符串,例如“ coffee”和“ cup of coffee”,则需要将第2步更改为带有正则表达式的grep
答案 2 :(得分:0)
您的意思是这样的吗?
#!/bin/bash
# define a dictionary
declare -A dict
# loop over all lines
while read -r line; do
# read first word as value and the rest as text
IFS=' ' read value text <<< "$line"
# use 'text' as key, get value for 'text', default 0
[ ${dict[$text]+exists} ] && dictvalue="${dict[$text]}" || dictvalue=0
# sum value
value=$(( $dictvalue + value ))
# save new value in dictionary
dict[$text]="$value"
done < data.txt
# loop over dictionary, print sum and text
for key in "${!dict[@]}"; do
printf "%s %s\n" "${dict[$key]}" "$key"
done
输出
37 cup of coffee
75 sign on the dotted
30 take a test
答案 3 :(得分:0)
以下是执行任务的简单awk
脚本:
script.awk
{ # for each input line
inpText = substr($0, length($1)+2); # read the input text after 1st field
inpArr[inpText] = inpArr[inpText] + 0 + $1; # accumulate the 1st field in array
}
END { # post processing
for (i in inpArr) { # for each element in inpArr
print inpArr[i], i; # print the sum and the key
}
}
input.txt
25 cup of coffee
75 sign on the dotted
28 take a test
2 take a test
12 cup of coffee
运行:
awk -f script.awk input.txt
输出:
75 sign on the dotted
37 cup of coffee
30 take a test
答案 4 :(得分:0)
另一个基于与here @David相同的逻辑的版本。
更改:它省略了循环以加快过程。
awk '
{
text=substr($0, index($0,$2))
if(!(text in text_sums)){ texts[i++]=text }
text_sums[text]+=$1
}
END {
for (i in texts) print text_sums[texts[i]],texts[i]
}' input.txt
说明:
substr
返回以字段2开头的字符串,即文本部分
数组texts
将文本存储在整数索引中,如果它不存在于text_sums
数组中。
text_sums
继续为相应的文本添加字段1。
在一个单独的数组后面存储文本作为值(由连续整数作为索引支持)的原因是为了确保值(文本)的顺序同时以相同的连续顺序进行访问。
请参见Array Intro
脚注:
awk实现之间的顺序会有所不同,这些实现通常使用哈希表来存储数组元素和值。
答案 5 :(得分:0)
使用datamash
相对简洁。首先使用sed
将第一个空格更改为制表符,(此作业datamash
必须具有一个且只有一个制表符分隔符),然后使用-s -g2
对第二个字段进行分组排序, ( ie “杯子” 等),然后使用sum 1
按组累加第一列编号,此操作就完成了。不,不完全是-由于某些原因,number列迁移到了 2nd 字段,因此reverse
将其迁移回了 1st 字段:
sed 's/ /\t/' file | datamash -s -g2 sum 1 | datamash reverse
输出:
37 cup of coffee
75 sign on the dotted
30 take a test