zoukankan      html  css  js  c++  java
  • 使用shell+awk完成Hive查询结果格式化输出

    好久不写,一方面是工作原因,有些东西没发直接发,另外的也是习惯给丢了,内因所致。今天是个好日子,走起!

    btw,实际上这种格式化输出应该不只限于某一种需求,差不多是通用的。

    需求

    --基本的:当前Hive查询结果存在数据与表头无法对齐的情况,不便于监控人员直接查看,或者导出到excel中,需要提供一个脚本,将查询结果处理下,便于后续的查看或者操作。

    --额外的:A、每次查询出来的结果字段数、字段长度不固定;B、每个数据文件中可能包含不只一套查询结果,即存在多个schema。

    想法

    对于基本需求而言,无非就是将数据文件用格式化输出整理一下,直接想到了awk。

    对于补充的情况,A:需要实现一种机制,基于数据文件,动态地确定格式化输出的参数:字段个数,以及每个格式化字符串的长度参数;B:实现对数据文件根据字段数切割成多段,然后对于每段数据套用前面的脚本处理。

    做法

    基本需求:

    1、指定字段分隔符为“ ”

    2、将每个字段按照指定长度格式化输出

    1 BEGIN{
    2 FS="	"
    3 }
    4 {
    5 printf "%-"len"s	",$i
    6 }

    额外需求A:

    需要把代码写成“活”的,适应各种不同的数据文件,如前面所说,实际上就是在执行格式化输出之前,将数据文件扫描一遍,用一个数组记录下文件中每个字段的max length,然后将这个max length作为该文件内格式化输出的额定宽度。

    1、初始化一个fieldLen数组

    2、扫描整个文件,更新fieldLen数组

    3、将fieldLen数组,用于格式化输出

     1 BEGIN{
     2 FS="	"
     3 }
     4 NR==1{
     5 for (i=1;i<=NF;i++)
     6         fieldLen[i]=0
     7 }
     8 {
     9 
    10 for (i=1;i<=NF;i++)
    11 {
    12         len=length($i)
    13         
    14         if (len>fieldLen[i])
    15         {
    16                 fieldLen[i]=len        
    17         }
    18 }
    19 
    20 }
    21 
    22 END{
    23 for (i=1;i<=NF;i++)
    24 {
    25         printf "%-s",fieldLen[i]
    26         if (i<NF)
    27                 printf "	"
    28         else
    29                 printf "
    "
    30 
    31 }
    32 }

    这里要注意的是,fieldLen的初始化要在NR==1的时候,在BEGIN里面,NF为0

    额外需求B:

    这里需要一些临时变量,标记分割出来的数据块分支:suffix标记不同的分支,fields当前处理数据块的字段数

    处理过程根据前面的临时变量,完成数据文件分割。此处有一个局限在于,对于文件内的多个数据分块,只能处理“AAABBBCCC”这样,同一类数据放在一起的,脚本会分成3块;而对于“AABCABBCC”这种的,则会分割成6块。

     1 BEGIN{
     2 FS="	"            
     3 suffix=0        
     4 filename=ARGV[1]    
     5 fields=0        
     6 }
     7 {
     8 if (NF!=fields)
     9 {
    10     fields=NF
    11     suffix+=1
    12 }
    13 print $0>filename"."suffix
    14 }
    15 END{
    16 print suffix        
    17 }

    基本的思路,就如上面所示。

    但是,完成上面的部分,可能不到一半的工作量,接下来,说几个比较麻烦的问题:

    A、汉字的问题

    这个也是对不齐的主要原因。

    在putty里面显示的时候,一个汉字占2个字宽,一个ASCII字符占一个字宽。但是,在调用awk内置的length()函数时,一个汉字跟一个ASCII字符长度是一样的。所以为了在putty上看到的内容是对齐的,需要在格式化输出的时候,对fieldLen的值进行修正。

    例子如:

    举例

    如上,计算得到的fieldLen为4,但实际上需要8;但是在printf的时候,为了对齐,从“abs”到“泰国香蕉”printf的len值是不一样的,根据字段情况,动态决定

    所以需要修正的有2处:

    1、在计算fieldLen的时候,根据汉字情况,将length($i)获取值加上一个变量

     1 for (i=1;i<=NF;i++)
     2 {
     3     len=length($i)
     4         for (j=1;j<=length($i);j++)
     5         if (substr($i,j,1) > "177")
     6             len+=1 
     7         if (len>fieldLen[i])
     8         {
     9                 fieldLen[i]=len        
    10         }
    11 }

    2、在printf格式化输出的时候,根据汉字情况,给fieldLen[i]减去一个变量

     1 for (i=1;i<=NF;i++)
     2 {
     3 
     4     len=0
     5     for (j=1;j<=length($i);j++)
     6         if (substr($i,j,1) > "177")
     7             len+=1
     8     printf "%-'"fieldLen[i]-len"'s",$i
     9         
    10     if (i<NF)
    11         printf "	"
    12     else
    13         printf "
    "
    14 }

    原理比较简单了,就是前面提到的,汉字比ASCII字符多占一个位置,所以在获取fiedlLen的时候,要加上汉字多占的部分;在格式化输出的时候,汉字要减去多占的部分。

    这里用到了一种awk内识别汉字的方法,参考了网上一个同学的帖子:

    1 for (j=1;j<=length($i);j++)
    2         if (substr($i,j,1) > "177")
    3              #TODO

    原理就是挨个字符进行检测,“177”是8进制的127,超过127的都算汉字。

    B、多文件输入的问题

    按照前面的思路,先要扫描一遍,将数据文件的字段信息存下来,然后再引入字段信息和数据文件,做最终的处理。

    这里有一个问题是:是否有必要将字段信息保存成单独文件?从awk的原理来看,基本上是一遍扫描,当第一遍扫描完,之后,游标已经到了文件末尾。这样看不太方便在一个awk处理流程中完成对同一个文件的2次扫描。即使有方法,或许也比较复杂,2遍就两遍吧。

    awk多文件输入比较简单,但是我们这里的需求是先读取第一个文件的内容,保存到fieldLen数组;然后利用fieldLen数组,处理第二个文件。这里用到的是NR,FNR这两个变量的作用域不同而完成的:NR服务于整个awk处理,FNR服务于某个文件。

    1 NR==FNR{
    2 for (i=1;i<=NF;i++)
    3     fieldLen[i]=$i
    4 }
    5 NR!=FNR{
    6       #TODO      
    7 }

    C、printf变量做字宽的问题

    前面一直说,根据数据文件,动态地确定字段宽度,所以到最后一步,格式化输出的时候,%s在指定宽度的时候,需要用一个变量指定宽度。这是一个awk语言了解是否透彻的问题,花费了不短时间才搞定,直接贴代码吧。

    1 printf "%-'"fieldLen[i]-len"'s",$i

    D、效率的问题

    在脚本执行过程中,出于了处理方便或者逻辑明确的考虑,存在不少的写文件操作。特做如下的测试:

    文件 记录数 size 处理时间
    a.dat 642

    240K

    <1s
    b.dat

    500000

    30M

    35s
    c.dat

    1000000

    168M

    3min42s
    combine.dat

    1500642

    198M

    4min9s

    从实际角度来说,这种格式化的处理,通常数据量不会特别大,同时对实时性要求不那么高。所以够用就行,暂时可以接受。后续在做改进吧。

    Over!

    最后附上代码

     1 #!/bin/sh
     2 
     3 if [ -f $1.txt ];then            
     4     rm $1.txt
     5 fi
     6 
     7 branch=`awk -f split.awk $1`        
     8 
     9 for ((i=1;i<=$branch;i++));do        
    10 
    11 current=$1.$i
    12                     
    13 awk '
    14 BEGIN{
    15 FS="	"
    16 }
    17 NR==1{
    18 for (i=1;i<=NF;i++)
    19         fieldLen[i]=0
    20 }
    21 {
    22 
    23 for (i=1;i<=NF;i++)
    24 {
    25     len=length($i)
    26         for (j=1;j<=length($i);j++)
    27         if (substr($i,j,1) > "177")
    28             len+=1 
    29         if (len>fieldLen[i])
    30         {
    31                 fieldLen[i]=len        
    32         }
    33 }
    34 
    35 }
    36 
    37 END{
    38 for (i=1;i<=NF;i++)
    39 {
    40         printf "%-s",fieldLen[i]
    41     if (i<NF)
    42                 printf "	"
    43         else
    44                 printf "
    "
    45 
    46 }
    47 }
    48 ' $current > $current.schema
    49 
    50 
    51 awk -f execFormat.awk $current.schema $current > $current.txt
    52 
    53 rm $current
    54 rm $current.schema
    55 
    56 done
    57 
    58 for ((i=1;i<=$branch;i++));do
    59 
    60 current=$1.$i.txt
    61 
    62 cat $current >> $1.txt            
    63 
    64 rm $current
    65 
    66 done
    format.sh
     1 #!/usr/bin/awk
     2 BEGIN{
     3 FS="	"        
     4 suffix=0        
     5 filename=ARGV[1]    
     6 fields=0        
     7 }
     8 {
     9 if (NF!=fields)
    10 {
    11     fields=NF
    12     suffix+=1
    13 }
    14 print $0>filename"."suffix
    15 }
    16 END{
    17 print suffix        
    18 }
    split.awk
     1 #!/usr/bin/awk
     2 BEGIN{
     3 FS="	" 
     4 }
     5 NR==FNR{
     6 for (i=1;i<=NF;i++)
     7     fieldLen[i]=$i
     8 }
     9 NR!=FNR{
    10 
    11 for (i=1;i<=NF;i++)
    12 {
    13     len=0
    14     for (j=1;j<=length($i);j++)
    15         if (substr($i,j,1) > "177")
    16             len+=1
    17     printf "%-'"fieldLen[i]-len"'s",$i
    18         
    19     if (i<NF)
    20         printf "	"
    21     else
    22         printf "
    "
    23 }
    24 }
    execFormat.awk
  • 相关阅读:
    商务通代码
    Ubuntu 创建快捷方式的方法
    Linux安装Nginx
    Linux安装jdk10
    Mycat实现Mysql数据库读写分离
    Mysql主从复制
    SpringBoot整合Redis集群
    Redis集群环境搭建
    SpringBoot整合redis哨兵主从服务
    redis 哨兵机制环境搭建
  • 原文地址:https://www.cnblogs.com/YFYkuner/p/3739083.html
Copyright © 2011-2022 走看看