Trạng thái thoát Bash được sử dụng với PIPE


10

Tôi đang cố gắng hiểu làm thế nào trạng thái thoát được truyền đạt khi một đường ống được sử dụng. Giả sử tôi đang sử dụng whichđể định vị một chương trình không tồn tại:

which lss
echo $?
1

whichkhông xác định được vị trí, lsstôi có trạng thái thoát là 1. Điều này tốt. Tuy nhiên khi tôi thử như sau:

which lss | echo $?
0

Điều này chỉ ra rằng lệnh cuối cùng được thực thi đã thoát bình thường. Cách duy nhất tôi có thể hiểu điều này là có lẽ PIPE cũng tạo ra trạng thái thoát. Đây có phải là cách đúng để hiểu nó?


2
Tất cả các thành phần của một đường ống chạy cùng một lúc . Vì vậy, trong which lss | echo $?, echođược bắt đầu trước khi whichđã thoát; Shell tạo dòng lệnh cho echokhông có cách nào có thể biết trạng thái thoát whichsau này sẽ là gì.
Charles Duffy

Họ không chạy cùng một lúc. Toàn bộ mục đích của đường ống là chuyển hướng đầu ra của một lệnh sang lệnh khác. Lệnh cuối cùng mà shell thực thi (tức là lệnh cuối cùng trong câu lệnh của bạn) sẽ là lệnh trả về mã trả về.
Tim S.

3
@TimS. Nhưng chúng chạy cùng một lúc, đó là cách bạn có thể nhận được các bit đầu ra đầu tiên trước khi lệnh đầu tiên kết thúc nếu đó là một lệnh chạy rất dài.
Random832

Tôi đoán rằng tôi đã suy nghĩ về việc bắt đầu thực hiện - có sự chồng chéo nhất định, nhưng các quy trình không bắt đầu cùng nhau. Tôi thấy những gì bạn có ý nghĩa mặc dù, sai lầm của tôi. (Đường ống đang chuyển hướng đầu ra, không phải là thực thi ...) Tôi đã viết sai ý kiến ​​của mình, nhưng hy vọng bạn biết ý tôi là gì. :)
Tim S.

Vâng, nhưng bắt đầu thực hiện không thành vấn đề, điều quan trọng là kết thúc; lệnh cuối cùng bắt đầu trước khi lệnh đầu tiên kết thúc
user371366

Câu trả lời:


14

Trạng thái thoát của đường ống là trạng thái thoát của lệnh cuối cùng trong đường ống (trừ khi tùy chọn shell pipefailđược đặt trong các shell hỗ trợ nó, trong trường hợp đó, trạng thái thoát sẽ là lệnh cuối cùng trong đường ống thoát ra một trạng thái khác không).

Không thể xuất trạng thái thoát của một đường ống từ bên trong một đường ống, vì không có cách nào để biết giá trị đó sẽ là gì cho đến khi đường ống thực sự kết thúc thực thi.

Đường ống dẫn

which lss | echo $?

là vô nghĩa vì echokhông đọc đầu vào tiêu chuẩn của nó (các đường ống được sử dụng để truyền dữ liệu giữa đầu ra của một lệnh sang đầu vào của lệnh tiếp theo). Cũng echosẽ không in trạng thái thoát của đường ống, nhưng trạng thái thoát của lệnh chạy ngay trước đường ống.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Đây là một ví dụ tốt hơn:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Điều này có nghĩa là một đường ống cũng có thể được sử dụng với if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi

10

Trạng thái thoát của đường ống là trạng thái thoát của lệnh bên phải. Trạng thái thoát của lệnh bên trái bị bỏ qua.

(Lưu ý rằng which lss | echo $?không hiển thị điều này. Bạn sẽ chạy which lss | true; echo $?để hiển thị điều này. Trong which lss | echo $?, echo $?báo cáo trạng thái của lệnh cuối cùng trước đường ống này.)

Lý do shell hoạt động theo cách này là có một kịch bản khá phổ biến trong đó một lỗi ở phía bên trái nên được bỏ qua. Nếu phía bên phải thoát (hoặc nói chung là đóng đầu vào tiêu chuẩn của nó) trong khi phía bên trái vẫn đang viết, thì phía bên trái nhận được tín hiệu SIGPIPE. Trong trường hợp này, thường không có gì sai: phía bên tay phải không quan tâm đến dữ liệu; nếu vai trò của phía bên trái chỉ là để tạo ra dữ liệu này thì nó có thể dừng lại.

Tuy nhiên, nếu phía bên trái chết vì một số lý do khác ngoài SIGPIPE hoặc nếu công việc của phía bên trái không chỉ tạo ra dữ liệu trên đầu ra tiêu chuẩn, thì lỗi ở phía bên trái là lỗi chính hãng nên được báo cáo.

Trong sh đơn giản, giải pháp duy nhất là sử dụng một đường ống có tên.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

Trong ksh, bash và zsh, bạn có thể ra lệnh cho shell thực hiện một lối thoát đường ống với trạng thái khác 0 nếu bất kỳ thành phần nào của đường ống thoát ra với trạng thái khác. Bạn cần đặt pipefailtùy chọn:

  • ksh: set -o pipefail
  • bash: shopt -s pipefail
  • zsh: setopt pipefail(hoặc setopt pipe_fail)

Trong mksh, bash và zsh, bạn có thể nhận trạng thái của mọi thành phần của đường ống bằng cách sử dụng biến PIPESTATUS(bash, mksh) hoặc pipestatus(zsh), là một mảng chứa trạng thái của tất cả các lệnh trong đường ống cuối cùng (tổng quát hóa của $?).


Cảm ơn bạn Gilles, tôi đã không nhận thức được những phức tạp liên quan. Điều này thực sự làm rõ điều này hơn nữa cho tôi.
sshekhar1980

4

Có, PIPE tạo trạng thái thoát; nếu bạn muốn mã thoát của lệnh trước PIPE, bạn có thể sử dụng$PIPESTATUS

Theo Hỏi & Đáp về Stack Overflow này , để in trạng thái thoát của lệnh khi bạn sử dụng đường ống, bạn có thể thực hiện:

your_command | echo $PIPESTATUS

Hoặc đặt pipefail bằng lệnh set -o pipefail(để hủy đặt nó, làm set +o pipefail) và sử dụng $?thay vì $PIPESTATUS.

your_command | echo $?

Các lệnh này chỉ hoạt động sau khi bạn chạy chúng ít nhất một lần; điều đó có nghĩa là PIPESTATUSchỉ hoạt động khi bạn chạy nó sau lệnh với đường ống, như thế này:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Bạn sẽ nhận được 10 cho ${PIPESTATUS[0]}và 1 cho ${PIPESTATUS[1]}. Xem phần Hỏi & Đáp này để biết thêm thông tin.


2
Mặc dù PIPESTATUSchứa các trạng thái thoát của các lệnh trong đường dẫn cuối cùng, ví dụ đầu tiên của bạn không có ý nghĩa gì hơn somecmd | echo $?, mà Kusalananda đã xử lý trong câu trả lời của chúng. false; true | echo $PIPESTATUSin 1cho trạng thái thoát của false.
ilkkachu

Upvote cho PIPESTATUS và pipefiail, nhưng điều |exhonày thực sự khó hiểu
eckes

0

Shell đánh giá dòng lệnh trước khi đường ống được thực thi. Vì vậy, $?giá trị của lệnh cuối cùng được thực hiện trước đường ống dẫn. Cho dù echobản thân được bắt đầu trước hay sau whichlà không liên quan.

Bây giờ nếu câu hỏi của bạn là cụ thể về sự lan truyền của trạng thái thoát, bạn có thể nghiên cứu hành vi mặc định như thế này:

% false | true
% echo $?
0

% true | false
% echo $?
1

Như bạn có thể thấy, trạng thái thoát của một đường ống là trạng thái thoát của lệnh cuối cùng của nó.

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.