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
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
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
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
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
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
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
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:
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/passwdKế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/passwdTrong 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/passwdKhi 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/passwdNế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/passwdTrong 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/passwdKhi 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/passwdKế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.inViệ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 Và 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.01Tò 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