Qual è lo scopo del: (colon) GNU Bash incorporato?


336

Qual è lo scopo di un comando che non fa nulla, essendo poco più che un commentatore, ma in realtà è una shell incorporata in sé e per sé?

È più lento dell'inserimento di un commento negli script di circa il 40% per chiamata, che probabilmente varia notevolmente a seconda della dimensione del commento. Le uniche possibili ragioni che posso vedere per questo sono queste:

# 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

Immagino che quello che sto veramente cercando è quale applicazione storica avrebbe potuto avere.



69
@Caleb - L'ho chiesto due anni prima di quello.
anfetamachina,

Non direi un comando che restituisce un valore specifico "non fa nulla". A meno che la programmazione funzionale non consista nel "non fare nulla". :-)
LarsH

Risposte:


415

Storicamente , le shell Bourne non avevano truee falsecome comandi integrati. trueera invece semplicemente aliasato :e falsequalcosa del genere let 0.

:è leggermente migliore rispetto truealla portabilità alle antiche conchiglie derivate da Bourne. A titolo di esempio, si consideri di non avere né l' !operatore pipeline né l' ||operatore elenco (come nel caso di alcune antiche shell Bourne). Questo lascia la elseclausola ifdell'istruzione come unico mezzo per la ramificazione in base allo stato di uscita:

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

Poiché ifrichiede una thenclausola non vuota e i commenti non contano come non vuoti, :funge da no-op.

Al giorno d'oggi (vale a dire: in un contesto moderno) di solito è possibile utilizzare :o true. Entrambi sono specificati da POSIX e alcuni lo trovano truepiù facile da leggere. Tuttavia c'è una differenza interessante: :è un cosiddetto incorporato speciale POSIX , mentre trueè un normale incorporato .

  • Sono necessari incorporamenti speciali da incorporare nella shell; I built-in normali sono solo "tipicamente" integrati, ma non sono strettamente garantiti. Di solito non dovrebbe esserci un programma normale chiamato :con la funzione truein PATH della maggior parte dei sistemi.

  • Probabilmente la differenza più cruciale è che con gli speciali incorporati, qualsiasi variabile impostata dall'integrato - anche nell'ambiente durante la semplice valutazione del comando - persiste dopo il completamento del comando, come dimostrato qui usando ksh93:

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

    Si noti che Zsh ignora questo requisito, così come GNU Bash, tranne quando si opera in modalità di compatibilità POSIX, ma tutte le altre principali shell "POSIX sh derivate" osservano questo incluso dash, ksh93 e mksh.

  • Un'altra differenza è che i normali built-in devono essere compatibili con exec- dimostrato qui usando Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX rileva inoltre esplicitamente che :potrebbe essere più veloce di true, sebbene questo sia ovviamente un dettaglio specifico dell'implementazione.


Intendevi che i normali built-in non devono essere compatibili exec?
Vecchio Pro,

1
@OldPro: No, ha ragione nel fatto che trueè un built-in normale, ma non è corretto nel fatto che execsta usando /bin/trueinvece il built-in.
In pausa fino a ulteriore avviso.

1
@DennisWilliamson Stavo solo seguendo le istruzioni. L'implicazione è ovviamente che i builtin regolari dovrebbero avere anche una versione autonoma presente.
ormaaj,

17
+1 Risposta eccellente. Vorrei ancora notare l'uso per inizializzare le variabili, come : ${var?not initialized}et al.
triplo il

Un seguito più o meno non correlato. Hai detto che :è uno speciale incorporato e non dovrebbe avere una funzione chiamata da esso. Ma l'esempio più comunemente visto della :(){ :|: & };:denominazione della bomba a forcella non è una funzione con nome :?
Chong,

63

Lo uso per abilitare / disabilitare facilmente i comandi variabili:

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

$vecho "Verbose echo is ON"

così

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

Questo crea uno script pulito. Questo non può essere fatto con '#'.

Anche,

: >afile

è uno dei modi più semplici per garantire che "afile" esista ma abbia una lunghezza pari a 0.


20
>afileè ancora più semplice e ottiene lo stesso effetto.
conte

2
Bene, userò quel trucco di $ vecho per semplificare gli script che sto mantenendo.
BarneySchmale,

5
Qual è il vantaggio di citare i due punti vecho=":"? Solo per leggibilità?
leoj,

56

Un'applicazione utile per: è se sei interessato solo ad utilizzare le espansioni dei parametri per i loro effetti collaterali piuttosto che passare effettivamente il loro risultato a un comando. In quel caso usi PE come argomento per: o false a seconda che tu voglia uno stato di uscita di 0 o 1. Un esempio potrebbe essere : "${var:=$1}". Poiché :è incorporato, dovrebbe essere piuttosto veloce.


2
Puoi anche usarlo per gli effetti collaterali dell'espansione aritmetica: : $((a += 1))( ++e gli --operatori non devono essere implementati secondo POSIX.). In bash, ksh e altre possibili shell puoi anche fare: ((a += 1))o ((a++))ma non è specificato da POSIX.
pabouk,

@pabouk Sì, (())è vero, sebbene sia specificato come funzionalità opzionale. "Se una sequenza di caratteri che inizia con" (("verrebbe analizzata dalla shell come espansione aritmetica se preceduta da un '$', le shell che implementano un'estensione per cui" ((espressione)) "viene valutata come espressione aritmetica possono trattare il "((" come introduzione come valutazione aritmetica anziché come comando di raggruppamento. "
ormaaj

50

:può anche essere per il commento a blocchi (simile a / * * / in linguaggio C). Ad esempio, se vuoi saltare un blocco di codice nello script, puoi farlo:

: << 'SKIP'

your code block here

SKIP

3
Cattiva idea. Eventuali sostituzioni di comandi all'interno del documento qui vengono comunque elaborate.
Chepner,

33
Non è una cattiva idea. Puoi evitare la risoluzione / sostituzione variabile nei documenti qui citando una sola virgola del delimitatore:: << 'SKIP'
Rondo

1
IIRC si può anche \ sfuggire nessuno dei caratteri di delimitazione per lo stesso effetto: : <<\SKIP.
yyny,

@zagpoint È qui che Python si serve dei docstring come commenti multilinea?
Sapphire_Brick il

31

Se desideri troncare un file a zero byte, utile per cancellare i log, prova questo:

:> file.log

16
> file.logè più semplice e ottiene lo stesso effetto.
anfetamachina,

55
Sì, ma la faccia felice è ciò che fa per me:>
Ahi Tuna,

23
@amphetamachine: :>è più portatile. Alcune shell (come la mia zsh) eseguono un'istanza automatica di un gatto nella shell corrente e ascoltano lo stdin quando viene assegnato un reindirizzamento senza alcun comando. Piuttosto che cat /dev/null, :è molto più semplice. Spesso questo comportamento è diverso nelle shell interattive piuttosto che negli script, ma se si scrive lo script in modo che funzioni anche interattivamente, il debug tramite copia-incolla è molto più semplice.
Caleb,

2
In che modo : > filedifferisce da true > file(a parte il conteggio dei personaggi e la faccia felice) in una shell moderna (supponendo :che truesiano altrettanto veloci)?
Adam Katz,


29

Altri due usi non menzionati in altre risposte:

Registrazione

Prendi questo script di esempio:

set -x
: Logging message here
example_command

La prima riga, set -xfa in modo che la shell stampi il comando prima di eseguirlo. È un costrutto abbastanza utile. Il rovescio della medaglia è che il solito echo Log messagetipo di istruzione ora stampa il messaggio due volte. Il metodo dei due punti lo aggira. Nota che dovrai comunque scappare da personaggi speciali proprio come faresti per te echo.

Titoli di lavoro Cron

Ho visto che viene utilizzato nei lavori cron, in questo modo:

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

Questo è un cron job che esegue lo script /opt/backup.shogni giorno alle 10:45. Il vantaggio di questa tecnica è che rende gli oggetti e-mail più belli quando si /opt/backup.shstampa un output.


Dov'è la posizione del registro predefinita? Posso impostare la posizione del registro? Lo scopo è maggiormente quello di creare output nello stdout durante gli script / i processi in background?
domdambrogia,

1
@domdambrogia Durante l'uso set -x, i comandi stampati (incluso qualcosa del genere : foobar) vanno su stderr.
Flimm,

23

Puoi usarlo insieme a backticks ( ``) per eseguire un comando senza visualizzare il suo output, in questo modo:

: `some_command`

Certo che potresti fare some_command > /dev/null, ma la :versione è leggermente più breve.

Detto questo, non consiglierei di farlo, poiché confonderebbe semplicemente le persone. Mi è appena venuto in mente un possibile caso d'uso.


25
Questo non è sicuro se il comando scaricherà alcuni megabyte di output, poiché la shell buffer l'output e quindi lo passa come argomenti della riga di comando (spazio dello stack) a ':'.
Juliano,

15

È anche utile per i programmi poliglotta:

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

Questo è ora sia un guscio-script eseguibile e un programma JavaScript: significato ./filename.js, sh filename.jse node filename.jstutto il lavoro.

(Sicuramente un po 'strano, ma comunque efficace.)


Qualche spiegazione, come richiesto:

  • Gli script di shell vengono valutati riga per riga; e il execcomando, quando eseguito, termina la shell e sostituisce il suo processo con il comando risultante. Ciò significa che per la shell, il programma è simile al seguente:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Finché non si verifica alcuna espansione o alias dei parametri nella parola, qualsiasi parola in uno script di shell può essere racchiusa tra virgolette senza cambiarne il significato; questo significa che ':'è equivalente a :(l'abbiamo racchiuso tra virgolette qui per ottenere la semantica JavaScript descritta di seguito)

  • ... e come descritto sopra, il primo comando sulla prima riga è un no-op (si traduce in : //, o se si preferisce citare le parole ':' '//',. Notare che //qui non ha alcun significato speciale, come fa in JavaScript; è solo una parola insignificante che viene buttata via.)

  • Infine, il secondo comando sulla prima riga (dopo il punto e virgola) è la vera carne del programma: è la execchiamata che sostituisce lo shell-script invocato , con un processo Node.js invocato per valutare il resto dello script.

  • Nel frattempo, la prima riga, in JavaScript, viene analizzata come stringa-letterale ( ':') e quindi un commento, che viene eliminato; quindi, a JavaScript, il programma si presenta così:

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

    Dal momento che il letterale stringa è su una riga da solo, è un'istruzione no-op e viene quindi rimosso dal programma; ciò significa che viene rimossa l'intera riga, lasciando solo il codice del programma (in questo esempio, il function(){ ... }corpo).


Ciao, puoi spiegare cosa : //;e cosa ~function(){}faccio? Grazie:)
Stphane,

1
@Stphane Aggiunta una suddivisione! Per quanto riguarda il ~function(){}, è un po 'più complicato. Ci sono un paio di altre risposte qui che lo toccano, sebbene nessuna di esse lo spieghi davvero con mia soddisfazione ... se nessuna di queste domande lo spiega abbastanza bene per te, sentiti libero di pubblicarlo come una domanda qui, sarò felice di rispondere in modo approfondito a una nuova domanda.
ELLIOTTCABLE

1
Non ho prestato attenzione node. Quindi la parte funzionale riguarda javascript! Sto bene con un operatore unario di fronte a IIFE. Pensavo che anche questo fosse bash in primo luogo e in realtà non ho davvero capito il significato del tuo post. Sono a posto ora, grazie per il tempo speso aggiungendo «guasto»!
Stphane,

~{ No problem. (= }
ELLIOTTCABLE

12

Funzioni di auto-documentazione

È inoltre possibile utilizzare :per incorporare la documentazione in una funzione.

Supponiamo di avere uno script di libreria mylib.sh, che offre una varietà di funzioni. È possibile o generare la libreria ( . mylib.sh) e chiamare le funzioni direttamente dopo quella ( lib_function1 arg1 arg2), oppure evitare di ingombrare lo spazio dei nomi e invocare la libreria con un argomento di funzione (mylib.sh lib_function1 arg1 arg2 ).

Non sarebbe bello se si potesse anche digitare mylib.sh --helpe ottenere un elenco di funzioni disponibili e il loro utilizzo, senza dover mantenere manualmente l'elenco delle funzioni nel testo della guida?

#! / Bin / bash

# tutte le funzioni "pubbliche" devono iniziare con questo prefisso
LIB_PREFIX = 'lib_'

# funzioni di libreria "pubbliche"
lib_function1 () {
    : Questa funzione fa qualcosa di complicato con due argomenti.
    :
    : Parametri:
    : 'arg1 - primo argomento ($ 1)'
    : 'arg2 - secondo argomento'
    :
    : Risultato:
    : " è complicato"

    # il codice funzione effettivo inizia qui
}

lib_function2 () {
    : Documentazione delle funzioni

    # codice funzione qui
}

# funzione di aiuto
--Aiuto() {
    echo MyLib v0.0.1
    eco
    echo Utilizzo: mylib.sh [nome_funzione [args]]
    eco
    echo Funzioni disponibili:
    dichiarare -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / [ '\' '"] \;? \ $ //;? p}}'
}

# codice principale
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; poi
    # lo script è stato eseguito anziché originato
    # invoca la funzione richiesta o visualizza la guida
    if ["$ (type -t -" $ 1 "2> / dev / null)" = funzione]; poi
        "$ @"
    altro
        --Aiuto
    fi
fi

Alcuni commenti sul codice:

  1. Tutte le funzioni "pubbliche" hanno lo stesso prefisso. Solo questi sono pensati per essere invocati dall'utente e per essere elencati nel testo di aiuto.
  2. La funzione di auto-documentazione si basa sul punto precedente e utilizza declare -fper enumerare tutte le funzioni disponibili, quindi le filtra attraverso sed per visualizzare solo le funzioni con il prefisso appropriato.
  3. È una buona idea racchiudere la documentazione tra virgolette singole, per evitare l'espansione indesiderata e la rimozione di spazi bianchi. Dovrai anche fare attenzione quando usi apostrofi / citazioni nel testo.
  4. È possibile scrivere codice per interiorizzare il prefisso della libreria, ovvero l'utente deve solo digitare mylib.sh function1e viene tradotto internamente lib_function1. Questo è un esercizio lasciato al lettore.
  5. La funzione di aiuto è denominata "--help". Questo è un approccio conveniente (cioè pigro) che utilizza il meccanismo di invocazione della libreria per visualizzare l'aiuto stesso, senza dover codificare un controllo extra per $1. Allo stesso tempo, ingombrerà il tuo spazio dei nomi se provi la libreria. Se non ti piace, puoi cambiare il nome in qualcosa di simile lib_helpo effettivamente controllare gli argomenti --helpnel codice principale e invocare la funzione di aiuto manualmente.

4

Ho visto questo uso in uno script e ho pensato che fosse un buon sostituto per invocare il nome di base all'interno di uno script.

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

... questo è un sostituto del codice: basetool=$(basename $0)


Preferiscobasetool=${0##*/}
Amit Naidu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.