Qual è il caso d'uso di noop [:] in bash?


124

Ho cercato noop in bash (:), ma non sono riuscito a trovare nessuna buona informazione. Qual è lo scopo esatto o il caso d'uso di questo operatore?

Ho provato a seguire e funziona così per me:

[mandy@root]$ a=11
[mandy@root]$ b=20
[mandy@root]$ c=30
[mandy@root]$ echo $a; : echo $b ; echo $c
10
30

Per favore fatemi sapere, qualsiasi caso d'uso di questo operatore in tempo reale o qualsiasi luogo in cui sia obbligatorio utilizzarlo.



Nota che il :built-in esiste in bourne shell e ksh così come in bash.
ghoti


6
Come mai questa non è una vera domanda? Penso che sia davvero una buona domanda. Ne faccio anche un buon uso, ma non posso pubblicare una risposta.
Steven Lu

Risposte:


176

C'è di più per ragioni storiche. I due punti incorporati :sono esattamente equivalenti a true. È tradizionale da utilizzare truequando il valore restituito è importante, ad esempio in un ciclo infinito:

while true; do
  echo 'Going on forever'
done

È tradizionale da usare :quando la sintassi della shell richiede un comando ma non hai nulla da fare.

while keep_waiting; do
  : # busy-wait
done

Il :builtin risale alla shell Thompson , era presente in Unix v6 . :era un indicatore di etichetta per l' gotoaffermazione della shell Thompson . L'etichetta potrebbe essere qualsiasi testo, quindi :raddoppiata come indicatore di commento (se non c'è goto comment, allora : commentè effettivamente un commento). Il guscio Bourne non aveva gotoma mantenuto :.

Un idioma comune che utilizza :è : ${var=VALUE}, che imposta varsu VALUEse non era impostato e non fa nulla se varera già impostato. Questo costrutto esiste solo nella forma di una sostituzione di variabile, e questa sostituzione di variabile deve in qualche modo far parte di un comando: un comando no-op serve bene.

Vedi anche A cosa serve il colon incorporato? .


11
Buona sintesi. Inoltre, la shell Bourne originale non veniva usata #per i commenti (o #!/bin/shshebang); il :comando introduceva i commenti, e guai al programmatore ingenuo che nei loro commenti creava una bella scatola di stelle: : ****(o, peggio, : * * *).
Jonathan Leffler

3
@JonathanLeffler: Vergogna su di me, ma non capisco. Cosa succederebbe : * * *?
DevSolar

7
Poiché :è un comando, la shell deve ancora elaborare i suoi argomenti prima di poter scoprire che :li ignora. Per lo più, stai solo facendo in modo che la shell svolga un lavoro extra per espandersi *a un elenco di file nella directory corrente; effettivamente non influenzerà il funzionamento dello script.
chepner

Suppongo che potrebbe far fallire il tuo script se ti capita di eseguire in una directory con molti file, facendo in modo che l'espansione glob superi il limite di lunghezza del comando? Forse solo se ce set -el' hai . Ad ogni modo, si spera che possiamo tutti accettare di usare #:)
Jack O'Connor

2
A volte lo uso while : ; do ... ; donese voglio un ciclo infinito veloce e sporco. (Di solito c'è un sleepin loop, e digito Ctrl-C per ucciderlo.)
Keith Thompson,

21

Lo uso per le istruzioni if ​​quando commento tutto il codice. Ad esempio hai un test:

if [ "$foo" != "1" ]
then
    echo Success
fi

ma vuoi commentare temporaneamente tutto ciò che è contenuto all'interno:

if [ "$foo" != "1" ]
then
    #echo Success
fi

Che fa sì che bash dia un errore di sintassi:

line 4: syntax error near unexpected token `fi'
line 4: `fi'

Bash non può avere blocchi vuoti (WTF). Quindi aggiungi una no-op:

if [ "$foo" != "1" ]
then
    #echo Success
    :
fi

oppure puoi usare il no-op per commentare le righe:

if [ "$foo" != "1" ]
then
    : echo Success
fi

12

Se si utilizza, set- eallora || :è un ottimo modo per non uscire dallo script se si verifica un errore (lo fa passare esplicitamente).


1
L'ho trovato utile per alcune applicazioni che chiamano script di shell. Ad esempio, Automator su Mac uscirà in caso di errore e non ti dirà perché. Ma utilizzare || :successivamente la gestione dell'errore da soli con un exit 0consentirà di visualizzare tutti gli stdout e stderr nella finestra dell'app durante il debug. Considera { foo ... 2>&1; } || :quale è molto più semplice che creare trappole e se-allora più complesse.
Beejor

7

Lo useresti :per fornire un comando che ha successo ma non fa nulla. In questo esempio il comando "verbosity" è disattivato per impostazione predefinita, impostandolo su :. L'opzione "v" lo attiva.

#!/bin/sh
# example
verbosity=:                         
while getopts v OPT ; do          
   case $OPT in                  
       v)        
           verbosity=/bin/realpath 
       ;;
       *)
           exit "Cancelled"
       ;;             
   esac                          
done                              

# `$verbosity` always succeeds by default, but does nothing.                              
for i in * ; do                   
  echo $i $($verbosity $i)         
done                              

$ example
   file

$ example -v
   file /home/me/file  

in senso stretto, assegnare un valore a una variabile significa "fare qualcosa", ecco perché non genera un errore nell'istruzione case.
qodeninja

@qodeninja: Nessuno ha affermato che l'assegnazione della variabile "non ha fatto nulla"; il punto è che il $(verbosity $i)secondo (e condizionatamente) non fa nulla.
Gare di leggerezza in orbita

6

Ignora gli aliasargomenti

A volte vuoi avere un alias che non accetta argomenti. Puoi farlo usando ::

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there

non il downvoter, ma questa risposta non dimostra come :funziona, sfortunatamente. L'esempio che hai fornito sembra ridondante.
qodeninja

2
La domanda non è come funziona, ma qual è il caso d'uso . È vero che la mia risposta è un po 'simile a stackoverflow.com/a/37170755/6320039 . Ma non è davvero lo stesso caso d'uso. Sarei felice di migliorare la mia risposta, ma non so davvero come qui ..
Ulysse BN

1
Questo è un po 'strano, un caso d'angolo, se vuoi, ma comunque piuttosto interessante e potrebbe essere un trucco intelligente da avere nella tasca posteriore dei pantaloni.
Mike S

2
Lo stesso può essere ottenuto con#
Zoey Hewll

2
@ZoeyHewll, non proprio. Poiché gli alias vengono sostituiti testualmente prima di qualsiasi tipo di esecuzione, l'uso di un alias per #farebbe sì che tutto ciò che viene sintatticamente dopo sulla stessa riga venga ignorato. Questo è davvero diverso (considera my_alias arg ; other_commando, al contrario, my_alias arg1 {BACKSLASH}{NEWLINE} arg2) e probabilmente non è quello che vuoi in quanto potrebbe causare errori di sintassi (indovina cosa while my_alias ; do true ; doneo while true ; do my_alias ; donefarà).
Maëlan

4

Un utilizzo è come commenti su più righe o per commentare parte del codice a scopo di test usandolo insieme a un file here.

: << 'EOF'

This part of the script is a commented out

EOF

Non dimenticare di usare le virgolette in EOFmodo che qualsiasi codice all'interno non venga valutato, come $(foo). Inoltre potrebbe essere utile utilizzare un nome di terminazione intuitivo come NOTES, SCRATCHPADo TODO.


Fantastico trucco! Specialmente per gli appunti e il codice che richiedono dozzine di "#" quando sono incartati. Un effetto collaterale è che, a differenza delle righe di commento, alcuni evidenziatori di sintassi ignoreranno gli heredoc, colorandoli come un normale codice (o almeno un semplice testo). Il che può essere ottimo per esaminare il codice commentato o salvare gli occhi dai paragrafi in un colore di commento al neon. L'unico avvertimento è che tali blocchi dovrebbero essere annotati come commenti nel codice condiviso, poiché la sintassi è unica e potrebbe causare confusione. Poi di nuovo, stiamo parlando di shell, dove il potenziale di confusione è sistemico.
Beejor

3

Due dei miei.

Incorpora commenti POD

Un'applicazione piuttosto originale di :è per incorporare commenti POD negli script bash , in modo che le pagine man possano essere generate rapidamente. Ovviamente, alla fine si riscriverà l'intera sceneggiatura in Perl ;-)

Associazione di funzioni in fase di esecuzione

Questa è una sorta di modello di codice per l'associazione di funzioni in fase di esecuzione. Fi, hai una funzione di debug per fare qualcosa solo se è impostato un determinato flag:

#!/bin/bash
# noop-demo.sh 
shopt -s expand_aliases

dbg=${DBG:-''}

function _log_dbg {
    echo >&2 "[DBG] $@"
}

log_dbg_hook=':'

[ "$dbg" ] && log_dbg_hook='_log_dbg'

alias log_dbg=$log_dbg_hook


echo "Testing noop alias..."
log_dbg 'foo' 'bar'

Ottieni:

$ ./noop-demo.sh 
Testing noop alias...
$ DBG=1 ./noop-demo.sh 
Testing noop alias...
[DBG] foo bar

2

A volte le clausole no-op possono rendere il tuo codice più leggibile.

Può essere una questione di opinione, ma ecco un esempio. Supponiamo che tu abbia creato una funzione che funziona prendendo due percorsi unix. Calcola il "percorso di modifica" necessario per passare da un percorso all'altro. Metti una restrizione sulla tua funzione in base alla quale i percorsi devono iniziare entrambi con una "/" OPPURE entrambi non devono.

function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        true      # continue with function
    else
        return 1  # Skip function.
    fi

Alcuni sviluppatori vorranno rimuovere il no-op ma ciò significherebbe negare il condizionale:

function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" != / || "$fromC" == / ]] && [[ "$toC" == / || "$fromC" != / ]]
    then
        return 1  # Skip function.
    fi

Ora, a mio parere, non è così chiaro dalla clausola if le condizioni in cui vorresti saltare facendo la funzione. Per eliminare il no-op e farlo chiaramente, dovresti spostare la clausola if dalla funzione:

    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        cdPath=$(chgPath pathA pathB)   # (we moved the conditional outside)

Sembra migliore, ma molte volte non possiamo farlo; vogliamo che il controllo venga fatto all'interno della funzione.

Quindi quanto spesso accade questo? Non molto spesso. Forse una o due volte all'anno. Succede abbastanza spesso che dovresti esserne consapevole. Non esito a usarlo quando penso che migliori la leggibilità del mio codice (indipendentemente dalla lingua).


3
Se stai rispondendo a una domanda sullo scopo di :, dovresti usare :, non true, nella risposta. Detto questo, il modo più semplice per negare il condizionale è quello di utilizzare uno [[ ... ]]dei comandi e premettere ad essa !: if ! [[ ( ... && ... ) || ( ... && ... ) ]]; then.
chepner

1
Ah, molto carino, e dovrei votare la mia risposta. Hmmm .... Sto ancora pensando a esempi in cui ho dovuto usare una clausola di divieto. Questo è il meglio che mi è venuto in mente.
Bitdiot

In realtà è solo mantenuto per compatibilità con le versioni precedenti, sebbene : ${...=...}sia probabilmente meno goffo di true ${...=...}. Alcuni potrebbero preferirlo while :; doanche while true; doper la concisione.
chepner

2

In qualche modo correlato a questa risposta , trovo questa no-op piuttosto conveniente per hackerare gli script poliglotti . Ad esempio, ecco un commento valido sia per bash che per vimscript:

":" #    this is a comment
":" #    in bash, ‘:’ is a no-op and ‘#’ starts a comment line
":" #    in vimscript, ‘"’ starts a comment line

Certo, potremmo averlo usato truealtrettanto bene, ma :essere un segno di punteggiatura e non una parola inglese irrilevante rende chiaro che si tratta di un segno di sintassi.


Per quanto riguarda il motivo per cui qualcuno dovrebbe fare una cosa così complicata come scrivere uno script poliglotta (oltre ad essere interessante): si rivela utile in situazioni in cui normalmente scriveremmo diversi file di script in diverse lingue, con file che si Xriferiscono a file Y.

In una tale situazione, combinare entrambi gli script in un unico file poliglotta evita qualsiasi lavoro Xper determinare il percorso a Y(è semplicemente "$0"). Ancora più importante, rende più comodo spostarsi o distribuire il programma.

  • Un esempio comune. C'è un problema ben noto e di vecchia data con gli shebang: la maggior parte dei sistemi (inclusi Linux e Cygwin) consente il passaggio di un solo argomento all'interprete. La seguente faccenda:

    #!/usr/bin/env interpreter --load-libA --load-libB

    lancerà il seguente comando:

    /usr/bin/env "interpreter --load-libA --load-libB" "/path/to/script"

    e non il previsto:

    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"

    Quindi, finiresti per scrivere uno script wrapper, come:

    #!/usr/bin/env sh
    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"

    È qui che entra in scena la poliglossia.

  • Un esempio più specifico. Una volta ho scritto uno script bash che, tra le altre cose, ha invocato Vim. Avevo bisogno di dare a Vim una configurazione aggiuntiva, che poteva essere eseguita con l'opzione --cmd "arbitrary vimscript command here". Tuttavia, quella configurazione era sostanziale, quindi incorporarla in una stringa sarebbe stata terribile (se mai possibile). Quindi, una soluzione migliore era scriverlo in extenso in qualche file di configurazione, quindi far leggere quel file a Vim con -S "/path/to/file". Quindi sono finito con un file bash / vimscript poliglotta.


1

Ho anche usato in esso script per definire le variabili predefinite.


: ${VARIABLE1:=my_default_value}
: ${VARIABLE2:=other_default_value}
call-my-script ${VARIABLE1} ${VARIABLE2}

0

supponi di avere un comando che desideri concatenare al successo di un altro:

cmd="some command..."
$cmd
[ $? -eq 0 ] && some-other-command

ma ora vuoi eseguire i comandi in modo condizionale e vuoi mostrare i comandi che verrebbero eseguiti (dry run):

cmd="some command..."
[ ! -z "$DEBUG" ] && echo $cmd
[ -z "$NOEXEC" ] && $cmd
[ $? -eq 0 ] && {
    cmd="some-other-command"
    [ ! -z "$DEBUG" ] && echo $cmd
    [ -z "$NOEXEC" ] && $cmd
}

quindi se imposti DEBUG e NOEXEC, il secondo comando non viene mai visualizzato. questo perché il primo comando non viene mai eseguito (perché NOEXEC non è vuoto) ma la valutazione di questo fatto ti lascia con un ritorno di 1, il che significa che il comando subordinato non viene mai eseguito (ma lo vuoi perché è un funzionamento a secco). quindi per risolvere questo problema puoi reimpostare il valore di uscita lasciato nello stack con un noop:

[ -z "$NOEXEC" ] && $cmd || :
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.