Googles Shell Style Guide (auf Russisch)

Vorwort


Welche Shell soll ich verwenden?


Bash einzige Shell-Skriptsprache, die für ausführbare Dateien verwendet werden kann.


Skripte sollten mit #!/bin/bash mit einem minimalen Satz von Flags beginnen. Verwenden Sie set , um Shell-Optionen bash <script_name> damit das Aufrufen Ihres Skripts als bash <script_name> seine Funktionalität nicht verletzt.


Wenn Sie alle Shell-Skripte auf Bash beschränken, erhalten Sie eine konsistente Shell-Sprache, die auf allen unseren Computern installiert ist.


Die einzige Ausnahme ist, wenn Sie durch die Bedingungen, für die Sie programmieren, eingeschränkt sind. Ein Beispiel wären Solaris SVR4-Pakete, für die für jedes Skript die übliche Bourne-Shell verwendet werden muss.


Wann wird Shell verwendet?


Shell sollte nur für kleine Dienstprogramme oder einfache Skript-Wrapper verwendet werden.


Obwohl Shell-Scripting keine Entwicklungssprache ist, wird es zum Schreiben verschiedener Dienstprogramme in Google verwendet. Dieser Styleguide ist eher eine Anerkennung seiner Verwendung als ein Vorschlag, ihn in großem Umfang zu verwenden.


Einige Empfehlungen:


  • Wenn Sie am häufigsten andere Dienstprogramme aufrufen und relativ wenig Daten bearbeiten, ist die Shell eine akzeptable Wahl für die Aufgabe.
  • Wenn es auf die Leistung ankommt, verwenden Sie etwas anderes, aber keine Shell.
  • Wenn Sie feststellen, dass Sie Arrays für mehr als die Zuweisung von ${PIPESTATUS} , sollten Sie Python verwenden.
  • Wenn Sie ein Skript schreiben, das länger als 100 Zeilen ist, sollten Sie es wahrscheinlich in Python schreiben. Denken Sie daran, dass Skripte wachsen. Schreiben Sie Ihr Skript früher in eine andere Sprache, um später ein zeitaufwändiges Umschreiben zu vermeiden.

Shell-Dateien und Interpreter-Aufruf


Dateierweiterungen


Ausführbare Dateien sollten nicht die Erweiterung (stark bevorzugt) oder die Erweiterung .sh . Bibliotheken müssen die Erweiterung .sh und dürfen nicht ausführbar sein.


Es ist nicht erforderlich zu wissen, in welcher Sprache das Programm während seiner Ausführung geschrieben wird, und die Shell benötigt keine Erweiterung. Daher ziehen wir es vor, sie nicht für ausführbare Dateien zu verwenden.


Für Bibliotheken ist es jedoch wichtig zu wissen, in welcher Sprache sie geschrieben sind, und manchmal ist es erforderlich, ähnliche Bibliotheken in verschiedenen Sprachen zu haben. Auf diese Weise können Sie Bibliotheksdateien mit identischen Namen und identischen Zielen verwenden, die jedoch in verschiedenen Sprachen geschrieben sind. Der Name sollte bis auf ein sprachspezifisches Suffix identisch sein.


SUID / SGID


SUID und SGID sind in Shell-Skripten verboten.


Es gibt zu viele Sicherheitsprobleme, so dass es nahezu unmöglich ist, einen ausreichenden SUID / SGID-Schutz bereitzustellen. Obwohl bash den Start von SUID erschwert, ist es auf einigen Plattformen immer noch möglich, weshalb wir die Verwendung von SUID ausdrücklich untersagen.


Verwenden Sie sudo für erweiterten Zugriff, wenn Sie es benötigen.


Die Umwelt


STDOUT vs STDERR


Alle Fehlermeldungen sollten an STDERR gesendet werden.


Dies hilft, den normalen Zustand von tatsächlichen Problemen zu trennen.


Es wird empfohlen, die Funktion zum Anzeigen von Fehlermeldungen zusammen mit anderen Statusinformationen zu verwenden.


 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 

Kommentare


Dateikopf


Beginnen Sie jede Datei mit einer Beschreibung ihres Inhalts.


Jede Datei sollte einen Titel aus dem Kommentar enthalten, einschließlich einer kurzen Beschreibung ihres Inhalts. Copyright-Hinweis und Autoreninformationen sind optional.


Ein Beispiel:


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

Funktionskommentare


Jede Funktion, die nicht offensichtlich und kurz ist, sollte kommentiert werden. Jede Funktion in der Bibliothek sollte unabhängig von ihrer Länge oder Komplexität kommentiert werden.


Sie müssen sicherstellen, dass jemand anderes versteht, wie Sie Ihr Programm verwenden oder wie Sie die Funktion in Ihrer Bibliothek verwenden, indem Sie einfach die Kommentare (und die Notwendigkeit der Selbstverbesserung) lesen, ohne den Code zu lesen.


Alle Funktionskommentare sollten Folgendes enthalten:


  • Funktionsbeschreibung
  • Verwendete und geänderte globale Variablen
  • Erhaltene Argumente
  • Rückgabewerte, die sich von den Standard-Exit-Codes im letzten Befehl unterscheiden.

Ein Beispiel:


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

Implementierungskommentare


Kommentieren Sie komplexe, nicht offensichtliche, interessante oder wichtige Teile Ihres Codes.


Es wird davon ausgegangen, dass dies die übliche Praxis ist, Code bei Google zu kommentieren. Kommentieren Sie nicht alles. Wenn es einen komplizierten Algorithmus gibt oder Sie etwas Ungewöhnliches tun, fügen Sie einen kurzen Kommentar hinzu.


TODO Kommentare


Verwenden Sie TODO-Kommentare für Code, der vorübergehend, kurzfristig oder ziemlich gut, aber nicht perfekt ist.


Dies steht im Einklang mit der Konvention im C ++ - Handbuch .


TODO-Kommentare sollten das Wort TODO in Großbuchstaben enthalten, gefolgt von Ihrem Namen in Klammern. Ein Doppelpunkt ist optional. Es ist auch vorzuziehen, die Fehler- / Ticketnummer neben dem TODO-Element anzugeben.


Ein Beispiel:


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

Formatierung


Obwohl Sie dem Stil folgen müssen, der bereits in den Dateien verwendet wird, die Sie bearbeiten, ist für jeden neuen Code Folgendes erforderlich.


Einrückung


2 Leerzeichen einrücken. Keine Registerkarten.


Verwenden Sie Leerzeilen zwischen Blöcken, um die Lesbarkeit zu verbessern. Einrückung ist zwei Leerzeichen. Egal was Sie tun, verwenden Sie keine Registerkarten. Bleiben Sie bei vorhandenen Dateien dem aktuellen Einzug treu.


Stringlänge und Wertlänge


Die maximale Zeilenlänge beträgt 80 Zeichen.


Wenn Sie Zeilen mit mehr als 80 Zeichen schreiben müssen, sollten Sie das here document oder, falls möglich, die integrierte newline . Literalwerte, die länger als 80 Zeichen sein können und nicht getrennt werden können, sind vernünftigerweise zulässig. Es wird jedoch dringend empfohlen, einen Weg zu finden, um sie kürzer zu machen.


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

Pipelines


Pipelines sollten jeweils in eine Linie unterteilt werden, wenn sie nicht in eine Linie passen.


Wenn eine Pipeline in eine Leitung passt, sollte sie in eine Leitung passen.


Wenn nicht, sollte es so geteilt werden, dass sich jeder Abschnitt in einer neuen Zeile befindet und für den nächsten Abschnitt um 2 Leerzeichen eingerückt wird. Dies bezieht sich auf die Befehlskette, die mit '|' kombiniert wurde. sowie logische Verbindungen mit '||' und '&&'.


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

Zyklen


Ort ; do ; do und ; then ; then in der gleichen Zeile wie while , for oder if .


Die Zyklen in der Shell unterscheiden sich geringfügig, aber wir folgen den gleichen Prinzipien wie bei geschweiften Klammern, wenn wir Funktionen deklarieren. Das heißt ; then ; then und ; do ; do sollte in derselben Zeile stehen wie if / for / while . else sollte sich die Anweisung in einer separaten Zeile befinden, und die Abschlussanweisungen sollten in einer eigenen Zeile stehen und vertikal zur Eröffnungsanweisung ausgerichtet sein.


Ein Beispiel:


 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 

Fallbeschreibung


  • Separate Optionen in 2 Feldern.
  • Einzeilige Optionen erfordern ein Leerzeichen nach der schließenden Klammer der Vorlage und davor ;; .
  • Lang- oder Mehrbefehlsoptionen sollten in mehrere Zeilen mit einer Vorlage, Aktionen und ;; in getrennten Zeilen.

Entsprechende Ausdrücke treten eine Ebene von case und esac . Mehrzeilige Aktionen haben auch Einrückungen auf einer separaten Ebene. Es ist nicht erforderlich, Ausdrücke in Anführungszeichen zu setzen. Vor Ausdrucksmustern dürfen keine offenen Klammern stehen. Vermeiden Sie die Verwendung von &; und ;;& Notation.


 case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac 

Einfache Befehle können in einer Zeile mit dem Muster und ;; während der Ausdruck lesbar bleibt. Dies ist häufig für die Behandlung von Einzelbuchstabenoptionen geeignet. Wenn Aktionen nicht in eine Zeile passen, lassen Sie die Vorlage in Ihrer Zeile, die nächste Aktion, dann ;; auch in eigener Linie. Wenn dies dieselbe Zeile wie bei den Aktionen ist, verwenden Sie ein Leerzeichen nach der schließenden Klammer der Vorlage und ein weiteres vor ;; .


 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 

Variable Erweiterung


In der Reihenfolge der Priorität: Beobachten Sie, was bereits verwendet wird. Variablen in Anführungszeichen setzen; bevorzugen Sie "${var}" mehr als "$var" , aber mit Blick auf den Verwendungskontext.


Dies sind eher Empfehlungen, da das Thema für die obligatorische Regulierung ziemlich kontrovers ist. Sie sind in der Reihenfolge ihrer Priorität aufgeführt.


  1. Verwenden Sie denselben Stil, den Sie im vorhandenen Code finden.
  2. Setzen Sie Variablen in Anführungszeichen, siehe Abschnitt "Anführungszeichen" unten.
  3. Setzen Sie keine einzelnen Zeichen, die für Shell- / Positionsparameter spezifisch sind, in Anführungszeichen und geschweifte Klammern, es sei denn, dies ist unbedingt erforderlich und um tiefe Verwirrung zu vermeiden.
    Bevorzugen Sie geschweifte Klammern für alle anderen Variablen.


      #    #    '' : 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" 

    Zitate


    • Verwenden Sie immer Anführungszeichen für Werte, die Variablen, Befehlsersetzungen, Leerzeichen oder Shell-Metazeichen enthalten, bis Sie Werte, die nicht in Anführungszeichen stehen, sicher verfügbar machen müssen.
    • Bevorzugen Sie Anführungszeichen für Werte, die "Wörter" sind (im Gegensatz zu Befehlsparametern oder Pfadnamen).
    • Zitieren Sie niemals ganze Zahlen.
    • Wissen, wie Anführungszeichen für Übereinstimmungsmuster in [[ .
    • Verwenden Sie "$@" wenn Sie keinen besonderen Grund haben, $* .


 # ''  ,     . # ""  ,   /. #   # "   " 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 "$#, $@") 

Funktionen und Fehler


Befehlsersetzung


Verwenden Sie $(command) anstelle von Backticks.


Für verschachtelte Backticks müssen interne Anführungszeichen mit \ maskiert werden. Das $ (command) Format ändert sich je nach Verschachtelung nicht und ist leichter zu lesen.


Ein Beispiel:


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

Schecks, [ und [[


[[ ... ]] bevorzugter als [ , test oder /usr/bin/[ .


[[ ... ]] verringert die Möglichkeit von Fehlern, da zwischen [[ und ]] keine Pfadauflösung oder Worttrennung besteht und [[ ... ]] Sie die Verwendung eines regulären Ausdrucks ermöglichen, wobei [ ... ] nicht der [ ... ] .


 #  ,       #  `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 

Werte prüfen


Verwenden Sie nach Möglichkeit Anführungszeichen anstelle von zusätzlichen Zeichen.


Bash ist klug genug, um in einem Test mit einer leeren Zeichenfolge zu arbeiten. Daher ist der resultierende Code viel einfacher zu lesen. Verwenden Sie Überprüfungen für leere / nicht leere Werte oder leere Werte, ohne zusätzliche Zeichen zu verwenden.


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

Verwenden Sie explizit -z oder -n um Verwirrung darüber zu vermeiden, was Sie überprüfen.


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

Substitutionsausdrücke für Dateinamen


Verwenden Sie den expliziten Pfad, wenn Sie Platzhalterausdrücke für Dateinamen erstellen.


Da Dateinamen mit dem Zeichen - beginnen können, ist es viel sicherer ./* Platzhalterausdruck als ./* anstelle von * ./* .


 #   : # -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 sollte vermieden werden.


Mit Eval können Sie die in der Eingabe übergebenen Variablen erweitern, aber auch andere Variablen festlegen, ohne sie überprüfen zu können.


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

Pipes in While


Verwenden Sie die Befehlssubstitution oder die for Schleife anstelle von Pipes in while . In der while geänderte Variablen werden nicht an das übergeordnete Element weitergegeben, da die Schleifenbefehle in einer Unterschale ausgeführt werden.


Eine implizite Unterschale in der while Pipe kann die Fehlerverfolgung erschweren.


 last_line='NULL' your_command | while read line; do last_line="${line}" done #   'NULL' echo "${last_line}" 

Verwenden Sie eine for-Schleife, wenn Sie sicher sind, dass die Eingabe keine Leerzeichen oder Sonderzeichen enthält (dies impliziert normalerweise keine Benutzereingabe).


 total=0 #  ,       . for value in $(command); do total+="${value}" done 

Durch die Verwendung der Befehlssubstitution können Sie die Ausgabe umleiten, aber Befehle in einer expliziten Unter-Shell ausführen, im Gegensatz zur impliziten Unter-Shell, die Bash für die 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}" 

Verwenden Sie while Schleifen, bei denen keine komplexen Ergebnisse an die übergeordnete Shell übergeben werden müssen. Dies ist typisch, wenn komplexere "Parsing" erforderlich sind. Denken Sie daran, dass einfache Beispiele mit einem Tool wie awk manchmal viel einfacher zu lösen sind. Dies kann auch nützlich sein, wenn Sie die Variablen der übergeordneten Umgebung nicht speziell ändern möchten.


 #    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 

Namenskonvention


Funktionsnamen


Kleinbuchstaben mit Unterstrichen zum Trennen von Wörtern. Separate Bibliotheken mit :: . Nach dem Funktionsnamen sind Klammern erforderlich. Das Funktionsschlüsselwort ist optional, aber wenn es verwendet wird, ist es im gesamten Projekt konsistent.


Wenn Sie separate Funktionen schreiben, verwenden Sie Kleinbuchstaben und separate Wörter mit Unterstrichen. Wenn Sie ein Paket schreiben, trennen Sie die Paketnamen durch :: . Die Klammern müssen sich in derselben Zeile wie der Funktionsname befinden (wie in anderen Sprachen bei Google) und dürfen kein Leerzeichen zwischen dem Funktionsnamen und der Klammer enthalten.


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

Wenn nach dem Funktionsnamen "()" steht, sieht das Funktionsschlüsselwort redundant aus, verbessert jedoch die schnelle Identifizierung von Funktionen.


Variablenname


In Bezug auf Funktionsnamen.


Variablennamen für Schleifen sollten für jede Variable, über die Sie iterieren, gleich benannt werden.


 for zone in ${zones}; do something_with "${zone}" done 

Konstantennamen von Umgebungsvariablen


Alle Großbuchstaben, die durch Unterstriche getrennt sind, werden oben in der Datei deklariert.


Konstanten und alles, was in die Umgebung exportiert wird, müssen in Großbuchstaben angegeben werden.


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

Einige Dinge bleiben bei der getopts konstant (z. B. über getopts ). Daher ist es ganz normal, eine Konstante durch getopts oder basierend auf einer Bedingung getopts , aber es sollte direkt danach readonly . Beachten Sie, dass declare nicht mit globalen Variablen in Funktionen funktioniert. readonly export stattdessen readonly oder export empfohlen.


 VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE 

Quelldateinamen


Kleinbuchstaben, bei Bedarf mit Unterstrich, um Wörter zu trennen.


Dies gilt für den Abgleich anderer maketemplate bei Google: maketemplate oder make_template , jedoch nicht make-template .


Schreibgeschützte Variablen


Verwenden Sie readonly oder declare -r , um sicherzustellen, dass sie schreibgeschützt sind.


Da globale in der Shell weit verbreitet sind, ist es wichtig, Fehler bei der Arbeit mit ihnen zu erkennen. , , .


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


Ein Beispiel:


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


Ein Beispiel:


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

Fazit


.


Bitte nehmen Sie sich ein paar Minuten Zeit, um den Abschnitt "Trennwörter" am Ende des C ++ - Handbuchs zu lesen .

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


All Articles