Nhận trạng thái thoát của quá trình được chuyển sang quy trình khác


Câu trả lời:


256

Nếu bạn đang sử dụng bash, bạn có thể sử dụng PIPESTATUSbiế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 bashhoặc zshbạn sẽ nhận được kết quả tương tự; chỉ một retval_bashretval_zshsẽ đượ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!).


9
pipestatustrong zsh. Thật không may, các vỏ khác không có tính năng này.
Gilles

8
Lưu ý: Mảng trong zsh bắt đầu ngược chiều ở chỉ số 1, vì vậy nó echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm

5
Bạn có thể kiểm tra toàn bộ đường ống như thế này:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0

7
@JanHudec: Có lẽ bạn nên đọc năm từ đầu tiên trong câu trả lời của tôi. Cũng vui lòng chỉ ra nơi câu hỏi yêu cầu câu trả lời chỉ POSIX.
camh 16/12/14

12
@JanHudec: Nó cũng không được gắn thẻ POSIX. Tại sao bạn cho rằng câu trả lời phải là POSIX? Nó không được chỉ định vì vậy tôi cung cấp một câu trả lời đủ điều kiện. Không có gì sai về câu trả lời của tôi, cộng với có nhiều câu trả lời để giải quyết các trường hợp khác.
camh

238

Có 3 cách phổ biến để làm điều này:

Đường ống

Cách đầu tiên là đặt pipefailtùy chọn ( ksh, zshhoặ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

$ PIPESTATUS

Bash cũng có một biến mảng được gọi là $PIPESTATUS( $pipestatusin 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.

Thi hành riêng

Đâ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

2
Chết tiệt! Tôi vừa mới đăng bài về PIPESTATUS.
slm

1
Để tham khảo, có một số kỹ thuật khác được thảo luận trong câu hỏi SO này: stackoverflow.com/questions/1221833/iêu
slm

1
@Patrick giải pháp pipestatus hoạt động trên bash, chỉ cần thêm quastion trong trường hợp tôi sử dụng tập lệnh ksh bạn nghĩ rằng chúng ta có thể tìm thấy một cái gì đó tương tự như pipestatus? , (trong khi tôi thấy pipestatus không được ksh hỗ trợ)
yael

2
@yael Tôi không sử dụng ksh, nhưng từ cái nhìn thoáng qua vào trang này, nó không hỗ trợ $PIPESTATUShay bất cứ thứ gì tương tự. Nó không hỗ trợ các pipefailtùy chọn mặc dù.
Patrick

1
Tôi quyết định sử dụng pipefail vì nó cho phép tôi có được trạng thái của lệnh thất bại ở đây:LOG=$(failed_command | successful_command)
vmrob

51

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 someprogvà đầ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ừ filterthiế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 someprogthoá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ụ someprogfilter:

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à someprogsẽ kế thừa mô tả tệp mở 3 và 4. Nếu someprogghi 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ì readchỉ đọc một lần.

Nếu bạn lo lắng rằng bạn someprogcó 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 someprognhữ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:

  1. Một subshell được tạo với bộ mô tả tập tin 4 được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là bất cứ điều gì được in ra để mô tả tệp 4 trong lớp con sẽ kết thúc như là thiết bị xuất chuẩn của toàn bộ cấu trúc.
  2. Một ống được tạo và các lệnh ở bên trái ( #part3) và phải ( #part2) được thực thi. exit $xscũ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.
  3. Một subshell được tạo với bộ mô tả tập tin 3 được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là bất cứ điều gì được in ra mô tả tập tin 3 trong lớp con này sẽ kết thúc #part2và lần lượt sẽ là trạng thái thoát của toàn bộ cấu trúc.
  4. Một ống được tạo và các lệnh ở bên trái ( #part5#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 #part1bộ 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 filterlà thiết bị xuất chuẩn của toàn bộ cấu trúc.
  5. Trạng thái thoát khỏi #part6được in đến bộ mô tả tệp 3. Trong #part3mô 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ừ #part6sẽ là trạng thái thoát cuối cùng cho toàn bộ cấu trúc.
  6. 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 #part4và chuyển tiếp đến filter. Đầu ra từ filtersẽ lần lượt đạt đến thiết bị xuất chuẩn như được giải thích trong#part4

1
Đẹp. Đối với chức năng tôi có thể làm(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)đơn giản hóa để someprog 3>&- 4>&-.
Roger Pate

Phương pháp này cũng hoạt động mà không có subshells:{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
josch

36

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

9
set -o pipefailbê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.
maxschlepzig

Làm thế nào mà làm việc? bạn có một ví dụ không?
Johan

2
Lưu ý rằng -o pipefailkhông có trong POSIX.
Scy

2
Điều này không hoạt động trong BASH 3.2.25 (1) của tôi. Ở đầu / tmp / ff tôi có #!/bin/bash -o pipefail. Lỗi là:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez

2
@FelipeAlvarez: Một số môi trường (bao gồm cả linux) không phân tích các khoảng trắng trên #!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- getoptphâ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
bắt đầu

22

Những gì tôi làm khi có thể là cung cấp mã thoát từ foovào bar. Ví dụ: nếu tôi biết rằng fookhô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ừ fookhô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 barlà 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_codesthường là foo:X bar:Y, nhưng nó có thể là bar:Y foo:Xnếu barbỏ 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 barkhô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 đơn giản trong sản xuất:

  • Nếu có nhiều tập lệnh chạy đồng thời hoặc nếu cùng một tập lệnh sử dụng phương thức này ở một số nơi, bạn cần đảm bảo rằng chúng sử dụng các tên tệp tạm thời khác nhau.
  • Tạo một tập tin tạm thời một cách an toàn trong một thư mục chia sẻ là khó khăn. Thông thường, /tmplà 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")

1
Khi sử dụng cách tiếp cận tệp tạm thời, tôi thích thêm bẫy cho EXIT loại bỏ tất cả các tệp tạm thời để không còn rác ngay cả khi tập lệnh chết
miracle173

17

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_statusestrong các bài kiểm tra của tôi; không có chúng grepkhông thể phân biệt được vì các dòng mới bị ép buộc vào không gian.


12

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>&1Phả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>&1sẽ 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.


Trông có vẻ thú vị, nhưng tôi không thể hiểu được bạn mong đợi lệnh này sẽ làm gì và máy tính của tôi cũng không thể; Tôi nhận được -bash: 3: Bad file descriptor.
G-Man

@ G-Man Phải, tôi cứ quên bash không biết nó đang làm gì khi nói đến mô tả tập tin, không giống như các shell tôi thường sử dụng (tro đi kèm với busybox). Tôi sẽ cho bạn biết khi tôi nghĩ về một cách giải quyết khiến bash hạnh phúc. Trong khi đó, nếu bạn có một hộp debian tiện dụng, bạn có thể dùng thử trong dấu gạch ngang hoặc nếu bạn có hộp bận rộn, bạn có thể dùng thử với tro / sh của hộp bận.
mtraceur

@ G-Man Theo như những gì tôi mong đợi lệnh sẽ làm, và những gì nó làm trong các shell khác, là chuyển hướng stdout từ lệnh1 để nó không bị bắt bởi thay thế lệnh, nhưng một khi bên ngoài thay thế lệnh, nó sẽ bị rớt fd3 trở lại thiết bị xuất chuẩn để nó được chuyển như mong đợi tới lệnh2. Khi lệnh1 thoát, printf kích hoạt và in trạng thái thoát của nó, được bắt vào biến bằng cách thay thế lệnh. Phân tích rất chi tiết ở đây: stackoverflow.com/questions/985876/tee-and-exit-status/. Ngoài ra, nhận xét đó của bạn có được đọc như thể nó có ý xúc phạm không?
mtraceur

Tôi sẽ bắt đầu từ đâu? (1) Tôi xin lỗi nếu bạn cảm thấy bị xúc phạm. Trông có vẻ thú vị, có nghĩa là nghiêm túc; Sẽ thật tuyệt nếu một cái gì đó nhỏ gọn như nó hoạt động tốt như bạn mong đợi. Ngoài ra, tôi đã nói, đơn giản, rằng tôi không hiểu giải pháp của bạn là gì. Tôi đã làm việc / chơi với Unix trong một thời gian dài (từ trước khi Linux tồn tại) và, nếu tôi không hiểu điều gì đó, thì đó là một lá cờ đỏ, có lẽ, những người khác cũng sẽ không hiểu điều đó, và nó cần giải thích thêm (IMNSHO). Tiết (Cont'd)
G-Man

(Tiếp theo) Bạn có thể nghĩ rằng, bạn rất thích nghĩ rằng bạn [bạn] hiểu về mọi thứ nhiều hơn người bình thường, có lẽ bạn nên nhớ rằng mục tiêu của Stack Exchange không phải là một dịch vụ viết lệnh, khuấy động đưa ra hàng ngàn giải pháp một lần cho các câu hỏi khác biệt nhỏ; mà là để dạy mọi người cách giải quyết vấn đề của chính họ. Và, đến cuối cùng, có lẽ bạn cần phải giải thích công cụ đủ tốt để một người bình thường có thể hiểu được. Nhìn vào câu trả lời của lesmana cho một ví dụ về một lời giải thích tuyệt vời. Tiết (Cont'd)
G-Man

11

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.


7

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.


Điều này hoạt động thực sự tốt với hầu hết các shell mà tôi đã thử với nó, bao gồm NetBSD sh, pdksh, mksh, dash, bash. Tuy nhiên, tôi không thể làm cho nó hoạt động với AT & T Ksh (93s +, 93u +) hoặc zsh (4.3.9, 5.2), ngay cả với set -o pipefailksh hoặc bất kỳ số waitlệ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 ifviệ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 .
Greg A. Woods

4

Đâ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))

3
Điều này không hoạt động vì nhiều lý do. 1. Tập tin tạm thời có thể được đọc trước khi nó được viết. 2. Tạo một tệp tạm thời trong một thư mục được chia sẻ với một tên có thể dự đoán được là không an toàn (tầm thường DoS, cuộc đua symlink). 3. Nếu cùng một tập lệnh sử dụng thủ thuật này nhiều lần, nó sẽ luôn sử dụng cùng một tên tệp. Để giải quyết 1, đọc tệp sau khi đường ống đã hoàn thành. Để giải quyết 2 và 3, sử dụng tệp tạm thời có tên được tạo ngẫu nhiên hoặc trong thư mục riêng.
Gilles

+1 Chà $ {PIPESTATUS [0]} thì dễ hơn nhưng ý tưởng cơ bản ở đây thực hiện được nếu người ta biết về các vấn đề mà Gilles đề cập.
Johan

Bạn có thể lưu một vài subshells : (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á.
dubiousjim

4

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:

  • Bạn có một vỏ mà không biết $PIPESTATUSvà cũng khôngset -o pipefail
  • Bạn muốn sử dụng một đường ống để thực hiện song song, vì vậy không có tệp tạm thời.
  • Bạn không muốn có thêm sự lộn xộn xung quanh nếu bạn làm gián đoạn tập lệnh, có thể do mất điện đột ngột.
  • Giải pháp này nên tương đối dễ theo dõi và sạch sẽ để đọc.
  • Bạn không muốn giới thiệu các subshells bổ sung.
  • Bạn không thể sử dụng các bộ mô tả tệp hiện có, vì vậy không được chạm vào stdin / out / err (tuy nhiên bạn có thể tạm thời giới thiệu một số cái mới)

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 | baztuy 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:

  • Một tempfile được tạo ra với mktemp. Điều này thường ngay lập tức tạo ra một tập tin trong/tmp
  • Tempfile này sau đó được chuyển hướng đến FD 9 để ghi và FD 8 để đọc
  • Sau đó tempfile ngay lập tức bị xóa. Nó vẫn mở, mặc dù, cho đến khi cả hai FD đi ra khỏi sự tồn tại.
  • Bây giờ đường ống được bắt đầu. Mỗi bước chỉ thêm vào FD 9, nếu có lỗi.
  • Điều waitnày là cần thiết cho ksh, bởi vì kshngườ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.
  • Sau đó, nội dung của tập tin được đọc. Nếu nó trống (vì tất cả đã hoạt động) readtrả về false, do đó truechỉ 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:

  • FD không sử dụng 9 và 8
  • Một biến môi trường duy nhất để giữ tên của tempfile
  • Và công thức này có thể được điều chỉnh phù hợp với mọi vỏ ngoài đó cho phép chuyển hướng IO
  • Ngoài ra, nó là nền tảng bất khả tri và không cần những thứ như /proc/fd/N

BUGs:

Kịch bản này có một lỗi trong trường hợp /tmphế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 0trong 000phụ 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:

  • kshvà vỏ tương tự mà chỉ chờ lệnh ống cuối cùng cần waitkhô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 -nchí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, sashvà thậm chícsh


3

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)

Làm thế nào để dọn dẹp như jlliagre? Bạn không để lại một tập tin phía sau được gọi là foo-status?
Johan

@Johan: Nếu bạn thích đề xuất của tôi, đừng ngần ngại bỏ phiếu;) Ngoài việc không để lại tệp, nó có lợi thế là cho phép nhiều quy trình chạy đồng thời và thư mục hiện tại không cần phải ghi.
jlliagre

2

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 -makerwvà 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ề haconflà đúng, thì khối 'if' sẽ được thực thi và grepsẽ đọ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.


1
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
$

0

(Với bash ít nhất) kết hợp với set -emộ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 foothấ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 fooin lỗi của chính nó, đủ để hiểu lý do thất bại)


-1

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.


Thêm một !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.
# =========================================================== #

1
Tôi nghĩ rằng điều này không liên quan. Trong ví dụ của bạn, tôi muốn biết mã thoát của ls, không đảo ngược mã thoát củabogus_command
Michael Mrozek

2
Tôi đề nghị rút lại câu trả lời đó.
maxschlepzig

3
Chà, có vẻ như tôi là một thằng ngốc. Tôi thực sự đã sử dụng điều này trong một kịch bản trước khi nghĩ rằng nó đã làm những gì OP muốn. Thật tốt là tôi đã không sử dụng nó cho bất cứ điều gì quan trọng
Falmarri
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.