2008-09-17 16 views
141

क्या किसी को भी ऐसे संसाधनों के बारे में पता है जो शैल स्क्रिप्ट्स (sh, bash आदि) के लिए सर्वोत्तम प्रथाओं या डिज़ाइन पैटर्न के बारे में बात करते हैं?शैल स्क्रिप्ट के लिए डिज़ाइन पैटर्न या सर्वोत्तम प्रथाएं

+0

मैंने कल रात [टेम्पलेट पैटर्न में BASH] (http://quickshiftin.com/blog/2014/01/template-method-pattern-bash/) पर एक छोटा सा लेख लिखा था। देखें कि आप क्या सोचते हैं। – quickshiftin

उत्तर

190

मैंने काफी जटिल शैल लिपियों को लिखा और मेरा पहला सुझाव "नहीं" है। इसका कारण यह है कि एक छोटी सी गलती करना काफी आसान है जो आपकी लिपि में बाधा डालता है, या यहां तक ​​कि इसे खतरनाक बना देता है।

उस ने कहा, मेरे पास आपके पास जाने के लिए अन्य संसाधन नहीं हैं बल्कि मेरा व्यक्तिगत अनुभव है। यहां मैं सामान्य रूप से करता हूं, जो अधिक है, लेकिन ठोस हो जाता है, हालांकि बहुत वर्बोज़।

प्रार्थना किए

अपनी स्क्रिप्ट लंबी और छोटी विकल्पों को स्वीकार करते हैं। सावधान रहें क्योंकि पार्स विकल्प, गेटोपेट और गेटोपेट्स के लिए दो कमांड हैं। गड़बड़ी का प्रयोग करें क्योंकि आपको कम परेशानी का सामना करना पड़ता है।

CommandLineOptions__config_file="" 
CommandLineOptions__debug_level="" 

getopt_results=`getopt -s bash -o c:d:: --long config_file:,debug_level:: -- "[email protected]"` 

if test $? != 0 
then 
    echo "unrecognized option" 
    exit 1 
fi 

eval set -- "$getopt_results" 

while true 
do 
    case "$1" in 
     --config_file) 
      CommandLineOptions__config_file="$2"; 
      shift 2; 
      ;; 
     --debug_level) 
      CommandLineOptions__debug_level="$2"; 
      shift 2; 
      ;; 
     --) 
      shift 
      break 
      ;; 
     *) 
      echo "$0: unparseable option $1" 
      EXCEPTION=$Main__ParameterException 
      EXCEPTION_MSG="unparseable option $1" 
      exit 1 
      ;; 
    esac 
done 

if test "x$CommandLineOptions__config_file" == "x" 
then 
    echo "$0: missing config_file parameter" 
    EXCEPTION=$Main__ParameterException 
    EXCEPTION_MSG="missing config_file parameter" 
    exit 1 
fi 

एक अन्य महत्वपूर्ण मुद्दा यह है कि एक कार्यक्रम हमेशा शून्य लौटना चाहिए अगर सफलतापूर्वक पूरा, गैर शून्य अगर कुछ गलत हो गया है।

समारोह कॉल

आप बैश में कार्यों कॉल कर सकते हैं, बस कॉल करने से पहले उन्हें परिभाषित करने के लिए याद है। कार्य स्क्रिप्ट की तरह हैं, वे केवल संख्यात्मक मान वापस कर सकते हैं। इसका मतलब है कि आपको स्ट्रिंग मानों को वापस करने के लिए एक अलग रणनीति का आविष्कार करना होगा। मेरी रणनीति परिणाम को संग्रहीत करने के लिए RESULT नामक एक चर का उपयोग करना है, और अगर कार्य साफ़ हो गया है तो 0 लौटा रहा है। इसके अलावा, आप अपवाद अगर आप शून्य से एक मूल्य के अलग लौट रहे हैं बढ़ा सकते हैं, और फिर दो "अपवाद चर" सेट (मेरा: अपवाद और EXCEPTION_MSG), पहली अपवाद प्रकार और दूसरा एक मानव पठनीय संदेश से युक्त। $ 0 $ 1 आदि मैं उन्हें और अधिक सार्थक नाम में डाल करने के लिए आप का सुझाव वार्स

जब आप एक समारोह फोन, समारोह के मापदंडों विशेष को सौंपा है। स्थानीय रूप में समारोह के अंदर चर घोषित:

function foo { 
    local bar="$0" 
} 

त्रुटि प्रवण स्थितियों

बैश में, जब तक कि आप की घोषणा अन्यथा, एक सेट नहीं चर एक खाली स्ट्रिंग के रूप में प्रयोग किया जाता है। यह, टाइपो के मामले में बहुत ही खतरनाक है के रूप में बुरी तरह से टाइप किया चर रिपोर्ट नहीं की जाएगी, और यह खाली के रूप में मूल्यांकन किया जाएगा।

set -o nounset 

ऐसा होने से रोकने के लिए उपयोग करें। हालांकि सावधान रहें, क्योंकि अगर आप ऐसा करते हैं, कार्यक्रम हर बार जब आप एक अपरिभाषित चर का मूल्यांकन रद्द कर देगा।

if test "x${foo:-notset}" == "xnotset" 
then 
    echo "foo not set" 
fi 

आप केवल पढ़ने के रूप में चर घोषणा कर सकते हैं:

readonly readonly_var="foo" 

Modularization

आप प्राप्त कर सकते हैं इस कारण से, यदि किसी वैरिएबल निर्धारित नहीं है पीछा कर रहा है एक ही तरीका है की जाँच करने के यदि आप निम्न कोड का उपयोग करते हैं तो "पाइथन" मॉड्यूलरलाइजेशन:

set -o nounset 
function getScriptAbsoluteDir { 
    # @description used to get the script path 
    # @param $1 the script $0 parameter 
    local script_invoke_path="$1" 
    local cwd=`pwd` 

    # absolute path ? if so, the first character is a/
    if test "x${script_invoke_path:0:1}" = 'x/' 
    then 
     RESULT=`dirname "$script_invoke_path"` 
    else 
     RESULT=`dirname "$cwd/$script_invoke_path"` 
    fi 
} 

script_invoke_path="$0" 
script_name=`basename "$0"` 
getScriptAbsoluteDir "$script_invoke_path" 
script_absolute_dir=$RESULT 

function import() { 
    # @description importer routine to get external functionality. 
    # @description the first location searched is the script directory. 
    # @description if not found, search the module in the paths contained in $SHELL_LIBRARY_PATH environment variable 
    # @param $1 the .shinc file to import, without .shinc extension 
    module=$1 

    if test "x$module" == "x" 
    then 
     echo "$script_name : Unable to import unspecified module. Dying." 
     exit 1 
    fi 

    if test "x${script_absolute_dir:-notset}" == "xnotset" 
    then 
     echo "$script_name : Undefined script absolute dir. Did you remove getScriptAbsoluteDir? Dying." 
     exit 1 
    fi 

    if test "x$script_absolute_dir" == "x" 
    then 
     echo "$script_name : empty script path. Dying." 
     exit 1 
    fi 

    if test -e "$script_absolute_dir/$module.shinc" 
    then 
     # import from script directory 
     . "$script_absolute_dir/$module.shinc" 
    elif test "x${SHELL_LIBRARY_PATH:-notset}" != "xnotset" 
    then 
     # import from the shell script library path 
     # save the separator and use the ':' instead 
     local saved_IFS="$IFS" 
     IFS=':' 
     for path in $SHELL_LIBRARY_PATH 
     do 
      if test -e "$path/$module.shinc" 
      then 
       . "$path/$module.shinc" 
       return 
      fi 
     done 
     # restore the standard separator 
     IFS="$saved_IFS" 
    fi 
    echo "$script_name : Unable to find module $module." 
    exit 1 
} 

आप तो निम्न सिंटैक्स

आयात "AModule/ModuleFile"

कौन सा SHELL_LIBRARY_PATH में खोज की जाएगी साथ .shinc विस्तार के साथ फ़ाइलें आयात कर सकते हैं। जैसा कि आप हमेशा वैश्विक नामस्थान में आयात करते हैं, अपने सभी कार्यों और चर को उचित उपसर्ग के साथ उपसर्ग करना याद रखें, अन्यथा आप नाम संघर्षों को जोखिम देते हैं। मैं पाइथन डॉट के रूप में डबल अंडरस्कोर का उपयोग करता हूं।

इसके अलावा, अपने मॉड्यूल में पहली बात यह है के रूप में इस डाल

# avoid double inclusion 
if test "${BashInclude__imported+defined}" == "defined" 
then 
    return 0 
fi 
BashInclude__imported=1 

वस्तु उन्मुख प्रोग्रामिंग

बैश में, आप वस्तु उन्मुख प्रोग्रामिंग ऐसा नहीं कर सकते, जब तक आप के आवंटन का एक काफी जटिल प्रणाली का निर्माण वस्तुओं (मैंने इसके बारे में सोचा था। यह व्यवहार्य है, लेकिन पागल है)। प्रैक्टिस में, हालांकि आप "सिंगलटन उन्मुख प्रोग्रामिंग" कर सकते हैं: आपके पास प्रत्येक ऑब्जेक्ट का एक उदाहरण है, और केवल एक ही है।

मैं क्या करता हूं: मैं किसी ऑब्जेक्ट को मॉड्यूल में परिभाषित करता हूं (मॉड्यूलरलाइज़ेशन एंट्री देखें)। तब मैं इस उदाहरण कोड में की तरह, खाली वार्स (सदस्य चर के अनुरूप) एक init समारोह (निर्माता) और सदस्य कार्यों को परिभाषित

# avoid double inclusion 
if test "${Table__imported+defined}" == "defined" 
then 
    return 0 
fi 
Table__imported=1 

readonly Table__NoException="" 
readonly Table__ParameterException="Table__ParameterException" 
readonly Table__MySqlException="Table__MySqlException" 
readonly Table__NotInitializedException="Table__NotInitializedException" 
readonly Table__AlreadyInitializedException="Table__AlreadyInitializedException" 

# an example for module enum constants, used in the mysql table, in this case 
readonly Table__GENDER_MALE="GENDER_MALE" 
readonly Table__GENDER_FEMALE="GENDER_FEMALE" 

# private: prefixed with p_ (a bash variable cannot start with _) 
p_Table__mysql_exec="" # will contain the executed mysql command 

p_Table__initialized=0 

function Table__init { 
    # @description init the module with the database parameters 
    # @param $1 the mysql config file 
    # @exception Table__NoException, Table__ParameterException 

    EXCEPTION="" 
    EXCEPTION_MSG="" 
    EXCEPTION_FUNC="" 
    RESULT="" 

    if test $p_Table__initialized -ne 0 
    then 
     EXCEPTION=$Table__AlreadyInitializedException 
     EXCEPTION_MSG="module already initialized" 
     EXCEPTION_FUNC="$FUNCNAME" 
     return 1 
    fi 


    local config_file="$1" 

     # yes, I am aware that I could put default parameters and other niceties, but I am lazy today 
     if test "x$config_file" = "x"; then 
      EXCEPTION=$Table__ParameterException 
      EXCEPTION_MSG="missing parameter config file" 
      EXCEPTION_FUNC="$FUNCNAME" 
      return 1 
     fi 


    p_Table__mysql_exec="mysql --defaults-file=$config_file --silent --skip-column-names -e " 

    # mark the module as initialized 
    p_Table__initialized=1 

    EXCEPTION=$Table__NoException 
    EXCEPTION_MSG="" 
    EXCEPTION_FUNC="" 
    return 0 

} 

function Table__getName() { 
    # @description gets the name of the person 
    # @param $1 the row identifier 
    # @result the name 

    EXCEPTION="" 
    EXCEPTION_MSG="" 
    EXCEPTION_FUNC="" 
    RESULT="" 

    if test $p_Table__initialized -eq 0 
    then 
     EXCEPTION=$Table__NotInitializedException 
     EXCEPTION_MSG="module not initialized" 
     EXCEPTION_FUNC="$FUNCNAME" 
     return 1 
    fi 

    id=$1 

     if test "x$id" = "x"; then 
      EXCEPTION=$Table__ParameterException 
      EXCEPTION_MSG="missing parameter identifier" 
      EXCEPTION_FUNC="$FUNCNAME" 
      return 1 
     fi 

    local name=`$p_Table__mysql_exec "SELECT name FROM table WHERE id = '$id'"` 
     if test $? != 0 ; then 
     EXCEPTION=$Table__MySqlException 
     EXCEPTION_MSG="unable to perform select" 
     EXCEPTION_FUNC="$FUNCNAME" 
     return 1 
     fi 

    RESULT=$name 
    EXCEPTION=$Table__NoException 
    EXCEPTION_MSG="" 
    EXCEPTION_FUNC="" 
    return 0 
} 

फँसाने और हैंडलिंग संकेतों

मैं इस पकड़ने के लिए उपयोगी पाया और अपवादों को संभालें।

function Main__interruptHandler() { 
    # @description signal handler for SIGINT 
    echo "SIGINT caught" 
    exit 
} 
function Main__terminationHandler() { 
    # @description signal handler for SIGTERM 
    echo "SIGTERM caught" 
    exit 
} 
function Main__exitHandler() { 
    # @description signal handler for end of the program (clean or unclean). 
    # probably redundant call, we already call the cleanup in main. 
    exit 
} 

trap Main__interruptHandler INT 
trap Main__terminationHandler TERM 
trap Main__exitHandler EXIT 

function Main__main() { 
    # body 
} 

# catch signals and exit 
trap exit INT TERM EXIT 

Main__main "[email protected]" 

संकेत और सुझावों

कुछ किसी कारण से काम नहीं करता है, कोड को पुन: व्यवस्थित करने के लिए प्रयास करें। आदेश महत्वपूर्ण है और हमेशा सहज नहीं है।

टीसीएच के साथ काम करने पर भी विचार न करें। यह कार्यों का समर्थन नहीं करता है, और यह सामान्य रूप से भयानक है।

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

+6

वाह, और मैंने सोचा कि मैं बैश में ओवरकिल के लिए जा रहा था ... मैं अलग-अलग कार्यों और दुर्व्यवहार सबहेल का उपयोग करता हूं (इस प्रकार जब मुझे गति किसी भी तरह से प्रासंगिक होती है तो मुझे पीड़ित होता है)। कोई वैश्विक चर कभी नहीं, न तो अंदर और न ही (सैनिटी के अवशेषों को संरक्षित करने के लिए)। सभी stdout या फ़ाइल आउटपुट के माध्यम से रिटर्न। set -u/set -e (बहुत खराब सेट-जैसे ही पहले होता है बेकार हो जाता है, और मेरा अधिकांश कोड अक्सर वहां होता है)। [स्थानीय कुछ = "$ 1" के साथ लिया गया फ़ंक्शन तर्क; शिफ्ट] (refactoring जब आसान reordering के लिए अनुमति देता है)। एक 3000 लाइनों की बैश स्क्रिप्ट के बाद मैं इस फैशन में भी छोटी स्क्रिप्ट लिखता हूं ... – Eugene

+5

आप पागल वैज्ञानिक – Prospero

+0

मॉड्यूलरलाइजेशन के लिए छोटे सुधार: 1 आपको के बाद वापसी की आवश्यकता है। अनुपलब्ध चेतावनी से बचने के लिए "$ script_absolute_dir/$ module.shinc" । 2 आपको SHELL_LIBRARY_PATH – Duff

8

आसान: शैल स्क्रिप्ट के बजाय पायथन का उपयोग करें। आपको कुछ भी जटिल करने के बिना, पठनीयता में लगभग 100 गुना वृद्धि प्राप्त होती है, और आपकी स्क्रिप्ट के कुछ हिस्सों को कार्यों, वस्तुओं, लगातार वस्तुओं (ज़ोडब), वितरित वस्तुओं (पायरो) में विकसित करने की क्षमता को संरक्षित करने के बिना लगभग कोई अतिरिक्त कोड

+7

आप "जटिल होने के बिना" कहकर अपने आप से विरोधाभास करते हैं और फिर उन जटिलताओं को सूचीबद्ध करते हैं जो आपको लगता है कि मूल्य जोड़ते हैं, जबकि अधिकांश मामलों में समस्याओं और कार्यान्वयन को सरल बनाने के लिए उपयोग किए जाने के बजाय बदसूरत राक्षसों में दुर्व्यवहार किया जाता है। – Evgeny

+3

यह एक बड़ी कमी का तात्पर्य है, आपकी स्क्रिप्ट उन प्रणालियों पर पोर्टेबल नहीं होंगे जहां पाइथन मौजूद नहीं है – astropanic

+1

मुझे एहसास हुआ कि इसका उत्तर '08 में दिया गया था (यह अब 12 दिनों से दो दिन पहले है); हालांकि, इस साल बाद में देख रहे लोगों के लिए, मैं किसी को भी पाइथन या रुबी जैसी भाषाओं पर अपनी ओर मोड़ने के खिलाफ चेतावनी दूंगा क्योंकि यह संभव है कि यह उपलब्ध हो और यदि नहीं, तो यह एक आदेश (या जोड़े क्लिक) स्थापित होने से दूर है । यदि आपको और पोर्टेबिलिटी की आवश्यकता है, तो जावा में अपना प्रोग्राम लिखने के बारे में सोचें क्योंकि आपको उस मशीन को खोजने के लिए कड़ी मेहनत की जाएगी जिसमें JVM उपलब्ध नहीं है। –

-1

या उससे अधिक क्या जोआओ कहा के समान बोली: "।। पर्ल का उपयोग करें आप बैश को पता है, लेकिन इसका इस्तेमाल नहीं करना चाहता होगा"

दुख की बात है कि मैं भूल गया कि किसने कहा था।

और हाँ इन दिनों मैं पार्ल पर पाइथन की सिफारिश करता हूं।

8

सेट-ए का उपयोग करें ताकि आप त्रुटियों के बाद आगे बढ़ें। यदि आप इसे लिनक्स पर चलाने के लिए चाहते हैं तो इसे बैश पर भरोसा किए बिना इसे संगत बनाने का प्रयास करें।

20

शैल स्क्रिप्टिंग पर बहुत सारे ज्ञान के लिए Advanced Bash-Scripting Guide पर नज़र डालें - न केवल बैश, या तो।

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

जैसा कि कहा गया है, शैल स्क्रिप्टिंग के लिए बहुत से "सर्वोत्तम अभ्यास" या "डिज़ाइन पैटर्न" नहीं हैं। अलग-अलग उपयोगों में विभिन्न दिशानिर्देश और पूर्वाग्रह होते हैं - किसी भी अन्य प्रोग्रामिंग भाषा की तरह।

+7

ध्यान दें कि थोड़ी जटिलता की लिपियों के लिए, यह एक सर्वोत्तम अभ्यास नहीं है। कोडिंग सिर्फ कुछ काम करने के बारे में नहीं है। यह इसे जल्दी से, आसानी से बनाने के बारे में है, और यह विश्वसनीय, पुन: प्रयोज्य, और पढ़ने और बनाए रखने में आसान है (विशेष रूप से दूसरों के लिए)। शैल स्क्रिप्ट किसी भी स्तर पर अच्छी तरह से स्केल नहीं करते हैं। किसी भी तर्क के साथ परियोजनाओं के लिए अधिक मजबूत भाषाएं बहुत सरल हैं। – drifter

8

पता करें उपयोग करने के लिए। एक साथ त्वरित और गंदे ग्लूइंग कमांड के लिए यह ठीक है। यदि आपको कुछ गैर-मामूली निर्णयों, लूप, कुछ भी करने की आवश्यकता है, तो पाइथन, पर्ल, और मॉड्यूलर के लिए जाएं।

खोल के साथ सबसे बड़ी समस्या अक्सर होती है कि अंत परिणाम केवल मिट्टी की एक बड़ी गेंद, 4000 लाइनों की बाश और बढ़ती दिखती है ... और आप इससे छुटकारा नहीं पा सकते हैं क्योंकि अब आपकी पूरी परियोजना इस पर निर्भर करती है। बेशक, यह सुंदर बाश के 40 लाइन पर शुरू हुआ।

6

कुछ "सर्वोत्तम प्रथाओं" ढूंढने के लिए, देखने के लिए कैसे लिनक्स distro के (जैसे डेबियन) अपने init-स्क्रिप्ट (आमतौर पर /etc/init.d में पाया)

उनमें से ज्यादातर के बिना "बैश-वाद" हैं बारे में और कॉन्फ़िगरेशन सेटिंग्स, लाइब्रेरी-फ़ाइलें और स्रोत स्वरूपण का एक अच्छा अलगाव है।

मेरी व्यक्तिगत शैली एक मास्टर-शेलस्क्रिप्ट लिखना है जो कुछ डिफ़ॉल्ट चर परिभाषित करता है, और फिर एक कॉन्फ़िगरेशन फ़ाइल ("स्रोत") लोड करने का प्रयास करता है जिसमें नए मान हो सकते हैं।

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

यह सुनिश्चित करने के लिए कि स्क्रिप्ट पोर्टेबल है, न केवल #!/Bin/sh के साथ परीक्षण करें, बल्कि #!/Bin/ash, #!/Bin/dash, आदि का भी उपयोग करें। ।आप जल्द ही बैश विशिष्ट कोड को खोज लेंगे।

17

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

उस सामान्य सिद्धांत के अलावा मैंने कुछ common shell script mistakes एकत्र किया है।

संबंधित मुद्दे