Was ist der Zweck des: (Doppelpunkt-) GNU Bash?

335

Was ist der Zweck eines Befehls, der nichts tut, nur ein Kommentarführer ist, sondern 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 Anruf, was wahrscheinlich stark von der Größe des Kommentars abhängt. Die einzigen möglichen Gründe, die ich dafür sehen kann, sind folgende:

# 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 gehabt haben könnte.

Amphetamachine
quelle
68
@Caleb - Ich habe das zwei Jahre zuvor gefragt.
Amphetamachine
Ich würde nicht sagen, dass ein Befehl, der einen bestimmten Wert zurückgibt, "nichts tut". Es sei denn, die funktionale Programmierung besteht darin, "nichts zu tun". :-)
LarsH

Antworten:

415

Historisch gesehen , haben Bourne - Shells nicht haben trueund falseals integrierte Befehle. truewurde stattdessen einfach auf :und falseauf so etwas ausgerichtet let 0.

:ist etwas besser als truefür die Portabilität auf alte Muscheln aus Bourne. Betrachten Sie als einfaches Beispiel, dass Sie weder den !Pipeline-Operator noch den ||Listen-Operator haben (wie dies bei einigen alten Bourne-Shells der Fall war). Damit bleibt die elseKlausel der ifAnweisung das einzige Mittel zur Verzweigung basierend auf dem Exit-Status:

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

Da ifeine nicht leere thenKlausel erforderlich ist und Kommentare nicht als nicht leer gelten, :dient sie als No-Op.

Heutzutage (das heißt: in einem modernen Kontext) können Sie normalerweise entweder :oder verwenden true. Beide werden von POSIX spezifiziert und einige sind trueleichter zu lesen. Es gibt jedoch einen interessanten Unterschied: Es :handelt sich um ein sogenanntes POSIX- Special , während truees sich um ein reguläres integriertes System handelt .

  • In die Shell müssen spezielle Einbauten eingebaut werden. Regelmäßige Einbauten sind nur "normalerweise" eingebaut, aber es ist nicht streng garantiert. Normalerweise sollte es :auf den truemeisten Systemen kein reguläres Programm mit der Funktion in PATH geben.

  • Der wahrscheinlich wichtigste Unterschied besteht darin, dass bei speziellen integrierten Funktionen jede von den integrierten Variablen festgelegte Variable - auch in der Umgebung während der einfachen Befehlsauswertung - nach Abschluss des Befehls bestehen bleibt, wie hier mit ksh93 gezeigt:

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

    Beachten Sie, dass Zsh diese Anforderung ignoriert, ebenso wie GNU Bash, außer wenn es im POSIX-Kompatibilitätsmodus arbeitet. Alle anderen wichtigen "POSIX sh-abgeleiteten" Shells, einschließlich dash, ksh93 und mksh, beachten dies.

  • Ein weiterer Unterschied besteht darin, dass reguläre integrierte Funktionen kompatibel sein müssen exec- hier anhand von Bash demonstriert:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX weist auch ausdrücklich darauf hin, dass dies :möglicherweise schneller ist als true, obwohl dies natürlich ein implementierungsspezifisches Detail ist.

Graf
quelle
Meinten Sie, dass reguläre Einbauten nicht kompatibel sein dürfen exec?
Old Pro
1
@OldPro: Nein, er hat trueRecht damit, dass es sich um ein reguläres eingebautes Gerät handelt, aber er ist falsch darin, dass execer /bin/trueanstelle des eingebauten verwendet wird.
Bis auf weiteres angehalten.
1
@ TennisWilliamson Ich habe mich nur an die Art und Weise gehalten, wie die Spezifikation formuliert ist. Die Implikation ist natürlich, dass reguläre Buildins auch eine eigenständige Version haben sollten.
Ormaaj
17
+1 Ausgezeichnete Antwort. Ich möchte immer noch die Verwendung zum Initialisieren von Variablen wie : ${var?not initialized}et al.
Tripleee
Ein mehr oder weniger unabhängiges Follow-up. Sie sagten, es :ist eine spezielle integrierte Funktion und sollte keine von ihr benannte Funktion haben. Aber ist das nicht das am häufigsten gesehene Beispiel für die :(){ :|: & };:Benennung einer Funktion mit Namen durch eine Gabelbombe :?
Chong
63

Ich benutze es, um Variablenbefehle 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"

Somit

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

Dies sorgt für ein sauberes Skript. Dies ist mit '#' nicht möglich.

Ebenfalls,

: >afile

ist eine der einfachsten Möglichkeiten, um sicherzustellen, dass 'afile' existiert, aber eine Länge von 0 hat.

Kevin Little
quelle
20
>afileist noch einfacher und erzielt den gleichen Effekt.
Earl
2
Cool, ich werde diesen $ vecho-Trick verwenden, um die von mir verwalteten Skripte zu vereinfachen.
BarneySchmale
5
Was ist der Vorteil des Zitierens von Doppelpunkten vecho=":"? Nur zur besseren Lesbarkeit?
Leoj
56

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

Ormaaj
quelle
2
Sie können es auch für Nebenwirkungen der arithmetischen Erweiterung verwenden: : $((a += 1))( ++und --Operatoren müssen nicht gemäß POSIX implementiert werden.) In bash, ksh und möglichen anderen Shells können Sie auch Folgendes tun: ((a += 1))oder ((a++))es wird jedoch nicht von POSIX angegeben.
Pabouk
@pabouk Ja, das ist alles wahr, (())wird jedoch als optionale Funktion angegeben. "Wenn eine Zeichenfolge, die mit" ((") beginnt, von der Shell als arithmetische Erweiterung analysiert wird, wenn ein '$' vorangestellt wird, können Shells, die eine Erweiterung implementieren, wobei" ((Ausdruck)) "als arithmetischer Ausdruck ausgewertet wird, behandelt werden das "((" als Einführung als arithmetische Auswertung anstelle eines Gruppierungsbefehls) "
ormaaj
50

:kann auch für Blockkommentare verwendet werden (ä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
Zagpunkt
quelle
3
Schlechte Idee. Alle Befehlsersetzungen innerhalb des hier gezeigten Dokuments werden weiterhin verarbeitet.
Chepner
33
Keine so schlechte Idee. Sie können eine variable Auflösung / Ersetzung in diesen Dokumenten vermeiden, indem Sie das Trennzeichen in einfache Anführungszeichen setzen :: << 'SKIP'
Rondo
1
IIRC Sie können auch \ jedes der Trennzeichen für den gleichen Effekt maskieren : : <<\SKIP.
yyny
31

Wenn Sie eine Datei auf null Byte kürzen möchten, was zum Löschen von Protokollen hilfreich ist, versuchen Sie Folgendes:

:> file.log
Ahi Thunfisch
quelle
16
> file.logist einfacher und erzielt den gleichen Effekt.
Amphetamachine
55
Yah, aber das glückliche Gesicht ist das, was es für mich tut:>
Ahi Tuna
23
@ Amphetamachine: :>ist tragbarer. Einige Shells (wie meine zsh) instanziieren automatisch eine Katze in der aktuellen Shell und warten auf stdin, wenn eine Umleitung ohne Befehl erfolgt. Anstatt cat /dev/null, :ist viel einfacher. Oft ist dieses Verhalten in interaktiven Shells anders als in Skripten. Wenn Sie das Skript jedoch so schreiben, dass es auch interaktiv funktioniert, ist das Debuggen durch Kopieren und Einfügen viel einfacher.
Caleb
2
Wie unterscheidet : > filesich true > file(abgesehen von der Anzahl der Charaktere und dem glücklichen Gesicht) von einer modernen Hülle (vorausgesetzt :und truegleich schnell)?
Adam Katz
30

Es ist ähnlich wie passin Python.

Eine Verwendung wäre, eine Funktion zu stubben, bis sie geschrieben wird:

future_function () { :; }
Bis auf weiteres angehalten.
quelle
29

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

Protokollierung

Nehmen Sie dieses Beispielskript:

set -x
: Logging message here
example_command

In der ersten Zeile set -xdruckt die Shell den Befehl aus, bevor sie ausgeführt wird. Es ist ein ziemlich nützliches Konstrukt. Der Nachteil ist, dass die übliche echo Log messageArt der Anweisung die Nachricht jetzt zweimal druckt. Die Doppelpunktmethode umgeht das. Beachten Sie, dass Sie Sonderzeichen immer noch so entkommen müssen, wie Sie es möchten echo.

Cron Berufsbezeichnungen

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

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

Dies ist ein Cron-Job, der das Skript /opt/backup.shjeden Tag um 10:45 Uhr ausführt. Der Vorteil dieser Technik besteht darin, dass die E-Mail-Betreffs besser aussehen, wenn /opt/backup.sheine Ausgabe gedruckt wird.

Flimm
quelle
Wo ist der Standardprotokollspeicherort? Kann ich den Protokollspeicherort festlegen? Ist der Zweck eher für die Erstellung von Ausgaben im Standard während Skripten / Hintergrundprozessen?
Domdambrogia
1
@domdambrogia Bei Verwendung gehen set -xdie ausgedruckten Befehle (einschließlich so etwas wie : foobar) zu stderr.
Flimm
22

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

: `some_command`

Natürlich könnte man das auch tun some_command > /dev/null, aber die :-Version ist etwas kürzer.

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

sepp2k
quelle
25
Dies ist nicht sicher, wenn der Befehl einige Megabyte Ausgabe ausgeben soll, da die Shell die Ausgabe puffert und sie dann als Befehlszeilenargumente (Stapelspeicher) an ':' übergibt.
Juliano
15

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

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

Dies ist nun sowohl ein ausführbarer Shell-Skript und ein JavaScript - Programm: Sinn ./filename.js, sh filename.jsund node filename.jsalle arbeitet.

(Auf jeden Fall ein bisschen seltsam, aber trotzdem effektiv.)


Einige Erklärungen, wie gewünscht:

  • Shell-Skripte werden zeilenweise ausgewertet; und der execBefehl, wenn ausgeführt, beendet die Schale und ersetzt es den Prozeß mit dem resultierenden Befehl. Dies bedeutet, dass das Programm für die Shell folgendermaßen aussieht:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Solange im Wort keine Parametererweiterung oder kein Aliasing auftritt, kann jedes Wort in einem Shell-Skript in Anführungszeichen gesetzt werden, ohne seine Bedeutung zu ändern. Dies bedeutet, dass dies ':'äquivalent zu ist :(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 (übersetzt : //oder, wenn Sie die Wörter lieber zitieren möchten) ':' '//'. Beachten Sie, dass der Befehl //hier keine besondere Bedeutung hat, wie dies in JavaScript der Fall ist. Es ist nur ein bedeutungsloses Wort, das weggeworfen wird.)

  • Schließlich ist der zweite Befehl in der ersten Zeile (nach dem Semikolon) das eigentliche Kernstück des Programms: Es ist der execAufruf, der das aufgerufene Shell-Skript durch einen Node.js-Prozess ersetzt, der aufgerufen wird, um den Rest des Skripts auszuwerten .

  • In der Zwischenzeit wird die erste Zeile in JavaScript als Zeichenfolgenliteral ( ':') und dann als Kommentar analysiert , der gelöscht wird. Für JavaScript sieht das Programm also folgendermaßen aus:

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

    Da sich das String-Literal in einer eigenen Zeile befindet, handelt es sich um eine No-Op-Anweisung und wird daher aus dem Programm entfernt. Das bedeutet, dass die gesamte Zeile entfernt wird und nur Ihr Programmcode (in diesem Beispiel der function(){ ... }Text) übrig bleibt .

ELLIOTTCABLE
quelle
Hallo, kannst du was erklären : //;und ~function(){}tun? Vielen Dank:)
Stphane
1
@Stphane Eine Aufschlüsselung hinzugefügt! Wie für die ~function(){}, das ist ein wenig komplizierter. Es gibt hier noch ein paar andere Antworten, die sich darauf beziehen, obwohl keine von ihnen es wirklich zu meiner Zufriedenheit erklärt. Wenn keine dieser Fragen es für Sie gut genug erklärt, können Sie es hier als Frage posten Gerne beantworte ich eine neue Frage ausführlich.
ELLIOTTCABLE
1
Ich habe nicht darauf geachtet node. Der Funktionsteil dreht sich also nur um Javascript! Ich bin ok mit einem unären Operator vor IIFE. Ich dachte, das wäre in erster Linie auch Bash und habe die Bedeutung Ihres Beitrags nicht wirklich verstanden. Ich bin jetzt in Ordnung, danke für Ihre Zeit, die Sie damit verbracht haben, «Zusammenbruch» hinzuzufügen!
Stphane
~{ No problem. (= }
ELLIOTTCABLE
12

Selbstdokumentierende Funktionen

Sie können :die Dokumentation auch in eine Funktion einbetten.

Angenommen, Sie haben ein Bibliotheksskript mylib.sh, das eine Vielzahl von Funktionen bietet. Sie können entweder die Bibliothek ( . mylib.sh) als Quelle verwenden und die Funktionen direkt danach aufrufen ( lib_function1 arg1 arg2) oder vermeiden, dass Ihr Namespace überfüllt ist, und die Bibliothek mit einem Funktionsargument ( mylib.sh lib_function1 arg1 arg2) aufrufen .

Wäre es nicht schön, wenn Sie auch mylib.sh --helpeine Liste der verfügbaren Funktionen und deren Verwendung eingeben und abrufen könnten, ohne die Funktionsliste im Hilfetext manuell pflegen zu müssen?

#! / bin / bash

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

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

    # Hier beginnt der eigentliche Funktionscode
}}

lib_function2 () {
    : Funktionsdokumentation

    # Funktionscode hier
}}

# Hilfefunktion
--Hilfe() {
    Echo MyLib v0.0.1
    Echo
    echo Verwendung: mylib.sh [Funktionsname [Argumente]]
    Echo
    echo Verfügbare Funktionen:
    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 bezogen
    # die angeforderte Funktion aufrufen oder Hilfe anzeigen
    if ["$ (Typ -t -" $ 1 "2> / dev / null)" = Funktion]; dann
        "$ @"
    sonst
        --Hilfe
    fi
fi

Einige Kommentare zum Code:

  1. Alle "öffentlichen" Funktionen haben das gleiche Präfix. Nur diese sollen vom Benutzer aufgerufen und im Hilfetext aufgeführt werden.
  2. Die selbstdokumentierende Funktion basiert auf dem vorherigen Punkt und declare -fzählt alle verfügbaren Funktionen auf. Anschließend werden sie durch sed gefiltert, um nur Funktionen mit dem entsprechenden Präfix anzuzeigen.
  3. Es ist eine gute Idee, die Dokumentation in einfache Anführungszeichen zu setzen, um unerwünschte Erweiterungen und das Entfernen von Leerzeichen zu verhindern. Sie müssen auch vorsichtig sein, wenn Sie Apostrophe / Anführungszeichen im Text verwenden.
  4. Sie können Code schreiben, um das Bibliothekspräfix zu verinnerlichen, dh der Benutzer muss nur eingeben mylib.sh function1und es wird intern in übersetzt lib_function1. Dies ist eine Übung, die dem Leser überlassen bleibt.
  5. Die Hilfefunktion heißt "--help". Dies ist ein praktischer (dh fauler) Ansatz, bei dem der Mechanismus zum Aufrufen der Bibliothek verwendet wird, um die Hilfe selbst anzuzeigen, ohne dass eine zusätzliche Prüfung erforderlich ist $1. Gleichzeitig wird Ihr Namespace unübersichtlich, wenn Sie die Bibliothek als Quelle verwenden. Wenn Ihnen das nicht gefällt, können Sie entweder den Namen in einen ähnlichen Namen ändern lib_helpoder die Argumente --helpim Hauptcode überprüfen und die Hilfefunktion manuell aufrufen.
Sir Athos
quelle
4

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

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

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

Griff Derryberry
quelle
Ich bevorzugebasetool=${0##*/}
Amit Naidu