Yanıtlar:
Oldukça karmaşık kabuk betikleri yazdım ve ilk önerim "yapma" dır. Bunun nedeni, senaryonuzu engelleyen küçük bir hata yapmak ve hatta tehlikeli hale getirmek oldukça kolaydır.
Bununla birlikte, sizi geçirecek başka kaynağım yok, kişisel deneyimim var. İşte normalde yaptığım şey, aşırı kilolu, ancak çok ayrıntılı olmasına rağmen katı olma eğilimindedir .
yakarma
betiğinizin uzun ve kısa seçenekleri kabul etmesini sağlayın. dikkatli olun çünkü seçenekleri ayrıştırmak için iki komut vardır, getopt ve getopts. Daha az sorunla karşılaştığınızda getopt kullanın.
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
Bir başka önemli nokta, bir programın başarılı bir şekilde tamamlanması durumunda daima sıfır, bir şeyler ters gittiğinde sıfırdan farklı olması gerektiğidir.
İşlev çağrıları
Bash işlevlerini çağırabilirsiniz, çağrıdan önce tanımlamayı unutmayın. İşlevler komut dosyaları gibidir, yalnızca sayısal değerler döndürebilirler. Bu, dize değerlerini döndürmek için farklı bir strateji icat etmeniz gerektiği anlamına gelir. Stratejim, sonucu saklamak için RESULT adlı bir değişken kullanmak ve işlev temiz şekilde tamamlandıysa 0 döndürmektir. Ayrıca, sıfırdan farklı bir değer döndürüyorsanız istisnaları yükseltebilir ve sonra birincisi istisna türünü ve ikincisi okunabilir mesajı içeren iki "istisna değişkeni" (mine: EXCEPTION ve EXCEPTION_MSG) ayarlayabilirsiniz.
Bir işlevi çağırdığınızda, işlevin parametreleri $ 0, $ 1 vb. Özel değişkenlere atanır. Onları daha anlamlı adlara koymanızı öneririm. işlevin içindeki değişkenleri yerel olarak bildirir:
function foo {
local bar="$0"
}
Hata eğilimli durumlar
Bash'da, aksi belirtilmedikçe, boş bir dize olarak ayarlanmamış bir değişken kullanılır. Yazım hatası durumunda bu çok tehlikelidir, çünkü kötü yazılan değişken rapor edilmeyecektir ve boş olarak değerlendirilecektir. kullanım
set -o nounset
bunun olmasını önlemek için. Ancak dikkatli olun, çünkü bunu yaparsanız tanımsız bir değişkeni her değerlendirdiğinizde program iptal edilir. Bu nedenle, bir değişkenin tanımlanıp tanımlanmadığını kontrol etmenin tek yolu şudur:
if test "x${foo:-notset}" == "xnotset"
then
echo "foo not set"
fi
Değişkenleri salt okunur olarak bildirebilirsiniz:
readonly readonly_var="foo"
modularization
Aşağıdaki kodu kullanırsanız "python benzeri" modülerleştirme elde edebilirsiniz:
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
}
Daha sonra .shinc uzantılı dosyaları aşağıdaki sözdizimiyle içe aktarabilirsiniz
"AModule / ModuleFile" dosyasını içe aktar
Hangisi SHELL_LIBRARY_PATH içinde aranacak. Genel ad alanında her zaman içe aktardığınız için, tüm işlevlerinize ve değişkenlerinize uygun bir önek önekini eklemeyi unutmayın, aksi takdirde isim çakışması riskiyle karşı karşıya kalırsınız. Python dot olarak çift alt çizgi kullanıyorum.
Ayrıca, bunu modülünüzde ilk şey olarak koyun
# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
return 0
fi
BashInclude__imported=1
Nesne yönelimli programlama
Bash'da, nesnelerin tahsis edilmesi için oldukça karmaşık bir sistem oluşturmadıkça, nesne yönelimli programlama yapamazsınız (bunu düşündüm. Bu mümkün, ancak çılgınca). Ancak pratikte "Singleton odaklı programlama" yapabilirsiniz: her nesnenin bir örneği ve sadece bir tane vardır.
Yaptığım şey: Modüle bir nesne tanımlamak (modülerleştirme girişine bakın). Sonra boş vars (üye değişkenlere benzer) bir init fonksiyonu (yapıcı) ve üye fonksiyonlarını tanımlarım, bu örnek kodda olduğu gibi
# 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
}
Yakalama ve işleme sinyalleri
Bunu istisnaları yakalamak ve ele almak için yararlı buldum.
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 "$@"
İpuçları ve püf noktaları
Bir şey herhangi bir nedenle çalışmazsa kodu yeniden sıralamayı deneyin. Düzen önemlidir ve her zaman sezgisel değildir.
tcsh ile çalışmayı bile düşünmeyin. işlevleri desteklemez ve genel olarak korkunçtur.
Umarım yardımcı olur, lütfen unutmayın. Burada yazdığım şeyleri kullanmak zorunda kalırsanız, bu sorununuzun kabukla çözülemeyecek kadar karmaşık olduğu anlamına gelir. başka bir dil kullanın. İnsan faktörleri ve mirası nedeniyle kullanmak zorunda kaldım.
getoptvs getopts? getoptsdaha taşınabilir ve herhangi bir POSIX kabuğunda çalışır. Özellikle soru, özellikle bash en iyi uygulamaları yerine mermi en iyi uygulamaları olduğundan, mümkün olduğunda çoklu mermileri desteklemek için POSIX uyumluluğunu destekleyeceğim.
Sadece Bash için değil, kabuk komut dosyalarında çok fazla bilgelik için Gelişmiş Bash-Scripting Rehberine bir göz atın .
Başka, tartışmasız daha karmaşık dillere bakmanızı söyleyen insanları dinlemeyin. Kabuk komut dosyası oluşturma gereksinimlerinizi karşılıyorsa, bunu kullanın. Fantezi değil işlevsellik istiyorsun. Yeni diller özgeçmişiniz için değerli yeni beceriler sağlar, ancak yapılması gereken bir işiniz varsa ve zaten kabuk biliyorsanız, bu yardımcı olmaz.
Belirtildiği gibi, kabuk komut dosyası oluşturma için pek çok "en iyi uygulama" veya "tasarım deseni" yoktur. Farklı kullanımlar, diğer programlama dilleri gibi farklı yönergelere ve önyargılara sahiptir.
kabuk betiği dosyaları ve işlemleri yönetmek için tasarlanmış bir dildir. Bunun için harika olsa da, genel amaçlı bir dil değildir, bu nedenle kabuk komut dosyasında yeni mantık yeniden oluşturmak yerine her zaman mevcut yardımcı programlardan mantığı yapıştırmaya çalışın.
Bu genel prensip dışında bazı yaygın kabuk senaryo hataları topladım .
OSCON'da bu yıl (2008) sadece bu konuda harika bir oturum yapıldı: http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf
Ne zaman kullanacağınızı bilin. Hızlı ve kirli yapıştırma komutları için sorun değil. Önemsiz birkaç karardan daha fazlasına ihtiyacınız varsa, döngüler, herhangi bir şey, Python, Perl için gidin ve modülerleştirin .
Kabuk ile ilgili en büyük sorun, genellikle nihai sonucun sadece büyük bir çamur topu, 4000 satır bash ve büyüyen gibi görünmesidir ... ve bundan kurtulamazsınız çünkü şimdi tüm projeniz buna bağlı. Tabii ki, 40 satır güzel bash başladı.
Kolay: kabuk komut dosyaları yerine python kullanın. Gereksinim duymadığınız herhangi bir şeyi karmaşıklaştırmak zorunda kalmadan ve komut dosyanızın parçalarını neredeyse hiç olmadan işlevlere, nesnelere, kalıcı nesnelere (zodb), dağıtılmış nesnelere (pyro) evrimleşme yeteneğini koruyarak, okunabilirlikte yaklaşık 100 kat artış elde edersiniz ekstra kod.
Bazı "en iyi uygulamaları" bulmak için Linux dağıtım şirketlerinin (örneğin Debian) init komut dosyalarını nasıl yazdıklarına bakın (genellikle /etc/init.d dosyasında bulunur)
Çoğu "bash-isms" içermez ve yapılandırma ayarları, kütüphane dosyaları ve kaynak biçimlendirmesi arasında iyi bir ayrım vardır.
Kişisel stilim, bazı varsayılan değişkenleri tanımlayan ve daha sonra yeni değerler içerebilecek bir yapılandırma dosyasını yüklemeye ("kaynak") yüklemeye çalışan bir master-shellscript yazmaktır.
Komut dosyasını daha karmaşık hale getirme eğilimindeyken işlevlerden kaçınmaya çalışıyorum. (Perl bu amaçla yaratıldı.)
Betiğin taşınabilir olduğundan emin olmak için, sadece #! / Bin / sh ile değil, aynı zamanda #! / Bin / ash, #! / Bin / dash, vb. İle de test edin. Bash'e özgü kodu yakında bulabilirsiniz.