Was ist der Zweck der: (Doppelpunkt) GNU Bash Builtin?

Was ist der Zweck eines Befehls, der nichts tut, wenig mehr als ein Kommentarführer ist, aber tatsächlich eine in sich selbst eingebaute Shell ist?

Es ist langsamer als das Einfügen eines Kommentars in Ihre Skripte um etwa 40% pro Aufruf, was wahrscheinlich stark von der Größe des Kommentars abhängt. Die einzigen möglichen Gründe, die ich dafür sehen kann, sind diese:

# poor man's delay function for ((x=0;x<100000;++x)) ; do : ; done # inserting comments into string of commands command ; command ; : we need a comment in here for some reason ; command # an alias for `true' (lazy programming) while : ; do command ; done 

Ich denke, was ich wirklich suche, ist, welche historische Anwendung es hätte haben können.

In der Vergangenheit hatten Bourne-Shells als integrierte Befehle weder true false . true wurde statt dessen einfach aliasiert zu : und false zu etwas wie let 0 .

: ist etwas besser als true für die Portabilität zu alten Bourne-abgeleiteten shelln. Betrachten Sie als einfaches Beispiel, dass Sie weder das ! Leitungsbetreiber noch die || Listenoperator (wie es bei einigen alten Bourne-Shells der Fall war). Dies lässt die else Klausel der if statement als einzige Möglichkeit für die Verzweigung basierend auf dem Exit-Status:

 if command; then :; else ...; fi 

Da, if eine nicht-leere Klausel und Kommentare erforderlich sind, nicht als nicht-leer gelten, gilt : als No-Op.

Heutzutage ( dh in einem modernen Kontext) können Sie normalerweise entweder : oder true . Beide werden von POSIX angegeben und einige sind leichter zu lesen. Es gibt jedoch einen interessanten Unterschied:: ist ein sogenanntes POSIX special built-in , wobei true ein reguläres built-in ist .

  • Spezielle Einbauten müssen in die shell eingebaut werden; Regelmäßige Einbauten sind nur “typisch” eingebaut, aber es ist nicht unbedingt garantiert. Es sollte normalerweise kein reguläres Programm namens : mit der function von true in PATH der meisten Systeme geben.

  • Der wohl wichtigste Unterschied ist, dass bei speziellen Einbauten jede Variable, die durch das eingebaute – selbst in der Umgebung während der einfachen Befehlsauswertung – gesetzt wird, nach dem Ausführen des Befehls bestehen bleibt, wie hier anhand von ksh93 demonstriert:

     $ unset x; ( x=hi :; echo "$x" ) hi $ ( x=hi true; echo "$x" ) $ 

    Beachten Sie, dass Zsh diese Anforderung ebenso wie GNU Bash ignoriert, außer wenn es im POSIX-Kompatibilitätsmodus arbeitet, aber alle anderen großen “POSIX sh abgeleiteten” Shells beobachten dies, einschließlich der Bindestriche, ksh93 und mksh.

  • Ein weiterer Unterschied ist, dass reguläre Built-Ins mit exec kompatibel sein müssen – hier demonstriert mit Bash:

     $ ( exec : ) -bash: exec: :: not found $ ( exec true ) $ 
  • POSIX stellt außerdem explizit fest, dass : möglicherweise schneller als true , obwohl dies natürlich ein implementierungsspezifisches Detail ist.

Ich benutze es, um variable Befehle einfach zu aktivieren / deaktivieren:

 #!/bin/bash if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then vecho=":" # no "verbose echo" else vecho=echo # enable "verbose echo" fi $vecho "Verbose echo is ON" 

So

 $ ./vecho $ VERBOSE=1 ./vecho Verbose echo is ON 

Dies sorgt für ein sauberes Skript. Dies kann nicht mit ‘#’ erfolgen.

Ebenfalls,

 : >afile 

ist eine der einfachsten Möglichkeiten zu garantieren, dass ‘afile’ existiert, aber 0 Länge ist.

Eine nützliche Anwendung für: ist, wenn Sie nur daran interessiert sind, Parametererweiterungen für ihre Nebenwirkungen zu verwenden, anstatt ihr Ergebnis tatsächlich an einen Befehl zu übergeben. In diesem Fall verwenden Sie den PE als Argument für entweder: oder false, je nachdem, ob Sie einen Exit-Status von 0 oder 1 haben möchten. Ein Beispiel könnte sein : "${var:=$1}" . Da : ist ein eingebauter sollte es ziemlich schnell sein.

: kann auch für Blockkommentar sein (ähnlich wie / * * / in C Sprache). Wenn Sie beispielsweise einen Codeblock in Ihrem Skript überspringen möchten, können Sie Folgendes tun:

 : < < 'SKIP' your code block here SKIP 

Wenn Sie eine Datei auf null Bytes abschneiden möchten, was zum Löschen von Protokollen nützlich ist, versuchen Sie Folgendes:

 :> file.log 

Es ist ähnlich wie in Python übergeben.

Eine Verwendung wäre, eine function auszugeben, bis sie geschrieben wird:

 future_function () { :; } 

Zwei weitere Anwendungen, die in anderen Antworten nicht erwähnt werden:

Protokollierung

Nimm dieses Beispielskript:

 set -x : Logging message here example_command 

Die erste Zeile set -x , dass die Shell den Befehl vor dem Ausführen ausgibt. Es ist ein ziemlich nützliches Konstrukt. Der Nachteil besteht darin, dass die normale echo Log message der statement jetzt die Nachricht zweimal ausgibt. Die Doppelpunktmethode macht das rund. Beachten Sie, dass Sie Sonderzeichen wie beim echo immer noch entziehen müssen.

Cron Berufsbezeichnungen

Ich habe gesehen, dass es in Cron-Jobs verwendet wird:

 45 10 * * * : Backup for database ; /opt/backup.sh 

Dies ist ein Cron-Job, der das Skript /opt/backup.sh jeden Tag um 10:45 Uhr /opt/backup.sh . Der Vorteil dieser Technik besteht darin, dass E-Mail- /opt/backup.sh besser aussehen, wenn die /opt/backup.sh Ausgaben ausgibt.

Sie können es in Verbindung mit Backticks ( `` ) verwenden, um einen Befehl auszuführen, ohne seine Ausgabe anzuzeigen, wie folgt:

 : `some_command` 

Natürlich könntest du einfach some_command > /dev/null , aber die : -version ist etwas kürzer.

Davon abgesehen würde ich nicht empfehlen, das zu tun, da es die Leute nur verwirren würde. Es kam mir als möglicher Anwendungsfall gerade in den Sinn.

Es ist auch nützlich für mehrsprachige Programme:

 #!/usr/bin/env sh ':' //; exec "$(command -v node)" "$0" "$@" ~function(){ ... } 

Dies ist jetzt sowohl ein ausführbares Shell-Skript als auch ein JavaScript-Programm: das heißt, ./filename.js , sh filename.js und node filename.js ./filename.js funktionieren alle.

(Definitiv ein bisschen eine seltsame Verwendung, aber trotzdem effektiv.)


Einige Erklärungen, wie gewünscht:

  • Shell-Skripte werden Zeile für Zeile ausgewertet; Wenn der Befehl exec ausgeführt wird, beendet er die Shell und ersetzt den process durch den resultierenden Befehl. Dies bedeutet, dass das Programm für die Shell wie folgt aussieht:

     #!/usr/bin/env sh ':' //; exec "$(command -v node)" "$0" "$@" 
  • Solange im Wort keine Parametererweiterung oder Aliasing auftritt, kann jedes Wort in einem Shell-Skript in Anführungszeichen gesetzt werden, ohne seine Bedeutung zu ändern. Das bedeutet: ':' entspricht : (Wir haben es hier nur in Anführungszeichen gesetzt, um die unten beschriebene JavaScript-Semantik zu erreichen.)

  • … und wie oben beschrieben, ist der erste Befehl in der ersten Zeile ein No-Op (es bedeutet : // , oder wenn Sie es vorziehen, die Wörter zu zitieren ':' '//' . Beachten Sie, dass // hat hier keine besondere Bedeutung, wie in JavaScript, es ist nur ein bedeutungsloses Wort, das weggeworfen wird.)

  • Schließlich ist der zweite Befehl in der ersten Zeile (nach dem Semikolon) der eigentliche core des Programms: Es ist der Aufruf exec , der das aufgerufene Shell-Skript ersetzt, wobei ein Node.js-process aufgerufen wird, um den Rest des Skripts auszuwerten .

  • In der Zwischenzeit wird die erste Zeile in JavaScript als Zeichenfolgenliteral ( ':' ) und dann als Kommentar gelesen und gelöscht. Daher sieht das Programm in JavaScript so aus:

     ':' ~function(){ ... } 

    Da das String-Literal auf einer eigenen Zeile steht, handelt es sich um eine no-op-statement, die somit aus dem Programm entfernt wird. das bedeutet, dass die gesamte Zeile entfernt wird und nur Ihr Programmcode übrig bleibt (in diesem Beispiel der Körper der function(){ ... } ).

Selbstdokumentierende functionen

Sie können auch : Dokumentation in eine function einbetten.

Angenommen, Sie haben ein Bibliotheksskript mylib.sh , das eine Vielzahl von functionen bietet. Sie könnten entweder die Bibliothek ( . mylib.sh ) . mylib.sh und die functionen direkt danach lib_function1 arg1 arg2 ( lib_function1 arg1 arg2 ) oder vermeiden, Ihren Namespace zu lib_function1 arg1 arg2 und die Bibliothek mit einem functionsargument aufzurufen ( mylib.sh lib_function1 arg1 arg2 ).

Wäre es nicht schön, wenn Sie auch mylib.sh --help und eine Liste der verfügbaren functionen und deren Verwendung erhalten, ohne die functionsliste im mylib.sh --help zu müssen?

 #! / bin / bash

 # Alle "öffentlichen" functionen müssen mit diesem Präfix beginnen
 LIB_PREFIX = 'lib_'

 # "öffentliche" Bibliotheksfunktionen
 lib_funktion1 () {
     : Diese function macht etwas Kompliziertes mit zwei Argumenten.
     :
     : Parameter:
     : 'arg1 - erstes Argument ($ 1)'
     : 'arg2 - zweites Argument'
     :
     : Ergebnis:
     : " es ist kompliziert"

     # Der eigentliche functionscode beginnt hier
 }

 lib_funktion2 () {
     : functionsdokumentation

     # functionscode hier
 }

 # Hilfefunktion
 --Hilfe() {
     echo MyLib v0.0.1
     Echo
     echo Verwendung: mylib.sh [functionsname [Argumente]]
     Echo
     echo Verfügbare functionen:
     deklariere -f |  sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
         s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}"
 }

 # Haupt code
 if ["$ {BASH_SOURCE [0]}" = "$ {0}"];  dann
     # Das Skript wurde ausgeführt, anstatt es zu bearbeiten
     # angeforderte function aufrufen oder Hilfe anzeigen
     if ["$ (type -t -" $ 1 "2> / dev / null)" = function];  dann
         "$ @"
     sonst
         --Hilfe
     fi
 fi

Ein paar Kommentare zum Code:

  1. Alle “öffentlichen” functionen haben das gleiche Präfix. Nur diese sollen vom Benutzer aufgerufen und im Hilfetext aufgelistet werden.
  2. Die selbstdokumentierende function stützt sich auf den vorherigen Punkt und verwendet declare -f um alle verfügbaren functionen declare -f , und filtert sie dann über sed, um nur functionen mit dem entsprechenden Präfix anzuzeigen.
  3. Es empfiehlt sich, die Dokumentation in einfache Anführungszeichen zu setzen, um unerwünschte Erweiterungen und Leerzeichen zu vermeiden. Sie müssen auch vorsichtig sein, wenn Sie Apostrophe / Anführungszeichen im Text verwenden.
  4. Sie könnten Code schreiben, um das Bibliothekspräfix zu internalisieren, dh der Benutzer muss nur mylib.sh function1 und wird intern in lib_function1 . Dies ist eine Übung, die dem Leser überlassen wird.
  5. Die Hilfefunktion heißt “–help”. Dies ist ein praktischer (dh fauler) Ansatz, der den Bibliotheksaufrufmechanismus verwendet, um die Hilfe selbst anzuzeigen, ohne eine zusätzliche Prüfung für $1 codieren zu müssen. Gleichzeitig wird Ihr Namespace durcheinander gebracht, wenn Sie die Bibliothek beziehen. Wenn Sie das nicht mögen, können Sie den Namen entweder in lib_help oder die --help für --help im lib_help überprüfen und die Hilfefunktion manuell aufrufen.

Ich habe diese Verwendung in einem Skript gesehen und dachte, dass es ein guter Ersatz für das Aufrufen des Basisnamens in einem Skript ist.

 oldIFS=$IFS IFS=/ for basetool in $0 ; do : ; done IFS=$oldIFS 

… das ist ein Ersatz für den Code: basetool=$(basename $0)