NỘI DUNG
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à For và While 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ử:

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

Kết quả chạy thử:

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 (-le là less 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

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ử:

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 và 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"

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ử:

#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 $?"

Kết quả chạy thử:

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 $?"

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

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 $?"

Và chạy lại:

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"

Như vậy, ở trên bạn sẽ có:
- 2 Global Variable là cost và overlay_cost (định nghĩa bên ngoài Function);
- 1 Local Variable là cost (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 Variable là overlay_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:

Để 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.