BASH: sử dụng trình thông dịch awk. Sử dụng awk trên Linux

04.10.2015
16:55

Tiện ích awk là một ví dụ điển hình Ứng dụng Linux cho việc xử lý văn bản. Nó rất linh hoạt và hiệu quả, mặc dù nó không cung cấp ngôn ngữ lập trình chính thức. Tuy nhiên, hãy yên tâm rằng khả năng của nó khá đủ để giải quyết nhiều vấn đề. xử lý tự động text (đặc biệt khi kết hợp với các tiện ích console khác).

Cách chạy chương trình awk

Nếu chương trình awk khá đơn giản và ngắn, thì mã của nó có thể được nhập trực tiếp vào bảng điều khiển:

Ối"< код awk-программы >" < имя_файла_для_обработки >

Bạn có thể sử dụng nhiều thứ hơn là chỉ awk làm đầu vào: tập tin văn bản, mà còn xuất ra luồng tiêu chuẩn của các ứng dụng khác:

< некое_приложение >| ôi"< код awk-программы >"

Trường hợp mã chương trình awkđủ thể tích hoặc phải được bảo quản để tái sử dụng, nó có thể được gọi từ một tệp có khóa chuyển đổi -f:

Ối -f< имя_файла_с_кодом_awk_программы > < имя_файла_для_обработки >

Để thực hiện các thử nghiệm, chúng tôi sử dụng tệp test.cpp, trên đó chúng tôi sẽ kiểm tra kết quả của các chương trình awk:

#bao gồm #bao gồm #bao gồm void test1(); int test2(); // Chú thích kiểu C cho hàm main() int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } // Комментарий в стиле С для функции test1() void test1() { std::cout << "Hello, test1!" << std::endl; } // Комментарий в стиле С для функции test2() int test2() { std::cout << "Hello, test2!" << std::endl; }

Quảng cáo

Lọc chuỗi bằng awk

Trước hết, awk cho phép bạn chọn các dòng từ văn bản dựa trên các biểu thức thông thường và một số điều kiện số.

Chọn các chuỗi khớp với biểu thức chính quy

Ví dụ: để lấy tất cả các dòng trong tệp test.cpp chứa chỉ thị tiền xử lý #include, chúng tôi sử dụng lệnh sau:

Ôi "/^#\s*include/" test.cpp

Biểu thức chính quy được viết giữa hai ký tự /. Kết quả là chúng tôi nhận được:

#bao gồm #bao gồm #bao gồm

Chọn các chuỗi KHÔNG khớp với biểu thức chính quy

Để loại bỏ tất cả các dòng không khớp với biểu thức chính quy, hãy sử dụng lệnh từ phần phụ trước và thêm dấu chấm than vào đầu mã awk. Ví dụ: theo cách này chúng tôi sẽ loại trừ tất cả các dòng nhận xét:

Ôi "! /^[/](2).*/" test.cpp

Đây là những gì còn lại:

#bao gồm #bao gồm #bao gồm void test1(); int test2(); int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } void test1() { std::cout << "Hello, test1!" << std::endl; } int test2() { std::cout << "Hello, test2!" << std::endl; }

Chọn các hàng từ một phạm vi nhất định

Bạn có thể xác định một phạm vi chuỗi sẽ hiển thị trên màn hình bằng hai biểu thức thông thường, được phân tách bằng dấu phẩy. Ví dụ: hãy tìm định nghĩa của tất cả các hàm trả về int:

Ôi "/^int .*(.*) (/, /^)/" test.cpp

Kết quả liên quan:

Int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } int test2() { std::cout << "Hello, test2!" << std::endl; }

Kết hợp các điều kiện lọc

Để kiểm tra các chuỗi theo nhiều điều kiện cùng một lúc, hãy sử dụng toán tử && (AND) và ||. (HOẶC) .

Lệnh sau in tất cả các bình luận không chứa main:

Ôi "/[/](2).*/ && ! /main/" test.cpp

Kết quả là chúng ta có:

// Chú thích kiểu C cho hàm test1() // Chú thích kiểu C cho hàm test2()

Trước đây, chúng tôi đã tìm kiếm một loạt các dòng bằng cách sử dụng hai biểu thức chính quy, nhưng nếu biết trước số dòng cần xuất ra thì mọi thứ sẽ được đơn giản hóa:

Ối "4< NR && NR < 7" test.cpp

NR là biến awk chỉ định số dòng. Do đó, mã được trình bày xuất ra dòng thứ 5 và thứ 6:

Kiểm tra trống1(); int test2();

Lựa chọn các dòng dựa trên các điều kiện liên quan đến từng từ riêng lẻ

Awk có thể lọc văn bản không chỉ theo dòng mà còn theo từng từ riêng lẻ. Từ thứ i trong một dòng có thể được tham chiếu bằng cách sử dụng $i . Việc đánh số bắt đầu từ một và $0 xác định nội dung của toàn bộ dòng. Số lượng từ trong một dòng được xác định bằng biến NF, do đó $NF trỏ đến từ cuối cùng. Ví dụ: hãy tìm các chuỗi có từ đầu tiên là int hoặc void:

Awk "$1 == "int" || $1 == "void"" test.cpp

Đầu ra giao diện điều khiển tương ứng:

Kiểm tra trống1(); int test2(); int main(int argc, char** argv) ( void test1() ( int test2() (

Tuy nhiên, việc sử dụng tính năng kiểm tra biểu thức chính quy đối với một từ sẽ dễ dàng hơn. Để làm điều này, awk cung cấp một toán tử đặc biệt ~, toán tử này phải được đặt giữa biến trỏ tới từ và biểu thức chính quy. Ví dụ: hãy viết lại lệnh trước đó ở dạng gọn hơn:

Awk "$1 ~ /int|void/" test.cpp

Chọn hàng dựa trên đặc điểm số

Các toán tử số học của ngôn ngữ C có sẵn trong awk, giúp bạn tự do hành động. Ví dụ bên dưới in tất cả các dòng chẵn (NR là số dòng):

Awk "NR % 2 == 0" test.cpp

Đầu ra có liên quan:

#bao gồm int test2(); // Chú thích kiểu C cho hàm main() std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { } return 0; void test1() { } // Комментарий в стиле С для функции test2() std::cout << "Hello, test2!" << std::endl;

Chương trình awk sau đây in tất cả các dòng có từ đầu tiên có độ dài bằng ba:

Awk "length($1) == 3" test.cpp

Kết quả là chúng tôi nhận được:

Int test2(); int main(int argc, char** argv) ( int test2() (

Ôi "NF == 2" test.cpp

Và đầu ra tương ứng:

#bao gồm #bao gồm #bao gồm void test1(); int test2(); trả về 0;

Quảng cáo

Làm việc với chuỗi trong awk

Như bạn có thể thấy, awk có một bộ hàm tốt để lọc chuỗi văn bản. Tuy nhiên, bạn vẫn có thể thực hiện nhiều phép biến đổi khác nhau trên các chuỗi này. Các lệnh chuỗi phải được gói trong dấu ngoặc nhọn (...). Mã trong ngoặc đơn được gọi tuần tự cho từng dòng văn bản đang được xử lý.

Đầu ra được định dạng

Awk có chức năng tương đương trực tiếp với hàm printf() của ngôn ngữ C. Ví dụ: hãy in số của nó ở đầu mỗi dòng:

Awk "( printf "%-2d %s\n", NR, $0 )" test.cpp

Đây là những gì chúng tôi có:

1 #bao gồm 2 #bao gồm 3 #bao gồm 4 5 void test1(); 6 int test2(); 7 8 // Chú thích kiểu C cho hàm main() 9 int main(int argc, char** argv) ( 10 std::cout<< "Hello, world!" << std::endl; 11 12 for(int i = 0; i < 10; ++i) { 13 std::cout << i << std::endl; 14 } 15 16 return 0; 17 } 18 19 // Комментарий в стиле С для функции test1() 20 void test1() { 21 std::cout << "Hello, test1!" << std::endl; 22 } 23 24 // Комментарий в стиле С для функции test2() 25 int test2() { 26 std::cout << "Hello, test2!" << std::endl; 27 }

Hàm chuyển đổi

Ngoài printf(), awk còn có các chức năng khác. Ví dụ: print() và toupper() :

Awk "( print toupper($0) )" test.cpp

Kết quả liên quan:

#BAO GỒM #BAO GỒM #BAO GỒM KIỂM TRA VOID1(); INT TEST2(); // BÌNH LUẬN C-STYLE CHO MAIN() FUNCTION INT MAIN(INT ARGC, CHAR** ARGV) ( STD::COUT<< "HELLO, WORLD!" << STD::ENDL; FOR(INT I = 0; I < 10; ++I) { STD::COUT << I << STD::ENDL; } RETURN 0; } // КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST1() VOID TEST1() { STD::COUT << "HELLO, TEST1!" << STD::ENDL; } // КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST2() INT TEST2() { STD::COUT << "HELLO, TEST2!" << STD::ENDL; }

Câu điều kiện

Câu lệnh if-else có sẵn trong các chương trình awk. Ví dụ: đoạn mã sau in mà không thay đổi các dòng có int ở vị trí đầu tiên và ( ở vị trí cuối cùng, nếu không thì --- được gửi tới bảng điều khiển:

Awk " ( if($1 == "int" && $NF == "() print; else print "---" )" test.cpp

Chạy mã sẽ tạo ra kết quả sau:

Int main(int argc, char** argv) ( --- --- --- --- --- --- --- --- --- --- --- --- -- - --- --- int test2() ( --- ---

Biến

Các biến không cần khai báo trước cũng có sẵn trong chương trình awk. Đoạn mã sau để đếm số dòng và số từ trong văn bản sẽ được đặt trong tệp stat.awk:

( lineCount++; wordCount += NF ) END ( printf "số dòng: %d, số từ: %d\n", lineCount, wordCount )

Sau đó, nó được gọi như sau:

Awk -f stat.awk test.cpp

Kết quả thực hiện:

Số dòng: 27, số từ: 88

Bộ lọc END chỉ định rằng mã trong dấu ngoặc đơn chỉ được thực thi sau khi tất cả các dòng đã được duyệt qua. Bộ lọc BEGIN cũng có sẵn trong awk, vì vậy trong trường hợp tổng quát hơn, chương trình có dạng:

BEGIN (Được gọi trước khi quá trình duyệt hàng bắt đầu) (Được gọi cho mỗi hàng sau phần BEGIN, nhưng trước phần END) END (Được gọi sau khi quá trình duyệt hàng hoàn tất)

Wc -lw test.cpp

Chu kỳ

Trong các chương trình awk, bạn cũng có quyền truy cập vào các vòng lặp for và while kiểu C. Ví dụ: hãy in tất cả các dòng theo thứ tự ngược lại. Hãy tạo một tệp Reverse.awk với nội dung sau:

( for(i = NF; i > 0; --i) printf "%s ", $i; printf "\n" )

Hãy gọi chương trình như sau:

Awk -f đảo ngược.awk test.cpp

Kết quả các từ ở mỗi dòng sẽ được in theo thứ tự ngược lại:

#bao gồm #bao gồm #include test1(); void test2(); int main() các hàm dành cho kiểu C trong Comment // () argv char** argc, int main(int std::endl;<< world!" "Hello, << std::cout {) ++i 10; < i 0; = i int for(std::endl; << i << std::cout } 0; return } test1() функции для С стиле в Комментарий // { test1() void std::endl; << test1!" "Hello, << std::cout } test2() функции для С стиле в Комментарий // { test2() int std::endl; << test2!" "Hello, << std::cout }

Dấu phân cách từ không chuẩn

Theo mặc định, awk sử dụng các ký tự khoảng trắng làm dấu phân cách từ, nhưng hành vi này có thể được thay đổi. Để thực hiện việc này, hãy sử dụng khóa chuyển -F, theo sau là dòng xác định dấu phân cách. Ví dụ: chương trình sau hiển thị tên của một nhóm và người dùng của nhóm đó (nếu nhóm có người dùng) từ tệp /etc/group, sử dụng ký tự dấu hai chấm làm dấu phân cách:

Awk -F: "( if($4) printf "%15s: %s\n", $1, $4 )" /etc/group

Kết hợp các bộ lọc và lệnh in

Tất cả các bộ lọc đã thảo luận trước đây có thể được sử dụng cùng với các lệnh xử lý chuỗi. Chỉ cần viết các giới hạn trước dấu ngoặc nhọn là đủ. Dưới đây là ví dụ để in 9 dòng đầu tiên của đầu ra của lệnh ps, chứa thông tin về người dùng, ID tiến trình và tên lệnh:

Ps axu | ôi "NR< 10 { print $1, $2, $NF }"

Sau khi ra mắt chúng ta sẽ thấy:

NGƯỜI DÙNG PID LỆNH root 1 /sbin/init root 2 root 3 root 5 root 7 root 8 root 9 root 10

Giới thiệu về một ngôn ngữ tuyệt vời với một cái tên lạ

Chuỗi nội dung:

Để bảo vệ awk

Trong loạt bài viết này, tôi sẽ giúp người đọc trở thành một lập trình viên awk lành nghề. Tôi đồng ý rằng awk không có cái tên đẹp nhất hoặc thời thượng nhất và phiên bản GNU của awk, được gọi là gawk, nghe có vẻ hết sức kỳ lạ. Các lập trình viên không quen với ngôn ngữ này có thể nghe thấy tên của nó và tưởng tượng ra một mớ mã cổ xưa và lỗi thời có thể khiến ngay cả chuyên gia UNIX hiểu biết nhất cũng phát điên (khiến anh ta kêu lên "kill -9!" và liên tục chạy đi uống cà phê).

Vâng, awk không có một cái tên hay. Nhưng đó là một ngôn ngữ tuyệt vời. Awk được thiết kế để xử lý văn bản và báo cáo, nhưng nó có nhiều tính năng được phát triển tốt cho phép lập trình nghiêm túc. Tuy nhiên, không giống như một số ngôn ngữ khác, cú pháp của awk rất quen thuộc và vay mượn những gì tốt nhất từ ​​các ngôn ngữ như C, python và bash (mặc dù chính thức awk đã được tạo trước python và bash). Awk là một trong những ngôn ngữ mà một khi đã học được sẽ trở thành một phần quan trọng trong kho vũ khí chiến lược của lập trình viên.

Bước đầu tiên trong awk

Hãy bắt đầu và thử trải nghiệm với awk để xem nó hoạt động như thế nào nhé. Tại dòng lệnh, nhập lệnh sau:

$awk "( print )" /etc/passwd

Kết quả sẽ hiển thị nội dung của tệp /etc/passwd. Bây giờ - giải thích về những gì awk đã làm. Khi gọi awk, chúng tôi đã chỉ định /etc/passwd làm tệp đầu vào. Khi chúng tôi chạy awk, nó xử lý lệnh in cho từng dòng trong /etc/passwd theo thứ tự. Tất cả đầu ra được gửi đến thiết bị xuất chuẩn và chúng tôi nhận được kết quả tương tự như cat /etc/passwd. Bây giờ hãy giải thích khối (in). Trong awk, dấu ngoặc nhọn được sử dụng để nhóm các khối văn bản, giống như trong C. Trong khối văn bản của chúng ta, chỉ có một lệnh in. Trong awk, lệnh in không có bất kỳ tham số bổ sung nào sẽ in toàn bộ nội dung của dòng hiện tại.

Đây là một ví dụ khác về chương trình awk thực hiện điều tương tự:

$awk "( print $0 )" /etc/passwd

Trong awk, biến $0 đại diện cho toàn bộ dòng hiện tại, vì vậy print và print $0 thực hiện chính xác điều tương tự. Nếu muốn, bạn có thể tạo một chương trình trong awk để xuất dữ liệu hoàn toàn không liên quan đến dữ liệu đầu vào. Đây là một ví dụ:

$awk "( print "" )" /etc/passwd

Khi bạn truyền chuỗi "" cho lệnh in, nó luôn in một chuỗi trống. Nếu bạn kiểm tra tập lệnh này, bạn sẽ thấy rằng awk xuất ra một dòng trống cho mỗi dòng trong /etc/passwd. Điều này một lần nữa xảy ra vì awk thực thi một tập lệnh cho mỗi dòng trong tệp đầu vào. Đây là một ví dụ khác:

$awk "( print "hiya")" /etc/passwd

Nếu bạn chạy tập lệnh này, nó sẽ lấp đầy màn hình với dòng chữ "yay". :)

Nhiều trường

Awk rất phù hợp để xử lý văn bản được chia thành nhiều trường logic và giúp bạn dễ dàng truy cập từng trường riêng lẻ từ bên trong tập lệnh awk. Đoạn script sau sẽ in danh sách tất cả các tài khoản trên hệ thống:

$ awk -F: "( print $1 )" /etc/passwd

Trong lệnh gọi awk trong ví dụ trên, tùy chọn –F chỉ định ://: dấu phân cách trường. Khi xử lý lệnh print $1, awk sẽ in trường đầu tiên gặp trên mỗi dòng của tệp đầu vào. Đây là một ví dụ khác:

$ awk -F: "( print $1 $3 )" /etc/passwd

Đây là một đoạn trích từ màn hình đầu ra của tập lệnh này:

dừng7 toán tử11 root0 tắt máy6 sync5 bin1 ....v.v.

Như bạn có thể thấy, awk xuất ra trường thứ nhất và thứ ba của tệp /etc/passwd, tương ứng là trường tên người dùng và uid. Đồng thời, mặc dù tập lệnh hoạt động nhưng nó không hoàn hảo - không có khoảng cách giữa hai trường đầu ra! Những người đã quen với việc lập trình bằng bash hoặc python có thể mong đợi lệnh print $1 $3 sẽ chèn khoảng trắng giữa hai trường này. Tuy nhiên, khi hai dòng nằm cạnh nhau trong chương trình awk, awk sẽ nối chúng mà không thêm khoảng trắng giữa chúng. Lệnh sau sẽ chèn khoảng trắng giữa các trường:

$ awk -F: "( print $1 " " $3 )" /etc/passwd

Khi bản in được gọi theo cách này, nó nối $1 , " " và $3 thành chuỗi, tạo ra kết quả mà con người có thể đọc được trên màn hình. Tất nhiên, chúng ta cũng có thể chèn nhãn trường nếu cần:

$ awk -F: "( print "tên người dùng: " $1 "\t\tuid: " $3" )" /etc/passwd

Kết quả là, chúng tôi nhận được kết luận sau:

tên người dùng: dừng uid:7 tên người dùng: toán tử uid:11 tên người dùng: root uid:0 tên người dùng: tắt máy uid:6 tên người dùng: sync uid:5 tên người dùng: bin uid:1 ....

Tập lệnh bên ngoài

Việc chuyển các tập lệnh tới awk làm đối số dòng lệnh có thể thuận tiện cho những người viết một dòng nhỏ, nhưng khi nói đến các chương trình nhiều dòng phức tạp, chắc chắn tốt hơn là soạn tập lệnh dưới dạng tệp bên ngoài. Sau đó, bạn có thể trỏ awk tới tệp tập lệnh này bằng tùy chọn -f:

$ awk -f myscript.awk myfile.in

Việc đặt tập lệnh vào các tệp văn bản riêng biệt cũng cho phép bạn tận dụng các lợi ích bổ sung của awk. Ví dụ: tập lệnh nhiều dòng sau đây thực hiện tương tự như một trong các tập lệnh một dòng trước đó của chúng tôi - in trường đầu tiên của mỗi dòng từ /etc/passwd:

BẮT ĐẦU ( FS=:// ) ( in $1 )

Sự khác biệt giữa hai phương pháp này là cách chúng tôi chỉ định dấu phân cách trường. Trong tập lệnh này, dấu phân cách trường được chính chương trình chỉ định nội bộ (bằng cách đặt biến FS), trong khi ở ví dụ trước của chúng tôi, FS được định cấu hình bằng cách chuyển awk tùy chọn -F: // trên dòng lệnh. Thông thường, tốt nhất bạn nên chỉ định dấu phân cách trường trong chính tập lệnh, đơn giản vì nó sẽ không yêu cầu bạn phải nhớ một đối số dòng lệnh khác. Chúng ta sẽ xem xét biến FS chi tiết hơn ở phần sau của bài viết này.

Khối BEGIN và END

Thông thường, awk thực thi mỗi khối trong văn bản tập lệnh một lần cho mỗi dòng đầu vào. Tuy nhiên, thường có những tình huống trong lập trình mà bạn cần thực thi mã khởi tạo trước khi awk bắt đầu xử lý văn bản từ tệp đầu vào. Đối với những trường hợp như vậy, awk cung cấp khả năng xác định khối BEGIN. Chúng ta đã sử dụng khối BEGIN trong ví dụ trước. Vì khối BEGIN được xử lý trước khi awk bắt đầu xử lý tệp đầu vào, đây là nơi tuyệt vời để khởi tạo biến FS (dấu tách trường), xuất tiêu đề hoặc khởi tạo các biến toàn cục khác sẽ được sử dụng sau này trong chương trình.

Awk cũng cung cấp một khối đặc biệt khác gọi là khối END. Awk thực thi khối này sau khi tất cả các dòng trong tệp đầu vào đã được xử lý. Thông thường, khối END được sử dụng để thực hiện các phép tính cuối cùng hoặc kết quả đầu ra sẽ xuất hiện ở cuối luồng đầu ra.

Biểu thức và khối thông thường

Awk cho phép bạn sử dụng các biểu thức chính quy để thực thi có chọn lọc các khối cụ thể của chương trình tùy thuộc vào việc biểu thức chính quy có khớp với dòng hiện tại hay không. Đây là một tập lệnh mẫu chỉ in những dòng chứa chuỗi ký tự foo:

/foo/ (in)

Tất nhiên, bạn có thể sử dụng các biểu thức chính quy phức tạp hơn. Đây là tập lệnh sẽ chỉ xuất ra các dòng có chứa dấu phẩy:

/+\.*/ ( in )

Biểu thức và khối

Có nhiều cách khác để thực thi có chọn lọc một khối chương trình. Chúng ta có thể đặt bất kỳ biểu thức Boolean nào trước khối chương trình để điều khiển việc thực thi khối đó. Awk sẽ chỉ thực thi một khối chương trình nếu biểu thức Boolean trước đó có giá trị đúng. Tập lệnh ví dụ sau sẽ xuất ra trường thứ ba của tất cả các dòng trong đó trường đầu tiên là fred . Nếu trường đầu tiên của dòng hiện tại không phải là fred , awk sẽ tiếp tục xử lý tệp và sẽ không đưa ra câu lệnh in cho dòng hiện tại: :

$1 == "fred" ( in $3 )

Awk cung cấp một bộ đầy đủ các toán tử so sánh, bao gồm cả các toán tử "==", " thông thường<", ">", "<=", ">=" và "!=". Ngoài ra, awk còn cung cấp các toán tử "~" và "!~", có nghĩa là "khớp" và "không khớp". Chúng đặt biến ở bên trái toán tử và biểu thức chính quy ở bên phải của nó. Đây là một ví dụ trong đó chỉ trường thứ ba của một dòng được in nếu trường thứ năm của cùng dòng chứa gốc của chuỗi ký tự:

$5 ~ /root/ ( in $3 )

Câu điều kiện

Awk cũng cung cấp các câu lệnh if giống C rất hay. Nếu muốn, bạn có thể viết lại tập lệnh trước đó bằng cách sử dụng if:

( if ($5 ~ /root/) ( in $3 ) )

Cả hai tập lệnh đều hoạt động giống hệt nhau. Trong ví dụ đầu tiên, biểu thức boolean nằm ngoài khối, trong khi ở ví dụ thứ hai, khối được thực thi cho mỗi dòng đầu vào và chúng tôi thực thi có chọn lọc lệnh in bằng câu lệnh if. Cả hai phương thức đều hoạt động và bạn có thể chọn một phương thức. kết hợp tốt nhất với các phần khác của kịch bản.

Đây là một ví dụ phức tạp hơn về câu lệnh if trong awk. Như bạn có thể thấy, ngay cả với các điều kiện lồng nhau phức tạp, các câu lệnh if trông giống hệt với câu lệnh C của chúng:

( if ($1 == "foo") ( if ($2 == "foo") ( print "uno" ) else ( print "one" ) ) else if ($1 == "bar") ( print "two" ) khác ( in "ba") )

Sử dụng câu lệnh if, chúng ta có thể chuyển đổi mã này:

! /matchme/ ( in $1 $3 $4 )( if ($0 !~ /matchme/) ( in $1 $3 $4 ) )

Cả hai tập lệnh sẽ chỉ in những dòng Không chứa chuỗi ký tự matchme . Và trong trường hợp này cũng vậy, bạn có thể chọn một phương pháp hoạt động tốt hơn trong một chương trình cụ thể. Cả hai đều làm điều tương tự.

Awk cũng cung cấp cho bạn khả năng sử dụng các toán tử Boolean "||" (“logic OR”) và “&&” (“logic AND”), cho phép bạn tạo các biểu thức Boolean phức tạp hơn:

($1 == "foo") && ($2 == "bar") ( print )

Ví dụ này sẽ chỉ xuất ra các dòng có trường đầu tiên là foo trường thứ hai là bar .

Biến số!

Cho đến nay chúng tôi đã in các biến chuỗi, toàn bộ chuỗi hoặc các trường cụ thể. Tuy nhiên, awk cũng cho chúng ta khả năng thực hiện so sánh trên cả số nguyên và số dấu phẩy động. Sử dụng các biểu thức toán học, rất dễ dàng để viết một tập lệnh đếm số dòng trống trong một tệp. Đây là một kịch bản như vậy:

BEGIN ( x=0 ) /^$/ ( x=x+1 ) END ( in "Đã tìm thấy " x " dòng trống. :))" )

Trong khối BEGIN, chúng ta khởi tạo biến số nguyên x về 0. Sau đó, mỗi khi awk gặp một dòng trống, nó sẽ thực thi câu lệnh x=x+1 , tăng x thêm 1. Khi tất cả các dòng đã được xử lý, khối END sẽ được thực thi và awk sẽ in tổng số cuối cùng, cho biết số dòng trống được tìm thấy.

Biến chuỗi

Một trong những điều thú vị về các biến awk là chúng là "chữ thường và chữ thường". Tôi gọi các biến awk là "chuỗi" vì tất cả các biến awk đều được lưu trữ nội bộ dưới dạng chuỗi. Đồng thời, các biến awk rất "đơn giản" vì bạn có thể thực hiện phép toán trên một biến và nếu nó chứa một chuỗi số hợp lệ, awk sẽ tự động đảm nhiệm việc chuyển đổi chuỗi đó thành một số. Để hiểu ý tôi, hãy xem ví dụ này:

x="1.01" # Chúng tôi đã tạo x chứa *string* "1.01" x=x+1 # Chúng tôi vừa thêm 1 vào *string* print x # Đây chỉ là một nhận xét :)

Awk sẽ xuất ra:

2.01

Tò mò! Mặc dù chúng tôi đã gán giá trị chuỗi 1,01 cho x nhưng chúng tôi vẫn có thể thêm một giá trị vào chuỗi đó. Chúng tôi không thể làm điều này trong bash hoặc python. Trước hết, bash không hỗ trợ số học dấu phẩy động. Và mặc dù bash có các biến "chuỗi", nhưng chúng không "đơn giản"; Để thực hiện bất kỳ phép toán nào, bash yêu cầu chúng ta gói các phép tính của mình trong các cấu trúc $() xấu xí. Nếu chúng ta đang sử dụng python, chúng ta sẽ cần chuyển đổi rõ ràng chuỗi 1.01 thành giá trị dấu phẩy động trước khi thực hiện bất kỳ phép tính nào với nó. Mặc dù điều này không khó nhưng vẫn là một bước bổ sung. Trong trường hợp awk, tất cả điều này được thực hiện tự động và nó làm cho mã của chúng tôi đẹp và rõ ràng. Nếu cần bình phương trường đầu tiên của mỗi chuỗi đầu vào và thêm một trường vào đó, chúng ta sẽ sử dụng tập lệnh như sau:

( in ($1^2)+1 )

Nếu thử nghiệm một chút, bạn sẽ thấy rằng nếu một biến không chứa số hợp lệ, awk sẽ coi biến đó là số 0 khi đánh giá một biểu thức toán học.

Nhiều nhà khai thác

Một tính năng thú vị khác của awk là bộ toán tử hoàn chỉnh. Ngoài phép cộng, trừ, nhân và chia tiêu chuẩn, awk còn cho chúng ta khả năng sử dụng toán tử số mũ đã được trình bày trước đó "^", toán tử số dư chia số nguyên "%" và nhiều toán tử gán thuận tiện khác mượn từ C.

Chúng bao gồm các toán tử gán trước và sau tăng/giảm (i++, --foo), các toán tử gán với phép cộng/trừ/nhân/chia (a+=3, b*=2, c/=2.2, d-=6.2) . Nhưng đó không phải là tất cả - chúng tôi còn có các toán tử gán thuận tiện để tính phần còn lại của phép chia số nguyên và lũy thừa (a^=2, b%=4).

Dấu phân cách trường

awk có bộ biến đặc biệt riêng. Một số trong số chúng cung cấp cho bạn khả năng tinh chỉnh cách hoạt động của awk, trong khi một số khác chứa thông tin đầu vào có giá trị. Chúng ta đã đề cập đến một trong những biến đặc biệt này, FS. Như đã đề cập trước đó, biến này cho phép bạn chỉ định chuỗi ký tự mà awk sẽ coi là dấu phân cách trường. Khi chúng tôi sử dụng /etc/passwd làm đầu vào, FS được đặt thành ://. Điều này hóa ra là đủ, nhưng FS còn mang lại cho chúng tôi sự linh hoạt hơn nữa.

Giá trị của biến FS không nhất thiết phải là một ký tự đơn; nó có thể được gán một biểu thức chính quy chỉ định mẫu ký tự có độ dài bất kỳ. Nếu bạn đang xử lý các trường được phân tách bằng một hoặc nhiều ký tự tab thì FS phải được định cấu hình như sau:

FS="\t+"

Ở trên chúng tôi đã sử dụng ký tự biểu thức chính quy đặc biệt "+", có nghĩa là "một hoặc nhiều lần xuất hiện của ký tự trước đó".

Nếu các trường được phân tách bằng khoảng trắng (một hoặc nhiều dấu cách hoặc tab), bạn có thể muốn đặt FS thành biểu thức chính quy sau:

FS="[[:dấu cách:]+]"

Mặc dù thiết lập này sẽ hoạt động nhưng nó không cần thiết. Tại sao? Bởi vì giá trị mặc định của FS là một ký tự khoảng trắng, được awk hiểu là "một hoặc nhiều khoảng trắng hoặc tab". Trong ví dụ cụ thể của chúng tôi, giá trị FS mặc định chính xác là những gì chúng tôi cần!

Cũng không có vấn đề gì với các biểu thức chính quy phức tạp. Ngay cả khi các bản ghi được phân tách bằng từ "foo" theo sau là ba chữ số, biểu thức chính quy sau đây sẽ phân tích dữ liệu một cách chính xác:

FS="foo"

Số lượng trường

Hai biến tiếp theo mà chúng ta sẽ xem xét thường không được dùng để ghi vào mà được sử dụng để đọc và lấy thông tin hữu ích về đầu vào. Đầu tiên trong số này là biến NF, còn được gọi là "số trường". Awk tự động đặt giá trị của biến này theo số trường trong bản ghi hiện tại. Bạn có thể sử dụng biến NF để chỉ hiển thị một số dòng đầu vào nhất định:

NF == 3 ( print "có ba trường trong mục này: " $0 )

Tất nhiên, biến NF cũng có thể được sử dụng trong các câu lệnh điều kiện, ví dụ:

( if (NF > 2) ( print $1 " " $2 ://: // $3 ) )

Con số kỷ lục

Một biến thuận tiện khác là số bản ghi (NR). Nó luôn chứa số của bản ghi hiện tại (awk coi bản ghi đầu tiên là bản ghi số 1). Cho đến nay chúng ta đã xử lý các tệp đầu vào chứa một bản ghi trên mỗi dòng. Trong những tình huống như vậy, NR cũng sẽ báo cáo số dòng hiện tại. Tuy nhiên, khi chúng ta bắt đầu xử lý các bản ghi nhiều dòng ở các phần sau của loạt bài này, điều này sẽ không còn xảy ra nữa nên bạn cần phải cẩn thận! NR có thể được sử dụng giống như biến NF để chỉ xuất ra một số dòng đầu vào nhất định:

(NR< 10) || (NR >100) ( in "Chúng tôi đang ở số kỷ lục 1-9 hoặc 101 trở lên")

Một ví dụ nữa:

( #skip tiêu đề if (NR > 10) ( print "bây giờ đã có thông tin thực sự!" ) )

Awk cung cấp các biến bổ sung có thể được sử dụng cho nhiều mục đích khác nhau. Chúng ta sẽ xem xét các biến này trong các bài viết sau. Chúng ta đã kết thúc quá trình khám phá ban đầu về awk. Trong các bài viết tiếp theo của loạt bài này, tôi sẽ trình bày chức năng awk nâng cao hơn và chúng tôi sẽ kết thúc loạt bài này bằng một ứng dụng awk trong thế giới thực. Trong thời gian chờ đợi, nếu muốn tìm hiểu thêm, bạn có thể xem các tài nguyên được liệt kê bên dưới.

Mục đích chính của chương trình ôi- giải thích ngôn ngữ lập trình awk, cho phép bạn nhanh chóng tạo một chương trình để phân tích và chuyển đổi các tệp văn bản. Một tập lệnh awk điển hình trông như thế này:

mẫu1 (hành động1) mẫu2 (hành động2) ...

Chương trình đọc tệp hoặc luồng đầu vào và phân tích từng dòng để tìm mẫu khớp. Nếu trùng khớp, các hành động được mô tả trong dấu ngoặc nhọn sẽ được thực hiện. Nếu mẫu bị thiếu, hành động sẽ được áp dụng cho từng hàng. Nếu không có hành động nào, dòng này sẽ được in ra đầu ra tiêu chuẩn. Một hành động có thể bao gồm một chuỗi các câu lệnh được phân tách bằng dấu chấm phẩy.

In trường thứ ba (cột, từ) của mỗi dòng:

Mã số:

ls -l | ôi "(in($3))"

In hai trường được chỉ định của mỗi dòng:

Mã số:

ls -l | awk "(print($9,$1))"

Các trường in có dấu cách:

Mã số:

ls -l | awk "(print($9," ",$1))"

Để chỉ định dấu tách trường không phải dấu cách, hãy sử dụng tùy chọn -F. Trong trường hợp này, dấu phân cách trường sẽ là dấu hai chấm:

Mã số:

Awk -F: "(print($2))" $filenameForProcessing

Để sử dụng tập lệnh awk được lưu trong một tệp:

Mã số:

Ôi -f $scriptFilename $filenameForProcessing

Tệp tập lệnh awk có thể được thực thi và sha-bang tương ứng được chỉ định trong đó. Tập lệnh như vậy sẽ lấy một tệp để xử lý làm tham số:

Mã số:

#!/usr/bin/awk -f

Các biến awk được tạo trong lần truy cập đầu tiên và có thể chứa số nguyên, số float hoặc chuỗi, tùy theo ngữ cảnh xác định. Biến đặc biệt RS lưu trữ giá trị của dấu phân cách bản ghi (\n theo mặc định) và biến FS lưu giá trị của dấu phân cách trường (mặc định - dấu cách). Nếu bất kỳ biến nào trong số này chứa nhiều hơn một ký tự, giá trị đó sẽ được hiểu là biểu thức chính quy. Ngôn ngữ awk chứa một số hàm toán học và chuỗi tích hợp, câu lệnh điều kiện và câu lệnh lặp, hỗ trợ mảng và xác định các hàm do người dùng xác định. Trên Internet, bạn có thể tìm thấy các hướng dẫn sử dụng rộng rãi về ngôn ngữ awk, cũng như các trình dịch tự động (“trình dịch”) các tập lệnh awk sang các ngôn ngữ khác (ví dụ: C hoặc Perl).

Các loại mẫu đặc biệt là BEGIN và END. Chúng không được kiểm tra đối với các mục nhập luồng đầu vào. Hành động mẫu BEGIN sẽ được thực thi một lần trước khi dữ liệu đầu vào được đọc và hành động mẫu KẾT THÚC sẽ được thực hiện một lần sau khi dữ liệu đầu vào được đọc.

Một ví dụ về loại bỏ các dòng trùng lặp liền kề trong một tệp:

Mã số:

Tên tệp=test.txt
res=`awk "BEGIN (PREV="") (if ($0 != PREV) (in $0; PREV=$0))" $filename`
echo "$res" > $filename

Trước khi bắt đầu, chúng ta đặt biến PREV thành chuỗi trống. Phần còn lại của tập lệnh awk không có mẫu và do đó được thực thi trên bất kỳ chuỗi nào. Nếu dòng hiện tại không phải là PREV, nó sẽ được in và ghi vào biến PREV. Như vậy, khi xử lý từng dòng nếu bằng dòng trước thì sẽ không in được.

Ví dụ về nối trường:

Mã số:

Ồ "(a = $3 $4; in a)" $filename

Ví dụ về các giá trị trường tổng hợp:

Mã số:

Ồ "(a = $3+$4; in a)" $filename

Khái niệm "bộ chọn" nên được hiểu là phần mở rộng của khái niệm "mẫu". Nói chung, khi một mẫu được chỉ định trong cấu trúc lệnh, bất kỳ bộ chọn nào cũng có thể xuất hiện.

Kiểm tra trường thứ ba theo biểu thức chính quy và in toàn bộ dòng nếu thành công:

Mã số:

Ồ "$3~/(7)$/ (print)" $filename

Kiểm tra sự bằng nhau của trường đầu tiên với chuỗi đã chỉ định và in trường thứ hai nếu thành công.

Văn bản là trái tim của Unix. Triết lý "mọi thứ đều là một tập tin" thấm vào toàn bộ hệ thống và các công cụ được phát triển cho nó. Đó là lý do tại sao làm việc
bằng văn bản là một trong những kỹ năng cần thiết đối với quản trị viên hệ thống hoặc người mới sử dụng Linux.

AWK là một trong những công cụ mạnh mẽ nhất để xử lý và lọc văn bản, có thể truy cập được ngay cả với những người không liên quan gì đến lập trình.

Sử dụng awk trên Linux

Tác vụ đơn giản và được sử dụng thường xuyên nhất là tìm nạp các trường từ đầu ra tiêu chuẩn. Bạn sẽ không tìm thấy công cụ nào tốt hơn cho nhiệm vụ này ngoài awk. Theo mặc định, awk phân tách các trường bằng dấu cách. Nếu bạn muốn in trường đầu tiên, bạn chỉ cần cung cấp cho awk tham số $1:

echo "một hai ba bốn" | ôi "(in $1)"

Đúng, việc sử dụng dấu ngoặc nhọn hơi khác thường nhưng đây chỉ là lần đầu tiên. Bạn đã đoán được cách in trường thứ hai, thứ ba, thứ tư hoặc các trường khác chưa? Đúng là lần lượt là $2, $3, $4.

echo "một hai ba bốn" | ôi "(in $3)"

Đôi khi cần trình bày dữ liệu theo một định dạng cụ thể, chẳng hạn như chọn một vài từ. AWK dễ dàng xử lý việc nhóm nhiều trường và thậm chí cho phép bạn bao gồm dữ liệu tĩnh:

echo "một hai ba bốn" | awk "(in $3, $1)"
ba một

echo "một hai ba bốn" | awk "(print "foo:",$3,"| bar:",$1)"
foo: ba | thanh: một

Nếu các trường được phân tách bằng dấu phân cách không phải dấu cách, chỉ cần chỉ định dấu phân cách mong muốn trong dấu ngoặc kép trong tham số -F, ví dụ: ://:

echo "một mississippi:hai mississippi:ba mississippi:bốn mississippi" | awk -F: "(in $4)"
bốn Mississippi

Nhưng dấu phân cách không nhất thiết phải được đặt trong dấu ngoặc kép. Đầu ra sau đây tương tự như đầu ra trước đó:

echo "một mississippi:hai mississippi:ba mississippi:bốn mississippi" | awk -F: "(in $4)"
bốn Mississippi

Đôi khi bạn cần xử lý dữ liệu với số lượng trường không xác định. Nếu bạn cần chọn trường cuối cùng, bạn có thể sử dụng biến $NF. Đây là cách bạn có thể hiển thị trường cuối cùng:

echo "một hai ba bốn" | ôi "(in $NF)"
bốn

Bạn cũng có thể sử dụng biến $NF để lấy trường thứ hai đến trường cuối cùng:

echo "một hai ba bốn" | awk "(in $(NF-1))"
ba

Hoặc các trường từ giữa:

echo "một hai ba bốn" | awk "(in $((NF/2)+1))"
ba

echo "một hai ba bốn năm" | awk "(in $((NF/2)+1))"
ba

Tất cả điều này có thể được thực hiện bằng cách sử dụng các tiện ích như sed, cut và grep, nhưng sẽ khó khăn hơn nhiều.

Và một tính năng nữa của awk, hỗ trợ trình xử lý chuỗi:

echo -e "một 1\n hai 2" | ôi "(in $1)"
một
hai

echo -e "một 1\n hai 2" | ôi "(in $2)"
1
2

echo -e "một 1\n hai 2" | awk "(sum+=$2) END (in tổng)"
3

Điều này có nghĩa là chúng ta phải thực thi khối mã sau cho mỗi dòng. Ví dụ: điều này có thể được sử dụng để đếm lượng dữ liệu được truyền cho các yêu cầu từ nhật ký máy chủ web.

Hãy tưởng tượng chúng ta có nhật ký truy cập trông như thế này:

Ngày 23 tháng 7 18:57:12 HTTPD: "GET/foo/bar HTTP/1.1" 200.344
Ngày 23 tháng 7 18:57:13 HTTPD: "GET/HTTP/1.1" 200 9300
23 tháng 7 19:01:27 HTTPD : "GET / HTTP / 1.1" 200 9300
Ngày 23 tháng 7 19:01:55 HTTPD : "GET / Foo / Baz HTTP / 1.1" 200 6401
Ngày 23 tháng 7 19:02:31 HTTPD: "? Trang GET/Foo/Baz = 2 HTTP/1.1" 200 6312

Chúng ta biết rằng trường cuối cùng là số byte được truyền, khi đó chúng ta có thể sử dụng biến $NF:

< requests.log awk "{print $NF}"
344
9300
9300