¿Cuál es el propósito de: (colon) GNU Bash incorporado?

336

¿Cuál es el propósito de un comando que no hace nada, es poco más que un líder de comentarios, pero en realidad es un shell integrado en sí mismo?

Es más lento que insertar un comentario en sus scripts en aproximadamente un 40% por llamada, lo que probablemente varía mucho según el tamaño del comentario. Las únicas razones posibles que puedo ver son:

# 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

Supongo que lo que realmente estoy buscando es qué aplicación histórica podría haber tenido.

anfetamaquina
fuente
69
@Caleb: pregunté esto dos años antes de esa.
anfetamáquina
No diría que un comando que devuelve un valor específico "no hace nada". A menos que la programación funcional consista en "no hacer nada". :-)
LarsH

Respuestas:

415

Históricamente , los shells Bourne no tenían truey falsecomo comandos incorporados. trueen su lugar, simplemente se le asignó un alias :y falsealgo parecido let 0.

:es un poco mejor que la trueportabilidad a las antiguas conchas derivadas de Bourne. Como un ejemplo simple, considere no tener ni el !operador de la tubería ni el ||operador de la lista (como fue el caso de algunas conchas de Bourne antiguas). Esto deja la elsecláusula de la ifdeclaración como el único medio para bifurcar según el estado de salida:

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

Dado que ifrequiere una thencláusula no vacía y los comentarios no cuentan como no vacíos, :sirve como no operativo.

Hoy en día (es decir: en un contexto moderno) generalmente puede usar cualquiera :o true. Ambos están especificados por POSIX, y algunos encuentran truemás fácil de leer. Sin embargo, hay una diferencia interesante: :es un llamado POSIX integrado especial , mientras que truees un incorporado normal .

  • Se requieren incorporaciones especiales para integrarse en el shell; Las incorporaciones regulares solo están incorporadas "típicamente", pero no están estrictamente garantizadas. Por lo general, no debería haber un programa normal denominado :con la función trueen PATH de la mayoría de los sistemas.

  • Probablemente, la diferencia más crucial es que con las funciones integradas especiales, cualquier variable establecida por la función incorporada, incluso en el entorno durante la evaluación de comandos simples, persiste después de que se completa el comando, como se demuestra aquí usando ksh93:

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

    Tenga en cuenta que Zsh ignora este requisito, al igual que GNU Bash, excepto cuando se opera en modo de compatibilidad POSIX, pero todos los demás shells principales "derivados de POSIX sh" lo observan, incluidos dash, ksh93 y mksh.

  • Otra diferencia es que las funciones integradas regulares deben ser compatibles con exec- demostradas aquí usando Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX también señala explícitamente que :puede ser más rápido que true, aunque esto es, por supuesto, un detalle específico de implementación.

conde
fuente
¿Quiso decir que las incorporaciones regulares no deben ser compatibles exec?
Old Pro
1
@OldPro: No, tiene razón en que truees una construcción regular, pero es incorrecto porque execestá usando en /bin/truelugar de la construcción.
Pausado hasta nuevo aviso.
1
@DennisWilliamson Solo estaba yendo por la forma en que está redactada la especificación. La implicación es, por supuesto, que las construcciones regulares también deberían tener una versión independiente presente.
ormaaj
17
+1 Excelente respuesta. Todavía me gustaría señalar el uso para inicializar variables, como : ${var?not initialized}et al.
tripleee
Un seguimiento más o menos no relacionado. Dijiste que :es una función especial incorporada y que no debería tener una función nombrada por ella. ¿Pero no es el ejemplo más comúnmente visto de la bomba tenedor :(){ :|: & };:nombrando una función con nombre :?
Chong
63

Lo uso para habilitar / deshabilitar fácilmente comandos variables:

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

$vecho "Verbose echo is ON"

Así

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

Esto lo convierte en un script limpio. Esto no se puede hacer con '#'.

También,

: >afile

es una de las formas más simples de garantizar que existe 'afile' pero tiene una longitud de 0.

Kevin Little
fuente
20
>afilees aún más simple y logra el mismo efecto.
earl
2
Genial, usaré ese truco de $ vecho para simplificar los scripts que mantengo.
BarneySchmale
55
¿Cuál es el beneficio de citar dos puntos vecho=":"? ¿Solo por legibilidad?
leoj
56

Una aplicación útil para: es si solo está interesado en usar expansiones de parámetros para sus efectos secundarios en lugar de pasar su resultado a un comando. En ese caso, utiliza el PE como argumento para: o falso dependiendo de si desea un estado de salida de 0 o 1. Un ejemplo podría ser : "${var:=$1}". Como :es una construcción, debería ser bastante rápido.

ormaaj
fuente
2
También puede usarlo para los efectos secundarios de la expansión aritmética: : $((a += 1))( ++y los --operadores no necesitan implementarse de acuerdo con POSIX). En bash, ksh y otros shells posibles también puede hacerlo: ((a += 1))o ((a++))pero POSIX no lo especifica.
pabouk
@pabouk Sí, todo eso es cierto, aunque (())se especifica como una característica opcional. "Si una secuencia de caracteres que comienza con" (("sería analizada por el shell como una expansión aritmética precedida por un '$', los shells que implementan una extensión por la cual" ((expresión)) "se evalúa como una expresión aritmética pueden tratar el "((" como introducción como una evaluación aritmética en lugar de un comando de agrupación. "
ormaaj
50

:También puede ser para comentarios de bloque (similar a / * * / en lenguaje C). Por ejemplo, si desea omitir un bloque de código en su secuencia de comandos, puede hacer esto:

: << 'SKIP'

your code block here

SKIP
zagpoint
fuente
3
Mala idea. Cualquier sustitución de comandos dentro del documento aquí todavía se procesa.
chepner
33
No es una mala idea. Puede evitar la resolución / sustitución variable en los documentos aquí citando con comillas simples el delimitador:: << 'SKIP'
Rondo
1
IIRC también se puede \ escapar de cualquiera de los caracteres delimitadores para el mismo efecto: : <<\SKIP.
yyny
@zagpoint ¿Es aquí donde Python utiliza las cadenas de documentos como comentarios de varias líneas?
Sapphire_Brick
31

Si desea truncar un archivo a cero bytes, útil para borrar registros, intente esto:

:> file.log
Ahi Tuna
fuente
16
> file.logEs más simple y logra el mismo efecto.
anfetamachine
55
Yah, pero la cara feliz es lo que hace por mí:>
Ahi Tuna
23
@amphetamachine: :>es más portátil. Algunos shells (como my zsh) crean una instancia automática de un gato en el shell actual y escuchan el stdin cuando reciben una redirección sin comando. En lugar de eso cat /dev/null, :es mucho más simple. A menudo, este comportamiento es diferente en shells interactivos en lugar de scripts, pero si escribe el script de una manera que también funciona de forma interactiva, la depuración mediante copiar y pegar es mucho más fácil.
Caleb
2
¿Cómo : > filedifieren de true > file(aparte del recuento de caracteres y la cara feliz) en una cáscara moderna (asumiendo :y trueson igual de rápido)?
Adam Katz
30

Es similar a passen Python.

Un uso sería desactivar una función hasta que se escriba:

future_function () { :; }
Pausado hasta nuevo aviso.
fuente
29

Dos usos más no mencionados en otras respuestas:

Inicio sesión

Tome este script de ejemplo:

set -x
: Logging message here
example_command

La primera línea, set -xhace que el shell imprima el comando antes de ejecutarlo. Es una construcción bastante útil. La desventaja es que el echo Log messagetipo habitual de declaración ahora imprime el mensaje dos veces. El método de los dos puntos se resuelve. Tenga en cuenta que aún tendrá que escapar de caracteres especiales como lo haría para echo.

Cron puestos de trabajo

He visto que se usa en trabajos cron, como este:

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

Este es un trabajo cron que ejecuta el script /opt/backup.shtodos los días a las 10:45. La ventaja de esta técnica es que mejora el aspecto de los correos electrónicos cuando /opt/backup.shimprime algún resultado.

Flimm
fuente
¿Dónde está la ubicación de registro predeterminada? ¿Puedo configurar la ubicación del registro? ¿El propósito es más para crear resultados en stdout durante los procesos de scripts / fondo?
domdambrogia
1
@domdambrogia Al usar set -x, los comandos impresos (incluyendo algo así : foobar) van a stderr.
Flimm
23

Puede usarlo junto con backticks ( ``) para ejecutar un comando sin mostrar su salida, de esta manera:

: `some_command`

Por supuesto que podrías hacerlo some_command > /dev/null, pero la :versión es algo más corta.

Dicho esto, no recomendaría hacerlo, ya que confundiría a las personas. Simplemente me vino a la mente como un posible caso de uso.

sepp2k
fuente
25
Esto no es seguro si el comando va a volcar unos pocos megabytes de salida, ya que el shell almacena la salida y luego la pasa como argumentos de línea de comando (espacio de pila) a ':'.
Juliano
15

También es útil para programas políglotas:

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

Esto es ahora tanto un shell-script ejecutable y un programa JavaScript: significado ./filename.js, sh filename.jsy node filename.jstodo el trabajo.

(Definitivamente un uso un poco extraño, pero efectivo de todos modos).


Alguna explicación, según lo solicitado:

  • Los scripts de shell se evalúan línea por línea; y el execcomando, cuando se ejecuta, finaliza el shell y reemplaza su proceso con el comando resultante. Esto significa que para el shell, el programa se ve así:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Mientras no se produzca una expansión o alias de parámetros en la palabra, cualquier palabra en un script de shell se puede incluir entre comillas sin cambiar su significado; esto significa que ':'es equivalente a :(solo lo hemos incluido entre comillas aquí para lograr la semántica de JavaScript que se describe a continuación)

  • ... y como se describió anteriormente, el primer comando en la primera línea es un no-op (se traduce : //, o si prefiere citar las palabras ':' '//',. Observe que //aquí no tiene un significado especial, como lo hace en JavaScript; es solo una palabra sin sentido que se está desechando).

  • Finalmente, el segundo comando en la primera línea (después del punto y coma), es la verdadera carne del programa: es la execllamada que reemplaza el script de shell que se invoca , con un proceso Node.js invocado para evaluar el resto del script.

  • Mientras tanto, la primera línea, en JavaScript, se analiza como un literal de cadena ( ':'), y luego un comentario, que se elimina; así, a JavaScript, el programa se ve así:

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

    Dado que el literal de cadena está en una línea por sí mismo, es una declaración no operativa y, por lo tanto, se elimina del programa; eso significa que se elimina toda la línea, dejando solo su código de programa (en este ejemplo, el function(){ ... }cuerpo).

ELLIOTTCABLE
fuente
Hola, ¿puedes explicar qué : //;y ~function(){}hacer? Gracias:)
Stphane
1
@Stphane ¡Se ha añadido un desglose! En cuanto al ~function(){}, eso es un poco más complicado. Hay un par de otras respuestas aquí que lo tocan, aunque ninguna de ellas realmente lo explica a mi satisfacción ... si ninguna de esas preguntas lo explica lo suficientemente bien para usted, no dude en publicarlo como una pregunta aquí, estaré feliz de responder en profundidad sobre una nueva pregunta.
ELLIOTTCABLE
1
No le presté atención node. ¡Entonces la parte de la función tiene que ver con javascript! Estoy bien con el operador unario frente a IIFE. En primer lugar, pensé que esto también era bash y que en realidad no entendí el significado de tu publicación. Estoy bien ahora, ¡gracias por su tiempo dedicado a agregar "desglose"!
Stphane
~{ No problem. (= }
ELLIOTTCABLE
12

Funciones de autodocumentación

También puede usar :para incrustar documentación en una función.

Suponga que tiene un script de biblioteca mylib.sh, que proporciona una variedad de funciones. Puede obtener la biblioteca ( . mylib.sh) y llamar a las funciones directamente después de eso ( lib_function1 arg1 arg2), o evitar abarrotar su espacio de nombres e invocar la biblioteca con un argumento de función ( mylib.sh lib_function1 arg1 arg2).

¿No sería bueno si también pudiera escribir mylib.sh --helpy obtener una lista de funciones disponibles y su uso, sin tener que mantener manualmente la lista de funciones en el texto de ayuda?

#! / bin / bash

# todas las funciones "públicas" deben comenzar con este prefijo
LIB_PREFIX = 'lib_'

# funciones de biblioteca "pública"
lib_function1 () {
    : Esta función hace algo complicado con dos argumentos.
    :
    : Parámetros:
    : 'arg1 - primer argumento ($ 1)'
    : 'arg2 - segundo argumento'
    :
    : Resultado:
    : " Es complicado"

    # el código de función real comienza aquí
}

lib_function2 () {
    : Documentación de funciones

    # código de función aquí
}

# función de ayuda
--ayuda() {
    echo MyLib v0.0.1
    eco
    Uso de echo: mylib.sh [nombre_función [args]]
    eco
    echo Funciones disponibles:
    declarar -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}'
}

# código principal
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; luego
    # el script se ejecutó en lugar de fuente
    # invocar la función solicitada o mostrar ayuda
    if ["$ (tipo -t -" $ 1 "2> / dev / null)" = función]; luego
        PS
    más
        --ayuda
    fi
fi

Algunos comentarios sobre el código:

  1. Todas las funciones "públicas" tienen el mismo prefijo. Solo estos deben ser invocados por el usuario y enumerados en el texto de ayuda.
  2. La función de autodocumentación se basa en el punto anterior y se usa declare -fpara enumerar todas las funciones disponibles, luego las filtra a través de sed para mostrar solo las funciones con el prefijo apropiado.
  3. Es una buena idea incluir la documentación entre comillas simples, para evitar la expansión no deseada y la eliminación de espacios en blanco. También deberá tener cuidado al usar apóstrofes / comillas en el texto.
  4. Puede escribir código para internalizar el prefijo de la biblioteca, es decir, el usuario solo tiene que escribir mylib.sh function1y se traduce internamente lib_function1. Este es un ejercicio que le queda al lector.
  5. La función de ayuda se llama "--help". Este es un enfoque conveniente (es decir, diferido) que utiliza el mecanismo de invocación de la biblioteca para mostrar la ayuda en sí, sin tener que codificar una verificación adicional $1. Al mismo tiempo, desordenará su espacio de nombres si obtiene la biblioteca. Si no le gusta, puede cambiar el nombre a algo parecido lib_helpo verificar los argumentos --helpen el código principal e invocar la función de ayuda manualmente.
Sir Athos
fuente
4

Vi este uso en un script y pensé que era un buen sustituto para invocar a basename dentro de un script.

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

... este es un reemplazo para el código: basetool=$(basename $0)

Griff Derryberry
fuente
Prefierobasetool=${0##*/}
Amit Naidu