Cách lưu và khôi phục tất cả các tùy chọn vỏ bao gồm errexit


9

Tôi đã đọc qua nhiều câu hỏi trên các trang web trao đổi ngăn xếp khác nhau và các diễn đàn trợ giúp unix về cách sửa đổi các tùy chọn shell và sau đó khôi phục chúng - câu hỏi toàn diện nhất tôi tìm thấy ở đây là tại Làm thế nào để "hoàn tác" một `set -x`?

Sự khôn ngoan nhận được dường như là để lưu kết quả của set +ohoặc shopt -posau evalđó để khôi phục các cài đặt trước đó.

Tuy nhiên, trong thử nghiệm của riêng tôi với bash 3.x và 4.x, errexittùy chọn không được lưu chính xác khi thực hiện thay thế lệnh.

Dưới đây là một kịch bản ví dụ để hiển thị vấn đề:

set -o errexit
set -o nounset
echo "LOCAL SETTINGS:"
set +o
OLDOPTS=$(set +o)
echo
echo "SAVED SETTINGS:"
echo "$OLDOPTS"

Và đầu ra (tôi đã cắt bớt một số biến không liên quan):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Điều này có vẻ cực kỳ nguy hiểm. Hầu hết các tập lệnh tôi viết phụ thuộc vào errexitviệc dừng thực thi nếu có bất kỳ lệnh nào bị lỗi. Tôi chỉ tìm thấy một lỗi trong một trong các tập lệnh của mình do lỗi này gây ra, trong đó chức năng được cho là khôi phục errexitở phần cuối đã ghi đè lên nó, đặt nó trở lại mặc định tắt trong suốt thời gian của tập lệnh.

Những gì tôi muốn có thể làm là viết các hàm có thể đặt tùy chọn khi cần và sau đó khôi phục tất cả các tùy chọn đúng trước khi thoát. Nhưng có vẻ như trong lớp con được gọi bằng lệnh thay thế, errexitkhông được kế thừa.

Tôi không biết làm cách nào để cứu kết quả set +omà không sử dụng thay thế lệnh hoặc nhảy qua các vòng của FIFO. Tôi có thể đọc từ $SHELLOPTSnhưng nó không thể ghi hoặc không thể evalđịnh dạng.

Tôi biết một cách khác là sử dụng hàm subshell , nhưng điều đó gây ra rất nhiều vấn đề đau đầu vì có thể đăng nhập đầu ra cũng như truyền lại nhiều biến.

Có thể liên quan: /programming/29532904/bash-subshell-errexit-semantics (có vẻ như có một cách giải quyết cho bash 4.4 trở lên nhưng tôi muốn có một giải pháp di động)


Điều này không khôi phục shoptcác tùy chọn bash set (như nullglob).
Isaac

Câu trả lời:


6

Những gì bạn đang làm nên làm việc. Nhưng bash tắt errexittùy chọn trong thay thế lệnh, vì vậy nó bảo toàn tất cả các tùy chọn ngoại trừ tùy chọn này. Điều này là cụ thể cho bash và cụ thể cho các errexittùy chọn. Bash không bảo quản errexitkhi chạy ở chế độ POSIX. Kể từ bash 4.4, bash cũng không rõ ràng errexittrong việc thay thế lệnh nếu shopt -s inherit_errexitcó hiệu lực.

Vì tùy chọn bị tắt trước khi bất kỳ mã nào chạy bên trong lệnh thay thế, bạn phải kiểm tra nó bên ngoài.

OLDOPTS=$(set +o)
case $- in
  *e*) OLDOPTS="$OLDOPTS; set -e";;
  *) OLDOPTS="$OLDOPTS; set +e";;
esac

Nếu bạn không thích sự phức tạp này, hãy sử dụng zsh thay thế.

setopt local_options

3
Lưu ý rằng bash4.4hiện nay có local -(à la ash) như là một tương đương với zsh's setopt localoptions(hoặc những gì ksh88 làm theo mặc định)
Stéphane Chazelas

Xem thêm shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(hai bộ tùy chọn là một bashđặc điểm riêng khác).
Stéphane Chazelas

Đơn giản hơn : OLDOPTS="$(set +o); set -$-".
Isaac

2

Sau khi thử cũ trên alpine 3.6, giờ đây tôi đã thực hiện cách tiếp cận đơn giản hơn nhiều sau đây:

OLDOPTS="$(set +o); set -${-//c}"
set -euf -o pipefail

... my stuff

# restore original options
set +vx; eval "${OLDOPTS}"

theo tài liệu, "$ -" giữ danh sách các tùy chọn hiện đang hoạt động. Có vẻ để làm việc tuyệt vời, tôi có thiếu gì không?


@Isaac Tôi sử dụng 'set + euf' vì $ - chỉ chứa danh sách các tùy chọn hoạt động . tức là khi đặt lại, trước tiên tôi hủy kích hoạt mọi thứ và sau đó chỉ kích hoạt lại danh sách các tùy chọn hoạt động trước đó (tôi chọn 'euf' vì đó là những tùy chọn duy nhất tôi thường sửa đổi - vì mục đích chung có lẽ nên chứa danh sách đầy đủ các tùy chọn có thể). Điểm hay về 'set + vx', tôi đã sửa đổi ví dụ trên cho phù hợp
Erich Eichinger

có thể hoạt động với errexit nhưng không phải là kiểm tra giới hạn hoặc kiểm tra giới hạn ( set -uf)
Erich Eichinger

@Isaac Mình đứng sửa. Tôi không biết mình đã làm gì sai. Cảm ơn rất nhiều vì sự giúp đỡ của bạn! Cũng rất tốt khi thấy bạn chạy vào và giải quyết vấn đề cờ -c - Tôi ước tôi đã thấy nhận xét của bạn trước đó;)
Erich Eichinger

Tôi đã cập nhật giải pháp trên dựa trên phản hồi của
@Isaac

1

errexit được tuyên truyền thành quá trình thay thế.

set -e

# Backup restore commands into an array
declare -a OPTS
readarray -t OPTS < <(shopt -po)

set +e

# Restore options
declare cmd
for cmd in "${OPTS[@]}"; do
    eval "$cmd"
done

Kiểm tra:

$  shopt -po errexit
set -o errexit

Phiên bản Bash:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

1

Giải pháp đơn giản là nối thêm errexitcài đặt vào OLDOPTS:

OLDOPTS="$(set +o)"
[ "${BASH_VERSION:+x}" ] && shopt -qo errexit && OLDOPTS+=";set -e" || true

Làm xong.


Xem thêm [[ -o errexit ]](ksh / bash / zsh) và [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas

Đây shoptlà một lệnh hợp lệ cho bash, không có lý do có ý nghĩa để tránh nó (tôi chỉ là vấn đề sở thích cá nhân của bạn về cách viết câu trả lời). Không phải là một sự thay đổi có ý nghĩa. @ StéphaneChazelas
Isaac
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.