Guía de estilo de Shell de Google (en ruso)

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:


 #!/bin/bash # # Perform hot backups of Oracle databases. 

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:


 #!/bin/bash # # Perform hot backups of Oracle databases. export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin' ######################################## # Cleanup files from the backup dir # Globals: # BACKUP_DIR # ORACLE_SID # Arguments: # None # Returns: # None ######################################## cleanup() { ... } 

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:


 # TODO(mrmonkey): Handle the unlikely edge cases (bug ####) 

Formateo


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.


 #  'here document's cat <<END; I am an exceptionally long string. END #  newlines   long_string="I am an exceptionally long string." 

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 '&&'.


 #      command1 | command2 #   command1 \ | command2 \ | command3 \ | command4 

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.


  1. Use el mismo estilo que encuentra en el código existente.
  2. Ponga las variables entre comillas, consulte la sección de Comillas a continuación.
  3. 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.


      #    #    '' : echo "Positional: $1" "$5" "$3" echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..." #   : echo "many parameters: ${10}" #    : # Output is "a0b0c0" set -- abc echo "${1}0${2}0${3}0" #     : echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}" while read f; do echo "file=${f}" done < <(ls -l /tmp) #    #   ,    , #     ,   shell echo a=$avar "b=$bvar" "PID=${$}" "${1}" #  : #    "${1}0${2}0${3}0",   "${10}${20}${30} set -- abc echo "$10$20$30" 

    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 $* .


 # ''  ,     . # ""  ,   /. #   # "   " flag="$(some_command and its args "$@" 'quoted separately')" # "  " echo "${flag}" # "      " value=32 # "    ",      number="$(generate_number)" # "   ",    readonly USE_INTEGER='true' # "    - shell" echo 'Hello stranger, and well met. Earn lots of $$$' echo "Process $$: Done making \$\$\$." # "    " # ( ,  $1  ) grep -li Hugo /dev/null "$1" #    # "   ,    ": ccs     git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"} #   : $1    #       . grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"} #   , # "$@"   ,  # $*    # # * $*  $@   ,   #      ; # * "$@"     ,   #       ; #     ,      #   # * "$*"    ,    #     () , #        # ( 'man bash'  nit-grits ;-) set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@") set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@") 

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:


 #  : var="$(command "$(command1)")" #  : var="`command \`command1\``" 

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.


 #  ,       #  `alnum`,     . #  ,       #  .  ,  # E14   https://tiswww.case.edu/php/chet/bash/FAQ if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi #     "f*" (    ) if [[ "filename" == "f*" ]]; then echo "Match" fi #    "too many arguments",   f*   #     if [ "filename" == f* ]; then echo "Match" fi 

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.


 #  : if [[ "${my_var}" = "some_string" ]]; then do_something fi # -z (   ),  -n (    ): #      if [[ -z "${my_var}" ]]; then do_something fi #   ( ),   : if [[ "${my_var}" = "" ]]; then do_something fi #   : if [[ "${my_var}X" = "some_stringX" ]]; then do_something fi 

Para evitar confusiones sobre lo que está comprobando, use explícitamente -z o -n .


 #   if [[ -n "${my_var}" ]]; then do_something fi #  ,    ,  ${my_var} #     . if [[ "${my_var}" ]]; then do_something fi 

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 * .


 #   : # -f -r somedir somefile #        force psa@bilby$ rm -v * removed directory: `somedir' removed `somefile' #   : psa@bilby$ rm -v ./* removed `./-f' removed `./-r' rm: cannot remove `./somedir': Is a directory removed `./somefile' 

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.


 #   ? #   ?   ? eval $(set_my_variables) #  ,         ? variable="$(eval some_function)" 

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 #   'NULL' echo "${last_line}" 

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 #  ,       . for value in $(command); do total+="${value}" done 

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) #         # . echo "Total = ${total}" echo "Last one = ${last_file}" 

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.


 #    awk: # awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts cat /proc/mounts | while read src dest type opts rest; do if [[ ${type} == "nfs" ]]; then echo "NFS ${dest} maps to ${src}" fi done 

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.


 #   my_func() { ... } #   mypackage::my_func() { ... } 

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.


 #  readonly PATH_TO_FILES='/some/path' # ,   declare -xr ORACLE_SID='PROD' 

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" #      : local my_var my_var="$(my_func)" || return #   : $?  exit code  'local',   my_func local my_var="$(my_func)" [[ $? -eq 0 ]] || return ... } 


. .


, . , 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 #  mv "${file_list}" "${dest_dir}/" if [[ "$?" -ne 0 ]]; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi Bash    `PIPESTATUS`,         .           ,   : ```bash tar -cf - ./* | ( cd "${dir}" && tar -xf - ) if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then echo "Unable to tar files to ${dir}" >&2 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:


 #  : addition=$((${X} + ${Y})) substitution="${string/#foo/bar}" #  : addition="$(expr ${X} + ${Y})" substitution="$(echo "${string}" | sed -e 's/^foo/bar/')" 

Conclusión


.


, Parting Words C++ .

Source: https://habr.com/ru/post/es413155/


All Articles