วิธีคืนค่าของตัวเลือกเชลล์เช่น `set -x`?


47

ฉันต้องการที่จะset -xจุดเริ่มต้นของสคริปต์ของฉันและ "ยกเลิก" มัน (กลับไปยังรัฐก่อนที่ผมตั้งไว้) +xหลังจากนั้นแทนสุ่มสี่สุ่มห้าการตั้งค่า เป็นไปได้ไหม

PS: ฉันตรวจสอบแล้วที่นี่ ; ที่ดูเหมือนจะไม่ตอบคำถามเท่าที่ฉันจะบอกได้

คำตอบ:


58

บทคัดย่อ

เพื่อย้อนกลับเพียงแค่ดำเนินการset -x set +xส่วนใหญ่เวลาที่กลับของสตริงนั้นset -strเป็นสายเดียวกันกับ:+set +str

โดยทั่วไปในการกู้คืนerrexitตัวเลือกเชลล์ทั้งหมด (อ่านด้านล่างเกี่ยวกับ bash ) (เปลี่ยนด้วยsetคำสั่ง) คุณสามารถทำได้ (อ่านด้านล่างเกี่ยวกับshoptตัวเลือกbash ):

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

คำอธิบายอีกต่อไป

ทุบตี

คำสั่งนี้:

shopt -po xtrace

จะสร้างสตริงปฏิบัติการที่สะท้อนถึงสถานะของตัวเลือก การpตั้งค่าสถานะหมายถึงการพิมพ์และการoตั้งค่าสถานะระบุว่าเรากำลังถามเกี่ยวกับตัวเลือกที่กำหนดโดยsetคำสั่ง (ตรงข้ามกับตัวเลือกที่กำหนดโดยshoptคำสั่ง) คุณสามารถกำหนดสตริงนี้ให้กับตัวแปรและดำเนินการตัวแปรที่ท้ายสคริปต์ของคุณเพื่อกู้คืนสถานะเริ่มต้น

# 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"

วิธีนี้ใช้ได้กับหลายตัวเลือกพร้อมกัน:

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"

การเพิ่มset +vxหลีกเลี่ยงการพิมพ์รายการตัวเลือกแบบยาว


และถ้าคุณไม่แสดงชื่อตัวเลือกใด ๆ

oldstate="$(shopt -po)"

มันให้คุณค่าของตัวเลือกทั้งหมด และถ้าคุณปล่อยให้oธงคุณสามารถทำสิ่งเดียวกันกับshoptตัวเลือก:

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

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

หากคุณต้องการทดสอบว่ามีการsetตั้งค่าตัวเลือกหรือไม่วิธีที่ใช้สำนวนที่สุด (Bash) คือ:

[[ -o xtrace ]]

ซึ่งดีกว่าการทดสอบที่คล้ายกันอีกสองแบบ:

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

ด้วยการทดสอบใด ๆ งานนี้:

# 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"

ต่อไปนี้เป็นวิธีทดสอบสถานะของshoptตัวเลือก:

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

โซลูชันที่ใช้งานง่าย POSIX ที่ใช้ในการจัดเก็บตัวsetเลือกทั้งหมดคือ:

set +o

ซึ่งอธิบายไว้ในมาตรฐาน POSIXเป็น:

+ o

    เขียนการตั้งค่าตัวเลือกปัจจุบันไปยังเอาต์พุตมาตรฐานในรูปแบบที่เหมาะสมสำหรับการใส่เข้าไปในเชลล์ใหม่เป็นคำสั่งที่ใช้การตั้งค่าตัวเลือกเดียวกัน

ดังนั้นเพียงแค่:

oldstate=$(set +o)

จะรักษาค่าสำหรับตัวเลือกทั้งหมดที่ตั้งค่าโดยใช้setคำสั่ง

การกู้คืนตัวเลือกต่าง ๆ ให้เป็นค่าดั้งเดิมนั้นเป็นเรื่องของการใช้งานตัวแปร:

set +vx; eval "$oldstate"

shopt -poตรงนี้เป็นเทียบเท่ากับการใช้ทุบตีของ หมายเหตุว่ามันจะไม่ครอบคลุมทั้งหมดที่เป็นไปได้ทุบตีshoptตัวเลือกเป็นบางส่วนของผู้ที่ถูกกำหนดโดย

ทุบตีกรณีพิเศษ

มีตัวเลือกเปลือกอื่น ๆ อีกมากมายที่ระบุไว้shoptในทุบตี:

$ 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

สิ่งเหล่านี้สามารถผนวกเข้ากับตัวแปรที่ตั้งไว้ด้านบนและเรียกคืนในลักษณะเดียวกัน

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

มันเป็นไปได้ที่จะทำ (คน$-ถูกผนวกเพื่อให้แน่ใจว่าerrexitจะถูกรักษาไว้):

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

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

หมายเหตุ : แต่ละเชลล์มีวิธีที่แตกต่างกันเล็กน้อยในการสร้างรายการตัวเลือกที่ถูกตั้งค่าหรือไม่ได้ตั้งค่า (ไม่พูดถึงตัวเลือกต่าง ๆ ที่กำหนดไว้) ดังนั้นสตริงจึงไม่สามารถเคลื่อนย้ายได้ระหว่างเชลล์ แต่จะใช้ได้สำหรับเชลล์ตัวเดียวกัน

กรณีพิเศษ zsh

zshยังทำงานได้อย่างถูกต้อง (ติดตาม POSIX) ตั้งแต่รุ่น 5.3 ในรุ่นก่อนหน้านั้นตาม POSIX เพียงบางส่วนเท่านั้นโดยset +oที่มันพิมพ์ตัวเลือกในรูปแบบที่เหมาะสำหรับการใส่เข้าไปใหม่ในเชลล์เป็นคำสั่ง แต่สำหรับตัวเลือกการตั้งค่าเท่านั้น(ไม่ได้พิมพ์ตัวเลือกยกเลิกการตั้งค่า )

กรณีพิเศษ mksh

mksh (และตามลำดับ lksh) ยังไม่ (MIRBSD KSH R54 2016/11/11) สามารถทำสิ่งนี้ได้ คู่มือ mksh ประกอบด้วย:

ในรุ่นอนาคตชุด + o จะทำงานตามคำสั่ง POSIX และพิมพ์เพื่อคืนค่าตัวเลือกปัจจุบันแทน

ชุดกรณีพิเศษ

ใน bash ค่าของset -e( errexit) จะถูกรีเซ็ตภายในเชลล์ย่อยซึ่งทำให้ยากต่อการดักจับค่าด้วยset +oเชลล์ย่อย $ (…)

วิธีแก้ปัญหาใช้:

oldstate="$(set +o); set -$-"

21

ด้วย Almquist shell และอนุพันธ์ ( อย่างน้อยdashNetBSD / FreeBSD sh) และ bash4.4 หรือสูงกว่าคุณสามารถสร้างตัวเลือกในท้องถิ่นให้กับฟังก์ชั่นด้วยlocal -(ทำ$-ตัวแปรท้องถิ่นหากคุณต้องการ):

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

แต่นั่นไม่ได้นำไปใช้กับที่มาไฟล์ แต่คุณสามารถ redefine sourceเป็นsource() { . "$@"; }ในการทำงานรอบที่

ด้วยksh88การเปลี่ยนแปลงตัวเลือกจะอยู่ภายในฟังก์ชันนั้นตามค่าเริ่มต้น ด้วยksh93นั่นเป็นเพียงกรณีของฟังก์ชั่นที่กำหนดด้วยfunction f { ...; }ไวยากรณ์ (และการกำหนดขอบเขตเป็นแบบคงที่เมื่อเทียบกับการกำหนดขอบเขตแบบไดนามิกที่ใช้ในเชลล์อื่นรวมถึง ksh88):

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

ในzshนั้นทำด้วยlocaloptionsตัวเลือก:

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

POSIXly คุณสามารถทำ:

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

อย่างไรก็ตามเชลล์บางตัวจะส่งออก a + 2> /dev/nullตามการกู้คืน (และคุณจะเห็นร่องรอยของcaseโครงสร้างนั้นถ้าset -xเปิดใช้งานแล้ว) วิธีการนั้นยังไม่ได้เข้ามาใหม่ (เช่นถ้าคุณทำในฟังก์ชั่นที่เรียกตัวเองหรือฟังก์ชั่นอื่นที่ใช้เคล็ดลับเดียวกัน)

ดูhttps://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (ขอบเขตโลคัลสำหรับตัวแปรและตัวเลือกสำหรับ POSIX เชลล์) สำหรับวิธีใช้สแต็กที่ทำงานโดยรอบ

ด้วยเชลล์ใด ๆ คุณสามารถใช้ subshells เพื่อ จำกัด ขอบเขตของตัวเลือก

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

อย่างไรก็ตามนั่น จำกัด ขอบเขตของทุกสิ่ง (ตัวแปรฟังก์ชั่นนามแฝงการเปลี่ยนเส้นทางไดเรกทอรีการทำงานปัจจุบัน ... ) ไม่ใช่แค่ตัวเลือก


bash 4.4 ออกมาเมื่อ 4 วันก่อน (16 กันยายน 2016) ดังนั้นคุณอาจจะต้องคอมไพล์ใหม่ด้วยตัวเอง แต่ทำไมไม่
edi9999

สำหรับฉันแล้วoldstate=$(set +o)มันเป็นวิธีที่ง่ายกว่า (และ POSIX) ในการจัดเก็บตัวเลือกทั้งหมด
sorontar

@sorontar จุดที่ดีแม้ว่ามันจะไม่ได้ผลสำหรับการใช้งานเชลล์เช่นpdksh/ mksh(และอนุพันธ์ pdksh อื่น ๆ ) หรือzshที่set +oจะส่งออกเฉพาะค่าเบี่ยงเบนจากการตั้งค่าเริ่มต้น นั่นจะใช้ได้กับ bash / dash / yash แต่ไม่สามารถพกพาได้
Stéphane Chazelas

13

คุณสามารถอ่าน$-ตัวแปรที่จุดเริ่มต้นเพื่อดูว่า-xมีการตั้งค่าหรือไม่แล้วบันทึกลงในตัวแปรเช่น

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

จากคู่มือ Bash :

($ -, ยัติภังค์.) ขยายเป็นแฟล็กตัวเลือกปัจจุบันตามที่ระบุไว้เมื่อร้องขอโดยคำสั่ง set builtin หรือที่กำหนดโดยเชลล์เอง (เช่นตัวเลือก -i)


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

ในทุบตี, $ SHELLOPTSถูกตั้งค่าด้วยการตั้งค่าสถานะที่เปิดอยู่ ตรวจสอบก่อนที่คุณจะเปิด xtrace และรีเซ็ต xtrace เฉพาะเมื่อปิดก่อนหน้านี้


5

เพียงเพื่อระบุความชัดเจนหากset -xจะมีผลบังคับใช้ในช่วงระยะเวลาของสคริปต์และนี่เป็นเพียงมาตรการทดสอบชั่วคราว (เพื่อไม่ให้เป็นส่วนหนึ่งของผลลัพธ์อย่างถาวร) จากนั้นเรียกใช้สคริปต์ด้วย-xตัวเลือกเช่น

$ bash -x path_to_script.sh

... หรือเปลี่ยนสคริปต์ชั่วคราว (บรรทัดแรก) เพื่อเปิดใช้งานการดีบักโดยเพิ่ม-xตัวเลือก:

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

ฉันรู้ว่านี่อาจเป็นจังหวะที่กว้างเกินไปสำหรับสิ่งที่คุณต้องการ แต่มันเป็นวิธีที่ง่ายที่สุดและเร็วที่สุดในการเปิด / ปิดโดยไม่ซับซ้อนเกินไปสคริปต์กับสิ่งชั่วคราวที่คุณอาจต้องการลบต่อไป (ในประสบการณ์ของฉัน)


3

สิ่งนี้จัดเตรียมฟังก์ชันเพื่อบันทึกและกู้คืนแฟล็กที่มองเห็นได้ผ่าน$-พารามิเตอร์พิเศษPOSIX เราใช้localส่วนขยายสำหรับตัวแปรท้องถิ่น ใน POSIX ที่สอดคล้องกับสคริปต์พกพาตัวแปรทั่วโลกจะถูกนำมาใช้ (ไม่มีlocalคำหลัก):

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"}
}

สิ่งนี้ถูกใช้คล้ายกับวิธีการบันทึกและขัดจังหวะแฟล็กในเคอร์เนล 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 ... */

สิ่งนี้จะไม่ทำงานสำหรับตัวเลือกเพิ่มเติมใด ๆ ที่ไม่รวมอยู่ใน$-ตัวแปร

เพิ่งสังเกตเห็นว่า POSIX มีสิ่งที่ฉันกำลังมองหา: +oอาร์กิวเมนต์ของsetไม่ได้เป็นตัวเลือก แต่คำสั่งที่ทิ้งคำสั่งจำนวนมากซึ่งถ้าeval-ed จะเรียกคืนตัวเลือก ดังนั้น:

flags=$(set +o)

set -x

# ...

eval "$flags"

แค่นั้นแหละ.

ปัญหาเล็ก ๆ อย่างหนึ่งคือถ้า-xตัวเลือกถูกเปิดใช้ก่อนหน้านี้จะเห็นคำสั่งที่evalน่าเกลียดset -oมากมาย เพื่อกำจัดสิ่งนี้เราสามารถทำสิ่งต่อไปนี้:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.