2011-12-21 9 views
13

मैं एक ऐसी स्क्रिप्ट पर काम कर रहा हूं जो इतना जटिल हो गया है कि मैं इसे नवीनतम संस्करण में अपडेट करने का एक आसान विकल्प शामिल करना चाहता हूं। यह मेरा दृष्टिकोण है:क्या यह एक बैश स्क्रिप्ट के लिए एक वैध स्व-अद्यतन दृष्टिकोण है?

set -o errexit 

SELF=$(basename $0) 
UPDATE_BASE=http://something 

runSelfUpdate() { 
    echo "Performing self-update..." 
    # Download new version 
    wget --quiet --output-document=$0.tmp $UPDATE_BASE/$SELF 
    # Copy over modes from old version 
    OCTAL_MODE=$(stat -c '%a' $0) 
    chmod $OCTAL_MODE $0.tmp 
    # Overwrite old file with new 
    mv $0.tmp $0 
    exit 0 
} 

स्क्रिप्ट उद्देश्य के अनुसार कार्य करने लगता है, लेकिन अगर वहाँ दृष्टिकोण के इस प्रकार के साथ चेतावनियां हो सकता है मैं सोच रहा हूँ। मुझे बस इतना कठिन समय लगता है कि एक स्क्रिप्ट किसी भी प्रतिक्रिया के बिना खुद को ओवरराइट कर सकती है।

अधिक स्पष्ट होने के लिए, मुझे आश्चर्य है, अगर, शायद, स्क्रिप्ट लाइन-दर-रेखा को पढ़ और निष्पादित करेगा और mv के बाद, exit 0 नई स्क्रिप्ट से कुछ और हो सकता है। मुझे लगता है कि मुझे याद है कि विंडोज .bat फाइलों के साथ ऐसा व्यवहार कर रहा है।

अद्यतन: मेरे मूल स्निपेट में set -o errexit शामिल नहीं था। मेरी समझ के लिए, मुझे wget के कारण होने वाले मुद्दों से मुझे सुरक्षित रखना चाहिए।
इसके अलावा, इस मामले में, UPDATE_BASE संस्करण नियंत्रण (चिंताओं को कम करने के लिए) के तहत एक स्थान पर इंगित करता है।

परिणाम: इन उत्तरों से इनपुट के आधार पर, मैं इस संशोधित दृष्टिकोण का निर्माण:

runSelfUpdate() { 
    echo "Performing self-update..." 

    # Download new version 
    echo -n "Downloading latest version..." 
    if ! wget --quiet --output-document="$0.tmp" $UPDATE_BASE/$SELF ; then 
    echo "Failed: Error while trying to wget new version!" 
    echo "File requested: $UPDATE_BASE/$SELF" 
    exit 1 
    fi 
    echo "Done." 

    # Copy over modes from old version 
    OCTAL_MODE=$(stat -c '%a' $SELF) 
    if ! chmod $OCTAL_MODE "$0.tmp" ; then 
    echo "Failed: Error while trying to set mode on $0.tmp." 
    exit 1 
    fi 

    # Spawn update script 
    cat > updateScript.sh << EOF 
#!/bin/bash 
# Overwrite old file with new 
if mv "$0.tmp" "$0"; then 
    echo "Done. Update complete." 
    rm \$0 
else 
    echo "Failed!" 
fi 
EOF 

    echo -n "Inserting update process..." 
    exec /bin/bash updateScript.sh 
} 
+0

बेहतर नहीं होगा 'के साथ बाहर निकलने के 0' को बदलने के लिए' कार्यकारी "$ 0" 'किसी भी तर्क आप शायद रिले (जरूरत प्लस:' कार्यकारी "$ 0" "$ @" ')?यही है, @shellter द्वारा चर्चा के अनुसार स्क्रिप्ट के पिछले अवतार को डाउनलोड करने, बदलने और बदलने के यांत्रिकी के साथ निपटने के बाद। –

+0

@ जोनाथन लेफ्लर हां, शायद। अभी के लिए, मैं इसे एक अलग और छोटी कार्रवाई रखना चाहता था ताकि यह संभवतः बाकी लिपि में हस्तक्षेप न करे। एक बार जब मैं दृष्टिकोण में आत्मविश्वास रखता हूं, तो मैं इसे बढ़ा सकता हूं। –

+0

ध्यान दें कि स्टेट के साथ अनुमतियां प्राप्त करने के लिए ओएसएक्स पर एक अलग ध्वज और प्रारूप की आवश्यकता होती है। समकक्ष 'stat -f '% ए' $ 0' है। – meonlol

उत्तर

5

(कम से कम यह अपने आप को अद्यतन करने के बाद चलते रहने की कोशिश नहीं करता!)

बात यह है कि मुझे अपने दृष्टिकोण के बारे में परेशान करता है कि आप वर्तमान स्क्रिप्ट (mv $0.tmp $0) अधिलेखित कर रहे हैं के रूप में यह चल रहा है है । शायद काम करने के कई कारण हैं, लेकिन मैं बड़ी मात्रा में शर्त नहीं लगाऊंगा कि यह सभी परिस्थितियों में काम करने की गारंटी है।मुझे POSIX या किसी अन्य मानक में कुछ भी पता नहीं है जो निर्दिष्ट करता है कि शेल एक फ़ाइल को कैसे संसाधित करता है जिसे वह स्क्रिप्ट के रूप में निष्पादित कर रहा है।

यहाँ शायद होने जा रहा है है:

आप स्क्रिप्ट को निष्पादित। कर्नेल #!/bin/sh लाइन देखता है (आपने इसे नहीं दिखाया, लेकिन मुझे लगता है कि यह वहां है) और /bin/sh को आपकी स्क्रिप्ट के नाम के साथ तर्क के रूप में आमंत्रित करता है। खोल तब आपकी स्क्रिप्ट खोलने के लिए fopen(), या शायद open() का उपयोग करता है, इससे पढ़ता है, और खोल सामग्री के रूप में इसकी सामग्री को समझना शुरू करता है।

पर्याप्त रूप से छोटी लिपि के लिए, खोल शायद पूरी चीज को मेमोरी में पढ़ता है, या तो स्पष्ट रूप से या सामान्य फ़ाइल I/O द्वारा किए गए बफरिंग के हिस्से के रूप में। एक बड़ी लिपि के लिए, यह इसे निष्पादित करने के रूप में इसे टुकड़ों में पढ़ सकता है। लेकिन किसी भी तरह से, यह शायद केवल एक बार फ़ाइल खोलता है, और जब तक यह निष्पादित हो जाता है तब तक इसे खोलता रहता है।

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

और इसके अलावा, mv कमांड जो स्क्रिप्ट फ़ाइल को क्लब्बर करता है तुरंत exit 0 द्वारा पीछा किया जाता है।

लेकिन यह कम से कम कल्पना करने योग्य है कि खोल फ़ाइल को बंद कर सकता है और फिर इसे नाम से फिर से खोल सकता है। मैं ऐसा करने के लिए किसी भी अच्छे कारण के बारे में नहीं सोच सकता, लेकिन मुझे कोई पूर्ण गारंटी नहीं है कि यह नहीं होगा।

और कुछ सिस्टम कठोर फ़ाइल लॉकिंग करते हैं जो अधिकांश यूनिक्स सिस्टम करते हैं। उदाहरण के लिए, विंडोज़ पर, मुझे संदेह है कि mv कमांड विफल हो जाएगा क्योंकि एक प्रक्रिया (खोल) में फ़ाइल खुलती है। आपकी स्क्रिप्ट सिगविन पर असफल हो सकती है। (मैं इसे की कोशिश की है नहीं।)

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

मेरा सुझाव: एक दूसरी स्क्रिप्ट लिखें जिसका एकमात्र काम केवल पहला अपडेट करना है। उस स्क्रिप्ट में runSelfUpdate() फ़ंक्शन, या समकक्ष कोड डालें। अपनी मूल स्क्रिप्ट में, अद्यतन स्क्रिप्ट का आह्वान करने के लिए exec का उपयोग करें, ताकि जब आप इसे अपडेट करते हैं तो मूल स्क्रिप्ट अब नहीं चल रही है। यदि आप दो अलग-अलग स्क्रिप्ट को बनाए रखने, वितरित करने और स्थापित करने की परेशानी से बचना चाहते हैं। आप मूल स्क्रिप्ट /tmp में अद्वितीय के साथ अद्यतन स्क्रिप्ट बना सकते हैं; जो अद्यतन स्क्रिप्ट को अद्यतन करने की समस्या को भी हल करेगा। (मैं /tmp में स्वत: जेनरेटेड अपडेट स्क्रिप्ट को साफ करने की चिंता नहीं करता; यह केवल कीड़े के समान कर को फिर से खोल देगा।)

+0

यह वही है जो मैं सोच रहा था। और मैं सबकुछ 1 फाइल में सबकुछ रखना चाहता हूं, इसलिए मुझे लगता है कि मैं 2-चरणीय दृष्टिकोण के साथ जा रहा हूं। –

4

हाँ, पर ... मैं आप अपनी स्क्रिप्ट के इतिहास का एक और अधिक स्तरित संस्करण रखने के लिए, जब तक की सिफारिश करेंगे रिमोट होस्ट इतिहास के साथ संस्करण-नियंत्रण भी कर सकता है। कहा जा रहा है कि, आपके द्वारा पोस्ट किए गए कोड को सीधे प्रतिक्रिया देने के लिए, निम्नलिखित टिप्पणियां देखें ;-)

जब आपके पास विगेट की हिचकी है, तो आपके सिस्टम के साथ क्या होता है, चुपचाप केवल एक आंशिक या अन्यथा भ्रष्ट के साथ आपकी कार्य स्क्रिप्ट का हिस्सा ओवरराइट करता है नकल? आपका अगला चरण mv $0.tmp $0 करता है ताकि आप अपना वर्किंग वर्जन खो चुके हैं। ,

(मुझे आशा है कि आप रिमोट पर संस्करण नियंत्रण में है!)

आप अगर wget किसी भी त्रुटि संदेश

if ! wget --quiet --output-document=$0.tmp $UPDATE_BASE/$SELF ; then 
    echo "error on wget on $UPDATE_BASE/$SELF" 
    exit 1 
fi 

इसके अलावा रिटर्न देखने के लिए जाँच कर सकते हैं, नियम-ऑफ-द अंगूठे परीक्षण में मदद मिलेगी यानी

if (($(wc -c < $0.tmp) >= $(wc -c < $0))); then 
    mv $0.tmp $0 
fi 

लेकिन शायद ही कभी मूर्खतापूर्ण हैं।

यदि आपका $ 0 इसमें रिक्त स्थान के साथ हवादार हो सकता है, तो "$0" जैसे सभी संदर्भों को घेरना बेहतर होगा।

सुपर बुलेट प्रूफ हो, सभी आदेश रिटर्न की जाँच पर विचार करें और मुझे आशा है कि Octal_Mode एक उचित मूल्य

OCTAL_MODE=$(stat -c '%a' $0) 
    case ${OCTAL_MODE:--1} in 
     -[1]) 
     printf "Error : OCTAL_MODE was empty\n" 
     exit 1 
    ;;  
    777|775|755) : nothing ;; 
    *) 
     printf "Error in OCTAL_MODEs, found value=${OCTAL_MODE}\n" 
     exit 1 
    ;;   
    esac 

    if ! chmod $OCTAL_MODE $0.tmp ; then 
    echo "error on chmod $OCTAL_MODE %0.tmp from $UPDATE_BASE/$SELF, can't continue" 
    exit 1 
fi 

है कि इस मदद करता है करने के लिए।

+0

बहुत बहुत धन्यवाद। यह सब बहुत मूल्यवान इनपुट है। लेकिन जब मैंने प्रश्न लिखा था तब मैं दोषपूर्ण डाउनलोड के बारे में चिंतित नहीं था। तो मैं ज्यादातर इस बारे में सोच रहा था कि कैसे बैश स्क्रिप्ट को संसाधित करेगा। मैं तदनुसार अपना प्रश्न अपडेट करूंगा। –

0

यहां बहुत देर से उत्तर दिया गया है, लेकिन जैसा कि मैंने अभी भी हल किया है, मैंने सोचा कि यह किसी की मदद कर सकता है दृष्टिकोण पोस्ट करने के लिए:

#!/usr/bin/env bash 
# 
set -fb 

readonly THISDIR=$(cd "$(dirname "$0")" ; pwd) 
readonly MY_NAME=$(basename "$0") 
readonly FILE_TO_FETCH_URL="https://your_url_to_downloadable_file_here" 
readonly EXISTING_SHELL_SCRIPT="${THISDIR}/somescript.sh" 
readonly EXECUTABLE_SHELL_SCRIPT="${THISDIR}/.somescript.sh" 

function get_remote_file() { 
    readonly REQUEST_URL=$1 
    readonly OUTPUT_FILENAME=$2 
    readonly TEMP_FILE="${THISDIR}/tmp.file" 
    if [ -n "$(which wget)" ]; then 
    $(wget -O "${TEMP_FILE}" "$REQUEST_URL" 2>&1) 
    if [[ $? -eq 0 ]]; then 
     mv "${TEMP_FILE}" "${OUTPUT_FILENAME}" 
     chmod 755 "${OUTPUT_FILENAME}" 
    else 
     return 1 
    fi 
    fi 
} 
function clean_up() { 
    # clean up code (if required) that has to execute every time here 
} 
function self_clean_up() { 
    rm -f "${EXECUTABLE_SHELL_SCRIPT}" 
} 

function update_self_and_invoke() { 
    get_remote_file "${FILE_TO_FETCH_URL}" "${EXECUTABLE_SHELL_SCRIPT}" 
    if [ $? -ne 0 ]; then 
    cp "${EXISTING_SHELL_SCRIPT}" "${EXECUTABLE_SHELL_SCRIPT}" 
    fi 
    exec "${EXECUTABLE_SHELL_SCRIPT}" "[email protected]" 
} 
function main() { 
    cp "${EXECUTABLE_SHELL_SCRIPT}" "${EXISTING_SHELL_SCRIPT}" 
    # your code here 
} 

if [[ $MY_NAME = \.* ]]; then 
    # invoke real main program 
    trap "clean_up; self_clean_up" EXIT 
    main "[email protected]" 
else 
    # update myself and invoke updated version 
    trap clean_up EXIT 
    update_self_and_invoke "[email protected]" 
fi 
संबंधित मुद्दे