Làm thế nào để thoát một tập lệnh shell nếu một phần của nó bị lỗi?


51

Làm thế nào tôi có thể viết một kịch bản shell thoát ra, nếu một phần của nó bị lỗi? Ví dụ: nếu đoạn mã sau không thành công, thì tập lệnh sẽ thoát.

n=0
until [ $n -ge 5 ]
do
  gksu *command* && break
  n=$[$n+1]
  sleep 3

Câu trả lời:


88

Một cách tiếp cận sẽ là thêm set -evào phần đầu của kịch bản của bạn. Điều đó có nghĩa là (từ help set):

  -e  Exit immediately if a command exits with a non-zero status.

Vì vậy, nếu bất kỳ lệnh nào của bạn thất bại, tập lệnh sẽ thoát.

Ngoài ra, bạn có thể thêm các exittuyên bố rõ ràng tại các điểm thất bại có thể:

command || exit 1

5
Tôi (và wiki bash ) thay vì mọi người nghĩ một chút về việc xử lý lỗi đúng hơn là sử dụng tính năng (bị hỏng do thiết kế IMO) set -e. Nó không thực sự áp dụng ở đây mặc dù. OP muốn thoát khỏi tập lệnh sau 5 lần thất bại để chạy lệnh.
Stéphane Chazelas

1
@ StéphaneChazelas Tôi sẽ không tranh luận với bạn về việc nó có bị hỏng hay không, tôi chắc chắn là bạn đúng. Tuy nhiên, OP đã hỏi "Làm thế nào tôi có thể viết một kịch bản shell thoát ra, nếu một phần của nó bị lỗi?", Điều gì khiến bạn nghĩ rằng nó sẽ thoát ra sau 5 lần thất bại?
terdon

bởi vì tôi không thể nghĩ ra bất kỳ cách nào khác cho câu hỏi có thể được giải thích.
Stéphane Chazelas

1
@ StéphaneChazelas bạn cũng có thể đúng. Tôi đã giải thích nó theo nghĩa đen: làm thế nào toàn bộ tập lệnh có thể thoát nếu bất kỳ phần nào của nó bị lỗi. Và set -elà cách duy nhất tôi biết để làm điều đó.
terdon

Trong đoạn mã script đó, các lệnh có thể kích hoạt set -esleep( breakđược dựng sẵn đặc biệt sẽ khiến tập lệnh thoát khỏi lỗi trong hầu hết các shell, các lệnh trong ifhoặc bên trái &&không bị ảnh hưởng set -e, n=...có thể thất bại nếu nchỉ đọc, nhưng sau đó điều đó cũng sẽ thoát khỏi tập lệnh mà không có set -ekhả năng giải thích), do đó việc giải thích nghe có vẻ khó xảy ra. Tôi đồng ý câu hỏi là từ kém.
Stéphane Chazelas

17

Bạn có thể thoát một tập lệnh tại bất kỳ nơi nào bằng cách sử dụng từ khóa exit. Bạn cũng có thể chỉ định một mã thoát dùng để báo hiệu cho các chương trình khác mà hay như thế nào kịch bản của bạn thất bại, ví dụ exit 1hoặc exit 2vv (Theo quy ước, lối ra mã 0 là cho sự thành công và lớn hơn bất cứ điều gì hơn 0 biểu thị sự thất bại, tuy nhiên, cũng theo quy ước, xuất cảnh các mã trên 127 được dành riêng để chấm dứt bất thường (ví dụ: bằng tín hiệu)).

Cấu trúc chung để thoát khỏi thất bại là

if [ failure condition ]; then
    exit n
fi

với phù hợp failure conditionn. Nhưng trong các kịch bản cụ thể, bạn có thể tiến hành khác nhau. Bây giờ đối với trường hợp của bạn, tôi giải thích câu hỏi của bạn rằng nếu bất kỳ một trong năm yêu cầu gksuthất bại, thì bạn có nghĩa là để thoát. Một cách là sử dụng một chức năng như thế này

function try_command {
    for i in 1 2 3 4 5 ; do
        if gksu command ; then
            return 0
        fi
    fi
    exit 1
}

và sau đó, gọi vòng lặp bằng try_command.

Có (nhiều hơn) các cách nâng cao hoặc tinh vi về cách giải quyết câu hỏi của bạn. Tuy nhiên, giải pháp ở trên dễ tiếp cận hơn với người mới bắt đầu hơn là, giải pháp của Stephane.


10
attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exitthoát khỏi tập lệnh trừ khi nó được gọi trong một subshell. Nếu đó là một phần của kịch bản là trong một subshell, ví dụ vì nó trong (...)hoặc $(...)hoặc một phần của đường ống dẫn, sau đó nó sẽ chỉ thoát subshell đó .

Trong trường hợp đó, nếu bạn muốn tập lệnh thoát ra ngoài lớp con, thì bạn sẽ cần phải gọi exitkhi thoát khỏi lớp con đó.

Chẳng hạn, ở đây có 2 cấp độ con:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

Nó có thể trở nên phức tạp hơn nếu lớp vỏ là một phần của đường ống. bashcó một đặc biệt $PIPESTATUSmảng, tương tự như zshcủa $pipestatusmột có thể giúp bạn ở đây:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi

3

Bẫy sẽ thực hiện một hành động khi nhận được tín hiệu.

trap "echo EXIT;  exit" 0
trap "echo HUP;   exit" 1
trap "echo CTL-C; exit" 2
trap "echo QUIT;  exit" 3
trap "echo ERR;   exit" ERR
n=0
until [ $n -ge 5 ]
do
  n=$[$n+1]
  echo $n
  sleep 3
done

Chạy cái này và để nó thoát bình thường. Nó bẫy tín hiệu 0.

EXIT

Chạy lại và ngắt với ^ C. Nó bẫy cả tín hiệu 2 và tín hiệu 0.

CTL-C
EXIT

Trạng thái thoát không bằng 0 sẽ bẫy trên ERR

ERR
EXIT
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.