Tăng lỗi trong tập lệnh Bash


103

Tôi muốn đưa ra lỗi trong tập lệnh Bash với thông báo "Các trường hợp kiểm tra không thành công !!!". Làm thế nào để làm điều này trong Bash?

Ví dụ:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi

1
Bạn muốn điều gì xảy ra với lỗi này? Kịch bản của bạn được gọi như thế nào? Nó chỉ là một kịch bản hay nhiều kịch bản? Công dụng của tập lệnh của bạn trông như thế nào?
Etan Reisner

chỉ một tập lệnh. Tôi đã gọi nó bằng cách sử dụng thiết bị đầu cuối ubuntu như ./script/test.sh
Naveen Kumar



Không có tình yêu cho echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
cập nhật

Câu trả lời:


121

Điều này phụ thuộc vào nơi bạn muốn thông báo lỗi được lưu trữ.

Bạn có thể làm như sau:

echo "Error!" > logfile.log
exit 125

Hoặc như sau:

echo "Error!" 1>&2
exit 64

Khi bạn đưa ra một ngoại lệ, bạn sẽ dừng quá trình thực thi của chương trình.

Bạn cũng có thể sử dụng giống như exit xxxnơi xxxlà mã lỗi mà bạn có thể muốn quay trở lại hệ điều hành (0-255). Đây 12564chỉ là những mã ngẫu nhiên mà bạn có thể thoát ra. Khi bạn cần cho HĐH biết rằng chương trình đã dừng bất thường (ví dụ: xảy ra lỗi), bạn cần chuyển một mã thoát khác 0 cho exit.

Như @chepner đã chỉ ra , bạn có thể làm được exit 1, điều này có nghĩa là một lỗi không xác định .


12
hoặc bạn có thể gửi nó đến stderr, nơi có lỗi xảy ra.

làm thế nào để gửi nó đến stderr?
Naveen Kumar

2
@ user3078630, Tôi vừa chỉnh sửa câu trả lời của mình. 1>&2sẽ thực hiện thủ thuật
ForceBru

Nếu đó là lỗi, bạn cũng nên thoát với trạng thái thoát khác 0. exittự nó sử dụng trạng thái thoát của lệnh hoàn thành gần đây nhất, có thể là 0
chepner

3
Trừ khi bạn có một ý nghĩa cụ thể trong đầu, bạn chỉ nên sử dụng exit 1, theo quy ước có nghĩa là một lỗi không xác định.
chepner

36

Xử lý lỗi cơ bản

Nếu trình chạy trường hợp thử nghiệm của bạn trả về mã khác 0 cho các thử nghiệm không thành công, bạn chỉ cần viết:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Hoặc thậm chí ngắn hơn:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Hoặc ngắn nhất:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Để thoát bằng mã thoát của test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Xử lý lỗi nâng cao

Nếu bạn muốn thực hiện một cách tiếp cận toàn diện hơn, bạn có thể có một trình xử lý lỗi:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

sau đó gọi nó sau khi chạy trường hợp thử nghiệm của bạn:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

hoặc là

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Những lợi thế của việc có một trình xử lý lỗi như exit_if_errorlà:

  • chúng tôi có thể chuẩn hóa tất cả các logic xử lý lỗi như ghi nhật ký , in dấu vết ngăn xếp , thông báo, thực hiện dọn dẹp, v.v., ở một nơi
  • bằng cách làm cho trình xử lý lỗi lấy mã lỗi làm đối số, chúng tôi có thể giải phóng người gọi khỏi sự lộn xộn của ifcác khối kiểm tra lỗi thoát mã
  • nếu chúng ta có một trình xử lý tín hiệu (sử dụng bẫy ), chúng ta có thể gọi trình xử lý lỗi từ đó

Xử lý lỗi và ghi nhật ký thư viện

Dưới đây là toàn bộ quá trình xử lý lỗi và ghi nhật ký:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Bài viết liên quan


9

Có một số cách khác mà bạn có thể tiếp cận vấn đề này. Giả sử một trong những yêu cầu của bạn là chạy một tập lệnh / hàm shell chứa một vài lệnh shell và kiểm tra xem tập lệnh có chạy thành công hay không và ném lỗi trong trường hợp không thành công.

Các lệnh shell thường dựa vào các mã thoát được trả về để cho shell biết nó thành công hay thất bại do một số sự kiện không mong muốn.

Vì vậy, những gì bạn muốn làm thuộc hai loại này

  • thoát khỏi lỗi
  • thoát và dọn dẹp khi bị lỗi

Tùy thuộc vào cách bạn muốn làm, có các tùy chọn trình bao có sẵn để sử dụng. Đối với trường hợp đầu tiên, vỏ cung cấp một tùy chọn với set -evà cho phần thứ hai bạn có thể làm một traptrênEXIT

Tôi có nên sử dụng exittrong script / function của mình không?

Sử dụng exitnói chung giúp tăng cường khả năng đọc Trong một số quy trình nhất định, khi bạn biết câu trả lời, bạn muốn thoát khỏi quy trình gọi điện ngay lập tức. Nếu quy trình được định nghĩa theo cách mà nó không yêu cầu phải dọn dẹp thêm khi phát hiện ra lỗi, thì việc không thoát ngay lập tức có nghĩa là bạn phải viết thêm mã.

Vì vậy, trong trường hợp nếu bạn cần thực hiện các hành động dọn dẹp script để làm cho việc chấm dứt script sạch sẽ, thì bạn nên không sử dụng exit.

Tôi có nên sử dụng set -echo lỗi khi thoát không?

Không!

set -elà một nỗ lực để thêm "phát hiện lỗi tự động" vào trình bao. Mục tiêu của nó là làm cho shell ngừng hoạt động bất kỳ khi nào xảy ra lỗi, nhưng nó đi kèm với rất nhiều cạm bẫy tiềm ẩn, chẳng hạn như

  • Các lệnh là một phần của kiểm tra if được miễn dịch. Trong ví dụ, nếu bạn mong đợi nó phá vỡ testkiểm tra trên thư mục không tồn tại, nó sẽ không, nó chuyển sang điều kiện khác

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
  • Các lệnh trong một đường dẫn không phải là lệnh cuối cùng, được miễn nhiễm. Trong ví dụ dưới đây, vì mã thoát của lệnh được thực thi gần đây nhất (ngoài cùng bên phải) được coi là ( cat) và nó đã thành công. Điều này có thể tránh được bằng cách cài đặt theo set -o pipefailtùy chọn nhưng nó vẫn là một cảnh báo.

    set -e
    somecommand that fails | cat -
    echo survived 

Được đề xuất để sử dụng - trapkhi thoát

Phán quyết là nếu bạn muốn có thể xử lý một lỗi thay vì thoát ra một cách mù quáng, thay vì sử dụng set -e, hãy sử dụng một traptrên ERRtín hiệu giả.

Cái ERRbẫy không phải để chạy mã khi trình bao tự thoát ra với mã lỗi khác 0, nhưng khi bất kỳ lệnh nào chạy bởi trình bao đó không thuộc điều kiện (như trong if cmdhoặc cmd ||) thoát với trạng thái thoát khác 0 .

Thực tiễn chung là chúng tôi xác định một trình xử lý bẫy để cung cấp thêm thông tin gỡ lỗi về dòng nào và nguyên nhân dẫn đến việc thoát. Hãy nhớ mã thoát của lệnh cuối cùng gây ra ERRtín hiệu sẽ vẫn có sẵn tại thời điểm này.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

và chúng tôi chỉ sử dụng trình xử lý này như bên dưới ở đầu tập lệnh không thành công

trap cleanup ERR

Đặt điều này lại với nhau trên một tập lệnh đơn giản có falseở dòng 15, thông tin bạn sẽ nhận được là

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

trapcũng cung cấp các tùy chọn không phân biệt lỗi để chỉ chạy dọn dẹp khi hoàn thành shell (ví dụ: thoát script shell của bạn), theo tín hiệu EXIT. Bạn cũng có thể bẫy nhiều tín hiệu cùng một lúc. Danh sách các tín hiệu được hỗ trợ để bẫy có thể được tìm thấy trên trap.1p - Trang hướng dẫn sử dụng Linux

Một điều khác cần lưu ý là phải hiểu rằng không có phương pháp nào được cung cấp hoạt động nếu bạn đang xử lý các trình bao con có liên quan đến trường hợp đó, bạn có thể cần thêm xử lý lỗi của riêng mình.

  • Trên một sub-shell với set -esẽ không hoạt động. Các falsebị hạn chế đến các tiểu vỏ và không bao giờ được tuyên truyền để các vỏ mẹ. Để thực hiện xử lý lỗi ở đây, hãy thêm logic của riêng bạn để làm(false) || false

    set -e
    (false)
    echo survived
  • Điều tương tự cũng xảy ra với trap. Logic bên dưới sẽ không hoạt động vì những lý do được đề cập ở trên.

    trap 'echo error' ERR
    (false)

5

Đây là một cái bẫy đơn giản in ra đối số cuối cùng của bất kỳ thứ gì không thành công với STDERR, báo cáo dòng nó bị lỗi và thoát tập lệnh với số dòng làm mã thoát. Lưu ý rằng đây không phải lúc nào cũng là những ý tưởng tuyệt vời, nhưng điều này chứng tỏ một số ứng dụng sáng tạo mà bạn có thể xây dựng.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Tôi đưa nó vào một tập lệnh có vòng lặp để kiểm tra nó. Tôi chỉ kiểm tra một lần truy cập vào một số số ngẫu nhiên; bạn có thể sử dụng các thử nghiệm thực tế. Nếu tôi cần tại ngoại, tôi gọi là false (kích hoạt cái bẫy) với thông điệp tôi muốn ném.

Đối với chức năng phức tạp, hãy đặt bẫy gọi một chức năng xử lý. Bạn luôn có thể sử dụng một câu lệnh trường hợp trên arg ($ _) của mình nếu bạn cần dọn dẹp nhiều hơn, v.v. Gán cho var một cú pháp nhỏ -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Đầu ra mẫu:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Rõ ràng, bạn có thể

runTest1 "Test1 fails" # message not used if it succeeds

Rất nhiều chỗ để cải tiến thiết kế.

Những điều ngược lại bao gồm thực tế falselà không đẹp (do đó, đường), và những thứ khác khi vấp bẫy có thể trông hơi ngu ngốc. Tuy nhiên, tôi thích phương pháp này.


4

Bạn có 2 tùy chọn: Chuyển hướng đầu ra của tập lệnh thành tệp, Giới thiệu tệp nhật ký trong tập lệnh và

  1. Chuyển hướng đầu ra thành tệp :

Ở đây, bạn giả sử rằng tập lệnh xuất ra tất cả các thông tin cần thiết, bao gồm cả cảnh báo và thông báo lỗi. Sau đó, bạn có thể chuyển hướng đầu ra đến một tệp bạn chọn.

./runTests &> output.log

Lệnh trên chuyển hướng cả đầu ra chuẩn và đầu ra lỗi đến tệp nhật ký của bạn.

Sử dụng cách tiếp cận này, bạn không phải giới thiệu tệp nhật ký trong tập lệnh và do đó, logic dễ dàng hơn một chút.

  1. Giới thiệu tệp nhật ký cho tập lệnh :

Trong tập lệnh của bạn, hãy thêm tệp nhật ký bằng cách mã hóa cứng nó:

logFile='./path/to/log/file.log'

hoặc chuyển nó bằng một tham số:

logFile="${1}"  # This assumes the first parameter to the script is the log file

Bạn nên thêm dấu thời gian tại thời điểm thực thi vào tệp nhật ký ở đầu tập lệnh:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Sau đó, bạn có thể chuyển hướng các thông báo lỗi của mình đến tệp nhật ký

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Điều này sẽ nối lỗi vào tệp nhật ký và tiếp tục thực hiện. Nếu bạn muốn dừng thực thi khi xảy ra lỗi nghiêm trọng, bạn có thể exittập lệnh:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Lưu ý rằng exit 1chương trình ngừng thực thi do lỗi không xác định. Bạn có thể tùy chỉnh điều này nếu bạn thích.

Sử dụng phương pháp này, bạn có thể tùy chỉnh nhật ký của mình và có một tệp nhật ký khác cho từng thành phần của tập lệnh của bạn.


Nếu bạn có một tập lệnh tương đối nhỏ hoặc muốn thực thi tập lệnh của người khác mà không sửa đổi nó thì cách tiếp cận đầu tiên phù hợp hơn.

Nếu bạn luôn muốn tệp nhật ký ở cùng một vị trí, đây là tùy chọn tốt hơn trong số 2. Ngoài ra, nếu bạn đã tạo một tập lệnh lớn với nhiều thành phần thì bạn có thể muốn ghi nhật ký từng phần khác nhau và cách tiếp cận thứ hai là cách duy nhất của bạn Lựa chọn.


3

Tôi thường thấy hữu ích khi viết một hàm để xử lý các thông báo lỗi để mã tổng thể sạch hơn.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Thao tác này lấy mã lỗi từ lệnh trước đó và sử dụng nó làm mã lỗi mặc định khi thoát toàn bộ tập lệnh. Nó cũng ghi chú thời gian, với micro giây ở những nơi được hỗ trợ (ngày của GNU %Nlà nano giây, sau đó chúng tôi sẽ cắt bớt thành micro giây).

Nếu tùy chọn đầu tiên bằng 0 hoặc số nguyên dương, nó sẽ trở thành mã thoát và chúng tôi xóa nó khỏi danh sách tùy chọn. Sau đó, chúng tôi báo cáo thông báo về lỗi chuẩn, với tên của tập lệnh, từ "LỖI" và thời gian (chúng tôi sử dụng mở rộng tham số để cắt ngắn nano giây thành micro giây hoặc đối với thời gian không phải GNU, để cắt ngắn ví dụ: 12:34:56.%Nthành 12:34:56). Dấu hai chấm và dấu cách được thêm vào sau từ ERROR, nhưng chỉ khi có thông báo lỗi được cung cấp. Cuối cùng, chúng tôi thoát khỏi tập lệnh bằng cách sử dụng mã thoát đã xác định trước đó, kích hoạt bất kỳ bẫy nào như bình thường.

Một số ví dụ (giả sử mã nằm trong script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.