linux: awk

作者: 随风化作雨 | 来源:发表于2017-11-29 11:31 被阅读9次

    awk 是一种对立的编程语言,集成于所有UNIX/Linux中,这个名字是它创建者的名字首字母组成的 Alfred Aho,Peter Weinberger, and Brian Kernighan。

    awk的基本语法

    普通模式
    awk '/pattern/{action}' files
    

    其中patter是一个正则表达式,action又是一系列命令,对于满足匹配的文本执行一些动作,files表示待操作的文件,如果不指定,则输入是STDIN。如果不指定pattern,则对所有文件的每一行都执行action。

    表达式模式

    当在awk中使用比较操作符时,使用下面的语法模式,详见后面。

    awk '(expression){action}' files
    

    有如下一个示例文件

    zdd.txt包含如下内容

    Fruit Price/lbs
    Banana 0.89
    Paech 0.79
    Kiwi 1.50
    Pineapple 1.29
    Apple 0.99

    显示一个文件的所有行

    awk '{ print ; }' zdd.txt 
    

    分号表示命令结束,这个例子没有pattern,只有action

    域编辑

    awk自动将读入的行分割成域(field),域是由一个或多个分隔符分割开的字符集,缺省的分隔符是tab和空格,访问域可以用$1,$2, ... $n的形式,域都是从1开始的,而$0表示整个行(以行本来的面目呈现)

    格式化输出水果及价格

    awk '{ printf "%-15s %s\n", $1, $2 ;}' zdd.txt
    

    输出如下

    Fruit Price/lbs
    Banana 0.89
    Paech 0.79
    Kiwi 1.50
    Pineapple 1.29
    Apple 0.99

    使用其他的域分隔符

    awk的-F参数可以制定其他的域分隔符,比如下面的代码将打印D

    echo A:B:C:D | awk -F: '{print $4}'
    
    多个命令同时执行

    在价格高于1美元的水果后面加* 以引起注意,这里包含了两个模式和动作对,直接书写即可,不必像sed那样加-e参数

    awk '/[1-9]\.[0-9][0-9]$/ { print $0, "*"} /0\.[1-9][1-9]/ {print ;}' zdd.txt
    

    输出如下

    Banana 0.89
    Paech 0.79
    Kiwi 1.50 *
    Pineapple 1.29 *
    Apple 0.99
    bash-3.2$

    比较操作

    awk中可以执行的比较操作有
    <
    >
    <=
    >=
    !=
    value ~ /pattern/
    value !~ /pattern/

    比较操作基本语法
    awk '(expression){action}' files
    

    其中expression是一个比较表达式,通常将其用括弧括起来。

    在价格大于1美元的水果后面打印expensive

    awk '$2 >= 1.0 { printf "%s\t%s\n", $0, "Expensive" ; }' zdd.txt
    

    输出

    Fruit Price/lbs Expensive
    Kiwi 1.50 Expensive
    Pineapple 1.29 Expensive

    复合表达式

    可以使用&&或||连接多个表达式,表达式用()扩起
    (expr1) && (expr2)
    (expr1) ||(expr)

    next命令
    看一个例子

    awk '
    $3 <= 75 { printf "%s\t%s\n",$0,"REORDER" ; }
    $3 > 75 { print $0 ; }
    ' zdd.txt
    

    这个命令的执行过程如下
    (1) 读入一行,检查价格是否小于等于75,如果为真,则打印出REORDER
    (2) 检查该行是否大于75,如果大于则直接打印
    (3) 处理下一行
    可见如果条件(1)满足,则不必再判断条件(2)了,如果避免这个多余的操作呢?使用next即可

    awk '
    $3 <= 75 { printf "%s\t%s\n",$0,"REORDER" ; next ; }
    $3 > 75 { print $0 ; }
    ' zdd.txt
    
    BEGIN和END

    基本语法

    awk '
        BEGIN { actions }
        /pattern/ { actions }
        /pattern/ { actions }
        END { actions }
    ' files
    

    注意BEGIN对应的模式必须是第一个模式,而END对应的模式必须是最后一个模式。这两者都不参与文本行的处理,只是做一些初始化及善后工作。

    BEGIN可以用来打印表头或者列名等,如下

    BEGIN{
    -F":"
    printf "----------------------------------------------------------------\n"
    printf "%-20s%-16s  Jan  |  Feb  |  Mar  |Total Donated\n ","NAME","PHONE"
    printf "----------------------------------------------------------------\n"
    }
    

    流控制

    if

    基本格式

    if (expression1) {
        action1
    } else if (expression2) {
        action2
    } else {
        action3
    }
    

    一个例子,根据每行的特征字,在行末加注释。

    awk '{ 
        print "%s\t", $0;
        if ($1 ~ /d/) {
            print "Directory\n";
        }
        else if ($NF ~ /\.pl/){
            print "Perl script\n";
        }
        else if ($NF ~ /\.bcp/) {
            print "BCP file\n";
        }
        else {
            print "\n";
        }
    }'
    
    while
    for

    awk中的for与C语言中的for类似。

    awk '{ 
        for (x = 1; x <= NF; x++){
            printf "%s ", $x;
        }
        print "\n";
    }' input_file ;
    

    特殊技巧

    NR==FNR

    这条语句只有在输入是多个文件的时候才有意义,用来判断当前正在处理第一个文件。

    NR 处理过的文件行数,多个文件累加。

    FNR 处理过的文件行数,仅限当前文件。

    所以,NR >= FNR。看一个例子

    #cat a
    张三|000001
    李四|000002

    #cat b
    000001|10
    000001|20
    000002|30
    000002|15

    想要得到的结果是将用户名,帐号和金额在同一行打印出来,如下:

    张三|000001|10
    张三|000001|20
    李四|000002|30
    李四|000002|15

    awk -F'|' 'NR==FNR{a[$2]=$0;next}{print a[$1] FS $2}' a b
    awk -F'|' '{a[$2]=$0}NR>FNR{print a[$1] FS $2}' a b
    

    [解析]
    由NR=FNR成立,判断当前读入的是第一个文件a,然后使用{a[$2]=$0;next}循环将a文件的每行记录都存入数组a,并使用$2作为下标引用.next,不在执行后面的语句.
    由 NR=FNR不成立,判断当前读入了第二个文件b,然后跳过{a[$2]=$0;next},对第二个文件b的每一行都无条件执行{print a[$1]FS$2},此时变量$1为第二个文件的第一个字段,与读入第一个文件时,采用第一个文件的$2为数组下标相同.因此可以在此输出该数组的值。

    awk应用

    按条件打印文件

    awk配合ls命令可以打印文件指定的某一列,ls -l的输出入下

    -rwxr-xr-x   1 user1     staff       1805 Dec  4 22:59 abc.sh
    drwxr-xr-x   3 user2     staff        512 Dec  4 22:59 test_report
    -rwxr-xr-x   1 user3     staff      12526 Feb  1 03:12 test.pl
    drwxr-xr-x   3 user1     staff        512 Feb  1 03:19 xyz
    

    如果只想打印文件名(第九列),那么可以使用下面的方法,$9表示文本行的第九列。

    ls -l | awk '{print $9;}'
    

    如果只想打印user1创建的文件名,可以使用下面的方法,这里/user1/是一个pattern,表示只处理包含user1的文本行。

    ls -l | awk '/user1/{print $9;}'
    
    格式化打印

    awk支持printf格式化打印。

    只打印文件名和文件大小(文件名位于第9列,文件大小位于第5列)

    ls -l | awk '{print $9, $5;}'
    

    上面的代码虽然能打印,但是格式很乱,可以使用printf格式化一下。

    %s参数,用来打印字符串,可以指定宽度,不足的补空格,正数表示右对齐,负数表示左对齐。%3s表示字符串宽度为3列,右边对齐,如果字符串实际宽度大于3,那么取实际宽度。

    文件名左对齐,大小左对齐

    ls -l | awk '{printf "%-16s%\t%-16s\n", $9, $5;}'
    

    文件名左对齐,大小右对齐

    ls -l | awk '{printf "%-16s%\t%16s\n", $9, $5;}'
    

    文件名右对齐,大小左对齐

    ls -l | awk '{printf "%16s%\t%-16s\n", $9, $5;}'
    

    文件名右对齐,大右左对齐

    ls -l | awk '{printf "%16s%\t%16s\n", $9, $5;}'
    
    按行打印环境变量PATH
    echo $PATH | awk -F: ' {
        for (i = 1; i <=NF; i++) {
            printf "%s\n", $i;
        }
    }'
    
    删除某个用户的所有文件

    删除用户zdd的所有文件,注意-rf后面有一个空格。

    ls -l | awk '/zdd/{print "rm -rf " $9} | sh
    

    相关文章

      网友评论

        本文标题:linux: awk

        本文链接:https://www.haomeiwen.com/subject/yilubxtx.html