What is the use case of noop [:] in bash?

2019-01-21 02:54发布

问题:

I searched for noop in bash (:), but was not able to find any good information. What is the exact purpose or use case of this operator?

I tried following and it's working like this for me:

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

Please let me know, any use case of this operator in real time or any place where it is mandatory to use it.

回答1:

It's there more for historical reasons. The colon builtin : is exactly equivalent to true. It's traditional to use true when the return value is important, for example in an infinite loop:

while true; do
  echo 'Going on forever'
done

It's traditional to use : when the shell syntax requires a command but you have nothing to do.

while keep_waiting; do
  : # busy-wait
done

The : builtin dates all the way back to the Thompson shell, it was present in Unix v6. : was a label indicator for the Thompson shell's goto statement. The label could be any text, so : doubled up as a comment indicator (if there is no goto comment, then : comment is effectively a comment). The Bourne shell didn't have goto but kept :.

A common idiom that uses : is : ${var=VALUE}, which sets var to VALUE if it was unset and does nothing if var was already set. This construct only exists in the form of a variable substitution, and this variable substitution needs to be part of a command somehow: a no-op command serves nicely.

See also What purpose does the colon builtin serve?.



回答2:

I use it for if statements when I comment out all the code. For example you have a test:

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

but you want to temporarily comment out everything contained within:

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

Which causes bash to give a syntax error:

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

Bash can't have empty blocks (WTF). So you add a no-op:

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

or you can use the no-op to comment out the lines:

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


回答3:

You would use : to supply a command that succeeds but doesn't do anything. In this example the "verbosity" command is turned off by default, by setting it to :. The 'v' option turns it on.

#!/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  


回答4:

If you use set- e then || : is a great way to not exit the script if a failure happens (it explicitly makes it pass).



回答5:

One use is as multiline comments, or to comment out part of your code for testing purposes by using it in conjunction with a here file.

: << 'EOF'

This part of the script is a commented out

EOF

Don't forget to use quotes around EOF so that any code inside doesn't get evaluated, like $(foo). It also might be worth using an intuitive terminator name like NOTES, SCRATCHPAD, or TODO.



回答6:

Ignore alias arguments

Some times you want to have an alias that doesn't take any argument. You can do it using ::

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there


回答7:

Two of mine.

Embed POD comments

A quite funky application of : is for embedding POD comments in bash scripts, so that man pages can be quickly generated. Of course, one would eventually rewrite the whole script in Perl ;-)

Run-time function binding

This is a sort of code pattern for binding functions at run-time. F.i., have a debugging function to do something only if a certain flag is set:

#!/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'

You get:

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


回答8:

Sometimes no-op clauses can make your code more readable.

That can be a matter of opinion, but here's an example. Let's suppose you've created a function that works by taking two unix paths. It calculates the 'change path' needed to cd from one path to another. You place a restriction on your function that the paths must both start with a '/' OR both must not.

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

Some developers will want to remove the no-op but that would mean negating the conditional:

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

Now -in my opinion- its not so clear from the if-clause the conditions in which you'd want to skip doing the function. To eliminate the no-op and do it clearly, you would want to move the if-clause out of the function:

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

That looks better, but many times we can't do this; we want the the check to be done inside the function.

So how often does this happen? Not very often. Maybe once or twice a year. It happens often enough, that you should be aware of it. I don't shy away from using it when I think it improves the readability of my code (regardless of the language).



回答9:

Somewhat related to this answer, I find this no-op rather convenient to hack polyglot scripts. For example, here is a valid comment both for bash and for vimscript:

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

Sure, we may have used true just as well, but : being a punctuation sign and not an irrelevant English word makes it clear that it is a syntax token.


As for why would someone do such a tricky thing as writing a polyglot script (besides it being cool): it proves helpful in situations where we would normally write several script files in several different languages, with file X referring to file Y.

In such a situation, combining both scripts in a single, polyglot file avoids any work in X for determining the path to Y (it is simply "$0"). More importantly, it makes it more convenient to move around or distribute the program.

  • A common example. There is a well-known, long-standing issue with shebangs: most systems (including Linux and Cygwin) allow only one argument to be passed to the interpreter. The following shebang:

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

    will fire the following command:

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

    and not the intended:

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

    Thus, you would end up writing a wrapper script, such as:

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

    This is where polyglossia enters the stage.

  • A more specific example. I once wrote a bash script which, among other things, invoked Vim. I needed to give Vim additional setup, which could be done with the option --cmd "arbitrary vimscript command here". However, that setup was substantial, so that inlining it in a string would have been terrible (if ever possible). Hence, a better solution was to write it in extenso in some configuration file, then make Vim read that file with -S "/path/to/file". Hence I ended up with a polyglot bash/vimscript file.



标签: bash shell noop