Thoát Shell Script dựa trên mã thoát quy trình


378

Tôi có một kịch bản shell thực thi một số lệnh. Làm cách nào để thực hiện thoát lệnh shell shell nếu bất kỳ lệnh nào thoát với mã thoát khác không?


3
Tôi đã trả lời giả sử bạn đang sử dụng bash, nhưng nếu đó là một vỏ rất khác bạn có thể chỉ định trong bài viết của mình không?
Martin W

Phương pháp cứng: kiểm tra giá trị $?sau mỗi lệnh. Phương pháp dễ dàng: đặt set -ehoặc #!/bin/bash -eở đầu tập lệnh Bash của bạn.
mwfearnley

Câu trả lời:


494

Sau mỗi lệnh, mã thoát có thể được tìm thấy trong $?biến để bạn có thể có một cái gì đó như:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Bạn cần cẩn thận với các lệnh đường ống vì $?chỉ cung cấp cho bạn mã trả về của phần tử cuối cùng trong đường ống, vì vậy, trong mã:

ls -al file.ext | sed 's/^/xx: /"

sẽ không trả về mã lỗi nếu tệp không tồn tại (vì sedmột phần của đường ống thực sự hoạt động, trả về 0).

Các bashvỏ thực sự cung cấp một mảng mà có thể hỗ trợ trong trường hợp đó, đó là PIPESTATUS. Mảng này có một phần tử cho mỗi thành phần đường ống, mà bạn có thể truy cập riêng lẻ như ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Lưu ý rằng điều này sẽ giúp bạn nhận được kết quả của falselệnh chứ không phải toàn bộ đường ống. Bạn cũng có thể lấy toàn bộ danh sách để xử lý khi bạn thấy phù hợp:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

Nếu bạn muốn lấy mã lỗi lớn nhất từ ​​một đường ống, bạn có thể sử dụng một cái gì đó như:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Điều này PIPESTATUSlần lượt đi qua từng phần tử, lưu trữ nó rcnếu nó lớn hơn rcgiá trị trước đó .


39
Tính năng tương tự chỉ trong một dòng mã di động: ls -al file.ext || exit $?([[]] không khả dụng)
MarcH

19
MarcH, tôi nghĩ bạn sẽ thấy nó [[ ]]khá dễ mang theo bash, đó là câu hỏi được gắn thẻ :-) Thật kỳ lạ, lsnó không hoạt động command.comnên nó cũng không di động, tôi biết, nhưng đó là loại tranh luận tương tự với bạn hiện tại.
paxdiablo

39
Tôi biết điều này là cổ xưa, nhưng cần lưu ý rằng bạn có thể lấy mã thoát của các lệnh trong một đường ống thông qua mảng PIPESTATUS(nghĩa là ${PIPESTATUS[0]}cho lệnh đầu tiên, ${PIPESTATUS[1]}cho lệnh thứ hai hoặc ${PIPESTATUS[*]}cho danh sách tất cả các stati thoát.
DevSolar

11
Cần phải nhấn mạnh rằng kịch bản shell thanh lịch và thành ngữ rất hiếm khi cần kiểm tra $?trực tiếp. Bạn thường muốn một cái gì đó giống như if ls -al file.ext; then : nothing; else exit $?; fitrong đó tất nhiên như @MarcH nói là tương đương với ls -al file.ext || exit $?nhưng nếu thenhay elseđiều khoản có phần phức tạp hơn, nó là dễ bảo trì hơn.
tripleee

9
[[ $rc != 0 ]]sẽ cung cấp cho bạn một 0: not foundhoặc một 1: not foundlỗi. Điều này nên được thay đổi thành [ $rc -ne 0 ]. Cũng rc=$?có thể được gỡ bỏ và chỉ cần sử dụng [ $? -ne 0 ].
CurtisLeeBolin

223

Nếu bạn muốn làm việc với $?, Bạn sẽ cần kiểm tra nó sau mỗi lệnh, kể từ $? được cập nhật sau mỗi lần thoát lệnh. Điều này có nghĩa là nếu bạn thực hiện một đường ống, bạn sẽ chỉ nhận được mã thoát của quy trình cuối cùng trong đường ống.

Một cách tiếp cận khác là làm điều này:

set -e
set -o pipefail

Nếu bạn đặt cái này ở đầu tập lệnh shell, có vẻ như bash sẽ lo việc này cho bạn. Như một poster trước đã lưu ý, "set -e" sẽ khiến bash thoát với lỗi trên bất kỳ lệnh đơn giản nào. "set -o pipefail" cũng sẽ khiến bash thoát với lỗi trên bất kỳ lệnh nào trong đường ống.

Xem ở đây hoặc ở đây để thảo luận thêm một chút về vấn đề này. Đây là phần hướng dẫn sử dụng bash trên tập hợp dựng sẵn.


6
Đây thực sự phải là câu trả lời hàng đầu: thực hiện việc này dễ dàng hơn nhiều so với việc sử dụng PIPESTATUSvà kiểm tra mã thoát ở mọi nơi.
candu

2
#!/bin/bash -elà cách duy nhất để bắt đầu một kịch bản shell. Bạn luôn có thể sử dụng những thứ như foo || handle_error $?nếu bạn thực sự cần kiểm tra trạng thái thoát.
Davis Herring

53

" set -e" Có lẽ là cách dễ nhất để làm điều này. Chỉ cần đặt nó trước bất kỳ lệnh nào trong chương trình của bạn.


6
@SwaroopCH set -etập lệnh của bạn sẽ hủy bỏ nếu bất kỳ lệnh nào trong tập lệnh của bạn thoát với trạng thái lỗi và bạn đã không xử lý lỗi này.
Andrew

2
set -elà 100% tương đương với set -o errexitcái mà không giống như trước đây có thể được tìm kiếm. Tìm kiếm opengroup + errexit cho các tài liệu chính thức.
MarcH

30

Nếu bạn chỉ gọi exit trong bash không có tham số, nó sẽ trả về mã thoát của lệnh cuối cùng. Kết hợp với OR, bash chỉ nên gọi thoát, nếu lệnh trước đó không thành công. Nhưng tôi đã không kiểm tra điều này.

lệnh1 | | lối ra;
lệnh2 | | lối ra;

Bash cũng sẽ lưu mã thoát của lệnh cuối cùng trong biến $?.


25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code

3
Không nên như vậy exit $?(không có parens)?
Malthe

21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. Làm thế nào để lấy mã lối ra cmd1vàocmd1|cmd2

    Đầu tiên, lưu ý rằng cmd1mã thoát có thể khác không và vẫn không có nghĩa là lỗi. Điều này xảy ra ví dụ trong

    cmd | head -1

    bạn có thể quan sát trạng thái thoát 141 (hoặc 269 với ksh93) cmd1, nhưng vì nó cmdbị gián đoạn bởi tín hiệu SIGPIPE khi head -1 bị chấm dứt sau khi đọc một dòng.

    Để biết trạng thái thoát của các thành phần của đường ống cmd1 | cmd2 | cmd3

    a. với zsh:

    Các mã thoát được cung cấp trong mảng đặc biệt pipestatus. cmd1mã thoát là trong $pipestatus[1], cmd3mã thoát trong $pipestatus[3], vì vậy $?luôn luôn giống như $pipestatus[-1] .

    b. với bash:

    Các mã thoát được cung cấp trong PIPESTATUSmảng đặc biệt. cmd1mã thoát là trong ${PIPESTATUS[0]}, cmd3mã thoát trong ${PIPESTATUS[2]}, vì vậy $?luôn luôn giống như ${PIPESTATUS: -1} .

    ...

    Để biết thêm chi tiết xem liên kết sau .


19

cho bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

19
Có lẽ không nên "thoát 0", vì điều đó cho thấy thành công.
tộc

3
exit_code = $?; echo "script bị hủy bỏ, vì lỗi"; exit $ exit_code
RaSergiy

1
@ HAL9001 bạn có bằng chứng về điều này? Tài liệu IBM nói khác .
Patrick James McDougle

11

Trong bash, điều này thật dễ dàng, chỉ cần gắn chúng lại với &&:

command1 && command2 && command3

Bạn cũng có thể sử dụng lồng nhau nếu cấu trúc:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi

+1 Đây là giải pháp đơn giản nhất mà tôi đang tìm kiếm. Ngoài ra, bạn cũng có thể viết if (! command)nếu bạn mong đợi một mã lỗi khác không từ lệnh.
Berci

đây là cho các lệnh liên tiếp .. nếu tôi muốn khởi chạy song song 3 thứ đó và giết tất cả mọi người nếu bất kỳ một trong số chúng thất bại thì sao?
Vasile Surdu

4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit

2
Ít khi có một lý do để sử dụng $*; sử dụng "$@"thay thế để bảo tồn không gian và ký tự đại diện.
Davis Herring
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.