Bagaimana cara mengembalikan nilai opsi shell seperti `set -x`?

47

Saya ingin set -xdi awal skrip saya dan "membatalkan" (kembali ke negara sebelum saya mengaturnya) sesudahnya daripada pengaturan membabi buta +x. Apakah ini mungkin?

PS: Saya sudah periksa di sini ; yang sepertinya tidak menjawab pertanyaan saya sejauh yang saya bisa katakan.

Roney Michael
sumber

Jawaban:

59

Abstrak

Untuk membalikkan set -xhanya menjalankan a set +x. Sebagian besar waktu, kebalikan dari string set -stradalah string yang sama dengan +: set +str.

Secara umum, untuk mengembalikan semua (baca di bawah tentang bash errexit) opsi shell (diubah dengan setperintah) yang dapat Anda lakukan (juga baca di bawah tentang shoptopsi bash ):

oldstate="$(set +o); set -$-"                # POSIXly store all set options.
.
.
set -vx; eval "$oldstate"         # restore all options stored.

Deskripsi yang lebih panjang

pesta

Perintah ini:

shopt -po xtrace

akan menghasilkan string yang dapat dieksekusi yang mencerminkan keadaan opsi. The pberarti bendera mencetak, dan omenspesifikasikan flag yang kita bertanya tentang pilihan (s) yang ditetapkan oleh setperintah (sebagai lawan pilihan (s) yang ditetapkan oleh shoptperintah). Anda dapat menetapkan string ini ke variabel, dan menjalankan variabel di akhir skrip Anda untuk mengembalikan keadaan awal.

# store state of xtrace option.
tracestate="$(shopt -po xtrace)"

# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"

# restore the value of xtrace to its original value.
eval "$tracestate"

Solusi ini berfungsi untuk beberapa opsi secara bersamaan:

oldstate="$(shopt -po xtrace noglob errexit)"

# change options as needed
set -x
set +x
set -f
set -e
set -x

# restore to recorded state:
set +vx; eval "$oldstate"

Menambahkan set +vxmenghindari pencetakan daftar opsi yang panjang.


Dan, jika Anda tidak mencantumkan nama opsi,

oldstate="$(shopt -po)"

ini memberi Anda nilai dari semua opsi. Dan, jika Anda meninggalkan obenderanya, Anda dapat melakukan hal yang sama dengan shoptopsi:

# store state of dotglob option.
dglobstate="$(shopt -p dotglob)"

# store state of all options.
oldstate="$(shopt -p)"

Jika Anda perlu menguji apakah suatu setopsi diset, cara paling idiomatis (Bash) untuk melakukannya adalah:

[[ -o xtrace ]]

yang lebih baik dari dua tes serupa lainnya:

  1. [[ $- =~ x ]]
  2. [[ $- == *x* ]]

Dengan salah satu tes, ini berfungsi:

# record the state of the xtrace option in ts (tracestate):
[ -o xtrace ] && ts='set -x' || ts='set +x'

# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"

# set the xtrace option back to what it was.
eval "$ts"

Berikut cara menguji keadaan shoptopsi:

if shopt -q dotglob
then
        # dotglob is set, so “echo .* *” would list the dot files twice.
        echo *
else
        # dotglob is not set.  Warning: the below will list “.” and “..”.
        echo .* *
fi

POSIX

Solusi sederhana yang sesuai dengan POSIX untuk menyimpan semua setopsi adalah:

set +o

yang dijelaskan dalam standar POSIX sebagai:

+ o

    Tulis pengaturan opsi saat ini ke output standar dalam format yang sesuai untuk dimasukkan kembali ke shell sebagai perintah yang mencapai pengaturan opsi yang sama.

Jadi, cukup:

oldstate=$(set +o)

akan mempertahankan nilai untuk semua opsi yang ditetapkan menggunakan setperintah.

Sekali lagi, mengembalikan opsi ke nilai aslinya adalah masalah mengeksekusi variabel:

set +vx; eval "$oldstate"

Ini persis sama dengan menggunakan Bash shopt -po. Perhatikan bahwa itu tidak akan mencakup semua opsi Bash yang mungkin , karena beberapa di antaranya ditetapkan oleh shopt.

bash kasus khusus

Ada banyak opsi shell lain yang terdaftar shoptdi dalam bash:

$ shopt
autocd          off
cdable_vars     off
cdspell         off
checkhash       off
checkjobs       off
checkwinsize    on
cmdhist         on
compat31        off
compat32        off
compat40        off
compat41        off
compat42        off
compat43        off
complete_fullquote  on
direxpand       off
dirspell        off
dotglob         off
execfail        off
expand_aliases  on
extdebug        off
extglob         off
extquote        on
failglob        off
force_fignore   on
globasciiranges off
globstar        on
gnu_errfmt      off
histappend      on
histreedit      off
histverify      on
hostcomplete    on
huponexit       off
inherit_errexit off
interactive_comments    on
lastpipe        on
lithist         off
login_shell     off
mailwarn        off
no_empty_cmd_completion off
nocaseglob      off
nocasematch     off
nullglob        off
progcomp        on
promptvars      on
restricted_shell    off
shift_verbose   off
sourcepath      on
xpg_echo        off

Itu bisa ditambahkan ke set variabel di atas dan dikembalikan dengan cara yang sama:

$ oldstate="$oldstate;$(shopt -p)"
.
.                                   # change options as needed.
.
$ eval "$oldstate" 

Itu mungkin dilakukan ( $-ditambahkan untuk memastikan errexitdilestarikan):

oldstate="$(shopt -po; shopt -p); set -$-"

set +vx; eval "$oldstate"             # use to restore all options.

Catatan : setiap shell memiliki cara yang sedikit berbeda untuk membangun daftar opsi yang disetel atau tidak disetel (belum lagi opsi yang berbeda yang ditentukan), sehingga string tidak portabel di antara shell, tetapi berlaku untuk shell yang sama.

kasus khusus zsh

zshjuga berfungsi dengan benar (mengikuti POSIX) sejak versi 5.3. Dalam versi sebelumnya, ia hanya mengikuti POSIX sebagian dengan set +omencetak opsi dalam format yang sesuai untuk diinput ke shell sebagai perintah, tetapi hanya untuk opsi set (tidak mencetak opsi yang tidak disetel ).

kasus khusus mksh

Mksh (dan sebagai konsekuensi lksh) belum (MIRBSD KSH R54 2016/11/11) dapat melakukan ini. Manual mksh berisi ini:

Dalam versi yang akan datang, set + o akan berperilaku sesuai perintah POSIX dan mencetak untuk mengembalikan opsi saat ini.

set -e kasus khusus

Di bash, nilai set -e( errexit) diatur ulang di dalam sub-shell, yang membuatnya sulit untuk menangkap nilainya dengan set +odi dalam sub-shell $ (...).

Sebagai solusinya, gunakan:

oldstate="$(set +o); set -$-"
sorontar
sumber
21

Dengan cangkang Almquist dan turunannya ( dash, shsetidaknya NetBSD / FreeBSD ) dan bash4.4 atau lebih, Anda dapat membuat opsi lokal ke suatu fungsi dengan local -(buat $-variabel lokal jika Anda suka):

$ bash-4.4 -c 'f() { local -; set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

Itu tidak berlaku untuk bersumber file, tetapi Anda dapat mendefinisikan sourcesebagai source() { . "$@"; }untuk bekerja di sekitar itu.

Dengan ksh88, perubahan opsi bersifat lokal ke fungsi secara default. Dengan ksh93, itu hanya kasus untuk fungsi yang didefinisikan dengan function f { ...; }sintaks (dan pelingkupan adalah statis dibandingkan dengan pelingkupan dinamis yang digunakan dalam shell lain termasuk ksh88):

$ ksh93 -c 'function f { set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

Di zsh, itu dilakukan dengan localoptionsopsi:

$ zsh -c 'f() { set -o localoptions; set -x; echo test; }; f; echo no trace'
+f:0> echo test
test
no trace

POSIXly, Anda dapat melakukan:

case $- in
  (*x*) restore=;;
  (*) restore='set +x'; set -x
esac
echo test
{ eval "$restore";} 2> /dev/null
echo no trace

Namun beberapa kerang akan menampilkan + 2> /dev/nullpada restore (dan Anda akan melihat jejak dari yang casemembangun tentu saja jika set -xsudah diaktifkan). Pendekatan itu juga tidak masuk kembali (seperti jika Anda melakukan itu dalam fungsi yang memanggil dirinya sendiri atau fungsi lain yang menggunakan trik yang sama).

Lihat https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (cakupan lokal untuk variabel dan opsi untuk cangkang POSIX) untuk cara mengimplementasikan tumpukan yang bekerja di sekitarnya.

Dengan shell apa pun, Anda dapat menggunakan subkulit untuk membatasi ruang lingkup opsi

$ sh -c 'f() (set -x; echo test); f; echo no trace'
+ echo test
test
no trace

Namun, itu membatasi ruang lingkup segalanya (variabel, fungsi, alias, pengalihan, direktori kerja saat ini ...), bukan hanya opsi.

Stéphane Chazelas
sumber
bash 4.4 keluar 4 hari yang lalu (16-september-2016), jadi mungkin Anda harus melakukan kompilasi ulang sendiri, tetapi mengapa tidak
edi9999
Menurut saya itu oldstate=$(set +o)adalah cara yang lebih sederhana (dan POSIX) untuk menyimpan semua opsi.
sorontar
@sorontar, poin bagusnya meskipun itu tidak bekerja untuk implementasi shell seperti pdksh/ mksh(dan turunan pdksh lainnya) atau di zshmana set +ohanya menampilkan penyimpangan dari pengaturan default. Itu akan bekerja untuk bash / dash / yash tetapi tidak portabel.
Stéphane Chazelas
13

Anda dapat membaca $-variabel di awal untuk melihat apakah -xsudah diatur atau tidak dan kemudian simpan ke variabel misalnya

if [[ $- == *x* ]]; then
  was_x_set=1
else
  was_x_set=0
fi

Dari manual Bash :

($ -, tanda hubung.) Memperluas ke flag opsi saat ini seperti yang ditentukan pada doa, oleh perintah builtin yang ditetapkan, atau yang ditetapkan oleh shell itu sendiri (seperti opsi -i).

Dawid Grabowski
sumber
7
[[ $SHELLOPTS =~ xtrace ]] && wasset=1
set -x
echo rest of your script
[[ $wasset -eq 0 ]] && set +x

Dalam bash, $ SHELLOPTS disetel dengan bendera yang dihidupkan. Periksa sebelum Anda menyalakan xtrace, dan setel ulang xtrace hanya jika dimatikan sebelumnya.

Jeff Schaller
sumber
5

Hanya untuk menyatakan yang sudah jelas, jika set -xakan berlaku selama durasi skrip, dan ini hanyalah ukuran pengujian sementara (tidak menjadi bagian dari output secara permanen), maka jalankan skrip dengan -xopsi, misalnya,

$ bash -x path_to_script.sh

... atau, ubah sementara skrip (baris pertama) untuk mengaktifkan hasil debug dengan menambahkan -xopsi:

#!/bin/bash -x
...rest of script...

Saya menyadari ini mungkin stroke yang terlalu luas untuk apa yang Anda inginkan, tetapi ini adalah cara paling sederhana & tercepat untuk mengaktifkan / menonaktifkan, tanpa terlalu menyulitkan skrip dengan hal-hal sementara yang mungkin ingin Anda hapus (menurut pengalaman saya).

michael
sumber
3

Ini menyediakan fungsi untuk menyimpan dan mengembalikan flag yang terlihat melalui $-parameter khusus POSIX . Kami menggunakan localekstensi untuk variabel lokal. Dalam skrip portabel yang sesuai dengan POSIX, variabel global akan digunakan (tanpa localkata kunci):

save_opts()
{
  echo $-
}

restore_opts()
{
  local saved=$1
  local on
  local off=$-

  while [ ${#saved} -gt 0 ] ; do
    local rest=${saved#?}
    local first=${saved%$rest}

    if echo $off | grep -q $first ; then
      off=$(echo $off | tr -d $first)
    fi

    on="$on$first"
    saved=$rest
  done

  set ${on+"-$on"} ${off+"+$off"}
}

Ini digunakan mirip dengan bagaimana flag interupsi disimpan dan dipulihkan di kernel Linux:

Shell:                                Kernel:

flags=$(save_opts)                    long flags;
                                      save_flags (flags);

set -x  # or any other                local_irq_disable(); /* disable irqs on this core */

# ... -x enabled ...                  /* ... interrupts disabled ... */

restore_opts $flags                   restore_flags(flags);

# ... x restored ...                  /* ... interrupts restored ... */

Ini tidak akan berfungsi untuk semua opsi yang diperluas yang tidak tercakup dalam $-variabel.

Hanya memperhatikan bahwa POSIX memiliki apa yang saya cari: +oargumen setbukanlah pilihan, tetapi perintah yang membuang banyak perintah yang, jika eval-ed akan mengembalikan opsi. Begitu:

flags=$(set +o)

set -x

# ...

eval "$flags"

Itu dia.

Satu masalah kecil adalah bahwa jika -xopsi diubah sebelum ini eval, kesibukan set -operintah terlihat. Untuk menghilangkan ini, kita dapat melakukan hal berikut:

set +x             # turn off trace not to see the flurry of set -o.
eval "$flags"      # restore flags
Kaz
sumber
1

Anda dapat menggunakan sub-shell.

(
   set 
   do stuff
)
Other stuff, that set does not apply to
ctrl-alt-delor
sumber
0

Mirip dengan apa yang dikatakan @Jeff Shaller tetapi diatur ke TIDAK gema bagian di tengah (yang saya butuhkan):

OPTS=$SHELLOPTS ; set +x
echo 'message' # or whatever
[[ $OPTS =~ xtrace ]] && set -x
Lee Meador
sumber