【问题标题】:awk sum up multiple files show lines which does not appear on both sets of filesawk sum up multiple files 显示没有出现在两组文件中的行
【发布时间】:2013-02-14 14:06:08
【问题描述】:

我一直在用awk总结多个文件,这个是用来总结服务器日志解析值的总结,确实加快了最终的整体计数但是我遇到了一个小问题和我的典型例子在网络上点击并没有帮助。

示例如下:

cat file1
aa 1
bb 2
cc 3
ee 4

cat file2
aa 1
bb 2
cc 3
dd 4

cat file3
aa 1
bb 2
cc 3
ff 4

还有脚本:

cat test.sh 
#!/bin/bash

files="file1 file2 file3"

i=0;
oldname="";
for names in $(echo $files); do
        ((i++));
        if [ $i == 1 ]; then
                oldname=$names
                #echo "-- $i $names"
                shift;
        else
               oldname1=$names.$$
        awk  'NR==FNR { _[$1]=$2 } NR!=FNR { if(_[$1] != "") nn=0; nn=($2+_[$1]); print $1" "nn }' $names $oldname> $oldname1
        if [ $i -gt 2 ]; then
            rm $oldname;
        fi
                oldname=$oldname1

    fi
done
echo "------------------------------ $i"
cat $oldname

当我运行它时,相同的列会被添加,但那些只出现在其中一个文件中的列不会

./test.sh 
------------------------------ 3
aa 3
bb 6
cc 9
ee 4

ff dd 没有出现在列表中,据我在 NR==FR 中看到的

我遇到过这个:

http://dbaspot.com/shell/246751-awk-comparing-two-files-problem.html

you want all the lines in file1 that are not in file2,
awk 'NR == FNR { a[$0]; next } !($0 in a)' file2 file1

If you want only uniq lines in file1 that are not in file2,
awk 'NR == FNR { a[$0]; next } !($0 in a) { print; a[$0] }'
file2
file1

但这只会在尝试时使当前问题进一步复杂化,因为许多其他字段会重复

发布问题后 - 更新内容……和测试……

我想坚持使用 awk,因为它似乎是一种更短的实现结果的方法,但仍然存在问题..

awk '{a[$1]+=$2}END{for (k in a) print k,a[k]}'  file1 file2 file3
aa 3
bb 6
cc 9
ee 4
ff 4
gg 4
RESULT_SET_4 0
RESULT_SET_3 0
RESULT_SET_2 0
RESULT_SET_1 0
$ cat file1 
RESULT_SET_1
aa 1
RESULT_SET_2
bb 2
RESULT_SET_3
cc 3
RESULT_SET_4
ff 4
$ cat file2
RESULT_SET_1
aa 1
RESULT_SET_2
bb 2
RESULT_SET_3
cc 3
RESULT_SET_4
ee 4

文件内容没有原样保留,即结果不在标题下,我原来的方法确实保持原样

更新的预期输出 - 正确上下文中的标题

cat file1 
RESULT_SET_1
aa 1
RESULT_SET_2
bb 2
RESULT_SET_3
cc 3
RESULT_SET_4
ff 4



cat file2 
RESULT_SET_1
aa 1
RESULT_SET_2
bb 2
RESULT_SET_3
cc 3
RESULT_SET_4
ee 4


cat file3
RESULT_SET_1
aa 1
RESULT_SET_2
bb 2
RESULT_SET_3
cc 3
RESULT_SET_4
gg 4
test.sh awk line to produce above is :

awk -v i=$i 'NR==FNR { _[$1]=$2 } NR!=FNR { if (_[$1] != "") { if  ($2 ~ /[0-9]/)   { nn=($2+_[$1]); print $1" "nn; } else { print;} }else { print; } }' $names $oldname> $oldname1

./test.sh 
------------------------------ 3
RESULT_SET_1
aa 3
RESULT_SET_2
bb 6
RESULT_SET_3
cc 9
RESULT_SET_4
ff 4

有效但破坏了所需的格式

  awk '($2 != "")  {a[$1]+=$2};  ($2 == "") {  a[$1]=$2 } END {for (k in a) print k,a[k]} '  file1 file2 file3
    aa 3
    bb 6
    cc 9
    ee 4
    ff 4
    gg 4
    RESULT_SET_4 
    RESULT_SET_3 
    RESULT_SET_2 
    RESULT_SET_1 

【问题讨论】:

  • 什么是“标题”?在您首次发布的示例数据中,我没有看到类似的内容。这使它成为一个不同的问题。如果您在问题被回答后如此显着地更改问题,您可能不应该期望人们再次回答它。
  • 是的,对不起 :) 我的错,报告确实有标题然后字段值我应该重新发布一个新问题吗?
  • 现在的预期输出是什么?
  • 用预期的输出更新了问题,基本上每个服务器输出都会有每个段的标题,后跟字段及其值......有点像 file1 file2 的最后更新内容,并且产生了预期的结果通过原始脚本 - 明显的问题仍然存在 - 下面的过程确实有效,但顺序丢失并且标题以不正确的格式显示
  • 我认为这对其他人非常有用,只要他们首先以不同的方式考虑日志。所以我把它作为一个项目在这里github.com/vahidhedayati/summarise-server-logs

标签: linux bash shell sorting awk


【解决方案1】:
$ awk '{a[$1]+=$2}END{for (k in a) print k,a[k]}' file1 file2 file3 | sort
aa 3
bb 6
cc 9
dd 4
ee 4
ff 4

编辑:

这有点小技巧,但确实可以:

$ awk 'FNR==NR&&!/RESULT/{a[$1]=$2;next}($1 in a){a[$1]+=$2}END{for (k in a) print k,a[k]}' file1 file2 file3 | sort | awk '$1="RESULTS_SET_"NR"\n"$1'
RESULTS_SET_1
aa 3
RESULTS_SET_2
bb 6
RESULTS_SET_3
cc 9
RESULTS_SET_4
ff 4

【讨论】:

  • +1 这几乎就是我的做法。 (我可能选择了不同的单字母变量和数组名称。;])
  • 在不应该添加的行的情况下仍然存在问题,我已经更新了原帖
  • 感谢 Sudo,只要每个标题中的内容是 1 个字段,它就可以正常工作,如果标题下有多个字段值,它只会显示第一个 :(
【解决方案2】:

您可以按照 sudo_O 的建议在 awk 中执行此操作,但您也可以在纯 bash 中执行此操作。

#!/bin/bash

# We'll use an associative array, where the indexes are strings.
declare -A a

# Our list of files, in an array (not associative)
files=(file1 file2 file3)

# Walk through array of files...
for file in "${files[@]}"; do
  # And for each file, increment the array index with the value.
  while read index value; do
    ((a[$index]+=$value))
  done < "$file"
done 

# Walk through array. ${!...} returns a list of indexes.
for i in ${!a[@]}; do
  echo "$i ${a[$i]}"
done

结果:

$ ./doit
dd 4
aa 3
ee 4
bb 6
ff 4
cc 9

如果你想对输出进行排序......你可以通过sort 传递它。 :)

【讨论】:

  • 是的,当然。复选标记在正确的位置。我包括这个是因为这个问题也被标记为 bash,而且 bash 脚本很有趣。
【解决方案3】:

这是使用GNU awk 的一种方式。运行如下:

awk -f script.awk File1 File2 File3

script.awk的内容:

sub(/RESULT_SET_/,"") {

    i = $1
    next
}

{
    a[i][$1]+=$2
}

END {
    for (j=1;j<=length(a);j++) {

        print "RESULT_SET_" j

        for (k in a[j]) {
            print k, a[j][k]
        }
    }
}

结果:

RESULT_SET_1
aa 3
RESULT_SET_2
bb 6
RESULT_SET_3
cc 9
RESULT_SET_4
ee 4
ff 4
gg 4

或者,这里是单行:

awk 'sub(/RESULT_SET_/,"") { i = $1; next } { a[i][$1]+=$2 } END { for (j=1;j<=length(a);j++) { print "RESULT_SET_" j; for (k in a[j]) print k, a[j][k] } }' File1 File2 File3

【讨论】:

  • 这是在 Linux 上运行的吗? awk 'sub(/RESULT_SET_/,"") { i = $1;下一个 } { a[i][$1]+=$2 } END { for (j=1;j 处或附近的语法错误
  • ./script.awk file1 file2 ./script.awk:第 1 行:意外标记附近的语法错误 /RESULT_SET_/,""' ./script.awk: line 1: sub(/RESULT_SET_/,"") {'
  • 它必须是 gawk 4 + 初始 gawk 3.XX 不工作 - 很酷
  • @vahid:是的,我希望使用GNU awk 来解决您的问题(我假设您已经安装了它的最新版本)。使用多维数组可以使解决方案本身变得更简单——而且它也更易于维护。如果您对此感到满意,请不要忘记接受它。如果没有,请告诉我。干杯。
  • 嗨,史蒂夫,我早些时候测试过这个,虽然在一个更简单的例子中一切看起来都很好,但在我的实际结果中它并没有很好地工作,每个子标题中项目的顺序已经改变,我还必须将标题定义为 RESULT_SET_1 _2 等等才能正常工作
【解决方案4】:

使用此修复 基本上它会遍历每个文件,如果条目存在于另一侧,它将将该条目添加到近似行号的 0 值,以便它可以总结内容 - 一直在我当前的输出上进行测试并且似乎正在工作真的很好

#!/bin/bash

 files="file1 file2 file3 file4 file5 file6 file7 file8"
RAND="$$"
i=0;
oldname="";
for names in $(echo $files); do
        ((i++));
        if [ $i == 1 ]; then
                oldname=$names
                shift;
        else
               oldname1=$names.$RAND
        for entries in $(awk -v i=$i 'NR==FNR { _[$1]=$2 } NR!=FNR { if (_[$1] == "") { if  ($2 ~ /[0-9]/)   { nn=0; nn=(_[$1]+=$2);  print FNR"-"$1"%0"} else { } } else { } }' $oldname $names); do
                line=$(echo ${entries%%-*})
                content=$(echo ${entries#*-})
                content=$(echo $content|tr "%" " ")

edit=$(ed -s $oldname  << EOF
$line
a
$content
.
w
q
EOF 
)

$edit  >/dev/null 2>&1

done

                awk -v i=$i 'NR==FNR { _[$1]=$2 } NR!=FNR { if (_[$1] != "") { if  ($2 ~ /[0-9]/)   { nn=0; nn=($2+_[$1]); print $1" "nn; } else { print $1;} }else { print; } }' $names $oldname> $oldname1
        oldname=$oldname1
    fi
done

cat $oldname
#rm file?.*

【讨论】:

    猜你喜欢
    • 2015-08-01
    • 2016-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-11
    • 2018-04-23
    • 1970-01-01
    • 2015-01-19
    相关资源
    最近更新 更多