أنماط التصميم أو أفضل الممارسات في البرامج النصية قذيفة [مغلقة]

StackOverflow https://stackoverflow.com/questions/78497

  •  09-06-2019
  •  | 
  •  

سؤال

لا أحد يعرف من أي الموارد التي تتحدث عن أفضل الممارسات أو تصميم أنماط البرامج النصية قذيفة (sh, باش الخ) ؟

هل كانت مفيدة؟

المحلول

كتبت معقدة جدا البرامج النصية قذيفة و اقتراحي الأول هو "لا".والسبب هو أنه من السهل لجعل خطأ صغير التي تعيق البرنامج النصي الخاص بك أو حتى خطرة.

وقال لا يجب أن تمر عليك ولكن تجربتي الشخصية.هنا هو ما كنت تفعل في العادة, وهي مبالغة ، ولكن تميل إلى أن تكون صلبة ، على الرغم من جدا مطول.

الاحتجاج

جعل الخاص بك النصي تقبل طويلة و قصيرة الخيارات.كن حذرا لأن هناك اثنين من الأوامر إلى تحليل الخيارات getopt و getopts.استخدام getopt كما كنت تواجه مشاكل أقل.

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"

البنيوية

يمكنك تحقيق "بايثون مثل" البنيوية إذا كنت تستخدم التعليمة البرمجية التالية:

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
} 

ثم يمكنك استيراد الملفات مع التمديد .shinc مع بناء الجملة التالي

استيراد "وحدة نمطية/ModuleFile"

التي سيتم البحث في SHELL_LIBRARY_PATH.كما كنت دائما الاستيراد في مساحة العالمي ، تذكر أن تسبق كل الوظائف والمتغيرات مع بادئة المناسبة, وإلا هل خطر اسم الاشتباكات.يمكنني استخدام مزدوج التأكيد كما الثعبان نقطة.

كما وضع هذا أول شيء في الوحدة النمطية الخاص بك

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

وجوه المنحى البرمجة

في باش, لا يمكنك أن تفعل وجوه المنحى البرمجة ، إلا إذا كنت بناء معقدة جدا ونظام توزيع الكائنات (لقد فكرت في ذلك.ذلك ممكنا ، ولكن مجنون).في الممارسة العملية, ومع ذلك يمكنك القيام به "المفرد البرمجة الموجهة":لديك حالة واحدة من كل كائن واحد فقط.

ما أقوم به هو:يمكنني تحديد كائن في وحدة (انظر البنيوية الدخول).ثم تحديد فارغة فأر (مماثلة إلى الأعضاء المتغيرات) تهيئة وظيفة (منشئ) وظائف الأعضاء, كما في هذا المثال رمز

# 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.لا وظائف الدعم ، مريع العامة.

آمل أن يساعد على الرغم يرجى الملاحظة.إذا كان لديك لاستخدام هذا النوع من الأشياء كتبته هنا ، فهذا يعني أن المشكلة أكثر تعقيدا من أن تحل مع شركة شل.استخدام لغة أخرى.استخدمه بسبب العوامل البشرية و تراث.

نصائح أخرى

نلقي نظرة على المتقدمة باش-دليل البرمجة الكثير من الحكمة على shell scripting - ليس فقط باش أيضا.

لا تستمع إلى الناس أقول لك أن ننظر إلى أخرى ، يمكن القول إن أكثر تعقيدا اللغات.إذا shell scripting تلبي الاحتياجات الخاصة بك ، واستخدام ذلك.تريد وظيفة لا fanciness.لغات جديدة توفر قيمة مهارات جديدة السيرة الذاتية الخاصة بك, ولكن هذا لا يساعد إذا كان لديك العمل الذي يجب القيام به وأنت تعرف بالفعل قذيفة.

كما ذكر, لا يوجد الكثير من "أفضل الممارسات" أو "أنماط التصميم" شل البرمجة.الاستخدامات المختلفة لديها مبادئ توجيهية مختلفة و التحيز - مثل أي لغة برمجة أخرى.

شل سكربت هي لغة تصميم التعامل مع الملفات والعمليات.في حين أنه لشيء رائع أن ليس الغرض العام اللغة ، لذا حاول دائما أن الغراء المنطق من المرافق الموجودة بدلا من إعادة المنطق الجديد في قذيفة النصي.

بخلاف هذا المبدأ العام لقد جمعت بعض المشتركة شيل الأخطاء.

كان هناك جلسة رائعة في OSCON هذا العام (2008) على هذا الموضوع: http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf

سهلة:استخدام بيثون بدلا من البرامج النصية قذيفة.يمكنك الحصول على بالقرب من 100 أضعاف الزيادة في readablility ، دون أن يعقد أي شيء لا تحتاج والحفاظ على القدرة على التطور أجزاء من السيناريو الخاص بك إلى وظائف الكائنات الأشياء الثابتة (zodb), توزيع الكائنات (بايرو) ما يقرب من دون أي تعليمات برمجية إضافية.

استخدام مجموعة e حتى لا المحراث إلى الأمام بعد أخطاء.محاولة إجراء sh متوافقة دون الاعتماد على باش إذا كنت تريد تشغيله على لينكس.

تعرف متى تستخدم. سريعة وقذرة لصق الأوامر معا لا بأس.إذا كنت تحتاج إلى إجراء أي أكثر من عدد قليل من غير تافهة القرارات, الحلقات, أي شيء تذهب Python, Perl ، modularize.

أكبر مشكلة مع شركة شل في كثير من الأحيان أن النتيجة النهائية تبدو تماما مثل كرة كبيرة من الطين ، 4000 خطوط باش تزايد...و لا يمكن التخلص منه لأنه إن المشروع كله يعتمد على ذلك.وبطبيعة الحال ، أنها بدأت في 40 خطوط جميلة باش.

العثور على بعض "أفضل الممارسات" ، انظروا كيف توزيعة لينكس (مثلاديبيان) كتابة الحرف الأول-النصوص (عادة ما تكون موجودة في /etc/init.د)

معظمهم من دون "باش المذهبين" وقد فصل جيد من إعدادات التكوين ، مكتبة الملفات بتنسيق المصدر.

رأيي الشخصي هو نمط الكتابة الرئيسية-shellscript الذي يحدد بعض المتغيرات الافتراضية ثم يحاول تحميل ("المصدر") تكوين الملفات التي قد تحتوي على قيم جديدة.

أنا في محاولة لتجنب وظائف لأنها تميل إلى جعل النص أكثر تعقيدا.(Perl تم إنشاؤه لهذا الغرض.)

للتأكد من السيناريو هو محمول اختبار ليس فقط مع #!/bin/sh, ولكن أيضا استخدام #!/بن/الرماد،#! /بن/dash, الخ.سوف بقعة باش رمز معين في وقت قريب بما فيه الكفاية.

أو كبار السن اقتباس غرار ما جواو قال:

"استخدام بيرل.هل تريد أن تعرف باش ولكن عدم استخدامها."

للأسف نسيت من قال ذلك.

ونعم هذه الأيام أنصح الثعبان على بيرل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top