स्टेटफुल बैश फंक्शन


16

मैं बैश में एक फ़ंक्शन लागू करना चाहता हूं जो हर कॉल के साथ एक गिनती (और रिटर्न) बढ़ाता है। दुर्भाग्य से यह गैर-तुच्छ लगता है क्योंकि मैं फ़ंक्शन को एक सबशेल के अंदर शामिल कर रहा हूं और इसके परिणामस्वरूप इसके मूल शेल के चर को संशोधित नहीं कर सकता।

यहाँ मेरा प्रयास है:

PS_COUNT=0

ps_count_inc() {
    let PS_COUNT=PS_COUNT+1
    echo $PS_COUNT
}

ps_count_reset() {
    let PS_COUNT=0
}

इसका उपयोग इस प्रकार किया जाएगा (और इसलिए मुझे एक उपधारा से कार्यों को लागू करने की आवश्यकता है):

PS1='$(ps_count_reset)> '
PS2='$(ps_count_inc)   '

इस तरह, मेरे पास एक नंबर बहु-पंक्ति संकेत होगा:

> echo 'this
1   is
2   a
3   test'

प्यारा। लेकिन उपर्युक्त सीमा के कारण काम नहीं करता है।

एक गैर-कार्यशील समाधान एक चर के बजाय एक फ़ाइल में गिनती लिखना होगा। हालाँकि, यह सत्र चलाने के साथ-साथ कई के बीच टकराव पैदा करेगा। मैं निश्चित रूप से फ़ाइल नाम में शेल की प्रक्रिया आईडी को जोड़ सकता हूं। लेकिन मुझे उम्मीद है कि वहाँ एक बेहतर समाधान है जो बहुत सारी फाइलों के साथ मेरे सिस्टम को अव्यवस्थित नहीं करेगा।


WRT टकराव एक फ़ाइल स्टैश देखने का उपयोग कर man 1 mktemp
गोल्डीलॉक्स

आपको मेरा संपादन देखना चाहिए - मुझे लगता है कि आप इसे पसंद करेंगे।
मोकेसर

जवाबों:


14

यहाँ छवि विवरण दर्ज करें

वही आउटपुट प्राप्त करने के लिए जिसे आप अपने प्रश्न में नोट करते हैं, इसकी आवश्यकता है:

PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '

आपको कॉन्ट्रास्ट की जरूरत नहीं है। उन दो पंक्तियों में यह सब किसी भी शेल में किया जाएगा जो POSIX संगतता के करीब कुछ भी दिखाएगा।

- > cat <<HD
1 >     line 1
2 >     line $((PS2c-1))
3 > HD
    line 1
    line 2
- > echo $PS2c
0

लेकिन मुझे यह पसंद आया। और मैं इस काम के मूल सिद्धांतों को प्रदर्शित करना चाहता था जो इस काम को थोड़ा बेहतर बनाता है। इसलिए मैंने इसे थोड़ा संपादित किया। मैंने इसे /tmpअभी के लिए रोक दिया है लेकिन मुझे लगता है कि मैं इसे अपने लिए भी रखने जा रहा हूं। यह यहाँ है:

cat /tmp/prompt

PROMPT SCRIPT:

ps1() { IFS=/
    set -- ${PWD%"${last=${PWD##/*/}}"}
    printf "${1+%c/}" "$@" 
    printf "$last > "
}

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '

नोट: हाल ही में सीखा यश के , मैंने इसे कल बनाया था। जो भी कारण के लिए यह %cस्ट्रिंग के साथ हर तर्क के पहले बाइट को प्रिंट नहीं करता है - हालांकि डॉक्स उस प्रारूप के लिए विस्तृत-चार एक्सटेंशन के बारे में विशिष्ट थे और इसलिए यह संबंधित हो सकता है - लेकिन यह सिर्फ ठीक करता है%.1s

यह पूरी बात है। वहाँ दो मुख्य बातें चल रही हैं। और ऐसा ही दिखता है:

/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 >

पदच्छेद $PWD

हर बार $PS1इसका मूल्यांकन किया जाता है और इसे $PWDशीघ्र जोड़ने के लिए प्रिंट करता है । लेकिन मुझे $PWDअपनी स्क्रीन पर पूरी भीड़ पसंद नहीं है , इसलिए मैं मौजूदा निर्देशिका में नीचे दिए गए वर्तमान पथ में हर ब्रेडक्रंब का पहला अक्षर चाहता हूं, जिसे मैं पूरी तरह से देखना चाहता हूं। ऐशे ही:

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv > 

यहाँ कुछ चरण हैं:

IFS=/

हम वर्तमान $PWDऔर सबसे विश्वसनीय तरीका है कि $IFSविभाजन पर है के साथ विभाजित करने के लिए जा रहे हैं /। बाद में इसके साथ परेशान करने की कोई आवश्यकता नहीं है - बाहर से यहाँ पर सभी विभाजन $@को अगले कमांड में शेल के स्थितीय पैरामीटर सरणी द्वारा परिभाषित किया जाएगा :

set -- ${PWD%"${last=${PWD##/*/}}"}

तो यह थोड़ा मुश्किल है, लेकिन मुख्य बात यह है कि हम प्रतीकों $PWDपर विभाजन कर रहे हैं /। मैं $lastबाईं और सबसे दाईं ओर सबसे अधिक /स्लैश के बीच होने वाले किसी भी मूल्य के बाद हर चीज को असाइन करने के लिए पैरामीटर विस्तार का उपयोग करता हूं । इस तरह से मुझे पता है कि अगर मैं अभी हूँ /और केवल एक ही हूँ /तो $lastअभी भी पूरा बराबर होगा $PWDऔर $1खाली हो जाएगा। यह मायने रखता है। मैं भी इसे देने $lastसे $PWDपहले पूंछ के अंत से पट्टी $@

printf "${1+%c/}" "$@"

तो यहाँ - जब तक ${1+is set}हम अपने प्रत्येक शेल के तर्कों printfके पहले %cहाॅटर हैं - जिसे हमने अभी-अभी अपनी वर्तमान में प्रत्येक निर्देशिका में सेट किया है $PWD- शीर्ष निर्देशिका को कम - विभाजित करते हैं /। इसलिए हम अनिवार्य रूप से हर निर्देशिका के पहले चरित्र को ही प्रिंट कर रहे हैं, $PWDलेकिन शीर्ष पर। यह महत्वपूर्ण है, हालांकि साकार करने के लिए यह केवल होता है अगर है $1सब पर सेट हो जाता है, जो जड़ में नहीं होगा /या एक पर से हटा /में के रूप में ऐसी /etc

printf "$last > "

$lastवह चर है जिसे मैंने अभी हमारी शीर्ष निर्देशिका को सौंपा है। तो अब यह हमारी शीर्ष निर्देशिका है। यह बताता है कि अंतिम वक्तव्य में क्या है या नहीं। और यह >अच्छे उपाय के लिए साफ सुथरा होता है ।

लेकिन इस बारे में क्या होगा?

और फिर $PS2सशर्त की बात है । मैंने पहले दिखाया था कि यह कैसे किया जा सकता है जिसे आप अभी भी नीचे पा सकते हैं - यह मूल रूप से गुंजाइश का एक मुद्दा है। लेकिन जब तक आप printf \backspaces का एक समूह बनाना शुरू नहीं करना चाहते हैं तब तक इसके लिए थोड़ा अधिक है और फिर उनके वर्ण गणना को संतुलित करने की कोशिश कर रहे हैं ... ugh। इसलिए मैं यह करता हूं:

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'

फिर, ${parameter##expansion}दिन बचाता है। हालांकि यह थोड़ा अजीब है - हम वास्तव में चर सेट करते हैं, जबकि हम इसे खुद से पट्टी करते हैं। हम इसके नए मूल्य का उपयोग करते हैं - मध्य-पट्टी सेट करते हैं - जिस गोला से हम पट्टी करते हैं। आप समझ सकते हैं? हम ##*अपने वेतन वृद्धि के सिर से अंतिम चरित्र तक सभी को पट्टी करते हैं जो कुछ भी हो सकता है [$((PS2c=0))-9]। हम इस तरह से मूल्य का उत्पादन नहीं करने की गारंटी देते हैं, और फिर भी हम इसे असाइन करते हैं। यह बहुत अच्छा है - मैंने पहले कभी ऐसा नहीं किया है। लेकिन POSIX भी हमें गारंटी देता है कि यह सबसे पोर्टेबल तरीका है जो यह किया जा सकता है।

और यह पोसिक्स-निर्दिष्ट के लिए धन्यवाद है ${parameter} $((expansion))जो इन परिभाषाओं को वर्तमान शेल में रखता है बिना आवश्यकता के कि हम उन्हें अलग-अलग उपधारा में सेट करें, भले ही हम उनका मूल्यांकन करें। और यही कारण है कि यह काम करता है dashऔर shसाथ ही साथ यह अंदर bashऔर भीतर भी करता है zsh। हम शेल / टर्मिनल आश्रित बच का उपयोग करते हैं और हम चर का परीक्षण स्वयं करते हैं। यही पोर्टेबल कोड को त्वरित बनाता है

बाकी काफी सरल है - बस हर बार हमारे काउंटर $PS2का मूल्यांकन किया जाता है जब तक कि $PS1एक बार फिर से रीसेट नहीं हो जाता। ऐशे ही:

PS2='$((PS2c=PS2c+1)) > '

तो अब मैं कर सकता हूँ:

DEM DEMO

ENV=/tmp/prompt dash -i

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c"
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
    0
/u/s/m/man3 > cd ~
/h/mikeserv >

SH DEMO

यह वही में काम करता है bashया sh:

ENV=/tmp/prompt sh -i

/h/mikeserv > cat <<HEREDOC
1 >     $( echo $PS2c )
2 >     $( echo $PS1 )
3 >     $( echo $PS2 )
4 > HEREDOC
    4
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
/h/mikeserv > echo $PS2c ; cd /
0
/ > cd /usr/share
/u/share > cd ~
/h/mikeserv > exit

जैसा कि मैंने ऊपर कहा, प्राथमिक समस्या यह है कि आपको यह विचार करने की आवश्यकता है कि आप अपनी गणना कहाँ करते हैं। आपको मूल शेल में राज्य नहीं मिलता है - इसलिए आप वहां गणना नहीं करते हैं। आपको उप-स्थिति में राज्य मिलता है - इसलिए कि आप कहां गणना करते हैं। लेकिन आप मूल शेल में परिभाषा करते हैं।

ENV=/dev/fd/3 sh -i  3<<\PROMPT
    ps1() { printf '$((PS2c=0)) > ' ; }
    ps2() { printf '$((PS2c=PS2c+1)) > ' ; }
    PS1=$(ps1)
    PS2=$(ps2)
PROMPT

0 > cat <<MULTI_LINE
1 > $(echo this will be line 1)
2 > $(echo and this line 2)
3 > $(echo here is line 3)
4 > MULTI_LINE
this will be line 1
and this line 2
here is line 3
0 >

1
@ बाइकें हम मंडलियों में बदल रहे हैं। मुझे यह सब पता है। लेकिन मैं अपनी परिभाषा में इसका उपयोग कैसे करूं PS2? यह मुश्किल हिस्सा है। मुझे नहीं लगता कि आपका समाधान यहां लागू किया जा सकता है। अगर आपको लगता है तो कृपया मुझे दिखाओ कैसे।
कोनराड रुडोल्फ

1
@ माइकर्स नो, यह असंबंधित है, क्षमा करें। विवरण के लिए मेरा प्रश्न देखें। PS1और PS2शेल में विशेष चर हैं जो कमांड प्रॉम्प्ट के रूप में मुद्रित होते हैं (इसे PS1एक नई शेल विंडो में एक अलग मान पर सेट करके देखें ), वे इस प्रकार आपके कोड से बहुत अलग तरीके से उपयोग किए जाते हैं। उनके उपयोग के बारे में कुछ और जानकारी यहाँ दी गई है: linuxconfig.org/bash-prompt-basics
कोनराड रुडोल्फ

1
@KonradRudolph आपको उन्हें दो बार परिभाषित करने से क्या रोकना है? यही मेरी मूल बात है ... मुझे आपका उत्तर देखना है ... यह हर समय किया जाता है।
मिकसेर

1
@mikeserv echo 'thisएक प्रॉम्प्ट पर टाइप करें , फिर समझाएं कि PS2क्लोजिंग सिंगल कोट टाइप करने से पहले वैल्यू कैसे अपडेट करें ।
चेंपनर

1
ठीक है, यह उत्तर अब आधिकारिक रूप से आश्चर्यजनक है। मुझे ब्रेडक्रंब भी पसंद है, हालांकि मैं इसे वैसे ही नहीं अपनाऊंगा
कोनराड रूडोल्फ

8

इस दृष्टिकोण के साथ (एक उपधारा में चल रहा फ़ंक्शन) आप गर्भनिरोधकों के माध्यम से जाने के बिना मास्टर शेल प्रक्रिया की स्थिति को अपडेट करने में सक्षम नहीं होंगे। इसके बजाय, फ़ंक्शन को मास्टर प्रक्रिया में चलाने की व्यवस्था करें।

PROMPT_COMMANDवेरिएबल का मान एक कमांड के रूप में इंटरप्रेट किया जाता है जिसे PS1प्रॉम्प्ट प्रिंट करने से पहले निष्पादित किया जाता है ।

इसके लिए PS2, तुलनीय कुछ भी नहीं है। लेकिन आप इसके बजाय एक ट्रिक का उपयोग कर सकते हैं: चूंकि आप जो करना चाहते हैं वह एक अंकगणितीय ऑपरेशन है, आप अंकगणितीय विस्तार का उपयोग कर सकते हैं, जिसमें एक सब-मेल शामिल नहीं है।

PROMPT_COMMAND='PS_COUNT=0'
PS2='$((++PS_COUNT))  '

अंकगणित संगणना का परिणाम प्रॉम्प्ट में समाप्त होता है। यदि आप इसे छिपाना चाहते हैं, तो आप इसे एक सरणी सबस्क्रिप्ट के रूप में पास कर सकते हैं जो मौजूद नहीं है।

PS1='${nonexistent_array[$((PS_COUNT=0))]}\$ '

4

यह थोड़ा सा I / O-गहन है, लेकिन आपको गणना के मान को रखने के लिए एक अस्थायी फ़ाइल का उपयोग करने की आवश्यकता होगी।

ps_count_inc () {
   read ps_count < ~/.prompt_num
   echo $((++ps_count)) | tee ~/.prompt_num
}

ps_count_reset () {
   echo 0 > ~/.prompt_num
}

यदि आप प्रति शेल सत्र के लिए एक अलग फ़ाइल की आवश्यकता के बारे में चिंतित हैं (जो कि एक छोटी सी चिंता की तरह प्रतीत होता है? क्या आप वास्तव में एक ही समय में दो अलग-अलग शेल में मल्टी-लाइन कमांड टाइप करेंगे?), आपको mktempप्रत्येक के लिए एक नई फ़ाइल बनाने के लिए उपयोग करना चाहिए। उपयोग।

ps_count_reset () {
    rm -f "$prompt_count"
    prompt_count=$(mktemp)
    echo 0 > "$prompt_count"
}

ps_count_inc () {
    read ps_count < "$prompt_count"
    echo $((++ps_count)) | tee "$prompt_count"
}

+1 I / O संभवतः बहुत महत्वपूर्ण नहीं है क्योंकि यदि फ़ाइल छोटी है और अक्सर एक्सेस की जा रही है, तो इसे कैश किया जाएगा, अर्थात, यह अनिवार्य रूप से साझा की गई मेमोरी के रूप में कार्य कर रहा है।
गोल्डीलॉक्स

1

आप इस तरह शेल चर का उपयोग नहीं कर सकते हैं और आप पहले से ही समझते हैं कि क्यों। एक सबशेल वैरिएबल को उसी तरह विरासत में मिला है, जिस तरह से एक प्रक्रिया अपने पर्यावरण को विरासत में मिली है: इसमें किए गए कोई भी परिवर्तन केवल इसके और इसके बच्चों पर लागू होते हैं और किसी पूर्वज प्रक्रिया के लिए नहीं।

अन्य उत्तरों के अनुसार सबसे आसान काम यह है कि किसी फ़ाइल में डेटा को स्टैश करें।

echo $count > file
count=$(<file)

आदि।


बेशक आप इस तरह से एक चर सेट कर सकते हैं। आपको एक अस्थायी फ़ाइल की आवश्यकता नहीं है। आप उप-चर में चर में सेट करें और इसके मान को मूल शेल में प्रिंट करें जहां आप उस मान को अवशोषित करते हैं। आपको राज्य के सभी मूल्य मिलते हैं जो आपको उपधारा में इसके मूल्य की गणना करने की आवश्यकता होती है, इसलिए आप इसे करते हैं।
mikeserv

1
@mikeserv यही बात नहीं है, यही वजह है कि ओपी ने कहा है कि ऐसा समाधान काम नहीं करेगा (हालांकि यह प्रश्न में अधिक स्पष्ट किया जाना चाहिए)। आप जिस चीज का जिक्र कर रहे हैं , वह IPC के माध्यम से किसी अन्य प्रक्रिया के लिए एक मान दे रहा है ताकि वह उस मान को जो कुछ भी निर्दिष्ट कर सके। ओपी चाहता था / करने के लिए आवश्यक कई प्रक्रियाओं द्वारा साझा वैश्विक चर के मूल्य को प्रभावित करता था, और आप पर्यावरण के माध्यम से ऐसा नहीं कर सकते; यह आईपीसी के लिए बहुत उपयोगी नहीं है।
गोल्डीलॉक्स

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

@ माइकर्स मुझे नहीं लगता कि आपने गलत समझा है और निष्पक्ष होना, आपके पास आईपीसी का एक रूप है और काम कर सकता है । मुझे यह स्पष्ट नहीं है कि कोनराड को यह पसंद क्यों नहीं है, लेकिन अगर यह पर्याप्त लचीला नहीं है, तो फ़ाइल स्टैश बहुत सीधा है (और इसलिए टकराव से बचने के तरीके हैं, जैसे mktemp)।
गोल्डीलॉक्स

2
@mikeserv इच्छित फ़ंक्शन को PS2शेल द्वारा विस्तारित किए जाने पर कहा जाता है। आपके पास उस समय पैरेंट शेल में वैरिएबल के मान को अपडेट करने का अवसर नहीं है।
चेपनर

0

संदर्भ के लिए, यहां अस्थायी फ़ाइलों का उपयोग करके मेरा समाधान है, जो प्रति शेल प्रक्रिया के लिए अद्वितीय हैं, और जितनी जल्दी हो सके हटा दिया जाता है (अव्यवस्था से बचने के लिए, जैसा कि प्रश्न में कहा गया है):

# Yes, I actually need this to work across my systems. :-/
_mktemp() {
    local tmpfile="${TMPDIR-/tmp}/psfile-$$.XXX"
    local bin="$(command -v mktemp || echo echo)"
    local file="$($bin "$tmpfile")"
    rm -f "$file"
    echo "$file"
}

PS_COUNT_FILE="$(_mktemp)"

ps_count_inc() {
    local PS_COUNT
    if [[ -f "$PS_COUNT_FILE" ]]; then
        let PS_COUNT=$(<"$PS_COUNT_FILE")+1
    else
        PS_COUNT=1
    fi

    echo $PS_COUNT | tee "$PS_COUNT_FILE"
}

ps_count_reset() {
    rm -f "$PS_COUNT_FILE"
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.