Bagaimana cara menyimpan & mengembalikan semua opsi shell termasuk errexit

9

Saya telah membaca banyak pertanyaan tentang berbagai situs pertukaran stack dan unix forum bantuan tentang cara memodifikasi opsi shell dan kemudian mengembalikannya - yang paling komprehensif yang saya temukan di sini adalah di Bagaimana "membatalkan" a `set -x`?

The kebijaksanaan yang diterima tampaknya baik menyimpan dari hasil set +oatau shopt -pokemudian evalnanti untuk mengembalikan pengaturan sebelumnya.

Namun, dalam pengujian saya sendiri dengan bash 3.x dan 4.x, errexitopsi tidak disimpan dengan benar saat melakukan penggantian perintah.

Berikut ini contoh skrip untuk menunjukkan masalah:

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

Dan output (saya memangkas beberapa variabel yang tidak relevan):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Ini sepertinya sangat berbahaya. Kebanyakan skrip yang saya tulis bergantung pada errexitpenghentian eksekusi jika ada perintah yang gagal. Saya baru saja menemukan bug di salah satu skrip saya yang disebabkan oleh ini, di mana fungsi yang seharusnya mengembalikan errexitpada akhirnya berakhir dengan menimpanya, mengaturnya kembali ke default off selama durasi skrip.

Yang ingin saya lakukan adalah menulis fungsi yang dapat mengatur opsi sesuai kebutuhan dan kemudian mengembalikan semua opsi dengan benar sebelum keluar. Tetapi tampaknya seolah-olah dalam subkulit dipanggil oleh substitusi perintah, errexittidak diwarisi.

Saya bingung bagaimana cara menyelamatkan hasil set +otanpa menggunakan substitusi perintah atau melompat melalui lingkaran FIFO. Saya dapat membaca dari $SHELLOPTStetapi evalformatnya tidak bisa ditulis atau dalam -able.

Saya tahu salah satu alternatifnya adalah menggunakan fungsi subshell , tapi itu memperkenalkan banyak sakit kepala karena bisa mencatat output dan juga mengembalikan beberapa variabel.

Mungkin terkait: /programming/29532904/bash-subshell-errexit-semantics (sepertinya ada solusi untuk bash 4.4 dan yang lebih tinggi, tetapi saya lebih suka memiliki solusi portabel)

Eliot
sumber
Ini tidak mengembalikan shoptopsi set bash (seperti nullglob).
Isaac

Jawaban:

6

Apa yang Anda lakukan harus berhasil. Tapi bash mematikan errexitopsi di pergantian perintah, jadi itu mempertahankan semua opsi kecuali yang ini. Ini khusus untuk bash dan khusus untuk errexitopsi. Bash tetap dipertahankan errexitsaat menjalankan dalam mode POSIX. Sejak bash 4.4, bash juga tidak jelas errexitdalam substitusi perintah jika shopt -s inherit_errexitberlaku.

Karena opsi ini dimatikan sebelum kode apa pun berjalan di dalam substitusi perintah, Anda harus memeriksanya di luar.

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

Jika Anda tidak menyukai kompleksitas ini, gunakan zsh sebagai gantinya.

setopt local_options
Gilles 'SANGAT berhenti menjadi jahat'
sumber
3
Perhatikan bahwa bash4.4sekarang memiliki local -(à la ash) sebagai setara dengan zsh's setopt localoptions(atau apa yang ksh88 lakukan secara default)
Stéphane Chazelas
Lihat juga shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(dua set pilihan adalah bashkeistimewaan lain).
Stéphane Chazelas
Sederhana: OLDOPTS="$(set +o); set -$-".
Isaac
2

Setelah mencoba yang lama di atas pada alpine 3.6, saya sekarang telah mengambil pendekatan yang jauh lebih sederhana:

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

... my stuff

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

sesuai dokumentasi, "$ -" menyimpan daftar opsi yang sedang aktif. Tampaknya bekerja dengan baik, apakah saya melewatkan sesuatu?

Erich Eichinger
sumber
@Isaac Saya menggunakan 'set + euf' karena $ - hanya berisi daftar opsi aktif . yaitu ketika mengatur ulang, saya pertama-tama menonaktifkan semua dan kemudian mengaktifkan kembali hanya daftar opsi yang sebelumnya aktif (saya memilih 'euf' karena itu adalah satu-satunya pilihan yang biasanya saya modifikasi - untuk penggunaan umum, mungkin harus berisi daftar lengkap opsi yang mungkin). Poin bagus tentang 'set + vx', saya memodifikasi contoh di atas sesuai
Erich Eichinger
yang mungkin berfungsi untuk errexit tetapi tidak misalnya untuk batas memeriksa atau globbing ( set -uf)
Erich Eichinger
@ Isaac saya berdiri dikoreksi. Saya tidak tahu apa yang saya lakukan salah. Terima kasih banyak atas bantuan Anda! Juga bagus untuk melihat bahwa Anda bertemu dan memecahkan masalah -c flag - Saya berharap saya telah melihat komentar Anda sebelumnya;)
Erich Eichinger
Saya memperbarui solusi di atas berdasarkan umpan balik @Iacac
Erich Eichinger
1

errexit disebarkan ke dalam proses substitusi.

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

Memeriksa:

$  shopt -po errexit
set -o errexit

Versi bash:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Sergej Alikov
sumber
1

Solusi sederhana adalah menambahkan errexitpengaturan ke OLDOPTS:

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

Selesai

Ishak
sumber
Lihat juga [[ -o errexit ]](ksh / bash / zsh) dan [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas
Ini shoptadalah perintah yang valid untuk bash, tidak ada alasan yang berarti untuk menghindarinya (saya hanya masalah preferensi pribadi Anda tentang bagaimana jawaban harus ditulis). Bukan perubahan yang berarti. @ StéphaneChazelas
Isaac