Lập trình shell

Ngôn ngữ lập trình awk

Nguyễn Hải Châu (nhchau@gmail.com)
Trường Đại học Công nghệ, ĐHQGHN

awk là gì?

  • awk, viết tắt từ tên của ba tác giả Alfred V. Aho, Brian W. Kernighan và Peter J. Weinberger, là ngôn ngữ lập trình có mục tiêu xử lý text có định dạng cột
  • awk xem text file cần xử lý như một tập hợp các bản ghi, mỗi bản ghi có nhiều trường
  • awk có các biến, điều kiện và vòng lặp, toán tử số học và string
  • awk có thể sinh ra các report
  • awk đọc file cần xử lý từ stdin và đưa kết quả ra stdout
  • Cú pháp:

awk 'pattern1 {actions} pattern2 {actions} ...' input_file

trong đó pattern1, pattern2 có thể là các mẫu tìm kiếm ở dạng /pattern/ hoặc điều kiện. Hành động tương ứng sẽ được thực hiện nếu tìm thấy mẫu hoặc điều kiện đúng.

  • Phần patternactions có thể không xuất hiện trong các chương trình awk

Ví dụ: Dữ liệu và yêu cầu

  • Dữ liệu: file text có 3 cột: Tên người làm việc, số giờ làm và giá tiền công cho mỗi giờ làm.
cat emp.txt
## Beth   4.00 0
## Dan  3.75 0 #     Comment
## Kathy   4.00 10
## Mark   5.00 20
## Mary 5.50 22
## Susie 4.25 18
  • Các cột phân cách nhau bởi dấu trắng
  • Yêu cầu:
    • Tính tiền công cho người làm có giá tiền công dương
    • Với những người có giá tiền công bằng 0, in ra tên và "no payment"

Ví dụ: chương trình viết trên awk

awk '$3 > 0 {print $1,": ", $2*$3} $3<=0 {print $1, ": No payment"}' emp.txt
## Beth : No payment
## Dan : No payment
## Kathy :  40
## Mark :  100
## Mary :  121
## Susie :  76.5
  • Chương trình có thể viết theo cách khác:

awk '$3 > 0 {print $1, ": ", $2*$3} $3<=0 {print $1, ": No payment"}' < emp.txt

hoặc

cat emp.txt | awk '$3 > 0 {print $1, ": ", $2*$3} $3<=0 {print $1, ": No payment"}'

Các cách viết chương trình awk

  • Như slide trước nếu chương trình ngắn
  • Đưa mã lệnh vào một file prog.awk và thực hiện awk -f prog.awk options...
  • Có hai cách đưa ra output của chương trình awk
    • print: In các biến, hằng cách nhau bởi dấu phẩy. Hằng string là chuỗi ký tự nằm trong cặp "
    • printf: In có khuôn dạng, cú pháp tương tự printf trong ngôn ngữ C

Các biến đặc biệt của awk

  • Toàn bộ một dòng: $0
  • Trường thứ i: $i, ví dụ $1, $2, ...
  • Số lượng trường: NF
  • Số hiệu dòng: NR

Ví dụ: Các biến đặc biệt

  • Chương trình sau in ra các dòng, mỗi dòng chứa: Số hiệu dòng, số lượng trường, trường đầu tiên và trường cuối cùng:
awk '{print NR, NF, $1, $NF}' emp.txt
## 1 3 Beth 0
## 2 5 Dan Comment
## 3 3 Kathy 10
## 4 3 Mark 20
## 5 3 Mary 22
## 6 3 Susie 18
  • Lưu ý:
    • Chương trình này không có pattern, nghĩa là mọi dòng đều được xử lý
    • Giá trị các biến NF, NR thay đổi mỗi khi awk xử lý một dòng mới

Một số pattern hữu ích của awk

  • So sánh số: Tìm những người có giờ công lao động cao hơn 5:
awk '$2 >= 5' emp.txt
## Mark   5.00 20
## Mary 5.50 22
  • So sánh sau khi tính toán: Tìm những người được trả công trên 50:
awk '$2*$3>50 {printf("%.2f$: %s\n", $2*$3, $1)}' emp.txt
## 100.00$: Mark
## 121.00$: Mary
## 76.50$: Susie

Một số pattern hữu ích của awk

  • So sánh text
awk '$1=="Susie"' emp.txt
# Hoặc so sánh theo cú pháp của biểu thức chính qui:
awk '/Susie/' emp.txt
## Susie 4.25 18
## Susie 4.25 18
  • Kết hợp các pattern: || (toán tử logic OR), && (AND) và ! (NOT):
awk '$2>=4 || $3>=20' emp.txt
## Beth   4.00 0
## Kathy   4.00 10
## Mark   5.00 20
## Mary 5.50 22
## Susie 4.25 18

Một số pattern hữu ích của awk

  • Ta có thể dùng pattern để kiểm tra tính hợp lệ của dữ liệu
awk 'NF != 4 {print $0, "number of fields is different from 3"} \
    $3 < 1 {print $0, "woking hour is less than 1"}' emp.txt
## Beth   4.00 0 number of fields is different from 3
## Beth   4.00 0 woking hour is less than 1
## Dan  3.75 0 #     Comment number of fields is different from 3
## Dan  3.75 0 #     Comment woking hour is less than 1
## Kathy   4.00 10 number of fields is different from 3
## Mark   5.00 20 number of fields is different from 3
## Mary 5.50 22 number of fields is different from 3
## Susie 4.25 18 number of fields is different from 3

BEGINEND

  • Được sử dụng như các pattern đặc biệt:
    • BEGIN: trước khi xử lý dòng đầu tiên của file
    • END: Sau khi xử lý dòng cuối cùng của file
  • Ví dụ:
awk 'BEGIN {printf("%-8s %8s %8s\n", "NAME", "RATE", "HOUR")}
    $3>0 {printf("%-8s %8.1f %8.1f\n", $1, $2, $3)}
    END {printf("%12s\n", "End")}' emp.txt
## NAME         RATE     HOUR
## Kathy         4.0     10.0
## Mark          5.0     20.0
## Mary          5.5     22.0
## Susie         4.2     18.0
##          End

Tính toán: Tổng và trung bình

awk 'BEGIN {number=0;total=0;printf("%-8s %8s %8s %8s\n\n", "NAME", "RATE", "HOUR", "PAYMENT")}
    $3>0 {number+=1;total+=$2*$3;printf("%-8s %8.1f %8.1f %8.1f\n", $1, $2, $3, $2*$3)}
    END {printf("\nNumber = %d Total = %.2f Average = %.2f\n", number, total, total/number)}' \
    emp.txt
## NAME         RATE     HOUR  PAYMENT
## 
## Kathy         4.0     10.0     40.0
## Mark          5.0     20.0    100.0
## Mary          5.5     22.0    121.0
## Susie         4.2     18.0     76.5
## 
## Number = 4 Total = 337.50 Average = 84.38

Xử lý chuỗi

  • NF: số trường, NR: số ký tự, length(): độ dài chuỗi ký tự
  • Chương trình sau đếm số dòng, từ và ký tự của một file:
awk '{nc = nc+length($0)+1;nw = nw+NF;}
    END {print NR " lines, " nw " words, " nc " characters"}' emp.txt
## 6 lines, 20 words, 98 characters
  • NR " lines, " nw a "words, " nc "characters": Toán tử nối các string (concatenation)

Cấu trúc điều khiển if-else

awk '{if ($3>0) print $0 ": valid"
else print $0 ": invalid"}' emp.txt
## Beth   4.00 0: invalid
## Dan  3.75 0 #     Comment: invalid
## Kathy   4.00 10: valid
## Mark   5.00 20: valid
## Mary 5.50 22: valid
## Susie 4.25 18: valid

hoặc nếu viết if-else trên cùng một dòng:

awk '{if ($3>0) print $0 ": valid";else print $0 ": invalid"}' emp.txt

Mảng và cấu trúc điều khiển while

  • In input với thứ tự ngược lại:
awk '{line[NR] = $0}
    END {
    i = NR
    while (i>0) {
    print line[i]
    i -= 1
    }}' emp.txt
## Susie 4.25 18
## Mary 5.50 22
## Mark   5.00 20
## Kathy   4.00 10
## Dan  3.75 0 #     Comment
## Beth   4.00 0

Cấu trúc điều khiển for

awk '{line[NR] = $0}
    END {
    for (i=NR;i>=1;i--)
    print line[i]
    }' emp.txt
## Susie 4.25 18
## Mary 5.50 22
## Mark   5.00 20
## Kathy   4.00 10
## Dan  3.75 0 #     Comment
## Beth   4.00 0

Định nghĩa hàm

function function_name(argument1, argument2, ...) { 
       function body
}
  • Ví dụ:
awk ' function my_max(num1, num2) {
    if (num1 < num2)
        return num2
    return num1
}
BEGIN { printf("Max(6,3)=%.1f\n", my_max(6,3));} ' /dev/null
## Max(6,3)=6.0

Một số hàm thông dụng

  • length(s): Tính độ dài xâu s
  • sqrt(x): Căn bậc 2
  • log(x): Logarit cơ số \(e\)
  • exp(x): Hàm mũ \(e\)
  • int(x): Lấy phần nguyên của một số thực
  • substr(string,start,max_length): Lấy chuỗi con của một chuỗi
  • split(string, array, separator): Cắt một chuỗi thành mảng với dấu phân cách là separator
  • index(string, search): Tìm vị trí xuất hiện đầu tiên của search trong string
  • system(command): Thực hiện lệnh command như được gọi từ shell

Một số biến đặc biệt khác

  • FS: Ký tự ngăn cách trường của input
  • OFS: Ký tự ngăn cách trường của output
  • RS: Ký tự ngăn cách các bản ghi input
  • ORS: Ký tự ngăn cách các bản ghi output
  • FILENAME: Tên file hiện đang được xử lý
  • ARGC: Số lượng tham biến dòng lệnh cho các script viết trên awk
  • ARGV: Mảng chứa các tham biến dòng lệnh cho các script viết trên awk

Ví dụ sử dụng biến đặc biệt

  • In tên một số user đặc biệt (có tiến trình server) trong /etc/passwd:
awk 'BEGIN {FS=":"} /[Ss]erver/ { printf("Username: %s HOME: %s\n", $1, $6); }' /etc/passwd
## Username: postgres HOME: /var/lib/pgsql
## Username: mysql HOME: /var/lib/mysql
## Username: jetty HOME: /usr/share/jetty
## Username: monetdb HOME: /var/MonetDB
awk 'BEGIN {FS=":"; OFS=","} /[Ss]erver/ { print "Username",$1,"HOME",$6 }' /etc/passwd
## Username,postgres,HOME,/var/lib/pgsql
## Username,mysql,HOME,/var/lib/mysql
## Username,jetty,HOME,/usr/share/jetty
## Username,monetdb,HOME,/var/MonetDB

Lấy dữ liệu từ các lệnh shell vào biến của awk

  • Chúng ta sử dụng pipe và lệnh getline var như sau:
awk 'BEGIN {
    "date" | getline datevar # Simple command
    printf("date is: %s\n", datevar);
    wordcountcmd = "wc"
    opt = "-l"
    pipecmd = "ls -l " "|" wordcountcmd " " opt
    pipecmd | getline filenum # Pipe command ls -l | wc -l
    printf("Number of file: %d\n", filenum);
}' /dev/null
## date is: Sat Nov  5 12:17:50 ICT 2016
## Number of file: 116

Lấy giá trị của biến shell vào awk

shellvar=123
awk -v awkvar=$shellvar 'BEGIN {
    printf("awk variable is: %s\n", awkvar);
}' /dev/null
## awk variable is: 123

Lấy giá trị của biến shell vào mẫu tìm kiếm của awk

mypat="postgres"
awk -v pattern="$mypat" '$0 ~ pattern {
    print $0
}' /etc/passwd
## postgres:x:26:26:PostgreSQL Server:/var/lib/pgsql:/bin/bash

Thực hành

  • 6.1. Chuyển khuôn dạng của tel.csv sang dạng XML với các cặp thẻ tương ứng: <stt></stt>, <dienthoai></dienthoai>, <hoten></hoten>, <chucdanh></chucdanh><coquan></coquan>.
  • 6.2. Cho file live.txt có nội dung tường thuật bóng đá, trong đó mỗi sự kiện được viết trên một paragraph. Hãy tường thuật nội dung theo thứ tự ngược lại.
  • 6.3. Viết một script đưa ra màn hình thông tin về các tiến trình đang có trong hệ thống với khuôn dạng nhiều dòng, mỗi dòng có các trường phân cách bởi dấu trắng:
    • Tên tiến trình có PID > 0 và có tiến trình con
    • Danh sách các tiến trình con của tiến trình nói trên, phân cách bằng dấu ,
  • 6.4. Sử dụng lệnh free -m để lấy thông tin về bộ nhớ đang sử dụng của máy tính trong 5 phút với tần suất 5 giây/lần. In ra các giá trị sau: Tổng dung lượng bộ nhớ; min, max, trung bình \(\mu\) và độ lệch chuẩn \(\sigma=\sqrt{\frac{(x-\mu)^2}{N-1}}\) của bộ nhớ đang sử dụng.

Tài liệu tham khảo

  1. A. V. Aho, B. W. Kernighan, P. J. Weinberger, The AWK programming language, Addison-Wesley, 1988.
  2. A. Robins, Effective awk programming, 4th edition, O'Reilly, 2015.