What is the purpose of the : (colon) GNU Bash builtin?

前端 未结 12 722
梦如初夏
梦如初夏 2020-11-22 14:01

What is the purpose of a command that does nothing, being little more than a comment leader, but is actually a shell builtin in and of itself?

It\'s slower than inse

相关标签:
12条回答
  • 2020-11-22 14:18

    Self-documenting functions

    You can also use : to embed documentation in a function.

    Assume you have a library script mylib.sh, providing a variety of functions. You could either source the library (. mylib.sh) and call the functions directly after that (lib_function1 arg1 arg2), or avoid cluttering your namespace and invoke the library with a function argument (mylib.sh lib_function1 arg1 arg2).

    Wouldn't it be nice if you could also type mylib.sh --help and get a list of available functions and their usage, without having to manually maintain the function list in the help text?

    #!/bin/bash
    
    # all "public" functions must start with this prefix
    LIB_PREFIX='lib_'
    
    # "public" library functions
    lib_function1() {
        : This function does something complicated with two arguments.
        :
        : Parameters:
        : '   arg1 - first argument ($1)'
        : '   arg2 - second argument'
        :
        : Result:
        : "   it's complicated"
    
        # actual function code starts here
    }
    
    lib_function2() {
        : Function documentation
    
        # function code here
    }
    
    # help function
    --help() {
        echo MyLib v0.0.1
        echo
        echo Usage: mylib.sh [function_name [args]]
        echo
        echo Available functions:
        declare -f | sed -n -e '/^'$LIB_PREFIX'/,/^}$/{/\(^'$LIB_PREFIX'\)\|\(^[ \t]*:\)/{
            s/^\('$LIB_PREFIX'.*\) ()/\n=== \1 ===/;s/^[ \t]*: \?['\''"]\?/    /;s/['\''"]\?;\?$//;p}}'
    }
    
    # main code
    if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
        # the script was executed instead of sourced
        # invoke requested function or display help
        if [ "$(type -t - "$1" 2>/dev/null)" = function ]; then
            "$@"
        else
            --help
        fi
    fi
    

    A few comments about the code:

    1. All "public" functions have the same prefix. Only these are meant to be invoked by the user, and to be listed in the help text.
    2. The self-documenting feature relies on the previous point, and uses declare -f to enumerate all available functions, then filters them through sed to only display functions with the appropriate prefix.
    3. It is a good idea to enclose the documentation in single quotes, to prevent undesired expansion and whitespace removal. You'll also need to be careful when using apostrophes/quotes in the text.
    4. You could write code to internalize the library prefix, i.e. the user only has to type mylib.sh function1 and it gets translated internally to lib_function1. This is an exercise left to the reader.
    5. The help function is named "--help". This is a convenient (i.e. lazy) approach that uses the library invoke mechanism to display the help itself, without having to code an extra check for $1. At the same time, it will clutter your namespace if you source the library. If you don't like that, you can either change the name to something like lib_help or actually check the args for --help in the main code and invoke the help function manually.
    0 讨论(0)
  • 2020-11-22 14:20

    : can also be for block comment (similar to /* */ in C language). For example, if you want to skip a block of code in your script, you can do this:

    : << 'SKIP'
    
    your code block here
    
    SKIP
    
    0 讨论(0)
  • 2020-11-22 14:21

    Another way, not yet mentioned here is the initialisation of parameters in infinite while-loops. Below is not the cleanest example, but it serves it's purpose.

    #!/usr/bin/env bash
    [ "$1" ] && foo=0 && bar="baz"
    while : "${foo=2}" "${bar:=qux}"; do
        echo "$foo"
        (( foo == 3 )) && echo "$bar" && break
        (( foo=foo+1 ))
    done
    
    0 讨论(0)
  • 2020-11-22 14:22

    A useful application for : is if you're only interested in using parameter expansions for their side-effects rather than actually passing their result to a command. In that case you use the PE as an argument to either : or false depending upon whether you want an exit status of 0 or 1. An example might be : "${var:=$1}". Since : is a builtin it should be pretty fast.

    0 讨论(0)
  • 2020-11-22 14:22

    It's also useful for polyglot programs:

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

    This is now both an executable shell-script and a JavaScript program: meaning ./filename.js, sh filename.js, and node filename.js all work.

    (Definitely a little bit of a strange usage, but effective nonetheless.)


    Some explication, as requested:

    • Shell-scripts are evaluated line-by-line; and the exec command, when run, terminates the shell and replaces it's process with the resultant command. This means that to the shell, the program looks like this:

      #!/usr/bin/env sh
      ':' //; exec "$(command -v node)" "$0" "$@"
      
    • As long as no parameter expansion or aliasing is occurring in the word, any word in a shell-script can be wrapped in quotes without changing its' meaning; this means that ':' is equivalent to : (we've only wrapped it in quotes here to achieve the JavaScript semantics described below)

    • ... and as described above, the first command on the first line is a no-op (it translates to : //, or if you prefer to quote the words, ':' '//'. Notice that the // carries no special meaning here, as it does in JavaScript; it's just a meaningless word that's being thrown away.)

    • Finally, the second command on the first line (after the semicolon), is the real meat of the program: it's the exec call which replaces the shell-script being invoked, with a Node.js process invoked to evaluate the rest of the script.

    • Meanwhile, the first line, in JavaScript, parses as a string-literal (':'), and then a comment, which is deleted; thus, to JavaScript, the program looks like this:

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

      Since the string-literal is on a line by itself, it is a no-op statement, and is thus stripped from the program; that means that the entire line is removed, leaving only your program-code (in this example, the function(){ ... } body.)

    0 讨论(0)
  • 2020-11-22 14:28

    I use it to easily enable/disable variable commands:

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

    Thus

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

    This makes for a clean script. This cannot be done with '#'.

    Also,

    : >afile
    

    is one of the simplest ways to guarantee that 'afile' exists but is 0 length.

    0 讨论(0)
提交回复
热议问题