Quel est le but de: (deux points) la commande intégrée GNU Bash?

336

Quel est le but d'une commande qui ne fait rien, étant un peu plus qu'un leader de commentaire, mais est en fait un shell intégré en soi?

C'est plus lent que l'insertion d'un commentaire dans vos scripts d'environ 40% par appel, ce qui varie probablement considérablement en fonction de la taille du commentaire. Les seules raisons possibles que je peux voir sont les suivantes:

# 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

Je suppose que ce que je recherche vraiment, c'est quelle application historique il aurait pu avoir.

amphétamachine
la source
69
@Caleb - J'ai demandé cela deux ans avant celui-là.
amphétamachine
Je ne dirais pas qu'une commande qui renvoie une valeur spécifique "ne fait rien". Sauf si la programmation fonctionnelle consiste à «ne rien faire». :-)
LarsH

Réponses:

415

Historiquement , les shells Bourne n'avaient pas trueet falsecomme intégré des commandes. trueétait plutôt simplement aliasé :, et falsequelque chose comme let 0.

:est légèrement meilleur que truepour la portabilité aux anciennes coquilles dérivées de Bourne. À titre d'exemple simple, envisagez de n'avoir ni l' !opérateur de pipeline ni l' ||opérateur de liste (comme c'était le cas pour certains anciens obus Bourne). Cela laisse la elseclause de l' ifinstruction comme le seul moyen de branchement basé sur le statut de sortie:

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

Étant donné que ifrequiert une thenclause non vide et que les commentaires ne comptent pas comme non vides, :sert de no-op.

De nos jours (c'est-à-dire: dans un contexte moderne), vous pouvez généralement utiliser soit :ou true. Les deux sont spécifiés par POSIX, et certains trouvent trueplus faciles à lire. Cependant, il y a une différence intéressante: il :s'agit d'un soi-disant POSIX spécial intégré , alors qu'il trueest un intégré standard .

  • Des éléments intégrés spéciaux doivent être intégrés à la coque; Les fonctions intégrées régulières ne sont «généralement» intégrées, mais elles ne sont pas strictement garanties. Il ne devrait généralement pas y avoir de programme régulier nommé :avec la fonction de truedans PATH de la plupart des systèmes.

  • La différence la plus cruciale est probablement qu'avec les modules intégrés spéciaux, toute variable définie par le module intégré - même dans l'environnement lors de l'évaluation d'une commande simple - persiste après la fin de la commande, comme illustré ici en utilisant ksh93:

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

    Notez que Zsh ignore cette exigence, tout comme GNU Bash sauf lorsqu'il fonctionne en mode de compatibilité POSIX, mais tous les autres principaux shells "dérivés POSIX sh" observent cela, y compris dash, ksh93 et ​​mksh.

  • Une autre différence est que les fonctions intégrées régulières doivent être compatibles avec exec- démontré ici en utilisant Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX note également explicitement que cela :peut être plus rapide que true, bien qu'il s'agisse bien sûr d'un détail spécifique à l'implémentation.

comte
la source
Voulez-vous dire que les fonctions intégrées standard ne doivent pas être compatibles avec exec?
Old Pro
1
@OldPro: Non, il a raison en ce sens qu'il trues'agit d'une intégration standard, mais il est incorrect en ce qu'il executilise à la /bin/trueplace de l'intégration.
pause jusqu'à nouvel ordre.
1
@DennisWilliamson J'allais juste par la façon dont la spécification est formulée. L'implication est bien sûr que les buildins réguliers devraient également avoir une version autonome présente.
ormaaj
17
+1 Excellente réponse. Je voudrais quand même noter l'utilisation pour l'initialisation des variables, comme : ${var?not initialized}et al.
tripleee
Un suivi plus ou moins indépendant. Vous avez dit que :c'est une fonction spéciale intégrée et ne devrait pas avoir de fonction nommée par elle. Mais l'exemple le plus courant de bombe à fourche ne :(){ :|: & };:nomme-t-il pas une fonction avec un nom :?
Chong
63

Je l'utilise pour activer / désactiver facilement les commandes variables:

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

$vecho "Verbose echo is ON"

Donc

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

Cela en fait un script propre. Cela ne peut pas être fait avec '#'.

Aussi,

: >afile

est l'un des moyens les plus simples de garantir que «afile» existe mais a une longueur nulle.

Kevin Little
la source
20
>afileest encore plus simple et obtient le même effet.
Earl
2
Cool, je vais utiliser cette astuce $ vecho pour simplifier les scripts que je gère.
BarneySchmale
5
Quel est l'avantage de citer deux points vecho=":"? Juste pour la lisibilité?
leoj
56

Une application utile pour: est si vous souhaitez uniquement utiliser des extensions de paramètres pour leurs effets secondaires plutôt que de transmettre leur résultat à une commande. Dans ce cas, vous utilisez le PE comme argument pour: ou false selon que vous souhaitez un état de sortie de 0 ou 1. Un exemple peut être : "${var:=$1}". Puisqu'il :est intégré, il devrait être assez rapide.

ormaaj
la source
2
Vous pouvez également l'utiliser pour les effets secondaires de l'expansion arithmétique: : $((a += 1))( ++et les --opérateurs n'ont pas besoin d'être implémentés selon POSIX.). En bash, ksh et d'autres shells possibles, vous pouvez aussi faire: ((a += 1))ou ((a++))mais ce n'est pas spécifié par POSIX.
pabouk
@pabouk Oui, tout cela est vrai, mais (())est spécifié comme une fonctionnalité facultative. "Si une séquence de caractères commençant par" (("serait analysée par le shell comme une expansion arithmétique si elle était précédée d'un '$', les shells qui implémentent une extension par laquelle" ((expression)) "est évalué comme une expression arithmétique peut traiter le "((" comme présentant comme une évaluation arithmétique au lieu d'une commande de regroupement. "
ormaaj
50

:peut également être pour le commentaire de bloc (similaire à / * * / en langage C). Par exemple, si vous souhaitez ignorer un bloc de code dans votre script, vous pouvez le faire:

: << 'SKIP'

your code block here

SKIP
zagpoint
la source
3
Mauvaise idée. Toutes les substitutions de commandes à l'intérieur du document ici sont toujours traitées.
chepner
33
Pas une si mauvaise idée. Vous pouvez éviter la résolution / substitution variable dans les documents ici en citant le délimiteur: << 'SKIP'
Rondo
1
IIRC vous pouvez aussi \ échapper à l' un des caractères délimiteurs pour le même effet: : <<\SKIP.
yyny
@zagpoint Est-ce là que Python obtient son utilisation des docstrings comme commentaires multilignes?
Sapphire_Brick
31

Si vous souhaitez tronquer un fichier à zéro octet, utile pour effacer les journaux, essayez ceci:

:> file.log
Ahi Tuna
la source
16
> file.logest plus simple et obtient le même effet.
amphetamachine
55
Yah, mais le visage heureux est ce que ça fait pour moi:>
Ahi Tuna
23
@amphetamachine: :>est plus portable. Certains shells (comme le mien zsh) instancient automatiquement un chat dans le shell actuel et écoutent stdin lorsqu'ils reçoivent une redirection sans commande. Plutôt que cat /dev/null, :c'est beaucoup plus simple. Souvent, ce comportement est différent dans les shells interactifs plutôt que dans les scripts, mais si vous écrivez le script d'une manière qui fonctionne également de manière interactive, le débogage par copier-coller est beaucoup plus facile.
Caleb
2
En quoi : > filediffère- t-il true > file(à part le nombre de personnages et le visage heureux) dans une coque moderne (en supposant :et truetout aussi rapide)?
Adam Katz
30

C'est similaire à passPython.

Une utilisation serait de bloquer une fonction jusqu'à ce qu'elle soit écrite:

future_function () { :; }
En pause jusqu'à nouvel ordre.
la source
29

Deux autres utilisations non mentionnées dans d'autres réponses:

Enregistrement

Prenez cet exemple de script:

set -x
: Logging message here
example_command

La première ligne set -x,, fait que le shell imprime la commande avant de l'exécuter. C'est une construction assez utile. L'inconvénient est que le echo Log messagetype habituel de déclaration imprime désormais le message deux fois. La méthode du côlon contourne cela. Notez que vous devrez toujours échapper les caractères spéciaux comme vous le feriez pour echo.

Titres de poste Cron

Je l'ai vu utilisé dans des tâches cron, comme ceci:

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

Il s'agit d'un travail cron qui exécute le script /opt/backup.shtous les jours à 10h45. L'avantage de cette technique est qu'elle permet de mieux voir les sujets des e-mails lors de l' /opt/backup.shimpression d'une sortie.

Flimm
la source
Où est l'emplacement du journal par défaut? Puis-je définir l'emplacement du journal? Le but est-il davantage de créer une sortie dans la sortie standard pendant les scripts / processus d'arrière-plan?
domdambrogia
1
@domdambrogia Lors de l'utilisation set -x, les commandes imprimées (y compris quelque chose comme : foobar) vont à stderr.
Flimm
23

Vous pouvez l'utiliser en conjonction avec backticks ( ``) pour exécuter une commande sans afficher sa sortie, comme ceci:

: `some_command`

Bien sûr, vous pouvez le faire some_command > /dev/null, mais la :version-est un peu plus courte.

Cela étant dit, je ne recommanderais pas de le faire, car cela ne ferait qu'embrouiller les gens. Cela m'est venu à l'esprit comme un cas d'utilisation possible.

sepp2k
la source
25
Ce n'est pas sûr si la commande va vider quelques mégaoctets de sortie, car le shell met en mémoire tampon la sortie, puis la transmet en tant qu'arguments de ligne de commande (espace de pile) à «:».
Juliano
15

Il est également utile pour les programmes polyglottes:

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

Ceci est maintenant à la fois un script shell exécutable et un programme JavaScript: ce qui signifie ./filename.js, sh filename.jset node filename.jstout le travail.

(Certainement un peu d'une utilisation étrange, mais néanmoins efficace.)


Quelques explications, comme demandé:

  • Les scripts shell sont évalués ligne par ligne; et la execcommande, lorsqu'elle est exécutée, termine le shell et remplace son processus par la commande résultante. Cela signifie que pour le shell, le programme ressemble à ceci:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Tant qu'aucune expansion de paramètre ou alias ne se produit dans le mot, n'importe quel mot dans un script shell peut être entouré de guillemets sans changer sa 'signification; cela signifie que ':'c'est équivalent à :(nous l'avons seulement mis entre guillemets ici pour obtenir la sémantique JavaScript décrite ci-dessous)

  • ... et comme décrit ci-dessus, la première commande sur la première ligne est un no-op (cela se traduit par : //, ou si vous préférez citer les mots ':' '//',. Notez que le //ne porte aucune signification particulière ici, comme il le fait en JavaScript; c'est juste un mot vide de sens qui est jeté.)

  • Enfin, la deuxième commande sur la première ligne (après le point-virgule), est la vraie viande du programme: c'est l' execappel qui remplace le shell-script invoqué , avec un processus Node.js invoqué pour évaluer le reste du script.

  • Pendant ce temps, la première ligne, en JavaScript, est analysée comme un littéral de chaîne ( ':'), puis un commentaire, qui est supprimé; ainsi, pour JavaScript, le programme ressemble à ceci:

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

    Puisque le littéral de chaîne est sur une ligne par lui-même, c'est une instruction no-op, et est donc supprimé du programme; cela signifie que la ligne entière est supprimée, ne laissant que votre code de programme (dans cet exemple, le function(){ ... }corps.)

ELLIOTTCABLE
la source
Bonjour, pouvez-vous expliquer quoi : //;et ~function(){}faire? Merci:)
Stéphane
1
@Stphane Ajout d'une panne! Quant au ~function(){}, c'est un peu plus compliqué. Il y a quelques autres réponses ici qui touchent à cela, bien qu'aucune ne l'explique vraiment à ma satisfaction ... si aucune de ces questions ne l'explique assez bien pour vous, n'hésitez pas à la poster comme une question ici, je serai heureux de répondre en profondeur à une nouvelle question.
ELLIOTTCABLE
1
Je n'y ai pas fait attention node. Donc, la partie fonction est entièrement javascript! Je suis d'accord avec un opérateur unaire devant l'IIFE. Je pensais que c'était trop bash en premier lieu et en fait, je n'ai pas vraiment compris le sens de votre message. Je vais bien maintenant, merci pour le temps que vous avez passé à ajouter «décomposition»!
Stéphane
~{ No problem. (= }
ELLIOTTCABLE
12

Fonctions d'auto-documentation

Vous pouvez également utiliser :pour incorporer de la documentation dans une fonction.

Supposons que vous ayez un script de bibliothèque mylib.sh, offrant une variété de fonctions. Vous pouvez soit source la bibliothèque ( . mylib.sh) et appeler les fonctions directement après cela ( lib_function1 arg1 arg2), soit éviter d'encombrer votre espace de noms et appeler la bibliothèque avec un argument de fonction ( mylib.sh lib_function1 arg1 arg2).

Ne serait-ce pas bien si vous pouviez également taper mylib.sh --helpet obtenir une liste des fonctions disponibles et leur utilisation, sans avoir à maintenir manuellement la liste des fonctions dans le texte d'aide?

#! / bin / bash

# toutes les fonctions "publiques" doivent commencer par ce préfixe
LIB_PREFIX = 'lib_'

# fonctions de bibliothèque "publiques"
lib_function1 () {
    : Cette fonction fait quelque chose de compliqué avec deux arguments.
    :
    : Paramètres:
    : 'arg1 - premier argument ($ 1)'
    : 'arg2 - deuxième argument'
    :
    : Résultat:
    : " c'est compliqué"

    # le code de fonction réel commence ici
}

lib_function2 () {
    : Documentation fonction

    # code de fonction ici
}

# fonction d'aide
--Aidez-moi() {
    echo MyLib v0.0.1
    écho
    echo Utilisation: mylib.sh [nom_fonction [arguments]]
    écho
    écho Fonctions disponibles:
    déclarer -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}'
}

# Code principal
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; puis
    # le script a été exécuté au lieu de provenir
    # appeler la fonction demandée ou afficher l'aide
    if ["$ (type -t -" $ 1 "2> / dev / null)" = fonction]; puis
        "$ @"
    autre
        --Aidez-moi
    Fi
Fi

Quelques commentaires sur le code:

  1. Toutes les fonctions "publiques" ont le même préfixe. Seuls ceux-ci sont destinés à être invoqués par l'utilisateur et à être répertoriés dans le texte d'aide.
  2. La fonction d'auto-documentation s'appuie sur le point précédent et l'utilise declare -fpour énumérer toutes les fonctions disponibles, puis les filtre via sed pour n'afficher que les fonctions avec le préfixe approprié.
  3. Il est judicieux de placer la documentation entre guillemets simples, pour éviter une expansion indésirable et la suppression des espaces blancs. Vous devrez également être prudent lorsque vous utilisez des apostrophes / guillemets dans le texte.
  4. Vous pouvez écrire du code pour internaliser le préfixe de la bibliothèque, c'est-à-dire que l'utilisateur n'a qu'à taper mylib.sh function1et il est traduit en interne lib_function1. C'est un exercice laissé au lecteur.
  5. La fonction d'aide est nommée "--help". Il s'agit d'une approche pratique (c'est-à-dire paresseuse) qui utilise le mécanisme d'invocation de bibliothèque pour afficher l'aide elle-même, sans avoir à coder une vérification supplémentaire $1. En même temps, il encombrera votre espace de noms si vous sourcez la bibliothèque. Si vous n'aimez pas cela, vous pouvez soit changer le nom en quelque chose comme, lib_helpsoit vérifier les arguments --helpdans le code principal et appeler manuellement la fonction d'aide.
Sir Athos
la source
4

J'ai vu cette utilisation dans un script et j'ai pensé que c'était un bon substitut pour invoquer un nom de base dans un script.

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

... ceci remplace le code: basetool=$(basename $0)

Griff Derryberry
la source
Je préfèrebasetool=${0##*/}
Amit Naidu