Tôi có hai quy trình foo
và bar
, được kết nối với một đường ống:
$ foo | bar
bar
luôn thoát 0; Tôi quan tâm đến mã thoát của foo
. Có cách nào để có được nó?
Tôi có hai quy trình foo
và bar
, được kết nối với một đường ống:
$ foo | bar
bar
luôn thoát 0; Tôi quan tâm đến mã thoát của foo
. Có cách nào để có được nó?
Câu trả lời:
Nếu bạn đang sử dụng bash
, bạn có thể sử dụng PIPESTATUS
biến mảng để lấy trạng thái thoát của từng thành phần của đường ống.
$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0
Nếu bạn đang sử dụng zsh
, mảng chúng được gọi pipestatus
(trường hợp quan trọng!) Và các chỉ số mảng bắt đầu tại một:
$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0
Để kết hợp chúng trong một hàm theo cách không làm mất các giá trị:
$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0
Chạy ở trên bash
hoặc zsh
bạn sẽ nhận được kết quả tương tự; chỉ một retval_bash
và retval_zsh
sẽ được thiết lập. Cái khác sẽ trống. Điều này sẽ cho phép một chức năng kết thúc bằng return $retval_bash $retval_zsh
(lưu ý thiếu dấu ngoặc kép!).
pipestatus
trong zsh. Thật không may, các vỏ khác không có tính năng này.
echo "$pipestatus[1]" "$pipestatus[2]"
.
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Có 3 cách phổ biến để làm điều này:
Cách đầu tiên là đặt pipefail
tùy chọn ( ksh
, zsh
hoặc bash
). Đây là cách đơn giản nhất và về cơ bản, nó đặt trạng thái $?
thoát thành mã thoát của chương trình cuối cùng để thoát khác không (hoặc bằng 0 nếu tất cả đã thoát thành công).
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
Bash cũng có một biến mảng được gọi là $PIPESTATUS
( $pipestatus
in zsh
) chứa trạng thái thoát của tất cả các chương trình trong đường ống cuối cùng.
$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1
Bạn có thể sử dụng ví dụ lệnh thứ 3 để lấy giá trị cụ thể trong đường ống mà bạn cần.
Đây là khó sử dụng nhất của các giải pháp. Chạy từng lệnh riêng biệt và nắm bắt trạng thái
$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1
ksh
, nhưng từ cái nhìn thoáng qua vào trang này, nó không hỗ trợ $PIPESTATUS
hay bất cứ thứ gì tương tự. Nó không hỗ trợ các pipefail
tùy chọn mặc dù.
LOG=$(failed_command | successful_command)
Giải pháp này hoạt động mà không sử dụng các tính năng cụ thể của bash hoặc các tệp tạm thời. Phần thưởng: cuối cùng, trạng thái thoát thực sự là trạng thái thoát và không phải là một chuỗi trong tệp.
Tình hình:
someprog | filter
bạn muốn trạng thái thoát khỏi someprog
và đầu ra từ filter
.
Đây là giải pháp của tôi:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
kết quả của cấu trúc này là thiết bị xuất chuẩn từ filter
thiết bị xuất chuẩn của trạng thái xây dựng và thoát khỏi trạng thái someprog
thoát của cấu trúc.
cấu trúc này cũng hoạt động với nhóm lệnh đơn giản {...}
thay vì các chuỗi con (...)
. subshells có một số hàm ý, trong số những người khác một chi phí hiệu suất, mà chúng ta không cần ở đây. đọc hướng dẫn sử dụng bash tốt để biết thêm chi tiết: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1
Thật không may, ngữ pháp bash yêu cầu không gian và dấu chấm phẩy cho dấu ngoặc nhọn để cấu trúc trở nên rộng rãi hơn nhiều.
Đối với phần còn lại của văn bản này, tôi sẽ sử dụng biến thể subshell.
Ví dụ someprog
và filter
:
someprog() {
echo "line1"
echo "line2"
echo "line3"
return 42
}
filter() {
while read line; do
echo "filtered $line"
done
}
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
Ví dụ đầu ra:
filtered line1
filtered line2
filtered line3
42
Lưu ý: quá trình con kế thừa các mô tả tệp mở từ cha mẹ. Điều đó có nghĩa là someprog
sẽ kế thừa mô tả tệp mở 3 và 4. Nếu someprog
ghi vào mô tả tệp 3 thì điều đó sẽ trở thành trạng thái thoát. Trạng thái thoát thực sự sẽ bị bỏ qua vì read
chỉ đọc một lần.
Nếu bạn lo lắng rằng bạn someprog
có thể ghi vào bộ mô tả tệp 3 hoặc 4 thì tốt nhất nên đóng bộ mô tả tệp trước khi gọi someprog
.
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
Cái exec 3>&- 4>&-
trước someprog
đóng bộ mô tả tập tin trước khi thực hiện someprog
để cho someprog
những bộ mô tả tập tin đó đơn giản là không tồn tại.
Nó cũng có thể được viết như thế này: someprog 3>&- 4>&-
Từng bước giải thích về cấu trúc:
( ( ( ( someprog; #part6
echo $? >&3 #part5
) | filter >&4 #part4
) 3>&1 #part3
) | (read xs; exit $xs) #part2
) 4>&1 #part1
Từ dưới lên:
#part3
) và phải ( #part2
) được thực thi. exit $xs
cũng là lệnh cuối cùng của đường ống và điều đó có nghĩa là chuỗi từ stdin sẽ là trạng thái thoát của toàn bộ cấu trúc.#part2
và lần lượt sẽ là trạng thái thoát của toàn bộ cấu trúc.#part5
và #part6
) và phải ( filter >&4
) được thực thi. Đầu ra của filter
được chuyển hướng đến bộ mô tả tệp 4. Trong #part1
bộ mô tả tệp 4 đã được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là đầu ra của filter
là thiết bị xuất chuẩn của toàn bộ cấu trúc.#part6
được in đến bộ mô tả tệp 3. Trong #part3
mô tả tệp 3 đã được chuyển hướng đến #part2
. Điều này có nghĩa là trạng thái thoát từ #part6
sẽ là trạng thái thoát cuối cùng cho toàn bộ cấu trúc.someprog
được thực thi. Tình trạng thoát được thực hiện trong #part5
. Thiết bị xuất chuẩn được lấy bằng đường ống trong #part4
và chuyển tiếp đến filter
. Đầu ra từ filter
sẽ lần lượt đạt đến thiết bị xuất chuẩn như được giải thích trong#part4
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
đơn giản hóa để someprog 3>&- 4>&-
.
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Mặc dù không chính xác những gì bạn yêu cầu, bạn có thể sử dụng
#!/bin/bash -o pipefail
để đường ống của bạn trả lại kết quả khác không.
có thể là một chút ít mã hóa
Chỉnh sửa: Ví dụ
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
set -o pipefail
bên trong tập lệnh nên mạnh mẽ hơn, ví dụ trong trường hợp ai đó thực thi tập lệnh thông qua bash foo.sh
.
-o pipefail
không có trong POSIX.
#!/bin/bash -o pipefail
. Lỗi là:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
các dòng ngoài cái đầu tiên, và vì vậy điều này trở thành/bin/bash
-o pipefail
/tmp/ff
, thay vì cần thiết /bin/bash
-o
pipefail
/tmp/ff
- getopt
phân tích cú pháp (hoặc tương tự) sử dụng optarg
, đó là mục tiếp theo trong ARGV
, như là đối số để -o
, vì vậy nó thất bại. Nếu bạn định tạo một trình bao bọc (giả sử, bash-pf
điều đó vừa làm exec /bin/bash -o pipefail "$@"
, và đặt nó trên #!
dòng, nó sẽ hoạt động. Xem thêm: en.wikipedia.org/wiki/Shebang_%28Unix%29
Những gì tôi làm khi có thể là cung cấp mã thoát từ foo
vào bar
. Ví dụ: nếu tôi biết rằng foo
không bao giờ tạo ra một dòng chỉ có chữ số, thì tôi chỉ có thể giải quyết mã thoát:
{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'
Hoặc nếu tôi biết rằng đầu ra từ foo
không bao giờ chứa một dòng chỉ .
:
{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'
Điều này luôn có thể được thực hiện nếu có một số cách bar
để làm việc trên tất cả trừ dòng cuối cùng và chuyển vào dòng cuối cùng dưới dạng mã thoát của nó.
Nếu bar
là một đường ống phức tạp mà đầu ra bạn không cần, bạn có thể bỏ qua một phần của nó bằng cách in mã thoát trên một mô tả tệp khác.
exit_codes=$({ { foo; echo foo:"$?" >&3; } |
{ bar >/dev/null; echo bar:"$?" >&3; }
} 3>&1)
Sau này $exit_codes
thường là foo:X bar:Y
, nhưng nó có thể là bar:Y foo:X
nếu bar
bỏ trước khi đọc tất cả các đầu vào của nó hoặc nếu bạn không may mắn. Tôi nghĩ rằng việc ghi vào các ống có tối đa 512 byte là nguyên tử trên tất cả các đơn vị, do đó foo:$?
, bar:$?
các bộ phận sẽ không được trộn lẫn với nhau miễn là các chuỗi thẻ dưới 507 byte.
Nếu bạn cần nắm bắt đầu ra từ bar
, nó sẽ khó khăn. Bạn có thể kết hợp các kỹ thuật ở trên bằng cách sắp xếp đầu ra bar
không bao giờ chứa một dòng trông giống như một dấu hiệu mã thoát, nhưng nó thực sự khó hiểu.
output=$(echo;
{ { foo; echo foo:"$?" >&3; } |
{ bar | sed 's/^/^/'; echo bar:"$?" >&3; }
} 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')
Và, tất nhiên, có tùy chọn đơn giản là sử dụng tệp tạm thời để lưu trữ trạng thái. Đơn giản, nhưng không phải là đơn giản trong sản xuất:
/tmp
là nơi duy nhất mà tập lệnh chắc chắn có thể ghi tệp. Sử dụng mktemp
, không phải là POSIX nhưng có sẵn trên tất cả các đơn vị nghiêm túc hiện nay.foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
Bắt đầu từ đường ống:
foo | bar | baz
Đây là một giải pháp chung chỉ sử dụng shell POSIX và không có tệp tạm thời:
exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
(bar || echo "1:$?" >&3) |
(baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-
$error_statuses
chứa mã trạng thái của bất kỳ quá trình thất bại nào, theo thứ tự ngẫu nhiên, với các chỉ mục để cho biết lệnh nào được phát ra mỗi trạng thái.
# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2
# test if all commands succeeded:
test -z "$error_statuses"
# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null
Lưu ý các trích dẫn xung quanh $error_statuses
trong các bài kiểm tra của tôi; không có chúng grep
không thể phân biệt được vì các dòng mới bị ép buộc vào không gian.
Vì vậy, tôi muốn đóng góp một câu trả lời như của lesmana, nhưng tôi nghĩ rằng giải pháp của tôi có lẽ đơn giản hơn một chút và có lợi hơn một chút:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
Tôi nghĩ rằng điều này được giải thích tốt nhất từ trong ra ngoài - lệnh1 sẽ thực thi và in đầu ra thông thường của nó trên thiết bị xuất chuẩn (mô tả tệp 1), sau đó, khi nó hoàn thành, printf sẽ thực thi và in mã thoát của lệnh1 trên thiết bị xuất chuẩn của nó, nhưng thiết bị xuất chuẩn đó được chuyển hướng đến mô tả tập tin 3.
Trong khi lệnh1 đang chạy, thiết bị xuất chuẩn của nó đang được chuyển sang lệnh2 (đầu ra của printf không bao giờ chuyển sang lệnh2 vì chúng tôi gửi nó tới tệp mô tả 3 thay vì 1, đó là những gì đường ống đọc). Sau đó, chúng tôi chuyển hướng đầu ra của lệnh2 sang bộ mô tả tệp 4, do đó nó cũng nằm ngoài bộ mô tả tệp 1 - vì chúng tôi muốn mô tả tệp 1 miễn phí sau một chút, vì chúng tôi sẽ đưa đầu ra printf trên bộ mô tả tệp 3 trở lại vào bộ mô tả tệp 1 - bởi vì đó là những gì thay thế lệnh (backticks), sẽ nắm bắt và đó là những gì sẽ được đặt vào biến.
Bit cuối cùng của phép thuật là lần đầu tiên exec 4>&1
chúng tôi thực hiện như một lệnh riêng - nó mở mô tả tệp 4 dưới dạng bản sao của thiết bị xuất chuẩn của lớp vỏ ngoài. Thay thế lệnh sẽ nắm bắt bất cứ điều gì được viết trên tiêu chuẩn từ phối cảnh của các lệnh bên trong nó - nhưng, vì đầu ra của lệnh2 sẽ chuyển sang mô tả 4 khi có liên quan đến việc thay thế lệnh, tuy nhiên, thay thế lệnh không nắm bắt được - tuy nhiên, một khi nó được "ra" thay thế lệnh, nó vẫn thực sự đi đến phần mô tả tập tin tổng thể của tập lệnh 1.
( exec 4>&1
Phải là một lệnh riêng vì nhiều shell thông thường không thích nó khi bạn cố ghi vào một mô tả tệp bên trong một lệnh thay thế, được mở trong lệnh "bên ngoài" đang sử dụng thay thế. Vì vậy, đây là cách di động đơn giản nhất để làm điều đó.)
Bạn có thể xem xét nó theo một cách ít kỹ thuật và vui tươi hơn, vì nếu các đầu ra của các lệnh đang nhảy vọt vào nhau: Command1 pipe sang lệnh2, thì đầu ra của printf nhảy qua lệnh 2 để lệnh2 không bắt được nó, và sau đó Đầu ra của lệnh 2 nhảy qua và thay thế lệnh giống như printf vừa kịp lúc bị bắt bởi sự thay thế để nó kết thúc trong biến và đầu ra của lệnh2 tiếp tục được ghi vào đầu ra tiêu chuẩn, giống như trong một đường ống bình thường.
Ngoài ra, theo tôi hiểu, nó $?
vẫn sẽ chứa mã trả về của lệnh thứ hai trong đường ống, bởi vì các phép gán biến, thay thế lệnh và các lệnh ghép đều hoàn toàn trong suốt đối với mã trả về của lệnh bên trong chúng, do đó trạng thái trả về của lệnh2 nên được truyền bá - điều này và không phải xác định hàm bổ sung, là lý do tại sao tôi nghĩ rằng đây có thể là một giải pháp tốt hơn so với giải pháp được đề xuất bởi lesmana.
Theo đề xuất của lesmana, có thể lệnh1 sẽ kết thúc bằng cách sử dụng mô tả tệp 3 hoặc 4, để mạnh mẽ hơn, bạn sẽ làm:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Lưu ý rằng tôi sử dụng các lệnh ghép trong ví dụ của mình, nhưng ( )
các chuỗi con (sử dụng thay vì { }
cũng sẽ hoạt động, mặc dù có thể kém hiệu quả hơn.)
Các lệnh kế thừa bộ mô tả tệp từ quá trình khởi chạy chúng, vì vậy toàn bộ dòng thứ hai sẽ kế thừa bộ mô tả tệp bốn và lệnh ghép tiếp theo 3>&1
sẽ kế thừa bộ mô tả tệp ba. Vì vậy, 4>&-
đảm bảo rằng lệnh ghép bên trong sẽ không kế thừa bộ mô tả tệp bốn và 3>&-
sẽ không kế thừa bộ mô tả tệp ba, vì vậy lệnh1 có được 'môi trường sạch hơn', chuẩn hơn. Bạn cũng có thể di chuyển bên trong bên 4>&-
cạnh 3>&-
, nhưng tôi hiểu tại sao không chỉ giới hạn phạm vi của nó càng nhiều càng tốt.
Tôi không chắc chắn mức độ thường xuyên mọi thứ sử dụng bộ mô tả tệp ba và bốn trực tiếp - Tôi nghĩ rằng hầu hết các chương trình sử dụng các tòa nhà chọc trời trả lại các bộ mô tả tệp không được sử dụng tại thời điểm đó, nhưng đôi khi mã được ghi trực tiếp vào bộ mô tả tệp 3, tôi đoán (tôi có thể tưởng tượng một chương trình kiểm tra bộ mô tả tệp để xem nó có mở không và sử dụng nó nếu có, hoặc hành xử khác đi nếu không). Vì vậy, cái sau có lẽ là tốt nhất để ghi nhớ và sử dụng cho các trường hợp mục đích chung.
-bash: 3: Bad file descriptor
.
Nếu bạn đã cài đặt gói moreutils, bạn có thể sử dụng tiện ích mispipe , chính xác những gì bạn yêu cầu.
Giải pháp của lesmana ở trên cũng có thể được thực hiện mà không cần chi phí bắt đầu các quy trình con lồng nhau bằng cách sử dụng { .. }
thay vào đó (hãy nhớ rằng hình thức các lệnh được nhóm này luôn phải kết thúc bằng dấu chấm phẩy). Một cái gì đó như thế này:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
Tôi đã kiểm tra cấu trúc này với phiên bản dash 0.5.5 và bash phiên bản 3.2.25 và 4.2.42, vì vậy ngay cả khi một số shell không hỗ trợ { .. }
nhóm, nó vẫn tuân thủ POSIX.
set -o pipefail
ksh hoặc bất kỳ số wait
lệnh rắc nào trong cả hai. Tôi nghĩ rằng, ít nhất, nó có thể là một vấn đề phân tích cú pháp cho ksh, vì nếu tôi sử dụng các subshells thì nó hoạt động tốt, nhưng ngay cả với if
việc chọn biến thể subshell cho ksh nhưng để lại các lệnh ghép cho người khác, nó đã thất bại .
Đây là tính di động, tức là hoạt động với bất kỳ shell tương thích POSIX nào, không yêu cầu thư mục hiện tại có thể ghi và cho phép nhiều tập lệnh sử dụng cùng một thủ thuật để chạy đồng thời.
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
Chỉnh sửa: đây là phiên bản mạnh hơn theo nhận xét của Gilles:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: và đây là một biến thể nhẹ hơn sau bình luận dubiousjim:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Tôi đồng ý với Bash dễ dàng hơn nhưng trong một số bối cảnh, biết cách tránh Bash là đáng giá.
Sau đây có nghĩa là một addon cho câu trả lời của @Patrik, trong trường hợp bạn không thể sử dụng một trong những giải pháp phổ biến.
Câu trả lời này giả định như sau:
$PIPESTATUS
và cũng khôngset -o pipefail
Giả định bổ sung. Bạn có thể thoát khỏi tất cả, nhưng điều này làm tắc nghẽn công thức quá nhiều, vì vậy nó không được đề cập ở đây:
- Tất cả những gì bạn muốn biết là tất cả các lệnh trong PIPE đều có mã thoát 0.
- Bạn không cần thêm thông tin ban nhạc bên.
- Shell của bạn không đợi tất cả các lệnh ống trở lại.
Trước: foo | bar | baz
tuy nhiên, điều này chỉ trả về mã thoát của lệnh cuối cùng ( baz
)
Muốn: $?
không được 0
(đúng), nếu bất kỳ lệnh nào trong đường ống không thành công
Sau:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
# $? now is 0 only if all commands had exit code 0
Giải thích:
mktemp
. Điều này thường ngay lập tức tạo ra một tập tin trong/tmp
wait
này là cần thiết cho ksh
, bởi vì ksh
người khác không đợi tất cả các lệnh ống hoàn thành. Tuy nhiên, xin lưu ý rằng có những tác dụng phụ không mong muốn nếu có một số tác vụ nền, vì vậy tôi đã nhận xét nó theo mặc định. Nếu chờ đợi không đau, bạn có thể bình luận.read
trả về false
, do đó true
chỉ ra lỗiĐiều này có thể được sử dụng như là một thay thế plugin cho một lệnh duy nhất và chỉ cần sau:
/proc/fd/N
BUGs:
Kịch bản này có một lỗi trong trường hợp /tmp
hết dung lượng. Nếu bạn cần bảo vệ chống lại trường hợp nhân tạo này cũng vậy, bạn có thể làm điều đó như sau, tuy nhiên điều này có nhược điểm, đó là số lượng 0
trong 000
phụ thuộc vào số lệnh trong đường ống, vì vậy nó là hơi phức tạp hơn:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
Ghi chú tính di động:
ksh
và vỏ tương tự mà chỉ chờ lệnh ống cuối cùng cần wait
không chú thích
Ví dụ cuối cùng sử dụng printf "%1s" "$?"
thay echo -n "$?"
vì bởi vì nó dễ mang theo hơn. Không phải mọi nền tảng diễn giải -n
chính xác.
printf "$?"
cũng sẽ làm điều đó, tuy nhiên sẽ printf "%1s"
nắm bắt được một số trường hợp góc trong trường hợp bạn chạy tập lệnh trên một số nền tảng thực sự bị hỏng. (Đọc: nếu bạn tình cờ lập trình paranoia_mode=extreme
.)
FD 8 và FD 9 có thể cao hơn trên các nền tảng hỗ trợ nhiều chữ số. AFAIR một vỏ phù hợp POSIX chỉ cần hỗ trợ các chữ số đơn.
Đã được thử ra với Debian 8.2 sh
, bash
, ksh
, ash
, sash
và thậm chícsh
Với một chút đề phòng, điều này sẽ hoạt động:
foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
Khối 'if' sau sẽ chỉ chạy nếu 'lệnh' thành công:
if command; then
# ...
fi
Nói một cách cụ thể, bạn có thể chạy một cái gì đó như thế này:
haconf_out=/path/to/some/temporary/file
if haconf -makerw > "$haconf_out" 2>&1; then
grep -iq "Cluster already writable" "$haconf_out"
# ...
fi
Cái nào sẽ chạy haconf -makerw
và lưu trữ thiết bị xuất chuẩn và thiết bị xuất chuẩn của nó thành "$ haconf thừng". Nếu giá trị được trả về haconf
là đúng, thì khối 'if' sẽ được thực thi và grep
sẽ đọc "$ haconf thừng", cố gắng khớp nó với "Cụm đã có thể ghi".
Lưu ý rằng các đường ống tự động tự làm sạch; với việc chuyển hướng, bạn sẽ phải cẩn thận để xóa "$ haconfợi" khi hoàn tất.
Không thanh lịch như pipefail
, nhưng một sự thay thế hợp pháp nếu chức năng này không nằm trong tầm tay.
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"
exec 8>&- 9>&-
{
{
{
{ #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8 # use exactly 1x prior to #END
#END
} 2>&1 | ${TEE} 1>&9
} 8>&1
} | exit $(read; printf "${REPLY}")
} 9>&1
exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$
(Với bash ít nhất) kết hợp với set -e
một người có thể sử dụng lớp con để mô phỏng rõ ràng đường ống và thoát khỏi lỗi đường ống
set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program
Vì vậy, nếu foo
thất bại vì một số lý do - phần còn lại của chương trình sẽ không được thực thi và tập lệnh thoát với mã lỗi tương ứng. (Điều này giả định rằng foo
in lỗi của chính nó, đủ để hiểu lý do thất bại)
EDIT : Câu trả lời này sai, nhưng thú vị, vì vậy tôi sẽ để nó để tham khảo trong tương lai.
!
lệnh vào đảo ngược mã trả về.
http://tldp.org/LDP/abs/html/exit-status.html
# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $? # 127
! ls | bogus_command # bash: bogus_command: command not found
echo $? # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #
ls
, không đảo ngược mã thoát củabogus_command