सेट-ई को कोष्ठक () के बाद उप-सूची के अंदर काम नहीं करता है या एक सूची के बाद क्यों?


30

मैंने हाल ही में इस तरह से कुछ स्क्रिप्टिंग को चलाया है:

( set -e ; do-stuff; do-more-stuff; ) || echo failed

यह मुझे ठीक लगता है, लेकिन यह काम नहीं करता है! set -eलागू नहीं होता है, जब आप जोड़ने ||। इसके बिना, यह ठीक काम करता है:

$ ( set -e; false; echo passed; ); echo $?
1

हालाँकि, अगर मैं जोड़ देता हूं ||, तो set -eइसे नजरअंदाज कर दिया जाता है:

$ ( set -e; false; echo passed; ) || echo failed
passed

एक वास्तविक, अलग शेल का उपयोग करना उम्मीद के मुताबिक काम करता है:

$ sh -c 'set -e; false; echo passed;' || echo failed
failed

मैंने इसे कई अलग-अलग गोले (बैश, डैश, ksh93) में आज़माया है और सभी एक जैसा व्यवहार करते हैं, इसलिए यह बग नहीं है। क्या कोई इसे समझा सकता है?


`(....)` `निर्माण अपनी सामग्री को चलाने के लिए एक अलग शेल शुरू करता है, इसमें कोई भी सेटिंग बाहर लागू नहीं होती है।
वॉनब्रांड

@vonbrand, आप इस बिंदु से चूक गए। वह चाहता है कि यह उपधारा के अंदर लागू हो, लेकिन ||बाहर की उपधारा उपधारा के अंदर के व्यवहार को प्रभावित करती है।
cjm

1
के (set -e; echo 1; false; echo 2)साथ तुलना(set -e; echo 1; false; echo 2) || echo 3
जोहान

जवाबों:


32

इस थ्रेड के अनुसार , यह व्यवहार है POSIX set -eएक उपधारा में " " का उपयोग करने के लिए निर्दिष्ट करता है ।

(मैं भी हैरान था।)

सबसे पहले, व्यवहार:

कुछ -eसमय बाद, जब तक, या एलिफ आरक्षित शब्द के साथ, एक पाइपलाइन शुरू हो, तब कंपाउंड सूची निष्पादित करते समय सेटिंग की अनदेखी की जाएगी! आरक्षित शब्द, या अंतिम के अलावा AND-OR सूची के किसी भी आदेश।

दूसरा पोस्ट नोट,

सारांश में, सेट-इन (सब कोड कोड) को स्वतंत्र रूप से आसपास के संदर्भ में संचालित नहीं करना चाहिए?

नहीं। POSIX विवरण स्पष्ट है कि आसपास का संदर्भ प्रभावित करता है कि क्या सेट-उप को एक उपधारा में अनदेखा किया गया है।

चौथे पोस्ट में थोड़ा और भी है, एरिक ब्लेक द्वारा,

बिंदु 3 को संदर्भों को अनदेखा करने के लिए उपधाराओं की आवश्यकता नहीं है, जहां set -eउपेक्षा की गई है। यही है, एक बार जब आप एक संदर्भ में होते हैं, जहां -eउपेक्षा की जाती है, तो कुछ भी नहीं है जिसे आप -eफिर से पालन करने के लिए कर सकते हैं , यहां तक ​​कि एक उपधारा भी नहीं।

$ bash -c 'set -e; if (set -e; false; echo hi); then :; fi; echo $?' 
hi 
0 

भले ही हमने set -eदो बार बुलाया (दोनों माता-पिता और उपधारा में), तथ्य यह है कि उप-संदर्भ एक ऐसे संदर्भ में मौजूद है जहां -eउपेक्षा की जाती है (यदि एक बयान की स्थिति), तो फिर से सक्षम करने के लिए हम कुछ भी नहीं कर सकते हैं। -e

यह व्यवहार निश्चित रूप से आश्चर्यजनक है। यह प्रति-सहज है: एक पुन: सक्षम करने की अपेक्षा करता set -eहै कि एक प्रभाव हो, और यह कि आस-पास के संदर्भ मिसाल नहीं लेंगे; इसके अलावा, POSIX मानक का शब्दांकन इसे विशेष रूप से स्पष्ट नहीं करता है। यदि आप इसे उस संदर्भ में पढ़ते हैं जहां कमांड विफल हो रही है, तो नियम लागू नहीं होता है: यह केवल आसपास के संदर्भ में लागू होता है, हालांकि, यह पूरी तरह से लागू होता है।


उन लिंक के लिए धन्यवाद, वे बहुत दिलचस्प थे। हालाँकि, मेरा उदाहरण (IMO) काफी अलग है। उस चर्चा में अधिकांश यह है कि क्या पैरेंट शेल में सेट-को सबशेल द्वारा विरासत में मिला है set -e; (false; echo passed;) || echo failed:। यह मुझे आश्चर्यचकित नहीं करता है, वास्तव में, इस मानक के शब्द को देखते हुए इस मामले में इसे अनदेखा किया जाता है। मेरे मामले में, हालांकि, मैं स्पष्ट रूप से उप -समूह में सेटिंग कर रहा हूं , और असफलता पर उप-समूह से बाहर निकलने की उम्मीद कर रहा हूं । उप-सूची में कोई AND-OR सूची नहीं है ...
MadScientist

मैं असहमत हूं। दूसरी पोस्ट (मुझे काम करने के लिए एंकर नहीं मिल सकते) कहते हैं, " POSIX विवरण स्पष्ट है कि आसपास का संदर्भ प्रभावित करता है कि क्या सेट-उप को एक उपधारा में अनदेखा किया गया है। " - उप-भाग AND-OR सूची में है।
आरोन डी। मरास्को

चौथा पोस्ट (एरिक ब्लेक) यह भी कहता है, " भले ही हम सेट-टू को दो बार कहते हैं (माता-पिता और उप-भाग दोनों में), यह तथ्य कि उप-संदर्भ एक संदर्भ में मौजूद है, जहां-पर ध्यान नहीं दिया जाता है (यदि एक की स्थिति कथन), कुछ भी नहीं है जो हम फिर से सक्षम करने के लिए
उपधारा

आप सही हे; मुझे यकीन नहीं है कि मैंने उन लोगों को कैसे गलत समझा। धन्यवाद।
मैडिसिन्टिस्ट

1
मुझे यह जानकर खुशी हुई कि यह व्यवहार मैं अपने बालों को फाड़ रहा हूं, पोसिक्स कल्पना में बदल जाता है। तो चारों ओर काम क्या है ?! ifऔर ||और &&संक्रामक कर रहे हैं? यह बेतुका है
स्टीवन लू

7

वास्तव में, set -eउपधाराओं के अंदर कोई प्रभाव नहीं है यदि आप ||उनके बाद ऑपरेटर का उपयोग करते हैं; उदाहरण के लिए, यह काम नहीं करेगा:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer

set -e

outer() {
  echo '--> outer'
  (inner) || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

हारून डी। मार्सको अपने जवाब में यह समझाने का एक बड़ा काम करते हैं कि यह इस तरह क्यों व्यवहार करता है।

यहाँ एक छोटी सी चाल है जिसका उपयोग इसे ठीक करने के लिए किया जा सकता है: पृष्ठभूमि में आंतरिक कमांड चलाएँ, और फिर तुरंत इसके लिए प्रतीक्षा करें। waitBuiltin भीतरी आदेश के निकास कोड प्राप्त होगा, और अब आप उपयोग कर रहे हैं ||के बाद wait, नहीं आंतरिक समारोह है, तो set -eबाद के अंदर ठीक से काम करता है:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup

set -e

outer() {
  echo '--> outer'
  inner &
  wait $! || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

यहाँ जेनेरिक फ़ंक्शन है जो इस विचार पर बनाता है। यदि आप localकीवर्ड हटाते हैं , तो सभी POSIX- संगत गोले में काम करना चाहिए , अर्थात सभी local x=yको केवल इसके साथ बदलें x=y:

# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
  local cmd="$1"; shift
  local exit_code=0

  local e_was_set=1; if ! is_shell_attribute_set e; then
    set -e
    e_was_set=0
  fi

  "$cmd" "$@" &

  wait $! || {
    exit_code=$?
  }

  if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
    set +e
  fi

  if [ -n "$CLEANUP" ]; then
    RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
    return $?
  fi

  return $exit_code
}


is_shell_attribute_set() { # attribute, like "x"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}

उपयोग का उदाहरण:

#!/bin/sh
set -e

# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh


main() {
  echo "--> main: $@"
  CLEANUP=cleanup run inner "$@"
  echo "<-- main"
}


inner() {
  echo "--> inner: $@"
  sleep 0.5; if [ "$1" = 'fail' ]; then
    oh_my_god_look_at_this
  fi
  echo "<-- inner"
}


cleanup() {
  echo "--> cleanup: $@"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "$@"

उदाहरण चल रहा है:

$ ./so_3 fail; echo "exit code: $?"

--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127

$ ./so_3 pass; echo "exit code: $?"

--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0

इस पद्धति का उपयोग करते समय आपको केवल एक चीज की जानकारी होनी चाहिए, वह यह है कि आपके द्वारा पास किए जाने वाले शेल वेरिएबल्स के सभी संशोधन runकॉलिंग फ़ंक्शन को प्रचारित नहीं करेंगे, क्योंकि कमांड सबस्क्रिप्शन में चलता है।


2

मैं यह नहीं बताऊंगा कि यह एक बग मात्र है क्योंकि कई गोले इस तरह से व्यवहार करते हैं। ;-)

मुझे पेशकश करने में अधिक मज़ा है:

start cmd:> ( eval 'set -e'; false; echo passed; ) || echo failed
passed

start cmd:> ( eval 'set -e; false'; echo passed; ) || echo failed
failed

start cmd:> ( eval 'set -e; false; echo passed;' ) || echo failed
failed

मैं आदमी बैश (4.2.24) से उद्धृत कर सकता हूं:

शेल विफल नहीं होता है यदि वह कमांड विफल हो जाती है जो [...] a && या में निष्पादित किसी कमांड का हिस्सा है || अंतिम और& या के बाद कमांड को छोड़कर सूची || [...]

संभवतया कई आदेशों की वजह से अनदेखी की गई है || संदर्भ।


ठीक है, अगर सभी गोले इस तरह से व्यवहार करते हैं, तो यह एक बग नहीं है ... यह मानक व्यवहार है :-)। हम व्यवहार को गैर-सहज रूप में विलाप कर सकते हैं लेकिन ... eval के साथ चाल बहुत दिलचस्प है, यह सुनिश्चित करने के लिए है।
मैडिसिनिस्ट

आप किस खोल का उपयोग करते हैं? evalचाल मेरे लिए काम नहीं करता। मैंने बैश की कोशिश की, पॉज़िक्स मोड में बैश, और डैश।
डुनाटोटोस

@Dunatotatos, जैसा कि Hauke ​​ने कहा, वह bash4.2 था। यह bash4.3 में "निश्चित" था। pdksh- आधारित गोले में एक ही "मुद्दा" होगा। और कई गोले के कई संस्करणों के साथ विभिन्न "मुद्दों" के सभी प्रकार हैंset -eset -eडिजाइन द्वारा तोड़ा गया है। मैं इसे किसी भी चीज़ के लिए उपयोग नहीं करूंगा, लेकिन नियंत्रण संरचनाओं, उपधारा या कमांड प्रतिस्थापन के बिना शेल स्क्रिप्ट का सबसे सरल।
स्टीफन चेजेलस 14

1

जब तना हुआ हो तो वर्कअराउंड करें set -e

मैं इस सवाल पर आया क्योंकि मैं set -eएक त्रुटि का पता लगाने की विधि के रूप में उपयोग कर रहा था :

/usr/bin/env bash
set -e
do_stuff
( take_best_sub_action_1; take_best_sub_action_2 ) || do_worse_fallback
do_more_stuff

और इसके बिना ||, स्क्रिप्ट चलना बंद हो जाएगी और कभी नहीं पहुंचेगी do_more_stuff

चूंकि कोई साफ समाधान नहीं लगता है, मुझे लगता है कि मैं set +eअपनी स्क्रिप्ट पर बस एक सरल काम करूंगा:

/usr/bin/env bash
set -e
do_stuff
set +e
( take_best_sub_action_1; take_best_sub_action_2 )
exit_status=$?
set -e
if [ "$exit_status" -ne 0 ]; then
  do_worse_fallback
fi
do_more_stuff
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.