बैश में अंतिम कमांड रन गूंज रहा है?


84

मैं बैश स्क्रिप्ट के अंदर चलाए गए अंतिम कमांड को प्रतिध्वनित करने की कोशिश कर रहा हूं। मैंने इसे कुछ के साथ करने का एक तरीका पाया history,tail,head,sedजो ठीक काम करता है जब कमांड एक स्क्रिप्ट लाइनर से मेरी स्क्रिप्ट में एक विशिष्ट रेखा का प्रतिनिधित्व करते हैं। हालाँकि कुछ परिस्थितियों में मुझे अपेक्षित आउटपुट नहीं मिलता है, उदाहरण के लिए जब कमांड को एक caseस्टेटमेंट के अंदर डाला जाता है :

लिपी:

#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"

case "1" in
  "1")
  date
  last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
  echo "last command is [$last]"
  ;;
esac

उत्पादन:

Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]

[क्यू] क्या कोई मेरी मदद कर सकता है जो अंतिम रन कमांड को इको करने का एक तरीका खोज सकता है, भले ही इस कमांड को बैश स्क्रिप्ट के भीतर कैसे / कहाँ कहा जाता है?

मेरा जवाब

मेरे साथी SO'ers के बहुत सराहनीय योगदान के बावजूद, मैंने एक runफ़ंक्शन लिखने का विकल्प चुना - जो अपने सभी मापदंडों को एक कमांड के रूप में चलाता है और कमांड और इसके त्रुटि कोड को प्रदर्शित करता है जब यह विफल हो जाता है - निम्नलिखित लाभों के साथ:
-मुझे केवल आवश्यकता है उन आदेशों को पहले से बताएं जिन्हें मैं जांचना चाहता हूं, runजो उन्हें एक पंक्ति में रखता है और मेरी स्क्रिप्ट की संक्षिप्तता को प्रभावित नहीं करता है-
जब भी स्क्रिप्ट इन आदेशों में से किसी एक पर विफल होती है, तो मेरी स्क्रिप्ट की अंतिम आउटपुट लाइन एक संदेश है जो स्पष्ट रूप से प्रदर्शित करती है जो कमांड इसके निकास कोड के साथ विफल रहता है, जो डिबगिंग को आसान बनाता है

उदाहरण लिपि:

#!/bin/bash
die() { echo >&2 -e "\nERROR: $@\n"; exit 1; }
run() { "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }

case "1" in
  "1")
  run ls /opt
  run ls /wrong-dir
  ;;
esac

उत्पादन:

$ ./test.sh
apacheds  google  iptables
ls: cannot access /wrong-dir: No such file or directory

ERROR: command [ls /wrong-dir] failed with error code 2

मैंने कई तर्कों के साथ विभिन्न आदेशों का परीक्षण किया, चर को तर्क के रूप में उद्धृत किया, तर्कों को उद्धृत किया ... और runफ़ंक्शन ने उन्हें नहीं तोड़ा। एकमात्र मुद्दा जो मुझे अब तक मिला है वह एक प्रतिध्वनि को चलाने के लिए है जो टूट जाता है लेकिन मैं वैसे भी अपने इको को जांचने की योजना नहीं बनाता हूं।


+1, शानदार विचार! ध्यान दें कि run()जब उद्धरण का उपयोग किया जाता है तो यह ठीक से काम नहीं करता है, उदाहरण के लिए यह विफल रहता है run ssh-keygen -t rsa -C info@example.org -f ./id_rsa -N "":।
जॉन्डोडो

@ जोंडोडो: यह तय किया जा सकता है: (या, बल्कि, अनुमति देने के लिए (पूर्व: चर) के "something"साथ तर्कों में परिवर्तन '"something"', पहले स्तर पर व्याख्या / मूल्यांकन किया जा सकता है, यदि आवश्यक हो)"'something'"something
ओलिवियर दुलक

2
मैंने त्रुटिपूर्ण run() { $*; … }को और अधिक सही में बदल दिया है run() { "$@"; … }क्योंकि त्रुटिपूर्ण उत्तर ने प्रश्न को समाप्त कर दिया है जो cp64 त्रुटि स्थिति के साथ बाहर निकलता है , जहां समस्या यह थी कि $*नामों में रिक्त स्थान पर कमांड के तर्क को तोड़ दिया, लेकिन "$@"ऐसा नहीं करेंगे।
जोनाथन लेफ़लर

Unix StackExchange पर संबंधित प्रश्न: unix.stackexchange.com/questions/21930/…
haridsv

last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')बेहतर काम किया, कम से कम zsh और macOS 10.11 के लिए
फिल पिरोकोव

जवाबों:


60

कमांड इतिहास एक संवादात्मक विशेषता है। इतिहास में केवल पूर्ण आदेश दर्ज किए जाते हैं। उदाहरण के लिए, caseनिर्माण एक पूरे के रूप में दर्ज किया गया है, जब शेल ने इसे पार्स करना समाप्त कर दिया है। न तो historyबिल्ट-इन के साथ इतिहास !:pको देखना (और न ही इसे शेल एक्सपेंशन के माध्यम से प्रिंट करना ) वह करता है जो आप चाहते हैं, जो कि साधारण कमांड के इनवोकेशन को प्रिंट करना है।

DEBUGजाल आप किसी भी साधारण आदेश निष्पादन से पहले एक आदेश सही निष्पादित करने देता है। निष्पादित करने के लिए कमांड का एक स्ट्रिंग संस्करण (रिक्त स्थान से अलग शब्दों के साथ) BASH_COMMANDचर में उपलब्ध है ।

trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"

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

cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi

इसके अलावा, यदि आप केवल एक विफल कमांड पर गर्भपात करना चाहते हैं, तो set -eपहले विफल कमांड पर अपनी स्क्रिप्ट से बाहर निकलने के लिए उपयोग करें। आप EXITजाल से अंतिम कमांड प्रदर्शित कर सकते हैं ।

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

ध्यान दें कि यदि आप अपनी स्क्रिप्ट को देखने की कोशिश कर रहे हैं कि यह क्या कर रहा है, यह सब भूल जाओ और उपयोग करो set -x


1
मैंने आपके DEBUG के जाल की कोशिश की है, लेकिन मैं इसे काम नहीं कर सकता, क्या आप कृपया एक पूर्ण उदाहरण प्रदान कर सकते हैं? -xहर एक कमांड को आउटपुट करता है लेकिन दुर्भाग्य से मुझे केवल उन कमांड्स को देखने में दिलचस्पी है जो विफल हो जाती हैं (जो मैं अपने कमांड के साथ प्राप्त कर सकता हूं अगर मैं एक [ ! "$? == "0" ]बयान के अंदर रखता हूं ।
अधिकतम

@ user359650: निश्चित। वर्तमान कमांड द्वारा अधिलेखित होने से पहले आपको पिछली कमांड को सहेजना होगा। अपनी स्क्रिप्ट को निरस्त करने के लिए यदि कोई कमांड विफल हो जाता है, तो उपयोग करें set -e(अक्सर, लेकिन हमेशा नहीं, कमांड एक अच्छा पर्याप्त त्रुटि संदेश देगा ताकि आपको आगे संदर्भ प्रदान करने की आवश्यकता न हो)।
गाइल्स का SO- बुराई पर रोक '24

आपके योगदान के लिए धन्यवाद। मैंने एक कस्टम फ़ंक्शन लिखना शुरू किया (मेरी पोस्ट देखें) क्योंकि आपका समाधान बहुत अधिक ओवरहेड था।
अधिकतम

कमाल की चाल। निश्चित +1। मेरे पास सेट-ई हिस्सा था और ईआरआर ट्रैप, आपने मुझे डेबग हिस्सा दिया। आपका बहुत बहुत धन्यवाद!
फिलिप ए।

1
@ JamesThomasMoon1979 सामान्य रूप eval echo "${BASH_COMMAND}"से कमांड प्रतिस्थापन में मनमाने कोड को निष्पादित कर सकता है। यह खतरनाक है। जैसे कमांड पर विचार करें cd $(ls -td | head -n 1)- और अब कमांड प्रतिस्थापन की कल्पना करें जिसे rmकुछ कहा जाता है।
गिल्स एसओ- बुराई को रोकना '

170

बैश ने अंतिम कमांड को निष्पादित करने के लिए सुविधाओं में बनाया है। लेकिन यह अंतिम पूरी कमांड (उदाहरण के लिए पूरी caseकमांड) है, न कि व्यक्तिगत साधारण कमांड जैसे कि आप मूल रूप से अनुरोध करते हैं।

!:0 = निष्पादित कमांड का नाम।

!:1 = पिछले कमांड का पहला पैरामीटर

!:* = पिछले कमांड के सभी पैरामीटर

!:-1 = पिछले कमांड का अंतिम पैरामीटर

!! = पिछली कमांड लाइन

आदि।

तो, सवाल का सबसे सरल जवाब है, वास्तव में:

echo !!

... वैकल्पिक रूप से:

echo "Last command run was ["!:0"] with arguments ["!:*"]"

इसे स्वयं आज़माएं!

echo this is a test
echo !!

स्क्रिप्ट में, इतिहास का विस्तार डिफ़ॉल्ट रूप से बंद हो जाता है, आपको इसे सक्षम करना होगा

set -o history -o histexpand

7
सबसे उपयोगी उपयोग का मामला जो मैंने देखा है वह सूडो एक्सेस के साथ अंतिम कमांड को फिर से चलाने के लिए है , यानीsudo !!
Travesty3

1
साथ set -o history -o histexpand; echo "!!": एक bash लिपि में मैं अभी भी त्रुटि संदेश मिलता है !!: event not found(यह उद्धरण चिह्नों के बिना ही है।)
Suzana

2
set -o history -o histexpandलिपियों में -> lifesaver! धन्यवाद!
अल्बर्टो मेगिया

क्या इस कार्य को टाइम फ़ंक्शन द्वारा उपयोग किए जाने वाले TIMEFORMAT स्ट्रिंग में लाने का कोई तरीका है? अर्थात निर्यात TIMEFORMAT = "*** !: 0% 0lR लिया"; / usr / bin / time find -name "* .log" ... जो काम नहीं करता है क्योंकि!: 0 का मूल्यांकन उस समय पर किया जाता है जब आप निर्यात चलाते हैं :(
मार्टिन

मुझे सेट-हिस्ट्री -o histexpand के उपयोग के बारे में अधिक पढ़ने की आवश्यकता है। एक फ़ाइल में इसका उपयोग करने bashपर मैं इसे प्रिंट करता रहता हूँ !! अंतिम रन कमांड के बजाय। यह कहां प्रलेखित है?
मुनो

17

गिलेस के उत्तर को पढ़ने के बाद , मैंने यह देखने का फैसला किया कि क्या जाल में भी उपलब्ध (और वांछित मूल्य) उपलब्ध था - और यह है!$BASH_COMMANDEXIT

तो, निम्नलिखित बैश स्क्रिप्ट उम्मीद के मुताबिक काम करती है:

#!/bin/bash

exit_trap () {
  local lc="$BASH_COMMAND" rc=$?
  echo "Command [$lc] exited with code [$rc]"
}

trap exit_trap EXIT
set -e

echo "foo"
false 12345
echo "bar"

आउटपुट है

foo
Command [false 12345] exited with code [1]

barकभी भी प्रिंट नहीं किया जाता है क्योंकि set -eकमांड के विफल होने पर स्क्रिप्ट से बाहर निकलने का कारण बनता है और झूठी कमांड हमेशा विफल होती है (परिभाषा के अनुसार)। यह दिखाने के लिए कि वहाँ 12345पारित किया गया falseहै कि विफल कमांड के तर्क को भी पकड़ लिया जाता है ( falseकमांड उस पर दिए गए किसी भी तर्क की उपेक्षा करता है)


यह बिल्कुल सबसे अच्छा समाधान है। "सेट -euo pipefail" के साथ मेरे लिए एक आकर्षण की तरह काम करता है
Vukasin

8

मैं इसे set -xमुख्य स्क्रिप्ट का उपयोग करके प्राप्त करने में सक्षम था (जो स्क्रिप्ट को प्रत्येक कमांड को निष्पादित करता है जो प्रिंट आउट करता है) और एक आवरण स्क्रिप्ट लिख रहा है जो कि केवल उत्पादन की अंतिम पंक्ति दिखाता है set -x

यह मुख्य स्क्रिप्ट है:

#!/bin/bash
set -x
echo some command here
echo last command

और यह आवरण लिपि है:

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

आवरण स्क्रिप्ट को चलाने से यह आउटपुट के रूप में उत्पन्न होता है:

echo last command

3

history | tail -2 | head -1 | cut -c8-999

tail -2इतिहास से अंतिम दो कमांड head -1लाइन cut -c8-999लौटाता है, पीआईडी ​​और स्पेस को हटाकर, सिर्फ पहली लाइन रिटर्न सिर्फ कमांड लाइन है।


1
क्या आप थोड़ी व्याख्या कर सकते हैं कि कमांड के तर्क क्या हैं? यह समझने में मदद करेगा कि आपने क्या किया था
सिगिस्टर

हालांकि यह इस सवाल का जवाब दे सकता है कि इस मुद्दे को हल करने में मदद करने के बारे में कुछ विवरण जोड़ना बेहतर होगा। कृपया पढ़ें कि मैं और अधिक जानने के लिए एक अच्छा उत्तर कैसे लिखूं
रोशन पिटिगला

1

अंतिम कमांड ($ _) और अंतिम त्रुटि ($?) चर के बीच एक दौड़ है। यदि आप उनमें से किसी एक को चर में संग्रहीत करने का प्रयास करते हैं, तो दोनों को सेट कमांड के कारण पहले से ही नए मूल्यों का सामना करना पड़ता है। वास्तव में, पिछले कमांड को इस मामले में कोई मूल्य नहीं मिला है।

यहाँ पर मैंने अपने चर में (लगभग) दोनों प्रकारों को संग्रहीत करने के लिए क्या किया, इसलिए मेरी बैश स्क्रिप्ट यह निर्धारित कर सकती है कि क्या कोई त्रुटि थी और अंतिम रन कमांड के साथ शीर्षक सेट करना:

   # This construct is needed, because of a racecondition when trying to obtain
   # both of last command and error. With this the information of last error is
   # implied by the corresponding case while command is retrieved.

   if   [[ "${?}" == 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✓' ;
   elif [[ "${?}" == 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✓' ;
   elif [[ "${?}" != 0 && "${_}" != "" ]] ; then
    # Last command MUST be retrieved first.
      LASTCOMMAND="${_}" ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   elif [[ "${?}" != 0 && "${_}" == "" ]] ; then
      LASTCOMMAND='unknown' ;
      RETURNSTATUS='✗' ;
      # Fixme: "$?" not changing state until command executed.
   fi

यह स्क्रिप्ट जानकारी को बनाए रखेगा, यदि कोई त्रुटि हुई और अंतिम रन कमांड प्राप्त करेगा। रेसकंडिशन के कारण मैं वास्तविक मूल्य को स्टोर नहीं कर सकता। इसके अलावा, अधिकांश कमांड वास्तव में त्रुटि नौसिखियों की भी परवाह नहीं करते हैं, वे सिर्फ '0' से कुछ अलग करते हैं। आप ध्यान देंगे, यदि आप बैरन के एर्रोनो एक्सटेंशन का उपयोग करते हैं।

यह बैश के लिए "इंटर्न" स्क्रिप्ट की तरह कुछ के साथ संभव हो सकता है, जैसे बैश एक्सटेन्शन में, लेकिन मैं कुछ इस तरह से परिचित नहीं हूं और यह संगत भी नहीं होगा।

भूल सुधार

मैंने नहीं सोचा था, कि एक ही समय में दोनों चर प्राप्त करना संभव था। हालाँकि मुझे कोड की शैली पसंद है, मैंने मान लिया कि इसकी व्याख्या दो आज्ञाओं के रूप में की जाएगी। यह गलत था, इसलिए मेरा जवाब नीचे दिया गया:

   # Because of a racecondition, both MUST be retrieved at the same time.
   declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ;

   if [[ "${RETURNSTATUS}" == 0 ]] ; then
      declare RETURNSYMBOL='✓' ;
   else
      declare RETURNSYMBOL='✗' ;
   fi

हालाँकि मेरी पोस्ट को कोई सकारात्मक रेटिंग नहीं मिली, लेकिन मैंने अपनी समस्या को खुद हल कर लिया। और यह इंटिअल पोस्ट के संबंध में उचित लगता है। :)


1
ओह प्रिय, आप उन्हें एक ही बार में प्राप्त करना चाहते हैं और यह संभव हो सकता है: RETURNSTATUS की घोषणा करें "" $ {!} "LASTCOMMAND =" $ {_} ";
WGRM

एक अपवाद के साथ महान काम करता है। यदि मेरे पास आगे के मापदंडों के लिए एक उपनाम है, तो यह सिर्फ मापदंडों को प्रदर्शित करता है। किसी को भी कोई निष्कर्ष?
WGRM
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.