Shell編程-awk


簡介

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"
}

7 以STDIN作為輸入
打印文件名及大小,在ls命令的輸出中,文件名位於第9列,而大小位於第5列
ls -l | awk '{ printf "%15s%15s\n", $9, $5}'
          ipck           1853
     ipcrm.exe           5632
      ipcs.exe          14336
      join.exe          52224
      kill.exe          10240
      less.exe         168960
  lessecho.exe           6144
   lesskey.exe           9728
      link.exe          42496
     lkbib.exe          52224
        ln.exe         114688
    locate.exe         122880
     login.exe          20992
   logname.exe          41472
   lookbib.exe          51712
       lpr.exe         299008
        ls.exe         122368
  makeinfo.exe         191488
       man.exe          37376
       man2dvi            409
  man2html.exe          45568
       manlint           7098

流控制

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

=====


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM