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:
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:
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:
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.
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 '&&'.
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.
- Verwenden Sie denselben Stil, den Sie im vorhandenen Code finden.
- Setzen Sie Variablen in Anführungszeichen, siehe Abschnitt "Anführungszeichen" unten.
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.
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, $*
.
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:
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 [ ... ]
.
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.
Verwenden Sie explizit -z
oder -n
um Verwirrung darüber zu vermeiden, was Sie überprüfen.
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 *
./*
.
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.
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
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
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)
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.
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.
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.
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"
. .
, . , 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
, 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:
Fazit
.
Bitte nehmen Sie sich ein paar Minuten Zeit, um den Abschnitt "Trennwörter" am Ende des C ++ - Handbuchs zu lesen .