Pergunta

Alguém sabe de todos os recursos que falar sobre as melhores práticas e padrões de projeto para scripts de shell (sh, bash, etc.)?

Foi útil?

Solução

Eu escrevi bastante complexo, shell scripts, e a minha primeira sugestão é "não".A razão é que é bastante fácil de fazer um pequeno erro que impeça seu script, ou até mesmo tornar-se perigoso.

O que disse, eu não tem outros recursos para passar a você, mas a minha experiência pessoal.Aqui está o que eu faço normalmente, o que é um exagero, mas tende a ser sólida, embora muito detalhado.

Invocação

fazer o script aceita curtas e longas de opções.tome cuidado, pois existem dois comandos para analisar opções, getopt e getopts.Use getopt como você enfrenta menos problemas.

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

Outro ponto importante é que um programa sempre deve retornar zero, se for concluída com êxito, não-zero se algo saiu errado.

Chamadas de função

Você pode chamar funções em bash, apenas lembre-se de defini-los antes de a chamada.Funções são, como scripts, eles só podem retornar valores numéricos.Isso significa que você tem que inventar uma estratégia diferente para retornar valores de seqüência de caracteres.A minha estratégia é usar uma variável chamada RESULTADO para armazenar o resultado, e retornando 0 se a função foi concluída corretamente.Além disso, você pode gerar exceções, se você estiver retornando um valor diferente de zero e, em seguida, definir dois "exceção de variáveis" (o meu:EXCEÇÃO e EXCEPTION_MSG), o primeiro contendo o tipo da exceção e o segundo um formato legível por humanos mensagem.

Quando você chamar uma função, os parâmetros da função são atribuídos aos vars $0, $1, etc.Eu sugiro que você colocá-los em nomes mais significativos.declare as variáveis dentro da função como local:

function foo {
   local bar="$0"
}

Propenso a erro situações

No bash, a menos que você declare o contrário, uma unset variável é usada como uma cadeia de caracteres vazia.Isso é muito perigoso em caso de erro de digitação, como o mal variável digitada não será divulgado, e será avaliada como vazio.utilização

set -o nounset

para evitar que isso aconteça.Porém, tome cuidado, porque se você fizer isso, o programa irá anular a cada vez que você avaliar uma variável indefinida.Por esta razão, a única maneira de verificar se uma variável não está definida é a seguinte:

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

Você pode declarar variáveis como somente leitura:

readonly readonly_var="foo"

Modularização

Você pode conseguir "python como" modularização se você usar o código a seguir:

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
} 

em seguida, você pode importar arquivos com a extensão .shinc com a seguinte sintaxe

importar "AModule/ModuleFile"

O que será pesquisado em SHELL_LIBRARY_PATH.Como sempre importar o espaço de nomes global, lembre-se de prefixo de todas as suas funções e variáveis com prefixo correcto, caso contrário, você corre o risco de nome de confrontos.Eu uso duplo sublinhado como o python ponto.

Também, colocar isso como a primeira coisa na sua módulo

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

Programação orientada a objeto

No bash, você não pode fazer programação orientada a objeto, a menos que você construir uma bastante complexo sistema de alocação de objetos (eu pensei sobre isso.é viável, mas insano).Na prática, no entanto, você pode fazer "Singleton programação orientada":você tem uma instância de cada objeto, e somente um.

O que eu faço é:eu defina um objeto em um módulo (consulte a modularização de entrada).Então eu definir vazio vars (análogos às variáveis de membro) uma função init (construtor) e funções de membro, como neste exemplo de código

# 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
}

Interceptação e tratamento de sinais

Eu encontrei este útil para capturar e manipular exceções.

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 "$@"

Dicas e sugestões

Se algo não funcionar por algum motivo, tente reorganizar o código.A ordem é importante e nem sempre é intuitivo.

não mesmo considerar a possibilidade de trabalhar com o bash.ele não oferece suporte a funções, e isso é horrível em geral.

Espero que ele ajuda, mas, por favor, note.Se você tiver que usar o tipo de coisas que eu escrevi aqui, isso significa que o problema é muito complexo para ser resolvido com a shell.usar outro idioma.Eu tive que usá-lo devido a fatores humanos e legado.

Outras dicas

Dê uma olhada no Avançado Bash-Scripting Guide para muita sabedoria em shell scripts - não apenas Bash, qualquer um.

Não ouvir as pessoas dizendo que você olhe para o outro, sem dúvida mais complexo idiomas.Se o shell script atenda às suas necessidades, use-o.Você quer funcionalidade, não fanciness.Novas linguagens e valiosa de novas habilidades para o seu currículo, mas que não ajuda se você tiver um trabalho que precisa ser feito e você já sabe o shell.

Como dito, não há um monte de "melhores práticas" ou "padrões de projeto" para a criação de shell scripts.Diferentes usos têm orientações diferentes e preconceito - como qualquer outra linguagem de programação.

shell script é uma linguagem projetada para manipular arquivos e processos.Enquanto ele é ótimo para isso, não é uma linguagem de propósito geral, então, tente sempre cola lógica de utilitários existentes, ao invés de incluir a recriação de uma nova lógica em shell script.

Que princípio geral eu coletei alguns comum de script do shell de erros.

Houve uma grande sessão na OSCON este ano (2008), apenas sobre este tema: http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf

Fácil:usar python em vez de scripts shell.Você chegar perto de 100 vezes mais que em readablility, sem ter para complicar qualquer coisa que você não precisa, e a preservação da capacidade para evoluir partes do seu script em funções, objetos, objetos persistentes (zodb), objetos distribuídos (pyro) quase sem qualquer código extra.

usar definir -e para que você não lavrar em frente depois de erros.Tente torná-lo compatível com sh, sem depender de bash se você deseja executá-lo em não-linux.

Sabe quando usá-lo. Para uma rápida e suja a colagem de comandos, juntos, está tudo bem.Se você precisar fazer mais do que poucos não-trivial de decisões, loops, qualquer coisa, vá para a linguagem Python, Perl, e modularizar.

O maior problema com a shell é, muitas vezes, que o resultado final só se parece com uma grande bola de lama, 4000 linhas de bash e crescendo...e você não pode se livrar dela, porque agora todo o seu projeto depende dele.Claro, tudo começou em 40 linhas de belo bash.

Para encontrar alguns "melhores práticas", olha como distro Linux (por exemplo,Debian) escrever seus init scripts (geralmente encontrado em /etc/init.d)

A maioria deles são sem "bash-ismos" e ter uma boa separação de definições de configuração, biblioteca, arquivos e formatação de origem.

Meu estilo pessoal é escrever um mestre-de-script de shell que define algumas variáveis predefinidas e, em seguida, tenta carregar ("fonte") um arquivo de configuração, o qual pode conter novos valores.

Eu tento evitar funções, já que tendem a fazer com que o script mais complicado.(Perl foi criado para esse propósito.)

Para certificar-se de que o script é portátil, o teste não só com #!/bin/sh, mas também usar #!/bin/ash, #!/bin/dash, etc.Você avistará o Bash código específico em breve.

Ou a mais antiga citação semelhante ao que João disse:

"Utilizar a linguagem perl.Você vai querer saber o bash, mas não usá-lo."

Infelizmente eu me esqueci de quem disse o que.

E sim, esses dias eu recomendaria python sobre o perl.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top