【问题标题】:Reducing number of awk passes减少 awk 传递次数
【发布时间】:2018-01-02 16:10:39
【问题描述】:

我在 Teradata 数据库上工作,并且有一个空间检查脚本。脚本应该根据脚本开始时定义的空间使用值将标志提升为 CRITICAL 或 WARNING。

我的示例 SQL 文件输出是 (DatabaseOutput.log) 文件如下,该文件用作 awk 块的输入。

### Some multiline Database query, resulting in Database Space usage

 *** Query completed. 11 rows found. 4 columns returned.
 *** Total elapsed time was 11 seconds.

## Output of the query, I am interested in DatabaseName, Perc2, MaxPerm

DatabaseName                                          Perc                    Perc2      MaxPerm
----------------------------------------------------  ----------------------  -------  -----------
AAA                                                   9.21899768137583E-001    92.19     10102320
BBB                                                   9.19923819717036E-001    91.99       524160
CCC                                                   9.17517791271651E-001    91.75      1687440
DDD                                                   9.15820363471060E-001    91.58       816720
EEE                                                   9.09293748338489E-001    90.93       149760
FFF                                                   9.07840905921109E-001    90.78      6934080
GGG                                                   9.04946085680591E-001    90.49      7273440
HHH                                                   8.54498111733230E-001    85.45      2538960
III                                                   8.22783559253080E-001    82.28      7598400
JJJ                                                   8.02181524253446E-001    80.22      8077680

+---------+---------+---------+---------+---------+---------+---+---------+-

需要的输出是

WARNING                           AAA     92.19  10102320
WARNING                           BBB     91.99    524160
WARNING                           CCC     91.75   1687440
WARNING                           DDD     91.58    816720
WARNING                           EEE     90.93    149760
WARNING                           FFF     90.78   6934080
WARNING                           GGG     90.49   7273440

我有工作 awk 代码需要三遍。它可以减少到一个 awk 遍吗?

工作 awk 代码:

cLvlCRIT=95
cLvlWARN=90

cat DatabaseOutput.log |
    awk '/-------------------/,/^$/' | # captures output block; it excludes query, logon, logoff information and header line but keeps separator's line.
        awk '{if (NR >= 2) {print}}' | # removes separator line, prints all lines from line 2 to EOF
            awk -v lLvlCRIT=$cLvlCRIT -v lLvlWARN=$cLvlWARN ' {
            if ( $1 != "StartCapture" && $3 >= lLvlCRIT ) {
                printf("%11s%30s%10s%10s\n", "CRITICAL",$1,$3,$4)
               }
            if ( $1 != "StartCapture" && $3 >= lLvlWARN && $3 < lLvlCRIT ) {
               printf("%11s%30s%10s%10s\n", "WARNING",$1,$3,$4)
              }
} '

提前致谢!

【问题讨论】:

标签: bash awk


【解决方案1】:

你可以在 awk 中使用标志来获取一个块:

awk -v lLvlCRIT="$cLvlCRIT" -v lLvlWARN="$cLvlWARN" '
/^----------------------------------------------------/ {block=1; next}
/^$/ && block {exit}    # if there is only one data block pattern - exit
                        # otherwise just reset block to 0 to find next block
block { your code on the block }'

所以重现你的例子:

awk -v lLvlCRIT="$cLvlCRIT" -v lLvlWARN="$cLvlWARN" '
/^----------------------------------------------------/ {block=1; next}
/^$/ && block {exit}
block {if ( $3 >= lLvlCRIT )
     printf("%11s%30s%10s%10s\n", "CRITICAL",$1,$3,$4)       
else if ( $3 >= lLvlWARN )
     printf("%11s%30s%10s%10s\n", "WARNING",$1,$3,$4)  }' file
WARNING                           AAA     92.19  10102320
WARNING                           BBB     91.99    524160
WARNING                           CCC     91.75   1687440
WARNING                           DDD     91.58    816720
WARNING                           EEE     90.93    149760
WARNING                           FFF     90.78   6934080
WARNING                           GGG     90.49   7273440

【讨论】:

    【解决方案2】:

    Google UUOC 并且从不使用范围表达式,因为它们使琐碎的任务变得非常简单,但随后需要完全重写或复制任何更有趣的条件:

    awk -v lLvlCRIT="$cLvlCRIT" -v lLvlWARN="$cLvlWARN" '
    inBlock {
        if      ( $3 >= lLvlCRIT ) { level = "CRITICAL" }
        else if ( $3 >= lLvlWARN ) { level = "WARNING" }
        else if (NF)               { next }
        else                       { exit }
        printf "%11s%30s%10s%10s\n", level, $1, $3, $4
    }
    /-------------------/ { inBlock=1 }
    ' DatabaseOutput.log
    

    【讨论】:

    • @EdMortan 我没有在我的代码中使用 cat,而是将我的数据库查询输出直接重定向到 awk。不过,在我的工作代码中添加它肯定是错误的。
    • 我没有在我的代码中使用 cat,我将我的数据库查询输出直接重定向到 awk。感谢您对 cat 和重复条件的建议。在我收到的解决方案中,您的最易读!
    【解决方案3】:

    您的awk 可能如下所示:

    awk -v lLvlCRIT="$cLvlCRIT" -v lLvlWARN="$cLvlWARN" '
    /^---/,/^$/ {
       if ( $0 ~ "^---" || $0 ~ "^$" ) next
       if ( $3 >= lLvlCRIT )
           printf("%11s%30s%10s%10s\n", "CRITICAL",$1,$3,$4)       
       else if ( $3 >= lLvlWARN )
           printf("%11s%30s%10s%10s\n", "WARNING",$1,$3,$4)               
    }' DatabaseOutput.lo
    

    awk 中指定模式范围可能很棘手,而标志是首选方法。如需更多信息,请参阅Specifying Record Ranges with Patterns

    【讨论】:

    • 您仍然需要NR&gt;=2{print} 位。当/----/,/^$/ 首次匹配时,您可能需要根据与 NR 的偏移量来实现自己的行计数器。 (在这种情况下,这只是丢弃第一个匹配的行。)
    • 可能会在第一行引用扩展,以确保我们在其他情况下展示最佳实践确实很重要。
    • @CharlesDuffy 真的,谢谢。我只是不假思索地复制了第一行并发布了我想到的第一个解决方案......我更喜欢其他答案,我什至可能会删除这个:)
    • 不要删除——这是一个可靠的答案,也是第一个。您可能想要添加 /^---/,/^$/ 正在做的事情。在 awk 中使用类似 sed 的范围运算符可能会很棘手,但这不是其中之一。作为肌肉记忆,使用 awk 时,使用标志通常比使用范围运算符要少一些麻烦。见example
    • 永远不要使用范围表达式。查看代码中^---^$ 的重复测试。
    【解决方案4】:

    关注awk 也可能对您有所帮助。

    awk -v cLvlCRIT="$cLvlCRIT" -v cLvlWARN="$cLvlWARN" -v space="                           " '
    /^$/||/^+/{
      flag="";
      next
    }
    /^----------/{
      flag=1;
      next
    }
    flag && $3>=cLvlWARN{
      val=$1 OFS $3 OFS $4;
      printf("%s"space"%s\n",$3>=cLvlCRIT?"CRITICAL":($3>=cLvlWARN && $3<cLvlCRIT?"WARNING":""),val)
    }
    '   Input_file
    

    【讨论】:

    • 谢谢拉文德,你的回答给了我想要的结果。三元运算符也很酷。我发现 PesaThe 和 Ed Mortan 的答案更容易理解,因此将使用他们的答案。
    【解决方案5】:

    这将根据您的示例输入进行。

     #!/bin/bash
    
        cLvlCRIT=95
        cLvlWARN=90
    
       grep -E '^[a-zA-Z]+[ ]+[0-9.]+' DatabaseOutput.log |
             awk -v lLvlCRIT=$cLvlCRIT -v lLvlWARN=$cLvlWARN ' {
                    if ( $1 != "StartCapture" && $3 >= lLvlCRIT ) {
                        printf("%11s%30s%10s%10s\n", "CRITICAL",$1,$3,$4)
                       }
                    if ( $1 != "StartCapture" && $3 >= lLvlWARN && $3 < lLvlCRIT ) {
                       printf("%11s%30s%10s%10s\n", "WARNING",$1,$3,$4)
                      }
        } '
    

    问候!

    【讨论】:

    • cat foo | grep ...grep ... &lt;foogrep ... foo 有性能损失,最好避免。此外,grep 的每行开销通常约为awk 的一半,因此除非您在 grep 阶段删除输入文件中至少 1/2 的行,否则您会落后就两者一起使用时的总 CPU 使用率而言。
    • @CharlesDuffy 完成! :)
    • Matias, Charles 感谢各位的投入。我想减少使用的命令数量和 awk 传递的数量。因此我没有使用您的解决方案,但它确实可以很好地工作:)
    【解决方案6】:

    这里的主要关注点是awk 的有效输入。下面使用标志的解决方案是一种方法。这也考虑了您输入的特定模式。

    crit=95
    warn=90
    awk -v crit=$crit -v warn=$warn '
    /^DatabaseName/{flag=1;next}
    {$2=""}!flag{next}
    $3>crit{printf "Critical\t\t%s%s",$0,ORS;next}
    $3>warn{printf "Warning \t\t%s%s",$0,ORS}' DatabaseOutput.lo
    
    Warning          AAA  92.19 10102320
    Warning          BBB  91.99 524160
    Warning          CCC  91.75 1687440
    Warning          DDD  91.58 816720
    Warning          EEE  90.93 149760
    Warning          FFF  90.78 6934080
    Warning          GGG  90.49 7273440
    

    旁注:但可以肯定的是,awk 方法对于 TB 大小的文件会很慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 2012-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-13
      • 1970-01-01
      相关资源
      最近更新 更多