Pergunta

Quais seriam as suas sugestões para um / modelo de script ksh boa bash para usar como padrão para todos os scripts recém-criados?

I geralmente iniciar (após a linha #!) com um cabeçalho comentada, com um nome de ficheiro, sinopse, uso, valores de retorno, autor (es), Alterações e se encaixam em linhas 80-carvão animal.

Todas as linhas de documentação que começam com símbolos duplo de hash ## para que eu possa grep para eles de forma fácil e nomes var locais são precedidas por "__".

Todas as outras melhores práticas? Dicas? Convenções de nomenclatura? E sobre códigos de retorno?

Comentários sobre controle de versão: usamos SVN tudo bem, mas outro departamento na empresa tem um repo separado e este é o seu script. Como eu sei que para contato com Q de se não houver informações @author? Usando entradas semelhantes para javadocs tem algum mérito, mesmo no contexto shell, IMHO, mas eu poderia estar errado.

Foi útil?

Solução

Eu estender a resposta de Norman a 6 linhas, ea última delas é em branco:

#!/bin/ksh
#
# @(#)$Id$
#
# Purpose
 

A terceira linha é uma string de identificação do controle de versão - na verdade é um híbrido com um marcador '@(#)' SCCS que podem ser identificados pelo (SCCS) what programa e uma cadeia de versão RCS que é expandido quando o arquivo é colocado sob RCS, o padrão VCS eu uso para o meu uso privado. Os RCS picaretas programa ident até a forma expandida de $Id$, que pode parecer $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $. A quinta linha me lembra que o script deve ter uma descrição de sua finalidade no topo; I substituir a palavra com uma descrição real do script (que é porque não há nenhuma cólon depois de, por exemplo).

Depois disso, não é essencialmente padrão nada para um shell script. Há fragmentos padrão que aparecem, mas nenhum fragmento padrão que aparece em cada script. (My discussão pressupõe que os scripts são escritos em Bourne, Korn, ou POSIX (Bash) notações de fachada. Há uma discussão toda separado sobre por que alguém colocando um derivado C Shell após o sigilo #! está vivendo em pecado.)

Por exemplo, este código aparece em algum forma ou formulário sempre que um script cria arquivos intermediários (temporárias):

tmp=${TMPDIR:-/tmp}/prog.$$
trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15

...real work that creates temp files $tmp.1, $tmp.2, ...

rm -f $tmp.?
trap 0
exit 0

A primeira linha escolhe um diretório temporário, ao incumprimento, / tmp se o usuário não especificou uma alternativa ($ TMPDIR é amplamente reconhecida e é padronizado por POSIX). Em seguida, cria um prefixo do nome do arquivo, incluindo a identificação do processo. Esta não é uma medida de segurança; é uma medida de simultaneidade simples, evitando que várias instâncias do roteiro de pisotear dados uns dos outros. (Por segurança, os nomes dos arquivos uso não-previsíveis em um diretório não-pública). A segunda Garante linha que o 'rm' e comandos 'exit' são executados se o shell recebe qualquer um dos SIGHUP sinais (1), SIGINT (2 ), SIGQUIT (3), SIGPIPE (13) ou SIGTERM (15). O comando 'rm' remove todos os arquivos intermediários que correspondam ao modelo; o comando exit garante que o status for diferente de zero, indicando algum tipo de erro. O 'trap' de 0 significa que o código também é executado se as saídas do escudo por qualquer motivo - ele cobre descuido na seção marcada 'trabalho real'. O código no final, em seguida, remove quaisquer arquivos temporários sobreviventes, antes levantando a armadilha na saída e, finalmente, sai com um estado zero (sucesso). Claramente, se você quiser sair com outro status, você pode -. Apenas certifique-se de configurá-lo em uma variável antes de executar as linhas rm e trap, e depois usar exit $exitval

Eu costumo usar o seguinte para remover o caminho eo sufixo do script, para que eu possa usar $arg0 ao relatar erros:

arg0=$(basename $0 .sh)

Costumo usar uma função shell para relatar erros:

error()
{
    echo "$arg0: $*" 1>&2
    exit 1
}

Se há apenas uma ou talvez duas saídas de erro, eu não me incomodo com a função; se não houver mais nada, eu faço porque simplifica a codificação. Eu também criar funções mais ou menos elaborados chamado usage para dar o resumo de como usar o comando -. Novamente, só se houver mais do que um lugar onde ele seria usado

Outro fragmento relativamente padrão é uma opção analisar ciclo, utilizando o getopts shell built-in:

vflag=0
out=
file=
Dflag=
while getopts hvVf:o:D: flag
do
    case "$flag" in
    (h) help; exit 0;;
    (V) echo "$arg0: version $Revision$ ($Date$)"; exit 0;;
    (v) vflag=1;;
    (f) file="$OPTARG";;
    (o) out="$OPTARG";;
    (D) Dflag="$Dflag $OPTARG";;
    (*) usage;;
    esac
done
shift $(expr $OPTIND - 1)

ou

shift $(($OPTIND - 1))

As aspas em torno de espaços punho "$ OPTARG" em argumentos. O Dflag é cumulativa, mas a notação usada aqui perde a noção de espaços nos argumentos. Existem (não-padrão) maneiras de contornar esse problema também.

A primeira notação turno funciona com qualquer shell (ou faria se eu usasse back-carrapatos em vez de '$(...)'. O SEobras cond em conchas modernas; não poderia mesmo ser uma alternativa com colchetes em vez de parênteses, mas isso funciona para que eu não tenha incomodado para trabalhar fora o que é.

Um truque final para agora é que eu muitas vezes têm tanto o GNU e uma versão não-GNU de programas ao redor, e eu quero ser capaz de escolher o que eu uso. Muitos dos meus scripts, portanto, variáveis ??de uso, tais como:

: ${PERL:=perl}
: ${SED:=sed}

E então, quando eu necessidade de invocar Perl ou sed, os usos de script $PERL ou $SED. Isso me ajuda quando algo se comporta de forma diferente - eu posso escolher a versão operacional - ou ao desenvolver o roteiro (I pode adicionar opções só de depuração adicionais para o comando sem modificar o script). (Veja Shell expansão de parâmetros para obter informações sobre o ${VAR:=value} e anotações relacionadas.)

Outras dicas

Eu uso o primeiro conjunto de ## linhas para a documentação de uso. Não me lembro agora onde eu vi pela primeira vez este.

#!/bin/sh
## Usage: myscript [options] ARG1
##
## Options:
##   -h, --help    Display this message.
##   -n            Dry-run; only show what would be done.
##

usage() {
  [ "$*" ] && echo "$0: $*"
  sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0"
  exit 2
} 2>/dev/null

main() {
  while [ $# -gt 0 ]; do
    case $1 in
    (-n) DRY_RUN=1;;
    (-h|--help) usage 2>&1;;
    (--) shift; break;;
    (-*) usage "$1: unknown option";;
    (*) break;;
    esac
  done
  : do stuff.
}

Qualquer código que vai ser lançado em estado selvagem deve ter o seguinte cabeçalho curto:

# Script to turn lead into gold
# Copyright (C) 2009 Joe Q Hacker - All Rights Reserved
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

Manter um log de alterações vai nos cabeçalhos de código é um retrocesso a partir de quando os sistemas de controle de versão foram terrivelmente inconveniente. A última modificação de data mostra alguém quantos anos o script é.

Se você estiver indo para estar contando com bashisms, use #! / Bin / bash, não / bin / sh, como sh é a invocação POSIX de qualquer shell. Mesmo se / bin / sh aponta para bash, muitos recursos serão desligados se você executá-lo via / bin / sh. A maioria das distribuições Linux não terá scripts que dependem de bashisms, tente ser portátil.

Para mim, os comentários em shell scripts são uma espécie de bobo a menos que ler algo como:

# I am not crazy, this really is the only way to do this

Shell scripting é tão simples que (a menos que a sua escrita uma demonstração para ensinar alguém a fazê-lo) o código quase sempre elimina-se.

Alguns escudos não gosta de ser alimentado digitado variáveis ??'locais'. Eu acredito até hoje Busybox (a shell comum de resgate) é um deles. Faça GLOBALS_OBVIOUS em vez disso, é muito mais fácil de ler, especialmente quando a depuração via / bin / sh -x ./script.sh.

A minha preferência pessoal é deixar falar lógica para si e para minimizar o trabalho para o analisador. Por exemplo, muitas pessoas podem escrever:

if [ $i = 1 ]; then
    ... some code 
fi

Onde eu apenas:

[ $i = 1 ] && {
    ... some code
}

Da mesma forma, alguém pode escrever:

if [ $i -ne 1 ]; then
   ... some code
fi

... onde eu tinha:

[ $i = 1 ] || {
   ... some code 
}

A única vez que eu uso convencional if / then / else é se há uma outra coisa-se para jogar na mistura.

Um exemplo horrivelmente insana de muito bom código shell portátil pode ser estudado por apenas visualizar o script 'configure' na maioria dos pacotes de software livre que o uso autoconf. Eu digo loucos porque suas 6300 linhas de código que atende a todos os sistemas conhecidos pelo homem que tem um UNIX como o escudo. Você não quer esse tipo de inchaço, mas é interessante para estudar alguns dos vários hacks portabilidade dentro .. como sendo bom para aqueles que possam apontar o / bin / sh para zsh:)

O único outro conselho que posso dar é assistir a sua expansão no aqui-docs, ou seja

cat << EOF > foo.sh
   printf "%s was here" "$name"
EOF

... vai ampliar $ nome, quando você provavelmente vai querer deixar a variável no lugar. Resolver esta via:

  printf "%s was here" "\$name"

que vai deixar $ nome de uma variável, em vez de expandi-lo.

Eu também recomendo aprender a usar armadilha para sinais de captura .. e fazer uso desses manipuladores como código clichê. Dizer a um script em execução a desacelerar com um simples SIGUSR1 é bastante útil:)

A maioria dos novos programas que eu escrevo (que são orientados linha de ferramenta / comando) começam como shell scripts, é uma ótima maneira de protótipo ferramentas UNIX.

Você pode também como o compilador script shell SHC, confira aqui .

Este é o uso I cabeçalho para o meu script shell (bash ou ksh). É um olhar man iguais e ele é usado para uso de display () também.

#!/bin/ksh
#================================================================
# HEADER
#================================================================
#% SYNOPSIS
#+    ${SCRIPT_NAME} [-hv] [-o[file]] args ...
#%
#% DESCRIPTION
#%    This is a script template
#%    to start any good shell script.
#%
#% OPTIONS
#%    -o [file], --output=[file]    Set log file (default=/dev/null)
#%                                  use DEFAULT keyword to autoname file
#%                                  The default value is /dev/null.
#%    -t, --timelog                 Add timestamp to log ("+%y/%m/%d@%H:%M:%S")
#%    -x, --ignorelock              Ignore if lock file exists
#%    -h, --help                    Print this help
#%    -v, --version                 Print script information
#%
#% EXAMPLES
#%    ${SCRIPT_NAME} -o DEFAULT arg1 arg2
#%
#================================================================
#- IMPLEMENTATION
#-    version         ${SCRIPT_NAME} (www.uxora.com) 0.0.4
#-    author          Michel VONGVILAY
#-    copyright       Copyright (c) http://www.uxora.com
#-    license         GNU General Public License
#-    script_id       12345
#-
#================================================================
#  HISTORY
#     2015/03/01 : mvongvilay : Script creation
#     2015/04/01 : mvongvilay : Add long options and improvements
# 
#================================================================
#  DEBUG OPTION
#    set -n  # Uncomment to check your syntax, without execution.
#    set -x  # Uncomment to debug this shell script
#
#================================================================
# END_OF_HEADER
#================================================================

E aqui é as funções de uso para ir com:

  #== needed variables ==#
SCRIPT_HEADSIZE=$(head -200 ${0} |grep -n "^# END_OF_HEADER" | cut -f1 -d:)
SCRIPT_NAME="$(basename ${0})"

  #== usage functions ==#
usage() { printf "Usage: "; head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#+" | sed -e "s/^#+[ ]*//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
usagefull() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
scriptinfo() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#-" | sed -e "s/^#-//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g"; }

Aqui está o que você deve obter:

# Display help
$ ./template.sh --help

    SYNOPSIS
    template.sh [-hv] [-o[file]] args ...

    DESCRIPTION
    This is a script template
    to start any good shell script.

    OPTIONS
    -o [file], --output=[file]    Set log file (default=/dev/null)
    use DEFAULT keyword to autoname file
    The default value is /dev/null.
    -t, --timelog                 Add timestamp to log ("+%y/%m/%d@%H:%M:%S")
    -x, --ignorelock              Ignore if lock file exists
    -h, --help                    Print this help
    -v, --version                 Print script information

    EXAMPLES
    template.sh -o DEFAULT arg1 arg2

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

# Display version info
$ ./template.sh -v

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

Você pode obter o modelo de script completo aqui: http: / /www.uxora.com/unix/shell-script/18-shell-script-template

A ativação de detecção de erro torna muito mais fácil para detectar problemas no script início:

set -o errexit

roteiro Sair no primeiro erro. Dessa forma você evita de continuar a fazer algo que dependia de algo anteriormente no script, talvez acabar com algum estado do sistema estranho.

set -o nounset

referências deleite para variáveis ??não definidas como erros. Muito importante para evitar correr coisas como rm -you_know_what "$var/" com um $var desactivado. Se você sabe que a variável pode ser definida, e esta é uma situação segura, você pode usar ${var-value} usar um valor diferente se é unset ou ${var:-value} usar um valor diferente se é unset ou esvaziar.

set -o noclobber

É fácil cometer o erro de inserção de um > onde pretendia inserir <, e substituir algum arquivo que você quis ler. Se você precisa espancar um arquivo em seu script, você pode desativar isso antes da linha relevante e ativá-lo novamente mais tarde.

set -o pipefail

Use o primeiro código de saída diferente de zero (se houver) de um conjunto de comando direcionado como o código de saída do conjunto completo de comandos. Isto torna mais fácil de depurar canalizada comandos.

shopt -s nullglob

Evite que seu glob /foo/* é interpretado literalmente , se não há arquivos correspondentes aquela expressão.

Você pode combinar tudo isso em duas linhas:

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob

O meu modelo bash é como abaixo (conjunto na minha vim configuração ):

#!/bin/bash

## DESCRIPTION: 

## AUTHOR: $USER_FULLNAME

declare -r SCRIPT_NAME=$(basename "$BASH_SOURCE" .sh)

## exit the shell(default status code: 1) after printing the message to stderr
bail() {
    echo -ne "$1" >&2
    exit ${2-1}
} 

## help message
declare -r HELP_MSG="Usage: $SCRIPT_NAME [OPTION]... [ARG]...
  -h    display this help and exit
"

## print the usage and exit the shell(default status code: 2)
usage() {
    declare status=2
    if [[ "$1" =~ ^[0-9]+$ ]]; then
        status=$1
        shift
    fi
    bail "${1}$HELP_MSG" $status
}

while getopts ":h" opt; do
    case $opt in
        h)
            usage 0
            ;;
        \?)
            usage "Invalid option: -$OPTARG \n"
            ;;
    esac
done

shift $(($OPTIND - 1))
[[ "$#" -lt 1 ]] && usage "Too few arguments\n"

#==========MAIN CODE BELOW==========

Eu sugeriria

#!/bin/ksh

E é isso. blocos de comentários pesados ??para scripts shell? Recebo os arrepios.

Sugestões:

  1. A documentação deve ser dado ou código, não comentários. Pelo menos uma função usage(). Dê uma olhada em como ksh e as outras ferramentas de AST documentar-se com opções --man em cada comando. (Não é possível vincular porque o site está em baixo.)

  2. Declare as variáveis ??locais com typeset. Isso é o que é para. Não há necessidade de sublinhados desagradáveis.

O que você pode fazer é fazer um script que cria um cabeçalho para um script & e tê-lo auto aberta em seu editor favorito. Eu vi um cara fazer isso neste site:

http://code.activestate.com/recipes/577862-bash-script-to-create-a-header-for-bash-scripts/?in=lang-bash

#!/bin/bash -       
#title           :mkscript.sh
#description     :This script will make a header for a bash script.
#author          :your_name_here
#date            :20110831
#version         :0.3    
#usage           :bash mkscript.sh
#notes           :Vim and Emacs are needed to use this script.
#bash_version    :4.1.5(1)-release
#===============================================================================

Geralmente, eu tenho algumas convenções que eu gosto de manter para cada gravação script que eu. Eu escrevo todos os scripts com suposição de que outras pessoas possam lê-los.

Eu começo a cada script com meu cabeçalho,

#!/bin/bash
# [ID LINE]
##
## FILE: [Filename]
##
## DESCRIPTION: [Description]
##
## AUTHOR: [Author]
##
## DATE: [XX_XX_XXXX.XX_XX_XX]
## 
## VERSION: [Version]
##
## USAGE: [Usage]
##

Eu uso esse formato de data, para facilitar grep / procura. Eu uso os '[' chaves para indicar texto pessoas têm de introduzir-se. se ocorrerem fora de um comentário, eu tento iniciá-los com '# ['. Dessa forma, se alguém cola-los como é, não vai ser confundido com entrada ou um comando de teste. Verifique a seção de uso em uma página de homem, para ver este estilo como um exemplo.

Quando eu quero comentar uma linha de código, eu uso um único '#'. Quando eu estou fazendo um comentário como uma nota, eu uso um duplo '##'. O /etc/nanorc utiliza esta convenção também. Acho que é útil, para diferenciar um comentário que foi escolhido não para executar; versos um comentário que foi criado como uma nota.

Todos os meus variáveis ??do shell, eu prefiro fazer no CAPS. Eu tento manter entre 4 - 8 caracteres, a menos que necessário contrário. Os nomes referem-se, da melhor forma possível, com a sua utilização.

Eu também sempre sair com 0 se for bem sucedido, ou um 1 para erros. Se o script tem muitos tipos diferentes de erros (e que realmente ajuda alguém, ou poderia ser usado em algum código de alguma forma), eu iria escolher uma seqüência documentado mais de 1. Em geral, os códigos de saída não são tão rigorosamente aplicadas no mundo * nix. Infelizmente eu nunca ter encontrado um bom esquema de número geral.

Eu gosto de argumentos de processo no modo padrão. Eu sempre prefiro getopts, a getopt. Eu nunca fazer algum corte com comandos 'ler' e se declarações. Eu também gosto de usar a instrução caso, para evitar ifs aninhados. Eu uso um script tradução para opções longas, então meio --help -h para getopts. Eu escrevo todos os scripts em qualquer bash (se aceitável) ou sh genérico.

Eu nunca uso o bash interpretado símbolos (ou qualquer símbolo interpretada) em nomes de arquivos, ou qualquer nome para esse assunto. especificamente ... " '` $ & * # () {} [] -., eu uso _ para espaços

Lembre-se, estas são apenas convenções. Melhores práticas, de grosseiro, mas às vezes você é forçado fora das linhas. O mais importante é ser consistente entre e dentro de seus projetos.

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