जवाबों:
मैंने काफी जटिल शेल स्क्रिप्ट लिखी हैं और मेरा पहला सुझाव "नहीं" है। कारण यह है कि एक छोटी सी गलती करना काफी आसान है जो आपकी स्क्रिप्ट में बाधा डालती है, या इसे खतरनाक भी बनाती है।
उस ने कहा, मेरे पास आपके व्यक्तिगत अनुभव को पारित करने के लिए अन्य संसाधन नहीं हैं। यहां मैं वही करता हूं जो सामान्य रूप से होता है, जो ओवरकिल होता है, लेकिन ठोस होता है, हालांकि बहुत ही क्रियात्मक होता है।
मंगलाचरण
अपनी स्क्रिप्ट को लंबे और छोटे विकल्पों को स्वीकार करें। सावधान रहें क्योंकि विकल्पों को पार्स करने के लिए दो आदेश हैं, गेटअप और गेटअप। कम परेशानी का सामना करने के साथ ही गेटअप का इस्तेमाल करें।
CommandLineOptions__config_file=""
CommandLineOptions__debug_level=""
getopt_results=`getopt -s bash -o c:d:: --long config_file:,debug_level:: -- "$@"`
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
एक और महत्वपूर्ण बिंदु यह है कि एक कार्यक्रम हमेशा शून्य पर लौटना चाहिए यदि सफलतापूर्वक पूरा हो जाता है, तो गैर-शून्य अगर कुछ गलत हो गया।
फंक्शन कॉल
आप फ़ंक्शन को कॉल कर सकते हैं, कॉल से पहले उन्हें परिभाषित करना याद रखें। फ़ंक्शंस स्क्रिप्ट की तरह हैं, वे केवल संख्यात्मक मान लौटा सकते हैं। इसका मतलब है कि आपको स्ट्रिंग मान वापस करने के लिए एक अलग रणनीति का आविष्कार करना होगा। मेरी रणनीति परिणाम को संग्रहीत करने के लिए परिणाम नामक चर का उपयोग करना है, और यदि फ़ंक्शन सफाई से पूरा हो गया है तो 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
}
फिर आप एक्सटेंशन के साथ फ़ाइलों को आयात कर सकते हैं। निम्नलिखित सिंटैक्स के साथ .incinc
आयात "AModule / ModuleFile"
जिसे SHELL_LIBRARY_PATH में खोजा जाएगा। जैसा कि आप हमेशा वैश्विक नामस्थान में आयात करते हैं, एक उचित उपसर्ग के साथ अपने सभी कार्यों और चर को उपसर्ग करना याद रखें, अन्यथा आप संघर्ष का जोखिम उठाते हैं। मैं अजगर डॉट के रूप में डबल अंडरस्कोर का उपयोग करता हूं।
इसके अलावा, इसे अपने मॉड्यूल में पहली चीज़ के रूप में रखें
# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
return 0
fi
BashInclude__imported=1
ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग
बाश में, आप ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग नहीं कर सकते, जब तक कि आप ऑब्जेक्ट के आवंटन की एक बहुत जटिल प्रणाली नहीं बनाते हैं (मैंने इसके बारे में सोचा था कि यह संभव है, लेकिन पागल है)। व्यवहार में, आप हालांकि "सिंगलटन ओरिएंटेड प्रोग्रामिंग" कर सकते हैं: आपके पास प्रत्येक ऑब्जेक्ट का एक उदाहरण है, और केवल एक है।
मैं क्या कर रहा हूं: मैं एक मॉड्यूल में एक मॉड्यूल को परिभाषित करता हूं (मॉड्यूलर प्रविष्टि देखें)। फिर मैं इस उदाहरण कोड की तरह खाली vars (सदस्य चर के अनुरूप) एक 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 "$@"
संकेत और सुझाव
यदि किसी कारण से कुछ काम नहीं करता है, तो कोड को फिर से व्यवस्थित करने का प्रयास करें। आदेश महत्वपूर्ण है और हमेशा सहज नहीं है।
tcsh के साथ काम करने पर भी विचार न करें। यह कार्यों का समर्थन नहीं करता है, और यह सामान्य रूप से भयानक है।
आशा है कि यह मदद करता है, हालांकि कृपया ध्यान दें। यदि आपको मेरे द्वारा लिखी गई चीजों का उपयोग करना है, तो इसका मतलब है कि आपकी समस्या शेल के साथ हल करने के लिए बहुत जटिल है। दूसरी भाषा का उपयोग करें। मुझे इसका उपयोग मानवीय कारकों और विरासत के कारण करना पड़ा।
getoptबनाम getopts? getoptsअधिक पोर्टेबल है और किसी भी POSIX शेल में काम करता है। विशेष रूप से सवाल शेल सर्वोत्तम प्रथाओं के बजाय विशेष रूप से सर्वश्रेष्ठ प्रथाओं को मारना है, मैं जब संभव हो तो कई गोले का समर्थन करने के लिए POSIX अनुपालन का समर्थन करेगा।
शेल स्क्रिप्टिंग पर बहुत ज्ञान के लिए एडवांस्ड बैश-स्क्रिप्टिंग गाइड पर एक नज़र डालें - सिर्फ बैश ही नहीं।
लोगों को यह बताने के लिए मत कहना कि आप अन्य को देखते हैं, यकीनन अधिक जटिल भाषाएं। यदि शेल स्क्रिप्टिंग आपकी आवश्यकताओं को पूरा करती है, तो इसका उपयोग करें। आप कार्यक्षमता चाहते हैं, काल्पनिकता नहीं। नई भाषाएँ आपके फिर से शुरू होने के लिए मूल्यवान नए कौशल प्रदान करती हैं, लेकिन इससे आपको मदद नहीं मिलती है अगर आपके पास काम है जिसे करने की आवश्यकता है और आप पहले से ही शेल जानते हैं।
जैसा कि कहा गया है, शेल स्क्रिप्टिंग के लिए बहुत सारे "सर्वोत्तम अभ्यास" या "डिज़ाइन पैटर्न" नहीं हैं। अलग-अलग उपयोगों में अलग-अलग दिशा-निर्देश और पूर्वाग्रह हैं - किसी भी अन्य प्रोग्रामिंग भाषा की तरह।
शेल स्क्रिप्ट फ़ाइलों और प्रक्रियाओं में हेरफेर करने के लिए डिज़ाइन की गई भाषा है। हालांकि यह उसके लिए बहुत अच्छा है, यह एक सामान्य उद्देश्य की भाषा नहीं है, इसलिए हमेशा शेल स्क्रिप्ट में नए तर्क को फिर से बनाने के बजाय मौजूदा उपयोगिताओं से तर्क को गोंद करने का प्रयास करें।
उस सामान्य सिद्धांत के अलावा मैंने कुछ सामान्य शेल स्क्रिप्ट गलतियों को एकत्र किया है ।
इस वर्ष (2008) में OSCON में इस विषय पर एक बहुत अच्छा सत्र था: http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf
जानिए इसका इस्तेमाल कब करना है। त्वरित और गंदी gluing आदेशों के लिए यह ठीक है। यदि आपको कुछ गैर-तुच्छ निर्णय लेने की आवश्यकता है, तो लूप, कुछ भी, पायथन, पर्ल और मॉड्यूलर के लिए जाएं ।
शेल के साथ सबसे बड़ी समस्या अक्सर यह होती है कि अंतिम परिणाम कीचड़ की एक बड़ी गेंद की तरह दिखता है, 4000 बैश और बढ़ते हुए ... और आप इससे छुटकारा नहीं पा सकते क्योंकि अब आपकी पूरी परियोजना इस पर निर्भर करती है। बेशक, यह सुंदर बैश की 40 लाइनों पर शुरू हुआ ।
आसान: शेल स्क्रिप्ट के बजाय अजगर का उपयोग करें। आपको अपनी आवश्यकता के अनुसार किसी भी चीज़ को जटिल बनाने के बिना, रीडबैलिटी में लगभग 100 गुना वृद्धि होती है, और अपनी स्क्रिप्ट के कुछ हिस्सों को कार्यों, वस्तुओं, लगातार वस्तुओं (ज़ोडब) में विकसित करने की क्षमता को संरक्षित किए बिना, वितरित ऑब्जेक्ट्स (पायरो) लगभग बिना किसी के अतिरिक्त कोड।
कुछ "सर्वोत्तम प्रथाओं" को खोजने के लिए, देखें कि लिनक्स डिस्ट्रो कैसे (जैसे डेबियन) अपनी init- स्क्रिप्ट लिखते हैं (आमतौर पर /etc/init.d में पाए जाते हैं)
उनमें से अधिकांश "बैश-इस्स" के बिना हैं और कॉन्फ़िगरेशन सेटिंग्स, लाइब्रेरी-फाइल्स और सोर्स फॉर्मेटिंग का अच्छा पृथक्करण है।
मेरी व्यक्तिगत शैली एक मास्टर-शेल्स्क्रिप्ट लिखने के लिए है जो कुछ डिफ़ॉल्ट चर को परिभाषित करती है, और फिर एक कॉन्फ़िगरेशन फ़ाइल को लोड करने ("स्रोत") को लोड करने का प्रयास करती है जिसमें नए मान हो सकते हैं।
मैं कार्यों से बचने की कोशिश करता हूं क्योंकि वे स्क्रिप्ट को अधिक जटिल बनाते हैं। (उस उद्देश्य के लिए पर्ल बनाया गया था।)
यह सुनिश्चित करने के लिए कि स्क्रिप्ट पोर्टेबल है, न केवल #! / Bin / sh के साथ परीक्षण करें, बल्कि #! / Bin / ash, #! / Bin / डैश इत्यादि का भी उपयोग करें, आप जल्द ही Bash के विशिष्ट कोड को देख लेंगे।