
Hệ điều hành Linux set
và pipefail
lệnh ra lệnh điều gì sẽ xảy ra khi lỗi xảy ra trong tập lệnh Bash. Còn nhiều điều để suy nghĩ hơn là nên dừng hay nên tiếp tục.
Tập lệnh Bash và điều kiện lỗi
Bash shell script rất tuyệt. Họ viết nhanh và họ không cần biên dịch. Mọi hành động lặp đi lặp lại hoặc nhiều giai đoạn mà bạn cần thực hiện đều có thể được gói gọn trong một kịch bản thuận tiện. Và bởi vì các tập lệnh có thể gọi bất kỳ tiện ích Linux tiêu chuẩn nào, bạn không bị giới hạn khả năng của chính ngôn ngữ shell.
Nhưng các vấn đề có thể phát sinh khi bạn gọi một tiện ích hoặc chương trình bên ngoài. Nếu nó không thành công, tiện ích bên ngoài sẽ đóng cửa và gửi mã trả lại đến trình bao, và thậm chí nó có thể in thông báo lỗi tới thiết bị đầu cuối. Nhưng tập lệnh của bạn sẽ tiếp tục xử lý. Có lẽ đó không phải là những gì bạn muốn. Nếu lỗi xảy ra sớm trong quá trình thực thi tập lệnh, nó có thể dẫn đến các vấn đề tồi tệ hơn nếu phần còn lại của tập lệnh được phép chạy.
Bạn có thể kiểm tra mã trả về từ mỗi quy trình bên ngoài khi chúng hoàn thành, nhưng điều đó sẽ trở nên khó khăn khi các quy trình được chuyển sang các quy trình khác. Mã trả về sẽ là từ quy trình ở cuối đường ống, không phải từ quy trình bị lỗi ở giữa. Tất nhiên, lỗi cũng có thể xảy ra bên trong tập lệnh của bạn, chẳng hạn như cố gắng truy cập vào một biến chưa được khởi tạo.
Các set
và pipefile
lệnh cho phép bạn quyết định điều gì sẽ xảy ra khi những lỗi như thế này xảy ra. Chúng cũng cho phép bạn phát hiện lỗi ngay cả khi chúng xảy ra ở giữa một chuỗi ống.
Đây là cách sử dụng chúng.
Chứng minh vấn đề
Đây là một tập lệnh Bash tầm thường. Nó lặp lại hai dòng văn bản đến thiết bị đầu cuối. Bạn có thể chạy tập lệnh này nếu sao chép văn bản vào trình chỉnh sửa và lưu nó dưới dạng “script-1.sh”.
#!/bin/bash echo This will happen first echo This will happen second
Để làm cho nó có thể thực thi, bạn sẽ cần sử dụng chmod
:
chmod +x script-1.sh
Bạn sẽ cần chạy lệnh đó trên mỗi tập lệnh nếu bạn muốn chạy chúng trên máy tính của mình. Hãy chạy tập lệnh:
./script-1.sh
Hai dòng văn bản được gửi đến cửa sổ đầu cuối như mong đợi.
Hãy sửa đổi script một chút. Chúng tôi sẽ hỏi ls
để liệt kê các chi tiết của một tệp không tồn tại. Điều này sẽ thất bại. Chúng tôi đã lưu nó dưới dạng “script-2.sh” và làm cho nó có thể thực thi được.
#!/bin/bash echo This will happen first ls imaginary-filename echo This will happen second
Khi chúng tôi chạy tập lệnh này, chúng tôi thấy thông báo lỗi từ ls
.
./script-2.sh
Mặc dù ls
lệnh không thành công, tập lệnh tiếp tục chạy. Và mặc dù đã xảy ra lỗi trong quá trình thực thi tập lệnh, mã trả về từ tập lệnh tới trình bao bằng 0, điều này cho biết thành công. Chúng tôi có thể kiểm tra điều này bằng cách sử dụng echo và $?
biến chứa mã trả về cuối cùng được gửi đến trình bao.
echo $?
Số 0 được báo cáo là mã trả về từ tiếng vọng thứ hai trong tập lệnh. Vì vậy, có hai vấn đề với kịch bản này. Đầu tiên là tập lệnh có lỗi nhưng nó vẫn tiếp tục chạy. Điều đó có thể dẫn đến các vấn đề khác nếu phần còn lại của kịch bản mong đợi hoặc phụ thuộc vào hành động thất bại thực sự thành công. Và thứ hai là nếu một tập lệnh hoặc quy trình khác cần kiểm tra sự thành công hay thất bại của tập lệnh này, thì nó sẽ đọc sai.
Tùy chọn set -e
Các set -e
(exit) tùy chọn khiến một tập lệnh thoát ra nếu bất kỳ quy trình nào mà nó gọi tạo ra mã trả về khác 0. Bất cứ điều gì khác 0 đều bị coi là thất bại.
Bằng cách thêm set -e
để bắt đầu tập lệnh, chúng ta có thể thay đổi hành vi của nó. Đây là “script-3.sh.”
#!/bin/bash set -e echo This will happen first ls imaginary-filename echo This will happen second
Nếu chúng tôi chạy tập lệnh này, chúng tôi sẽ thấy hiệu ứng của set -e
.
./script-3.sh
echo $?
Tập lệnh bị tạm dừng và mã trả về được gửi đến trình bao là một giá trị khác 0.
Xử lý sự cố trong đường ống
Đường ống dẫn đến vấn đề phức tạp hơn. Mã trả về xuất phát từ một chuỗi lệnh được phân phối là mã trả về từ lệnh cuối cùng trong chuỗi. Nếu có lỗi xảy ra với một lệnh ở giữa chuỗi, chúng ta sẽ quay lại hình vuông. Mã trả lại đó bị mất và tập lệnh sẽ tiếp tục xử lý.
Chúng ta có thể thấy tác động của các lệnh đường ống với các mã trả lại khác nhau bằng cách sử dụng true
và false
tích hợp shell. Hai lệnh này không làm gì nhiều hơn là tạo ra một mã trả về không hoặc một, tương ứng.
true
echo $?
false
echo $?
Nếu chúng ta tẩu false
vào trong true
-với false
đại diện cho một quá trình thất bại — chúng tôi nhận được true
mã trả về của số không.
false | true
echo $?
Bash có một biến mảng được gọi là PIPESTATUS
và điều này nắm bắt tất cả các mã trả về từ mỗi chương trình trong chuỗi ống dẫn.
false | true | false | true
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}"
PIPESTATUS
chỉ giữ các mã trả về cho đến khi chương trình tiếp theo chạy và việc cố gắng xác định mã trả về nào đi cùng với chương trình nào có thể trở nên lộn xộn rất nhanh.
Đây là đâu set -o
(tùy chọn) và pipefail
mời vào. Đây là “script-4.sh.” Điều này sẽ cố gắng chuyển nội dung của một tệp không tồn tại vào wc
.
#!/bin/bash set -e echo This will happen first cat script-99.sh | wc -l echo This will happen second
Điều này không thành công, như chúng tôi mong đợi.
./script-4.sh
echo $?
Số 0 đầu tiên là đầu ra từ wc
, cho chúng tôi biết nó không đọc bất kỳ dòng nào cho tệp bị thiếu. Số 0 thứ hai là mã trả về từ số thứ hai echo
yêu cầu.
Chúng tôi sẽ thêm vào -o pipefail
lưu nó dưới dạng “script-5.sh” và làm cho nó có thể thực thi được.
#!/bin/bash set -eo pipefail echo This will happen first cat script-99.sh | wc -l echo This will happen second
Hãy chạy điều đó và kiểm tra mã trả lại.
./script-5.sh
echo $?
Kịch bản tạm dừng và đoạn thứ hai echo
lệnh không được thực thi. Mã trả về được gửi đến trình bao là một, chỉ ra một cách chính xác lỗi.
Bắt các biến chưa được khởi tạo
Các biến chưa được khởi tạo có thể khó phát hiện trong tập lệnh thế giới thực. Nếu chúng ta cố gắng echo
giá trị của một biến chưa khởi tạo, echo
chỉ cần in một dòng trống. Nó không đưa ra một thông báo lỗi. Phần còn lại của tập lệnh sẽ tiếp tục thực thi.
Đây là script-6.sh.
#!/bin/bash set -eo pipefail echo "$notset" echo "Another echo command"
Chúng tôi sẽ chạy nó và quan sát hành vi của nó.
./script-6.sh
echo $?
Tập lệnh bước qua biến chưa khởi tạo và tiếp tục thực thi. Mã trả về là số không. Việc cố gắng tìm một lỗi như thế này trong một tập lệnh rất dài và phức tạp có thể rất khó.
Chúng tôi có thể mắc phải loại lỗi này bằng cách sử dụng set -u
(chưa đặt) tùy chọn. Chúng tôi sẽ thêm điều đó vào bộ sưu tập ngày càng tăng của các tùy chọn đặt ở đầu tập lệnh, lưu nó dưới dạng “script-7.sh” và làm cho nó có thể thực thi được.
#!/bin/bash set -eou pipefail echo "$notset" echo "Another echo command"
Hãy chạy tập lệnh:
./script-7.sh
echo $?
Biến chưa khởi tạo được phát hiện, tập lệnh tạm dừng và mã trả về được đặt thành một.
Các -u
(chưa đặt) tùy chọn đủ thông minh không được kích hoạt bởi các tình huống mà bạn có thể tương tác hợp pháp với một biến chưa được khởi tạo.
Trong “script-8.sh”, tập lệnh kiểm tra xem biến New_Var
được khởi tạo hay không. Bạn không muốn tập lệnh dừng lại ở đây, trong một tập lệnh trong thế giới thực, bạn sẽ thực hiện các xử lý tiếp theo và tự mình đối phó với tình huống.
Lưu ý rằng chúng tôi đã thêm -u
tùy chọn như là thứ hai tùy chọn trong câu lệnh set. Các -o pipefail
tùy chọn phải đến cuối cùng.
#!/bin/bash set -euo pipefail if [ -z "${New_Var:-}" ]; then echo "New_Var has no value assigned to it." fi
Trong “script-9.sh”, biến chưa khởi tạo sẽ được kiểm tra và nếu nó chưa được khởi tạo, một giá trị mặc định sẽ được cung cấp thay thế.
#!/bin/bash set -euo pipefail default_value=484 Value=${New_Var:-$default_value} echo "New_Var=$Value"
Các tập lệnh được phép chạy cho đến khi hoàn thành.
./script-8.sh
./script-9.sh
Kín bằng rìu
Một tùy chọn tiện dụng khác để sử dụng là set -x
(thực thi và in) tùy chọn. Khi bạn đang viết kịch bản, đây có thể là một cứu cánh. nó in các lệnh và các tham số của chúng khi chúng được thực thi.
Nó cung cấp cho bạn một dạng dấu vết thực thi “thô và sẵn sàng” nhanh chóng. Cô lập các lỗi logic và phát hiện lỗi trở nên dễ dàng hơn rất nhiều.
Chúng tôi sẽ thêm tùy chọn set -x vào “script-8.sh”, lưu nó dưới dạng “script-10.sh” và làm cho nó có thể thực thi được.
#!/bin/bash set -euxo pipefail if [ -z "${New_Var:-}" ]; then echo "New_Var has no value assigned to it." fi
Chạy nó để xem các đường dấu vết.
./script-10.sh
Dễ dàng phát hiện ra các lỗi trong các đoạn mã ví dụ nhỏ nhặt này. Khi bạn bắt đầu viết nhiều kịch bản có liên quan hơn, những tùy chọn này sẽ chứng minh giá trị của chúng.