一个什么都不做的命令,只不过是一个注释引导,但实际上是一个内置的shell,它的目的是什么?

它比每次调用在脚本中插入注释要慢40%左右,这可能取决于注释的大小。我认为唯一可能的原因是:

# 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'
while : ; do command ; done

我想我真正想要的是它在历史上的应用。


从历史上看,Bourne shell并没有内置的true和false命令。True被改成了:,false被改成了let 0。

:在可移植性方面比真实的稍微好一些。作为一个简单的例子,考虑没有!管道操作符或||列表操作符(就像一些古老的Bourne shell的情况一样)。这使得if语句的else子句成为基于退出状态进行分支的唯一方法:

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

因为if要求一个非空的then子句和注释不算作非空,:作为一个no-op。

现在(在现代语境中)你通常可以用:或者true。两者都是由POSIX指定的,其中一些发现true更容易阅读。然而,有一个有趣的区别::是所谓的POSIX特殊内置,而true是常规内置。

Special built-ins are required to be built into the shell; Regular built-ins are only "typically" built in, but it isn't strictly guaranteed. There usually shouldn't be a regular program named : with the function of true in PATH of most systems. Probably the most crucial difference is that with special built-ins, any variable set by the built-in - even in the environment during simple command evaluation - persists after the command completes, as demonstrated here using ksh93: $ unset x; ( x=hi :; echo "$x" ) hi $ ( x=hi true; echo "$x" ) $ Note that Zsh ignores this requirement, as does GNU Bash except when operating in POSIX compatibility mode, but all other major "POSIX sh derived" shells observe this including dash, ksh93, and mksh. Another difference is that regular built-ins must be compatible with exec - demonstrated here using Bash: $ ( exec : ) -bash: exec: :: not found $ ( exec true ) $ POSIX also explicitly notes that : may be faster than true, though this is of course an implementation-specific detail.


你可以将它与反撇号(' ')结合使用来执行命令而不显示输出,如下所示:

: `some_command`

当然,你可以只执行some_command > /dev/null,但是:-version稍微短一些。

话虽如此,我不建议这样做,因为这会让人们感到困惑。它只是作为一个可能的用例出现在脑海中。


它类似于Python中的传递。

一种用法是将一个函数存根,直到它被写入:

future_function () { :; }

我用它来轻松地启用/禁用变量命令:

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

这有助于编写干净的脚本。这不能用'#'完成。

同时,

: >afile

是保证'afile'存在但长度为0的最简单方法之一。


:的一个有用的应用程序是,如果您只对使用参数展开的副作用感兴趣,而不是实际将其结果传递给命令。

在这种情况下,您使用参数展开作为参数:或false,这取决于您想要的退出状态是0还是1。一个例子是

: "${var:=$1}"

由于:是内置的,它应该相当快。


如果你想把一个文件截断为零字节,这对清除日志很有用,试试这个:

:> file.log

:也可以是块注释(类似于C语言中的/* */)。例如,如果你想在脚本中跳过一段代码,你可以这样做:

: << 'SKIP'

your code block here

SKIP

其他答案中没有提到的两个用法:

日志记录

以这个示例脚本为例:

set -x
: Logging message here
example_command

第一行set -x使shell在运行命令之前打印出该命令。这是一个非常有用的结构。缺点是通常的echo Log消息类型的语句现在打印两次消息。冒号方法避开了这个问题。请注意,您仍然必须转义特殊字符,就像您对echo所做的那样。

Cron职位名称

我看到它在cron作业中被使用,像这样:

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

这是一个cron作业,每天在10:45运行脚本/opt/backup.sh。这种技术的优点是,当/opt/backup.sh打印一些输出时,它使电子邮件主题看起来更好看。


它对多语言程序也很有用:

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

现在这既是一个可执行的shell脚本,也是一个JavaScript程序:这意味着。/filename.js、sh filename.js和node filename.js都可以工作。

(这种用法确实有点奇怪,但还是很有效。)


请作一些说明:

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.)


自我记录的功能

您还可以使用:将文档嵌入到函数中。

假设您有一个库脚本mylib.sh,它提供了各种函数。您可以将库(。Mylib.sh),然后直接调用函数(lib_function1 arg1 arg2),或者避免使您的命名空间混乱,并使用函数参数调用库(Mylib.sh lib_function1 arg1 arg2)。

如果您还可以输入mylib.sh——help并获得可用函数及其用法的列表,而不必在帮助文本中手动维护函数列表,这不是很好吗?

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

关于代码的一些注释:

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. 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. 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. 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. 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.


我在一个脚本中看到了这种用法,并认为它可以很好地替代在脚本中调用basename。

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

... 这是替换代码:baseool =$(basename $0)


另一种方法,这里还没有提到的是在无限while循环中初始化参数。下面不是最清晰的示例,但它达到了它的目的。

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