hello - script bash linux ejemplos
plantillas de script de shell (9)
Cualquier código que se lanzará en la naturaleza debería tener el siguiente encabezado breve:
# 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 incómodos. Una última fecha de modificación muestra a alguien la antigüedad del script.
Si va a confiar en bashisms, use #! / Bin / bash, not / 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 bashisms, intenten ser portátiles.
Para mí, los comentarios en los guiones de shell son algo tontos a menos que lean algo así 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 escriba una demostración para enseñarle a alguien cómo hacerlo), el código casi siempre se obvia.
A algunos proyectiles no les gusta ser alimentados con variables tipeadas ''locales''. Creo que hasta hoy Busybox (una carcasa de rescate común) es uno de ellos. En su lugar, GLOBALS_OBVIOUS 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 solo me gustaría
[ $i = 1 ] && {
... some code
}
Del mismo modo, alguien podría escribir:
if [ $i -ne 1 ]; then
... some code
fi
... donde podría:
[ $i = 1 ] || {
... some code
}
La única vez que uso convencional if / then / else es si hay un else-if para lanzar en la mezcla.
Se puede estudiar un ejemplo terriblemente loco de código de shell portátil muy bueno simplemente viendo el script ''configurar'' en la mayoría de los paquetes de software libre que usan autoconf. Digo loco porque tiene 6300 líneas de código que atienden a todos los sistemas conocidos por el hombre que tienen un shell como UNIX. No quieres ese tipo de abotagamiento, pero es interesante estudiar algunos de los diversos ataques de portabilidad dentro de ... como ser amable con aquellos que podrían apuntar / bin / sh a zsh :)
El único otro consejo que puedo dar es ver su expansión en here-docs, es decir,
cat << EOF > foo.sh
printf "%s was here" "$name"
EOF
... ampliará $ nombre, cuando probablemente quiera dejar la variable en su lugar. Resuelve 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 trampa para atrapar señales ... y hacer uso de esos controladores como código repetitivo. Es muy útil decirle a un script en ejecución que reduzca la velocidad con un simple SIGUSR1 :)
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 de UNIX.
También es posible que le guste el compilador de script SHC shell, compruébelo aquí .
¿Cuáles serían sus sugerencias para una buena plantilla de script bash / ksh para usar como estándar para todos los scripts creados recientemente?
Normalmente comienzo (después de #! Line) con un encabezado comentado con un nombre de archivo, sinopsis, uso, valores devueltos, autor (es), changelog y encajaría en líneas de 80 caracteres.
todas las líneas de documentación empiezo con símbolos de doble hash: "##", así que puedo grep para ellos fácilmente y los nombres locales var se anteponen con "__".
otras mejores prácticas? ¿consejos? ¿convenciones de nombres? ¿Qué pasa con los códigos de retorno?
Gracias
edit: comentarios sobre el control de versiones: obtuvimos svn bien, pero otro departamento de la empresa tiene un repositorio separado y este es su guión: ¿cómo sé a quién contactar con Q si no hay información @author? Las entradas javadocs-similares tienen algún mérito incluso en el contexto del shell, en mi humilde opinión, pero podría estar equivocado. gracias por las respuestas
Yo sugeriría
#!/bin/ksh
y eso es. ¿Comentarios de bloques pesados para scripts de shell? Me dan los pelos de punta.
Sugerencias:
La documentación debe ser datos o código, no comentarios. Al menos una función de
usage()
. Eche un vistazo a cómo ksh y las otras herramientas AST se documentan con opciones de menú en cada comando. (No se puede vincular porque el sitio web está caído).Declara variables locales con
typeset
. Para eso es para eso. No hay necesidad de guiones bajos desagradables.
Generalmente, tengo algunas convenciones a las que me gusta adherirme para cada script que escribo. Escribo todos los guiones con la suposición de 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]
##
Utilizo ese formato de fecha para facilitar grep / searching. Utilizo los '''' ''refuerzos para indicar el texto que las personas necesitan para ingresar. si ocurren fuera de un comentario, intento comenzar con ''# [''. De esta forma, si alguien los pega como está, no se confundirá con la entrada o un comando de prueba. Verifique la sección de uso en una página man, para ver este estilo como un ejemplo.
Cuando quiero comentar una línea de código, uso un solo ''#''. Cuando estoy haciendo un comentario como una nota, uso un doble ''##''. El /etc/nanorc
usa esa convención también. Encuentro útil, para diferenciar un comentario que se eligió para no ejecutar; Versos de un comentario que fue creado como una nota.
Todas mis variables de shell, prefiero hacer en CAPS. Intento mantener entre 4 y 8 caracteres, a menos que sea necesario. Los nombres se relacionan, de la mejor manera posible, con su uso.
También siempre salgo con 0 si tiene éxito o con 1 para errores. Si el script tiene muchos tipos diferentes de errores (y realmente ayudaría a alguien, o podría usarse en algún código de alguna manera), elegiría una secuencia documentada sobre 1. En general, los códigos de salida no se aplican tan estrictamente en * nix world. Desafortunadamente, nunca he encontrado un buen esquema de números generales.
Me gusta procesar los argumentos de la manera estándar. Siempre prefiero getopts, getopt. Nunca hago algún truco con comandos ''leer'' y declaraciones if. También me gusta usar el enunciado de caso para evitar los if anidados. Utilizo un script de traducción para opciones largas, por lo que --help significa -h para getopts. Escribo todos los scripts en bash (si es aceptable) o genérico sh.
NUNCA uso símbolos interpretados por bash (o cualquier símbolo interpretado) en nombres de archivo, o cualquier nombre para ese asunto. específicamente ... "''` $ & * # () {} [] -, uso _ para espacios.
Recuerde, estas son solo convenciones. La mejor práctica, grosera, pero a veces te fuerzan fuera de las líneas. Lo más importante es ser coherente a través y dentro de sus proyectos.
Uso el primer conjunto de ## líneas para la documentación de uso. 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.
}
Extendería la respuesta de Norman a 6 líneas, y la última está en blanco:
#!/bin/ksh
#
# @(#)$Id$
#
# Purpose
La tercera línea es una cadena de identificación de control de versión: en realidad es un híbrido con un marcador SCCS '' @(#)
'' que puede identificarse con el programa (SCCS) y una cadena de versión RCS que se expande cuando se coloca el archivo RCS, el VCS predeterminado que uso para mi uso privado. El ident
programa RCS recoge la forma expandida de $Id$
, que podría parecerse a $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $
. La quinta línea me recuerda que el script debe tener una descripción de su propósito en la parte superior; Sustituyo la palabra con una descripción real de la secuencia de comandos (que es por lo que no hay dos puntos después de ella, por ejemplo).
Después de eso, esencialmente no hay nada estándar para un script de shell. Hay fragmentos estándar que aparecen, pero no un fragmento estándar que aparece en cada script. (Mi discusión asume que los guiones están escritos en notación de shell Bourne, Korn o POSIX (Bash). Hay una discusión por separado sobre por qué alguien pone un derivado de C Shell después de que el #!
Sigil está viviendo en pecado.)
Por ejemplo, este código aparece en alguna forma o forma cada vez que una secuencia de comandos crea archivos intermedios (temporales):
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 a / tmp si el usuario no especificó una alternativa ($ TMPDIR es ampliamente reconocido y estandarizado por POSIX). Luego crea un prefijo de nombre de archivo que incluye la identificación del proceso. Esta no es una medida de seguridad; es una medida de simultaneidad simple que evita que varias instancias de la secuencia de comandos pisoteen los datos de la otra parte. (Para mayor seguridad, use nombres de archivo no predecibles en un directorio que no sea público). La segunda línea garantiza que los comandos '' rm
'' y '' exit
'' se ejecuten si el shell recibe cualquiera de las señales SIGHUP (1), SIGINT (2 ), SIGQUIT (3), SIGPIPE (13) o SIGTERM (15). El comando '' rm
'' elimina cualquier archivo intermedio que coincida con la plantilla; el comando de exit
asegura que el estado sea distinto de cero, lo que indica algún tipo de error. La '' trap
'' de 0 significa que el código también se ejecuta si el intérprete de comandos sale por alguna razón: cubre el descuido en la sección ''trabajo real''. El código al final elimina los archivos temporales que quedan, antes de levantar la captura al salir, y finalmente sale con un estado de cero (correcto). Claramente, si desea salir con otro estado, puede - solo asegúrese de configurarlo en una variable antes de ejecutar las líneas rm
y trap
, y luego use exit $exitval
.
Usualmente utilizo lo siguiente para eliminar la ruta y el sufijo del script, así que puedo usar $arg0
al informar errores:
arg0=$(basename $0 .sh)
A menudo uso una función de shell para informar errores:
error()
{
echo "$arg0: $*" 1>&2
exit 1
}
Si solo hay una o quizás dos salidas de error, no me molesto con la función; si hay más, lo hago porque simplifica la codificación. También creo funciones más o menos elaboradas llamadas usage
para dar el resumen de cómo usar el comando, de nuevo, solo si hay más de un lugar donde se usaría.
Otro fragmento bastante estándar es un ciclo de análisis de opciones, que utiliza el shell getopts
incorporado:
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" manejan espacios en argumentos. El Dflag es acumulativo, pero la notación utilizada aquí pierde el seguimiento de los espacios en los argumentos. Existen formas (no estándar) de evitar ese problema también.
La primera notación de cambio funciona con cualquier shell (o lo haría si utilizara back-ticks en lugar de '' $(...)
''. El segundo funciona en shell moderno, incluso podría haber una alternativa con corchetes en lugar de paréntesis, pero esto funciona, así que no me he molestado en averiguar qué es eso.
Un truco final por el momento es que a menudo tengo tanto la versión GNU como una versión no GNU de los programas, y quiero poder elegir cuál uso. Muchos de mis scripts, por lo tanto, usan variables como:
: ${PERL:=perl}
: ${SED:=sed}
Y luego, cuando necesito invocar a Perl o sed
, el script usa $PERL
o $SED
. Esto me ayuda cuando algo se comporta de manera diferente (puedo elegir la versión operativa) o mientras desarrollo el guión (puedo agregar opciones extra de solo depuración al comando sin modificar el guión).
Lo que puede hacer es crear un script que cree un encabezado para un script y que se abra automáticamente en su editor favorito. Vi a un chico hacer eso en este sitio:
#!/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
#===============================================================================
La habilitación de la detección de errores facilita la detección temprana de problemas en el script:
set -o errexit
Salir de la secuencia de comandos en el primer error. De esta forma, evitarás continuar haciendo algo que dependía de algo anterior en el guión, quizás terminando con un estado de sistema extraño.
set -o nounset
Trate las referencias a variables no establecidas como errores. Es muy importante evitar ejecutar cosas como rm -you_know_what "$var/"
con un $var
. Si sabe que la variable puede ser desarmada, y esta es una situación segura, puede usar ${var-value}
para usar un valor diferente si está desarmado o ${var:-value}
para usar un valor diferente si está desarmado o vacío
set -o noclobber
Es fácil cometer el error de insertar un >
donde pretendía insertar <
, y sobrescribir algún archivo que quisiera leer. Si necesita interceptar un archivo en su secuencia de comandos, puede desactivar esto antes de la línea relevante y habilitarlo nuevamente después.
set -o pipefail
Use el primer código de salida distinto de cero (si lo hay) de un conjunto de comando canalizado como el código de salida del conjunto completo de comandos. Esto facilita la depuración de los comandos canalizados.
shopt -s nullglob
Evite que su /foo/*
glob se interprete literalmente si no hay archivos que coincidan con esa expresión.
Puede combinar todos estos en dos líneas:
set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob
Mi plantilla de bash es la siguiente (configurada 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==========
Este es el encabezado que uso para mi script shell (bash o ksh). Es un man
parecido y se usa para mostrar el uso () también.
#!/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