Prólogo
Qué Shell usar
Bash
único lenguaje de script de shell que se puede usar para archivos ejecutables.
Los scripts deberían comenzar con #!/bin/bash
con un conjunto mínimo de banderas. Use set
para configurar las opciones de shell de modo que llamar a su script como bash <script_name>
no viole su funcionalidad.
Limitar todos los scripts de shell a bash nos da un lenguaje de shell consistente que está instalado en todas nuestras máquinas.
La única excepción es si está limitado por las condiciones de lo que está programando. Un ejemplo serían los paquetes Solaris SVR4, que requieren el uso del shell Bourne habitual para cualquier script.
Cuando usar Shell
Shell debe usarse solo para pequeñas utilidades o simples envoltorios de guiones.
Aunque el script de shell no es un lenguaje de desarrollo, se usa para escribir varias utilidades en Google. Esta guía de estilo es más un reconocimiento de su uso, en lugar de una propuesta para usarla en un uso generalizado.
Algunas recomendaciones:
- Si a menudo llama a otras utilidades y hace relativamente poca manipulación de datos, Shell es una opción aceptable para la tarea.
- Si el rendimiento es importante, use otra cosa pero no shell.
- Si encuentra que necesita usar matrices para algo más que asignar
${PIPESTATUS}
, debe usar Python. - Si está escribiendo un script que tiene más de 100 líneas, probablemente debería escribirlo en Python. Tenga en cuenta que los scripts están creciendo. Vuelva a escribir su secuencia de comandos en otro idioma antes para evitar una larga reescritura posterior.
Archivos de shell y llamada de intérprete
Extensiones de archivo
Los archivos ejecutables no deben tener la extensión (muy recomendable) o la extensión .sh
. Las bibliotecas deben tener la extensión .sh
y no deben ser ejecutables.
No es necesario saber en qué idioma está escrito el programa durante su ejecución, y el shell no requiere una extensión, por lo que preferimos no usarlo para archivos ejecutables.
Sin embargo, es importante que las bibliotecas sepan en qué idioma está escrito, y a veces es necesario tener bibliotecas similares en diferentes idiomas. Esto le permite tener archivos de biblioteca con nombres idénticos con objetivos idénticos, pero escritos en diferentes idiomas deben tener un nombre idéntico, excepto por un sufijo específico del idioma.
SUID / SGID
SUID y SGID están prohibidos en los scripts de shell.
Hay demasiados problemas de seguridad, por lo que es casi imposible proporcionar suficiente protección SUID / SGID. Aunque bash complica el lanzamiento de SUID, todavía es posible en algunas plataformas, por lo que prohibimos explícitamente su uso.
Use sudo
para un acceso mejorado si lo necesita.
El medio ambiente
STDOUT vs STDERR
Todos los mensajes de error deben enviarse a STDERR
.
Esto ayuda a separar el estado normal de los problemas reales.
Se recomienda utilizar la función para mostrar mensajes de error junto con otra información de estado.
err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2 } if ! do_something; then err "Unable to do_something" exit "${E_DID_NOTHING}" fi
Comentarios
Encabezado de archivo
Comience cada archivo con una descripción de su contenido.
Cada archivo debe tener un título del comentario, incluida una breve descripción de su contenido. El aviso de copyright y la información del autor son opcionales.
Un ejemplo:
Comentarios de funciones
Cualquier función que no sea obvia y breve debe comentarse. Cualquier función en la biblioteca debe comentarse independientemente de su longitud o complejidad.
Debe asegurarse de que otra persona entienda cómo usar su programa o cómo usar la función en su biblioteca simplemente leyendo los comentarios (y la necesidad de superación personal) sin leer el código.
Todos los comentarios de características deben incluir:
- Descripción de la función
- Variables globales usadas y modificadas
- Argumentos recibidos
- Devuelve valores que son diferentes de los códigos de salida estándar en el último comando.
Un ejemplo:
Comentarios de implementación
Comente partes complejas, no obvias, interesantes o importantes de su código.
Se supone que esta es la práctica habitual de comentar código en Google. No hagas comentarios sobre todo. Si hay un algoritmo complicado o está haciendo algo inusual, agregue un breve comentario.
TODO Comentarios
Use TODO comentarios para el código que es temporal, a corto plazo o bastante bueno, pero no perfecto.
Esto es consistente con la convención en el manual de C ++ .
Los comentarios TODO deben incluir la palabra TODO en letras mayúsculas, seguido de su nombre entre paréntesis. Un colon es opcional. También es preferible indicar el número de error / ticket junto al elemento TODO.
Un ejemplo:
Aunque debe seguir el estilo que ya se usa en los archivos que está editando, se requiere lo siguiente para cualquier código nuevo.
Sangría
Sangría 2 espacios. Sin pestañas
Use líneas en blanco entre bloques para mejorar la legibilidad. La sangría es dos espacios. No importa lo que hagas, no uses pestañas. Para los archivos existentes, manténgase fiel a la sangría actual.
Longitud de cadena y longitud de valor
La longitud máxima de la línea es de 80 caracteres.
Si necesita escribir líneas de más de 80 caracteres, debe hacerlo utilizando el here document
o, si es posible, la newline
incorporada. Los valores literales que pueden tener más de 80 caracteres y no pueden separarse están razonablemente permitidos, pero se recomienda encarecidamente que encuentre una manera de acortarlos.
Tuberías
Las tuberías deben dividirse cada una en una línea si no encajan en una línea.
Si una tubería cabe en una línea, debe estar en una línea.
De lo contrario, debe dividirse de modo que cada sección esté en una nueva línea y sangrada por 2 espacios para la siguiente sección. Esto se refiere a la cadena de comando combinada usando '|' así como conexiones lógicas usando '||' y '&&'.
Ciclos
Lugar ; do
; do
y ; then
en la misma línea que while
, for
o if
.
Los ciclos en el shell son ligeramente diferentes, pero seguimos los mismos principios que con las llaves cuando declaramos funciones. Es decir ; then
; then
y ; do
; do
debe estar en la misma línea que if
/ for
/ while
. else
debería estar en una línea separada, y las declaraciones de cierre deberían estar en su propia línea, alineadas verticalmente con la declaración de apertura.
Un ejemplo:
for dir in ${dirs_to_cleanup}; do if [[ -d "${dir}/${ORACLE_SID}" ]]; then log_date "Cleaning up old files in ${dir}/${ORACLE_SID}" rm "${dir}/${ORACLE_SID}/"* if [[ "$?" -ne 0 ]]; then error_message fi else mkdir -p "${dir}/${ORACLE_SID}" if [[ "$?" -ne 0 ]]; then error_message fi fi done
Declaración del caso
- Opciones separadas en 2 espacios.
- Las opciones de línea única requieren un espacio después del corchete de cierre de la plantilla y antes
;;
. - Las opciones largas o de comandos múltiples deben dividirse en varias líneas con una plantilla, acciones y
;;
en lineas separadas.
Las expresiones correspondientes retroceden un nivel de case
y esac
. Las acciones multilínea también tienen sangrías en un nivel separado. No es necesario poner expresiones entre comillas. Los patrones de expresión no deben ir precedidos de paréntesis abiertos. Evite usar &;
y ;;&
notación.
case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac
Se pueden colocar comandos simples en una línea con el patrón y ;;
mientras que la expresión sigue siendo legible. Esto suele ser adecuado para manejar opciones de una letra. Cuando las acciones no caben en una línea, deje la plantilla en su línea, la siguiente acción, entonces ;;
También en línea propia. Cuando esta es la misma línea que con las acciones, use un espacio después del corchete de cierre de la plantilla y otro antes ;;
.
verbose='false' aflag='' bflag='' files='' while getopts 'abf:v' flag; do case "${flag}" in a) aflag='true' ;; b) bflag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) error "Unexpected option ${flag}" ;; esac done
Expansión variable
En orden de prioridad: observe lo que ya está en uso; encierre las variables entre comillas; prefiera "${var}"
más que "$var"
, pero teniendo en cuenta el contexto de uso.
Estas son más bien recomendaciones, ya que el tema es bastante contradictorio para la regulación obligatoria. Se enumeran en orden de prioridad.
- Use el mismo estilo que encuentra en el código existente.
- Ponga las variables entre comillas, consulte la sección de Comillas a continuación.
No coloque caracteres individuales específicos para parámetros de shell / posición entre comillas y llaves a menos que sea estrictamente necesario y para evitar una profunda confusión.
Prefiere llaves para todas las demás variables.
Cotizaciones
- Utilice siempre comillas para los valores que contienen variables, sustituciones de comandos, espacios o metacaracteres de shell, hasta que necesite exponer con seguridad los valores que no están entre comillas.
- Prefiera las comillas para los valores que son "palabras" (en oposición a los parámetros de comando o nombres de ruta)
- Nunca cites enteros.
- Sepa cómo funcionan las comillas para los patrones de coincidencia en
[[
. - Use
"$@"
si no tiene una razón particular para usar $*
.
Características y errores
Sustitución de comandos
Use $(command)
lugar de backticks.
Los backticks anidados requieren comillas internas escapadas con \
. El formato $ (command)
no cambia dependiendo de la anidación y es más fácil de leer.
Un ejemplo:
Cheques, [
y [[
[[ ... ]]
más preferible que [
, test
o /usr/bin/[
.
[[ ... ]]
reduce la posibilidad de error, porque no hay resolución de ruta o separación de palabras entre [[
y ]]
, y [[ ... ]]
permite usar una expresión regular, donde [ ... ]
no.
Verificar valores
Use comillas en lugar de caracteres adicionales cuando sea posible.
Bash es lo suficientemente inteligente como para trabajar con una cadena vacía en una prueba. Por lo tanto, el código resultante es mucho más fácil de leer; use verificaciones para valores vacíos / no vacíos o valores vacíos, sin usar caracteres adicionales.
Para evitar confusiones sobre lo que está comprobando, use explícitamente -z
o -n
.
Expresiones de sustitución para nombres de archivo
Use la ruta explícita al crear expresiones comodín para los nombres de archivo.
Dado que los nombres de archivo pueden comenzar con el carácter -
, es mucho más seguro ./*
expresión comodín como ./*
lugar de *
.
Eval
eval
debe ser evitado.
Eval le permite expandir las variables pasadas en la entrada, pero también puede establecer otras variables, sin la posibilidad de verificarlas.
Tuberías en while
Utilice la sustitución de comandos o el bucle for
, en lugar de las tuberías en while
. Las variables cambiadas en el while
no se propagan al padre, porque los comandos del bucle se ejecutan en un sub-shell.
Un sub-shell implícito en la tubería while
puede dificultar el seguimiento de errores.
last_line='NULL' your_command | while read line; do last_line="${line}" done
Use un bucle for si está seguro de que la entrada no contendrá espacios o caracteres especiales (por lo general, esto no implica la entrada del usuario).
total=0
El uso de la sustitución de comandos le permite redirigir la salida, pero ejecuta comandos en un sub-shell explícito, a diferencia del sub-shell implícito, que crea bash para el while
.
total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c)
Utilice los bucles while cuando no sea necesario pasar resultados complejos al shell principal; esto es típico cuando se requiere un "análisis" más complejo. Recuerde que los ejemplos simples a veces son mucho más fáciles de resolver utilizando una herramienta como awk. También puede ser útil cuando específicamente no desea modificar las variables del entorno principal.
Convención de nomenclatura
Nombres de funciones
Minúscula con guiones bajos para separar las palabras. Bibliotecas separadas con ::
. Se requieren paréntesis después del nombre de la función. La palabra clave de función es opcional, pero si se usa, es coherente en todo el proyecto.
Si escribe funciones separadas, use minúsculas y palabras separadas con guiones bajos. Si está escribiendo un paquete, separe los nombres de los paquetes con ::
. Los corchetes deben estar en la misma línea que el nombre de la función (como en otros idiomas en Google) y no deben tener un espacio entre el nombre de la función y el corchete.
Cuando "()" aparece después del nombre de la función, la palabra clave de la función parece redundante, pero mejora la identificación rápida de las funciones.
Nombre de variable
En cuanto a los nombres de las funciones.
Los nombres de las variables para los bucles deben tener el mismo nombre para cualquier variable que repita.
for zone in ${zones}; do something_with "${zone}" done
Nombres constantes de variables de entorno
Todos en mayúscula, separados por guiones bajos, se declaran en la parte superior del archivo.
Las constantes y todo lo que se exporta al medio ambiente debe estar en mayúsculas.
Algunas cosas permanecen constantes cuando se instalan por primera vez (por ejemplo, a través de getopts
). Por lo tanto, es bastante normal establecer una constante a través de getopts
o en función de una condición, pero debe hacerse readonly
después de eso. Tenga en cuenta que declare
no funciona con variables globales dentro de las funciones, por lo que export
recomienda readonly
o export
.
VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE
Nombres de archivo fuente
Minúscula, con guión bajo para separar las palabras, si es necesario.
Esto se aplica a la coincidencia de otros estilos de código en Google: maketemplate
o make_template
, pero no make-template
.
Variables de solo lectura
Use readonly
o declare -r
para asegurarse de que sean de solo lectura.
Dado que los globales se usan ampliamente en el shell, es importante detectar errores al trabajar con ellos. , , .
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi
, , local
. .
, , local
. , .
, ; local
exit code .
my_func2() { local name="$1"
. .
, . , set , .
. .
main
, main
, , .
, main
. , ( , ). main:
main "$@"
, , , main
— , .
.
$?
if
, .
Un ejemplo:
if ! mv "${file_list}" "${dest_dir}/" ; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi
, PIPESTATUS
, - , , PIPESTATUS
( , [
PIPESTATUS
).
tar -cf - ./* | ( cd "${DIR}" && tar -xf - ) return_codes=(${PIPESTATUS[*]}) if [[ "${return_codes[0]}" -ne 0 ]]; then do_something fi if [[ "${return_codes[1]}" -ne 0 ]]; then do_something_else fi
shell , .
, bash, ( , sed
).
Un ejemplo:
Conclusión
.
, Parting Words C++ .