Pregunta

¿Cuáles serían sus sugerencias para un buen bash/ksh plantilla de script para usar como un estándar para todos los recién creado scripts?

Yo generalmente de inicio (después de la #! línea) con un comentadas, encabezado con un nombre de archivo, la sinopsis, el uso, los valores de retorno, autor(s), lista de cambios y se adapten a 80-char líneas.

Toda la documentación líneas que empiezan con un doble-hash símbolos ## así que puedo grep para ellos fácilmente y locales var nombres se antepone con "__".

Cualquier otra de las mejores prácticas?Consejos?Convenciones de nomenclatura?¿Qué acerca de los códigos de retorno?

Comentarios sobre el control de la versión :usamos SVN, pero en otro departamento de la empresa se ha separado de repos y este es su guión.¿Cómo puedo saber a quién contactar con Q si no hay @información de autor?El uso de las entradas similares a los javadocs tiene su mérito, incluso en el shell contexto, en mi humilde opinión, pero puedo estar equivocado.

¿Fue útil?

Solución

Me gustaría extender Norman respuesta a 6 líneas, y la última de ellas es en blanco:

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

La tercera línea es una versión de control de la cadena de identificación, en realidad es un híbrido con un SCC en el marcador@(#)'que puede ser identificado por el (CCSC) programa de what y un conmutador de consola remota cadena de la versión que se expande cuando el archivo se coloca por debajo de RCS, el valor predeterminado VCS yo uso para mi uso privado.El RCS programa ident recoge la forma expandida de $Id$, el cual podría ser $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $.La quinta línea me recuerda que la secuencia de comandos debe tener una descripción de su finalidad en la parte superior;Puedo sustituir la palabra con una descripción real de la secuencia de comandos (que es la razón por la que no hay dos puntos después de que, por ejemplo).

Después de eso, no hay prácticamente nada estándar para una secuencia de comandos de shell.Hay estándar de los fragmentos que aparecen, pero no hay una norma fragmento que aparece en cada secuencia de comandos.(Mi discusión se supone que los guiones son escritos en Bourne, Korn o POSIX (Bash) shell notaciones.Hay toda una discusión por separado sobre por qué alguien pone un Shell C derivado después de la #! el sigilo está viviendo en pecado.)

Por ejemplo, este código aparece en una forma u otra siempre una secuencia de comandos crea intermedio (temporal) de los archivos:

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

La primera línea elige un directorio temporal por defecto en /tmp si el usuario no especifica una alternativa ($TMPDIR es muy ampliamente reconocido y se ha estandarizado por POSIX).A continuación, crea un prefijo de nombre de archivo, incluyendo el IDENTIFICADOR de proceso.Esta no es una medida de seguridad;se trata de un simple concurrencia de medida, la prevención de varias instancias de la secuencia de comandos desde el pisoteo de la vegetación en cada uno de los datos de los demás.(Por seguridad, el uso de la no predictibilidad de los nombres de archivo en un no-directorio público.) La segunda línea se asegura de que el 'rm'y 'exit'comandos se ejecutan si el shell recibe cualquiera de las señales de SIGHUP (1), SIGINT (2), SIGQUIT (3), SIGPIPE (13) o SIGTERM (15).El 'rm'comando elimina cualquier archivos intermedios que coincide con la plantilla;el exit comando asegura que el estado es distinto de cero, lo que indica algún tipo de error.El 'trap"de 0 significa que el código se ejecute si la cáscara sale por cualquier razón - que cubre el descuido en la sección marcada como "verdadero trabajo".El código en el final, a continuación, quita cualquier sobreviviente archivos temporales, antes de el levantamiento de la trampa en la salida, y finalmente se cierra con un cero (éxito) de estado.Claramente, si quieres salir con otro estado, puede - sólo asegúrese de que usted lo fija en una variable antes de ejecutar el rm y trap líneas y, a continuación, utilizar exit $exitval.

Yo suelo usar lo siguiente para quitar la ruta de acceso y el sufijo de la secuencia de comandos, así que puedo usar $arg0 al informar de errores:

arg0=$(basename $0 .sh)

Yo uso a menudo una función de shell para el informe de errores:

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

Si sólo hay uno o tal vez dos de error sale, que no se molestan con la función;si hay más, que hacer, ya que simplifica la codificación.Yo también crear más o menos elaborada de las funciones de llamada usage para dar el resumen de cómo utilizar el comando - de nuevo, sólo si hay más de un lugar donde será utilizado.

Otro bastante estándar, el fragmento es una opción de análisis de bucle, el uso de la getopts shell integrado:

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)

o:

shift $(($OPTIND - 1))

Las comillas alrededor de "$OPTARG" manejo de espacios en los argumentos.El Dflag es acumulativa, pero la notación utilizada aquí pierde la pista de los espacios en los argumentos.Hay (no estándar) formas de solucionar ese problema, también.

El primer cambio de notación funciona con cualquier shell (o haría si he utilizado de nuevo-las garrapatas en lugar de '$(...)'.El segundo funciona en los modernos proyectiles;incluso podría haber una alternativa con corchetes en lugar de paréntesis, pero esto funciona así que no me he molestado en trabajar de lo que es.

Un último truco por ahora es que a veces tengo tanto el GNU y un no-GNU versión de programas de todo, y quiero ser capaz de elegir el que yo uso.Muchos de mis scripts, por lo tanto, el uso de variables tales como:

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

Y luego, cuando yo sea necesaria la invocación de Perl o sed, la secuencia de comandos utiliza $PERL o $SED.Esto me ayuda cuando algo se comporta de forma diferente - me puede elegir la versión operativa - o, mientras que el desarrollo de la secuencia de comandos (puedo añadir extra de depuración-sólo opciones para el comando sin modificar la secuencia de comandos).(Ver Shell parámetro de expansión para obtener información sobre la ${VAR:=value} y relacionados con las notaciones.)

Otros consejos

Utilizo el primer conjunto de ## líneas para la documentación de uso. Ahora no recuerdo dónde vi esto por primera vez.

#!/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.
}

Cualquier código que se lanzará en la naturaleza debe tener el siguiente encabezado corto:

# 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

Mantener un registro de cambios en los encabezados de código es un retroceso de cuando los sistemas de control de versiones eran terriblemente inconvenientes. Una última fecha de modificación muestra a alguien la antigüedad del script.

Si va a confiar en bashisms, use #! / bin / bash, no / bin / sh, ya que sh es la invocación POSIX de cualquier shell. Incluso si / bin / sh apunta a bash, muchas funciones se desactivarán si lo ejecuta a través de / bin / sh. La mayoría de las distribuciones de Linux no tomarán scripts que dependan de bashismos, intentan ser portátiles.

Para mí, los comentarios en los scripts de shell son algo tontos a menos que lean algo como:

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

Las secuencias de comandos de Shell son tan simples que (a menos que esté escribiendo una demostración para enseñarle a alguien cómo hacerlo) el código casi siempre se obvia.

A algunos shells no les gusta que se les alimente con variables 'locales' escritas. Creo que hasta el día de hoy Busybox (un caparazón de rescate común) es uno de ellos. Hacer GLOBALS_OBVIOUS en su lugar, es mucho más fácil de leer, especialmente cuando se depura a través de / bin / sh -x ./script.sh.

Mi preferencia personal es dejar que la lógica hable por sí misma y minimizar el trabajo para el analizador. Por ejemplo, muchas personas podrían escribir:

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

Donde simplemente:

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

Del mismo modo, alguien podría escribir:

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

... donde debería:

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

La única vez que uso convencional if / then / else es si hay un else-if para agregar la mezcla.

Un ejemplo horriblemente loco de muy buen código de shell portátil puede estudiarse simplemente viendo el script 'configure' en la mayoría de los paquetes de software gratuitos que usan autoconf. Digo loco porque sus 6300 líneas de código que abastecen a todos los sistemas conocidos por el hombre que tienen un shell como UNIX. No quieres ese tipo de hinchazón, pero es interesante estudiar algunos de los diversos hacks de portabilidad dentro ... como ser amable con aquellos que podrían señalar / bin / sh a zsh :)

El único otro consejo que puedo dar es ver su expansión en documentos aquí, es decir,

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

... expandirá $ name, cuando probablemente quiera dejar la variable en su lugar. Resuelva esto a través de:

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

que dejará $ name como variable, en lugar de expandirlo.

También recomiendo aprender a usar trap para captar señales ... y utilizar esos controladores como código repetitivo. Decirle a un script en ejecución que disminuya la velocidad con un simple SIGUSR1 es bastante útil :)

La mayoría de los programas nuevos que escribo (que están orientados a herramientas / línea de comandos) comienzan como scripts de shell, es una excelente manera de crear prototipos de herramientas UNIX.

También te puede interesar el compilador de scripts de shell SHC, échale un vistazo aquí .

Este es el encabezado que uso para mi shell de script (bash o ksh). Es un man parecido y también se usa para mostrar el uso ().

#!/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
#================================================================

Y aquí están las funciones de uso para ir:

  #== 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"; }

Esto es lo que debe obtener:

# 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

Puede obtener la plantilla de script completa aquí: http: / /www.uxora.com/unix/shell-script/18-shell-script-template

La habilitación de la detección de error hace que sea mucho más fácil de detectar problemas en la secuencia de comandos principios:

set -o errexit

La salida de secuencia de comandos en el primer error.Que manera de evitar continuar a hacer algo que dependía de algo anterior en la secuencia de comandos, tal vez terminar con algún extraño estado del sistema.

set -o nounset

El tratamiento de las referencias a unset variables como errores.Muy importante para evitar cosas como rm -you_know_what "$var/" con un unset $var.Si sabemos que la variable puede ser desactivado, y esta es una situación segura, puede utilizar ${var-value} para utilizar un valor diferente si es anular o ${var:-value} para utilizar un valor diferente si es unset o vacío.

set -o noclobber

Es fácil cometer el error de inserción de un > donde usted quiere insertar <, y sobrescribir los archivos que usted quiere leer.Si usted necesita para darle una paliza a un archivo en su secuencia de comandos, puede deshabilitar esta antes de la línea relevante y habilitar de nuevo después.

set -o pipefail

El uso de la primera salida distinto de cero código (si existe) de un conjunto de corriente de comando como el código de salida del conjunto completo de comandos.Esto hace que sea más fácil de depurar hilo de comandos.

shopt -s nullglob

Evitar que su /foo/* glob es interpretado literalmente si no hay archivos que coinciden con la expresión.

Usted puede combinar todos estos en dos líneas:

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

Mi plantilla bash es la siguiente (establecida en mi configuración de vim ):

#!/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==========

Sugeriría

#!/bin/ksh

y eso es todo. ¿Comentarios de bloque pesado para scripts de shell? Tengo los pelos de punta.

Sugerencias:

  1. La documentación debe ser datos o código, no comentarios. Al menos una función usage(). Eche un vistazo a cómo ksh y las otras herramientas AST se documentan con las opciones --man en cada comando. (No se puede vincular porque el sitio web no funciona).

  2. Declara variables locales con typeset. Para eso es. No hay necesidad de guiones bajos desagradables.

Lo que puede hacer es crear un script que cree un encabezado para un script & amp; y haz que se abra automáticamente en tu editor favorito. Vi a un chico hacer eso en este sitio:

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

Generalmente, tengo algunas convenciones que me gusta cumplir para cada script que escribo. Escribo todos los guiones asumiendo que otras personas podrían leerlos.

Comienzo cada script con mi encabezado,

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

Uso ese formato de fecha, para facilitar la búsqueda / búsqueda. Utilizo las llaves '[' para indicar el texto que las personas necesitan ingresar. si aparecen fuera de un comentario, trato de comenzar con '# ['. De esa manera, si alguien los pega tal cual, no se confundirá con la entrada o un comando de prueba. Consulte la sección de uso en una página de manual para ver este estilo como ejemplo.

Cuando quiero comentar una línea de código, uso un solo '#'. Cuando estoy haciendo un comentario como nota, uso un doble '##'. El /etc/nanorc usa esa convención también. Me resulta útil diferenciar un comentario que se eligió para no ejecutar; Versa un comentario que se creó como una nota.

Todas mis variables de shell, prefiero hacer en mayúsculas. Intento mantener entre 4 y 8 caracteres, a menos que sea necesario. Los nombres se relacionan, lo mejor posible, con su uso.

También siempre salgo con 0 si tiene éxito, o un 1 para errores. Si el script tiene muchos tipos diferentes de errores (y realmente ayudaría a alguien, o podría usarse de alguna manera en algún código), elegiría una secuencia documentada sobre 1. En general, los códigos de salida no se aplican estrictamente en el mundo * nix. Lamentablemente, nunca he encontrado un buen esquema general de números.

Me gusta procesar argumentos de la manera estándar. Siempre prefiero getopts, a getopt. Nunca hago algún truco con los comandos 'leer' y las declaraciones if. También me gusta usar la declaración de caso, para evitar ifs anidados. Utilizo una secuencia de comandos de traducción para opciones largas, por lo que --help significa -h para getopts. Escribo todos los scripts en bash (si es aceptable) o sh genérico.

NUNCA uso símbolos interpretados de bash (o cualquier símbolo interpretado) en los nombres de archivo, o cualquier nombre para el caso. específicamente ... " '' $ & amp; * # () {} [] -, uso _ para espacios.

Recuerde, estas son solo convenciones. La mejor práctica, de grosero, pero a veces te obligan a salir de las líneas. Lo más importante es ser coherente en sus proyectos y dentro de ellos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top