Làm cách nào tôi có thể nhận bash để thoát khỏi lỗi backtick theo cách tương tự như đường ống dẫn?


55

Vì vậy, tôi muốn làm cứng các tập lệnh bash của mình bất cứ nơi nào tôi có thể (và khi không thể ủy quyền cho một ngôn ngữ như Python / Ruby) để đảm bảo các lỗi không bị phát hiện.

Trong đó tôi có một nghiêm ngặt.sh, có chứa những thứ như:

set -e
set -u
set -o pipefail

Và nguồn nó trong các kịch bản khác. Tuy nhiên, trong khi đường ống sẽ nhận:

false | echo it kept going | true

Nó sẽ không nhận:

echo The output is '`false; echo something else`' 

Đầu ra sẽ là

Đầu ra là ''

Sai trả về trạng thái khác không và không xuất chuẩn. Trong một đường ống, nó sẽ thất bại, nhưng ở đây lỗi không bị bắt. Khi đây thực sự là một phép tính được lưu trữ trong một biến cho sau này và giá trị được đặt thành trống, điều này có thể gây ra các vấn đề sau này.

Vì vậy - có cách nào để bash xử lý một mã trả về khác không trong một backtick là đủ lý do để thoát không?

Câu trả lời:


51

Ngôn ngữ chính xác được sử dụng trong đặc tả UNIX đơn để mô tả ý nghĩa củaset -e là:

Khi tùy chọn này được bật, nếu một lệnh đơn giản không thành công vì bất kỳ lý do nào được liệt kê trong Hậu quả của lỗi Shell hoặc trả về giá trị trạng thái thoát> 0 và không phải là [lệnh có điều kiện hoặc bị phủ định], thì shell sẽ thoát ngay lập tức.

Có một sự mơ hồ về những gì xảy ra khi một lệnh như vậy xảy ra trong một lớp con . Từ quan điểm thực tế, tất cả các lớp con có thể làm là thoát và trả lại trạng thái khác không cho vỏ cha. Liệu shell cha sẽ lần lượt thoát ra hay không phụ thuộc vào việc trạng thái khác không này có chuyển thành một lệnh đơn giản không thành công trong shell cha hay không.

Một trường hợp có vấn đề như vậy là trường hợp bạn gặp phải: trạng thái trả về khác không từ thay thế lệnh . Vì trạng thái này được bỏ qua, nó không khiến shell cha thoát ra. Như bạn đã phát hiện ra , một cách để đưa trạng thái thoát vào tài khoản là sử dụng thay thế lệnh trong một nhiệm vụ đơn giản : sau đó trạng thái thoát của nhiệm vụ là trạng thái thoát của thay thế lệnh cuối cùng trong (các) nhiệm vụ .

Lưu ý rằng điều này sẽ chỉ thực hiện như dự định nếu có một thay thế lệnh duy nhất, vì chỉ có trạng thái thay thế cuối cùng được tính đến. Ví dụ: lệnh sau thành công (cả theo tiêu chuẩn và trong mọi triển khai tôi đã thấy):

a=$(false)$(echo foo)

Một trường hợp khác để theo dõi là subshells rõ ràng : (somecommand). Theo cách giải thích ở trên, lớp con có thể trả về trạng thái khác không, nhưng vì đây không phải là một lệnh đơn giản trong vỏ cha, nên vỏ cha nên tiếp tục. Trong thực tế, tất cả các vỏ tôi biết làm cho cha mẹ trở lại vào thời điểm này. Mặc dù điều này hữu ích trong nhiều trường hợp như (cd /some/dir && somecommand)sử dụng dấu ngoặc đơn để duy trì hoạt động như thư mục hiện tại thay đổi cục bộ, nhưng nó vi phạm đặc tả nếu set -ebị tắt trong lớp con hoặc nếu lớp con trả về trạng thái khác không theo cách sẽ không chấm dứt nó, chẳng hạn như sử dụng !trên một lệnh thực sự. Ví dụ: tất cả các lối ra tro, bash, pdksh, ksh93 và zsh mà không hiển thị footrên các ví dụ sau:

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

Tuy nhiên, không có lệnh đơn giản đã thất bại trong khi set -ecó hiệu lực!

Một trường hợp có vấn đề thứ ba là các yếu tố trong một đường ống không cần thiết . Trong thực tế, tất cả các vỏ đều bỏ qua các lỗi của các phần tử của đường ống khác với phần tử cuối cùng và thể hiện một trong hai hành vi liên quan đến phần tử đường ống cuối cùng:

  • ATT ksh và zsh, thực thi phần tử cuối cùng của đường ống trong shell cha, hoạt động như bình thường: nếu một lệnh đơn giản thất bại trong phần tử cuối cùng của đường ống, shell thực thi lệnh đó, xảy ra là shell cha, lối thoát hiểm
  • Các shell khác xấp xỉ hành vi bằng cách thoát nếu phần tử cuối cùng của đường ống trả về trạng thái khác không.

Giống như trước đây, tắt set -ehoặc sử dụng phủ định trong phần tử cuối cùng của đường ống làm cho nó trả về trạng thái khác không theo cách không nên chấm dứt lớp vỏ; shell khác với ATT ksh và zsh sau đó sẽ thoát.

pipefailTùy chọn của Bash làm cho một đường ống thoát ra ngay lập tức set -enếu bất kỳ phần tử nào của nó trả về trạng thái khác.

Lưu ý rằng như một sự phức tạp hơn nữa, bash sẽ tắt set -etrong các lớp con trừ khi nó ở chế độ POSIX ( set -o posixhoặc có POSIXLY_CORRECTtrong môi trường khi bash bắt đầu).

Tất cả những điều này cho thấy rằng đặc tả POSIX không may làm một công việc kém trong việc chỉ định -etùy chọn. May mắn thay, vỏ hiện có chủ yếu là phù hợp trong hành vi của họ.


Cảm ơn vì điều đó. Một kinh nghiệm tôi có là một số lỗi trong phiên bản bash sau này, đã bị bỏ qua trong các phiên bản trước với set -e. Mục đích của tôi ở đây là làm cứng các tập lệnh đến mức bất kỳ điều kiện trả lại / lỗi không được xử lý nào sẽ khiến tập lệnh thoát ra. Đây là một hệ thống cũ được biết là tạo ra các tệp đầu ra rác và mã thoát hạnh phúc "0" sau nửa giờ hỗn loạn với env sai - trừ khi bạn đang xem đầu ra như chim ưng (và không phải tất cả các lỗi là trên stderr, một số là trên thiết bị xuất chuẩn và một số phần là / dev / null'd), bạn không biết.
Daniel Staple

"Ví dụ: tất cả các lối ra tro, bash, pdksh, ksh93 và zsh mà không hiển thị foo trên các ví dụ sau": BusyBox ash không hoạt động theo cách đó, nó sẽ hiển thị đầu ra theo thông số kỹ thuật. (Đã thử nghiệm với BusyBox 1.19.4.) Nó cũng không thoát set -e; (cd /nonexisting).
dubiousjim

27

(Trả lời câu hỏi của riêng tôi vì tôi đã tìm thấy giải pháp) Một giải pháp là luôn gán giá trị này cho một biến trung gian. Bằng cách này, returncode ( $?) được đặt.

Vì thế

ABC=`exit 1`
echo $?

Sẽ xuất ra 1(hoặc thay vào đó là thoát nếu set -ecó), tuy nhiên:

echo `exit 1`
echo $?

Sẽ xuất 0sau một dòng trống. Mã trả về của echo (hoặc lệnh khác chạy với đầu ra backtick) sẽ thay thế mã trả về 0.

Tôi vẫn mở cho các giải pháp không yêu cầu biến trung gian, nhưng điều này giúp tôi có một số cách.


23

Như OP đã chỉ ra trong câu trả lời của riêng mình, việc gán đầu ra của tiểu ban cho một biến sẽ giải quyết vấn đề; cái $?còn lại vô sự.

Tuy nhiên, trường hợp một cạnh vẫn có thể đánh đố bạn với các phủ định sai (nghĩa là lệnh thất bại nhưng lỗi không nổi lên), localkhai báo biến:

local myvar=$(subcommand)sẽ luôn trở về 0!

bash(1) chỉ ra điều này:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

Đây là một trường hợp thử nghiệm đơn giản:

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

Đầu ra:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

cảm ơn bạn, sự mâu thuẫn giữa VAR=...local VAR=...thực sự làm tôi bối rối!
Jonny

13

Như những người khác đã nói, localsẽ luôn trả về 0. Giải pháp là khai báo biến trước:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

Đầu ra:

$ testcase
False returned false!
$ 

4

Để thoát khỏi lỗi thay thế lệnh, bạn có thể đặt rõ ràng -etrong một khung con, như thế này:

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

Điểm thú vị!

Tôi chưa bao giờ vấp phải điều đó, bởi vì tôi không phải là bạn của set -e(Thay vào đó tôi thích trap ... ERR) nhưng đã kiểm tra rằng: trap ... ERRcũng không bắt lỗi trong $(...)(hoặc các backticks lỗi thời).

Tôi nghĩ vấn đề là (thường xuyên như vậy) rằng ở đây một lớp con được gọi và -erõ ràng có nghĩa là lớp vỏ hiện tại .

Chỉ có giải pháp khác xuất hiện trong tâm trí lúc này là sử dụng đọc:

 ls -l ghost_under_bed | read name

Điều này ném ERRvà với -evỏ sẽ bị chấm dứt. Vấn đề duy nhất: điều này chỉ hoạt động đối với các lệnh có một dòng đầu ra (hoặc bạn chuyển qua một cái gì đó nối các dòng).


1
Không chắc chắn về thủ thuật đọc, cố gắng dẫn đến biến không bị ràng buộc. Tôi nghĩ điều này có thể là do phía bên kia của đường ống thực sự là một nhánh con, tên sẽ không có sẵn.
Daniel Staple
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.