Bash Script – Đánh tan lờ đờ, mệt mỏi của repetitive tasks với Loops và Function

Kết thúc nội dung Bash Script – Dàn cảnh và phức tạp hóa vấn đề với Conditional statements (If) và Boolean Logical Operations (And & Or), tôi đã tạm bàn xong câu chuyện về Conditional statements. Và khi bàn về programming, sẽ thật thiếu sót nếu chỉ nói về Conditional statements mà không bàn về Loops (Vòng lặp). Loops với 2 đại diện nổi bật là ForWhile sẽ đặc biệt hữu ích trong việc xử lý đám công việc nhàm chán cứ lập đi lập lại (repetitive tasks) mà sếp bắt bạn phải hốt.

Bên cạnh đó, để triệt hạ tận gốc đám repetitive tasks, ngoài Loops, bạn cũng sẽ cần thêm trợ giúp của một thứ có tên gọi là Function.

#1 Giữ đam mê để chạy repetitive tasks với Loops trong Bash Script

#1.1 For dùng để làm gì?

Để hiểu nó dùng để làm gì, mời bạn xem qua cú pháp cơ bản của thằng For như sau:

for item in <list>

do

        <action>

done

Trong đó:

  • item: là một phần tử thuộc <list>;
  • <list>: tạm hiểu là một “danh sách” chứa các item (Tôi biết, cái này đúng kiểu giải thích lòng vòng. Bạn chịu khó đợi tí xuống ví dụ cụ thể tôi sẽ làm rõ hơn);
  • <action>: hành động nào đó (có thể liên quan đến item hoặc không)

Như vậy, ứng với số lượng n item trong <list>, bạn sẽ triển một <action> nào đó, lập đi lập lại n lần.

Tôi chiến thử một cái ví dụ cụ thể sau (viết gọn theo kiểu Bash one-liners):

for number in {1..5}; do echo 10.11.1.$number;done

Lưu ý:

  • Lúc này item (tương ứng với number trong ví dụ trên) sẽ là một con số trong <list> gồm 5 số từ 1 đến 5;
  • Action tương ứng là Echo ra cái IP address.

Kết quả chạy thử:

test for
test for

Ví dụ trên tôi minh họa với Echo cho dễ hình dung, thực tế bạn có thể triển các thứ hữu ích hơn như ping hoặc chạy nmap với các IP Address nói trên.

#1.2 Trời đã sinh For sao còn sinh While?

Nếu nghĩ rằng For có thể giúp bạn một tay che trời thì bạn đã nhầm. Thay vì giao cho một “list” công việc rõ ràng, sếp bạn có thể chơi khó bằng cách chỉ đạo “Làm cái X này cho anh đến khi thấy nó ra Y thì mới được nghỉ”. Đấy có thể là dấu hiệu của sự vùi dập hoặc là một chỉ dẫn cho biết bạn nên cất thằng For và rút thằng While ra. Cú pháp cơ bản của thằng While như sau:

while [ <condition> ]

do

        < action>

done

Với While, mọi thứ tương tự như For. Tuy nhiên, thay vì hùng hục xúc hết các item bên trong <list>, While sẽ tinh tế kiểm tra <condition> tương tự như If, chừng nào kết quả kiểm tra còn True thì nó còn mần <action> (và nếu <action> dẫn đến việc kiểm tra <condition> ra False thì nghỉ).

Tôi minh họa với file ví dụ test_while.sh có nội dung như sau:

#!/bin/bash

counter=1

while [ $counter -le 5 ]

do

        echo "10.11.1.$counter"

        ((counter++))

done

test while
test while

Kết quả chạy thử:

test while result
test while result

Lưu ý: Vấn đề xử lý với text editor và cấp quyền thực thi cho script bạn coi lại mấy kỳ trước nếu chưa biết nhé.

Trong ví dụ này, <condition> tương ứng với “$counter -le 5” (bạn nhớ để ý khoảng trắng bên trong “[]”). Như vậy, chừng nào counter còn nhỏ hơn 5 (-leless than, tôi có nói kỹ trong kỳ trước rồi) thì script còn tiếp tục Echo ra các IP Address.

Ngoài ra, do dòng ((counter++)) trong action nên giá trị của counter sẽ tăng dần (ban đầu là 1) cho đến khi nó làm cho <condition> trả về False thì vòng lặp bị ngắt.

#2 Tăng độ sát thương với Function trong Bash Script

#2.1 Function là cái vẹo gì?

Sẽ không đến nỗi quá sai trái nếu bạn lập lại một đoạn code 1 lần. Tuy nhiên, sẽ là tội ác nếu bạn lạm dùng trò copy/paste cho một đoạn code quá nhiều lần trong script. Đó là lúc khái niệm Function tỏa sáng.

Function có thể hiểu là “script within a script”, dùng để “đặt nhân tử chung” một đoạn code mà bạn phải triển nhiều lần. Nghĩa là thay vì liên tục copy/paste thì bạn sẽ định nghĩa Function một lần và sau đó tha hồ sử dụng lại thông qua việc gọi function_name. Cú pháp của Function trong Bash Script có dạng như sau:

function function_name {

        commands...

}

Và như mọi khi, tôi minh họa với file ví dụ test_function.sh có nội dung như sau:

#!/bin/bash

meme_function () {

        echo "Peter! Stop! Stop! It’s me!"

}

meme_function

test function
test function

Lưu ý: Bạn phải định nghĩa Function (meme_function trong ví dụ trên) trước rồi mới xài nó thông qua lệnh gọi meme_function ở dòng cuối cùng được (hiển nhiên rồi, sao bạn xài/gọi thứ bạn chưa có được???). 

Kết quả chạy thử:

test function result
test function result

Như bạn thấy, cái Bash Script trên nhạt như nước ốc vì chỉ bao gồm định nghĩa một Function in ra text xác định trước và sau đó chạy nó. Nếu chỉ dùng Function như thế này thì có vẻ hơi dở người. Thực tế, bạn sẽ đụng các Bash Script phức tạp hơn, ví dụ như cái đám tôi giới thiệu ngay sau đây.

#2.2 Tăng độ ảo diệu cho Function với Argument và Variable

Trong nội dung Bash Script – La liếm với bộ 3 Variable, Argument & User Input, tôi đã giới thiệu về việc sử dụng Argument Variable trong Bash Script. Ở đây, trước khi tiếp tục cho đám này chui sâu vào bên trong Function của script, tôi xin nhắc lại cái bảng sau (đã rút gọn bớt cho dễ theo dõi).

Tên biến Mô tả
$1 – $9 9 argument đầu của Bash script
$? Exit status của process chạy gần nhất
$RANDOM Một số ngẫu nhiên

Tôi minh họa trước cho Argument với ví dụ test_function_arg.sh có nội dung như sau:

#!/bin/bash

test_function_arg() {

        echo "What did it cost? $1"

}

test_function_arg $"Everything"

test function argument
test function argument

Lưu ý:

  • $1 là argument đầu tiên như bảng tôi nhắc lại ở trên, ở đây nó là “Everything”;
  • ()” trong test_arg() chỉ để trang trí (dòng cuối của script nó gọi Function theo cú pháp “test_function_arg + space + argument” ) chứ không nhận các arguments vào bên trong như Python theo kiểu test_function_arg(argument) ở dòng cuối cùng;

Kết quả chạy thử:

test function argument result
test function argument result

#2.3 Giá trị trả về của Function trong Bash Script

Tiếp tục phức tạp hóa vấn đề, bạn cũng có thể ép Function phọt ra giá trị để sử dụng cho các mưu đồ thâm sâu khác. Tuy nhiên, cần lưu ý (nhất là mấy ông đã biết lập trình sơ sơ), Bash Function không thực sự cho phép trả về một giá trị tùy ý kiểu truyền thống như các ngôn ngữ khác mà nó có thể trả về exit status (zero nếu chạy ngon và non-zero nếu bị lỗi) hoặc một giá trị bạn gán để sau đó truy cập lại thông qua Global Variable $? (Exit status của process chạy gần nhất). Ngoài ra, bạn cũng có thể dùng giải pháp thay thế là command substitution như nội dung Bash Script – La liếm với bộ 3 Variable, Argument & User Input) hoặc thiết lập Global Variable bên trong Function như nội dung ở Mục 2.4.

Phần này hơi rối rắm, tôi xúc ngay cái file ví dụ test_function_return.sh có nội dung như sau để làm rõ:

#!/bin/bash

test_function_return() {

        echo "What did it cost?"

}

test_function_return

echo "It costs $?"

test function return 1
test function return 1

Kết quả chạy thử:

test function return 1 result
test function return 1 result

Phía trên bạn thấy nó chạy ngon nên $? sẽ có giá trị 0. Tôi đổi lại thử như sau:

#!/bin/bash

test_function_return() {

        echo "What did it cost?"

        return $"Everything"

}

test_function_return

echo "It costs $?"

test function return 2
test function return 2

Tôi chạy lại thử:

test function return 2 result
test function return 2 result

Lúc này nó báo lỗi chạy không được (cần numeric argument), nên $? sẽ có giá trị 2.

Tôi thử cập nhật một lần nữa với nội dung:

#!/bin/bash

test_function_return() {

        echo "What did it cost?"

        return $RANDOM

}

test_function_return

echo "It costs $?"

test function return 3
test function return 3

Và chạy lại:

test function return 3 result
test function return 3 result

Giờ nó đã hết chửi bới và trả về một số ngẫu nhiên ($RANDOM) để gọi thông qua $? như ý đồ tôi thiết kế.

#2.4 Lưu ý Variable scope để không bị ăn hành

Như tôi nói trên, tùy thuộc vào tình huống thực tế, đôi khi bạn sẽ cần Global Variable nhưng cũng sẽ có lúc bạn cần một Variable chỉ có thể truy cập cục bộ trong Function hoặc một bộ phận của Function (nghĩa là nó tự sinh tự diệt trong Function). Lúc đấy bạn sẽ cần đến một nhân vật có tên gọi Local Variable. Tôi làm rõ chỗ này với file minh họa test_var_scope.sh như sau:

#!/bin/bash

cost="Nothing"

overlay_cost="Overlay Nothing"

cost_change() {

        local cost="Everything"

        echo "Inside of this function, cost is $cost"

        overlay_cost="Overlay Everything"

}

echo "Before the function call, cost is $cost and overlay_cost is $overlay_cost"

cost_change

echo "After the function call, cost is $cost and overlay_cost is $overlay_cost"

test var scope
test var scope

Như vậy, ở trên bạn sẽ có:

  • 2 Global Variablecostoverlay_cost (định nghĩa bên ngoài Function);
  • 1 Local Variablecost (bạn để ý có xuất hiện từ khóa local bên trong Function) không ảnh hưởng gì đến Global Variable cost sau khi Function được gọi;
  • Global Variableoverlay_cost sẽ bị ghi đè bên trong Function bằng cách gán cho nó giá trị “Overlay Everything” vì mặc định nếu không có từ khóa local, nó sẽ được xem là Global Variable.

Kết quả chạy thử, bạn sẽ thấy:

test var scope result
test var scope result

Để kết bài, tôi xin đúc kết kinh nghiệm thương đau để tránh cảnh vò đầu bứt tóc tự hỏi “Thế éo nào mà lại ra giá trị như thế?”:

  • Ưu tiên dùng Local Variable và chỉ dùng Global Variable khi thực sự cần;
  • Nếu bắt buộc dùng Global Variable, cần bảo đảm kiểm soát giá trị của nó. Tuyệt đối không để nó giao lưu phối hợp vô tội vạ trong và ngoài Function.

Đến đây tôi cũng xin  dừng luôn phần giới thiệu các thành phần cơ bản của Bash Script. Kỳ tới, nếu có thể, tôi sẽ kiếm cái demo nào có nội dung gay cấn tí minh họa cho đỡ nhạt mồm.

Leave a Reply

Your email address will not be published. Required fields are marked *