![无需 AWK 的多遍方法](https://linux22.com/image/105801/%E6%97%A0%E9%9C%80%20AWK%20%E7%9A%84%E5%A4%9A%E9%81%8D%E6%96%B9%E6%B3%95.png)
今天我在其他网站上发现了一个关于shell脚本的有趣问题。问题是根据另一个矩阵的值构建一个矩阵
https://askubuntu.com/questions/884372/count-number-in-two-columns-and-generate-matrix
原始矩阵是这样的:
joe it 9
wolf it 10
wolf pr 9
mark pm 6
jack pr 20
anton pm 5
joe pm 20
mark sa 35
输出矩阵必须是这样的
0 anton jack joe mark wolf
it 0 0 9 0 10
pm 5 0 20 6 0
pr 0 20 0 0 9
sa 0 0 0 35 0
如您所见,如果第一个矩阵是第二个矩阵的摘要
我尝试按如下方式解决该问题:
我将第一个矩阵保存在名为 test01.txt 的文件中。我使用空格作为分隔符
从第一列中提取不同的元素,将其转换为一行并将其保存到名为 new 的文件中
cut -d ' ' -f1-1 test01 |sort |uniq |awk '{ printf( "%s ", $1 ); } END { printf( "\n" ); }' > new
提取第二列的不同元素并保存在名为 new2 的文件中
cut -d ' ' -f2-2 test01|sort|uniq > new2
添加 0 作为 new 的第一个元素并将其保存为 new1
while read line; do echo "0 $line"; done < new > new1
由于文件中第一列有 5 个不同的元素,因此 new2 文件的每一行添加了 5 个零,以便进行交叉比较
while read line; do echo "$line 0 0 0 0 0"; done < new2 > new3
并且将new3的内容添加到文件new1的末尾
while read line; do echo $line |awk '{print $1,$2}'; done < new1
现在new1的内容是:
0 anton jack joe mark wolf
it 0 0 0 0 0
pm 0 0 0 0 0
pr 0 0 0 0 0
sa 0 0 0 0 0
就在那时我被困住了。
我不知道如何浏览 new1 中的矩阵并将每个元素与 test01 的行进行比较,以便在必要时替换 0。最终结果必须是:
0 anton jack joe mark wolf
it 0 0 9 0 10
pm 5 0 20 6 0
pr 0 20 0 0 9
sa 0 0 0 35 0
也许有一种比我迄今为止使用的方法更有效的方法来获得结果,而无需那么多中间文件
对于帖子的长度,我深表歉意
答案1
无需 AWK 的多遍方法
首先读取文件以提取行标签和列标签。然后它打印 0,后跟第一行的每个列标签。
该循环负责处理非标签行。首先,它打印行标签,然后搜索文件中与该(行、列)对匹配的所有条目。dc
如果返回多行,则此结果的第三列中的条目将与 相加。
这种方法的一个明显问题是文件被读取每个条目一次在结果矩阵中。因此,计算最初两次获取行标签和列标签的次数,您的示例将被读取 22 次!
调用方式为./contingency-table input-file
:
#!/bin/sh
# file: contingency-table
columns=$(cut -d' ' -f 1 "$1" | sort | uniq)
rows=$(cut -d' ' -f 2 "$1" | sort | uniq)
printf '0'
printf ' %s' ${columns}
printf '\n'
for row in ${rows}; do
printf "${row} "
for col in ${columns}; do
(grep "${col} ${row}" "${1}" \
| cut -d' ' -f 3 \
| tr '\n' '+'
printf '\n') \
| sed -e 's/^/0 /' \
-e 's/$/pq/' \
| dc \
| tr '\n' ' '
done
printf '\n'
done
使用 AWK 的更有效方法
#!/usr/bin/awk -f
function max(val1, val2) {
return ((val1 > val2) ? val1 : val2)
}
BEGIN {
name_length = 0
department_length = 0
# This line influences sorting in GNU awk
PROCINFO["sorted_in"] = "@ind_str_asc"
}
(!($1 in names)) {
names[$1]
name_length = max(length($1), name_length)
}
(!($2 in departments)) {
departments[$2]
department_length = max(length($2), department_length)
}
{
hours[$2, $1] += $3
}
END {
printf "%" department_length "s", 0
for (name in names) {
printf " %" name_length "s", name
}
printf "\n"
for (department in departments) {
printf "%" department_length "s", department
for (name in names) {
printf " %" name_length "d", hours[department, name]
}
printf "\n"
}
}
begin 块设置一些变量并配置 GNU awk 对数组遍历进行排序。接下来的两个块在扫描输入时根据需要添加名称和部门。第三块计算每个运行总计。
如果您不想要“人类可读”的格式,请注释掉这些…_length = max(…
行。
该END
块是通过遍历之前创建的数组来进行所有输出和格式化的地方。这允许对输入文件进行一次传递,而不是对输出表中的每个条目传递一次。