402

一个什么都不做的命令的目的是什么,只不过是一个评论领导者,但实际上是一个内置的 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

我想我真正在寻找的是它可能具有的历史应用。

4

12 回答 12

471

从历史上看,Bourne shell 没有truefalse作为内置命令。true取而代之的是简单地别名为:, 和false类似的东西let 0

:true对古代 Bourne 衍生贝壳的可移植性略好。作为一个简单的例子,考虑既没有!管道运算符也没有||列表运算符(就像一些古老的 Bourne shell 的情况)。这使得语句的else子句if成为基于退出状态进行分支的唯一方法:

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

由于if需要一个非空then子句并且注释不计为非空,:因此用作无操作。

如今(即:在现代语境中)您通常可以使用:true。两者都是由 POSIX 指定的,有些true更容易阅读。但是有一个有趣的区别::是所谓的 POSIX special built-in,而true常规 built-in

  • 需要在 shell 中内置特殊的内置函数;常规内置仅“通常”内置,但并不能严格保证。大多数系统的 PATH 中通常不应该有一个以:函数命名的常规程序。true

  • 可能最关键的区别在于,对于特殊的内置插件,内置插件设置的任何变量 - 即使在简单命令评估期间的环境中 - 在命令完成后仍然存在,如使用 ksh93 所示:

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

    请注意,Zsh 忽略了这一要求,GNU Bash 也忽略了这一要求,除非在 POSIX 兼容模式下运行,但所有其他主要的“POSIX sh 派生”shell 都遵守这一要求,包括 dash、ksh93 和 mksh。

  • 另一个区别是常规内置插件必须兼容exec- 此处使用 Bash 演示:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
    
  • POSIX 还明确指出:可能比 更快true,尽管这当然是特定于实现的细节。

于 2010-07-11T22:42:16.007 回答
83

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

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

$vecho "Verbose echo is ON"

因此

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

这使得一个干净的脚本。这不能用“#”来完成。

还,

: >afile

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

于 2010-07-12T05:15:47.593 回答
70

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

在这种情况下,您可以使用参数扩展作为参数,:或者false取决于您想要退出状态为 0 还是 1。一个示例可能是

: "${var:=$1}"

由于:是内置的,它应该非常快。

于 2011-02-03T22:36:25.943 回答
59

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

: << 'SKIP'

your code block here

SKIP
于 2013-09-21T07:15:28.897 回答
36

这与 Python 中的类似pass

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

future_function () { :; }
于 2010-07-11T22:55:45.630 回答
36

其他答案中未提及的另外两个用途:

日志记录

以这个示例脚本为例:

set -x
: Logging message here
example_command

第一行 ,set -x使 shell 在运行之前打印出命令。这是一个非常有用的结构。缺点是通常echo Log message类型的语句现在会打印两次消息。冒号方法绕过了这一点。请注意,您仍然必须像对echo.

Cron 职位

我已经看到它被用于 cron 作业,如下所示:

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

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

于 2015-04-24T10:33:23.633 回答
35

如果您想将文件截断为零字节,这对清除日志很有用,请尝试以下操作:

:> file.log
于 2011-06-23T16:27:39.333 回答
26

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

: `some_command`

当然你可以这样做some_command > /dev/null,但是:-version 会更短一些。

话虽如此,我不建议实际这样做,因为它只会使人们感到困惑。它只是作为一个可能的用例想到的。

于 2010-07-11T22:43:10.867 回答
18

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

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

这现在既是一个可执行的 shell 脚本是一个 JavaScript 程序:意思是./filename.js, sh filename.js, 并且node filename.js一切正常。

(肯定有点奇怪的用法,但仍然有效。)


一些解释,根据要求:

  • Shell 脚本是逐行评估的;并且该exec命令在运行时会终止 shell 并用生成的命令替换它的进程。这意味着对于 shell,程序如下所示:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
    
  • 只要单词中没有出现参数扩展或别名, shell 脚本中的任何单词都可以用引号括起来而不会改变其含义;这意味着':'相当于:(我们在这里只用引号将其包裹起来以实现下面描述的 JavaScript 语义)

  • ...并且如上所述,第一行的第一个命令是空操作(它转换为: //,或者如果您更喜欢引用单词,':' '//'。请注意//这里没有特殊含义,就像在 JavaScript 中一样;这只是一个被丢弃的毫无意义的词。)

  • 最后,第一行的第二个命令(分号之后)是程序的真正内容:它是exec替换被调用的shell 脚本的调用,调用了一个 Node.js 进程来评估脚本的其余部分

  • 同时,在 JavaScript 中,第一行解析为字符串文字 ( ':'),然后是注释,该注释被删除;因此,对于 JavaScript,程序如下所示:

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

    由于字符串文字本身在一行,它是一个无操作语句,因此被从程序中剥离;这意味着整行被删除,只留下的程序代码(在这个例子中,function(){ ... }主体。)

于 2015-12-30T17:14:14.000 回答
17

自记录功能

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

假设你有一个库脚本mylib.sh,提供了多种功能。您可以获取库 ( . mylib.sh) 并在此之后直接调用函数 ( lib_function1 arg1 arg2),或者避免混淆命名空间并使用函数参数 ( mylib.sh lib_function1 arg1 arg2) 调用库。

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

#!/bin/bash

# 所有“公共”函数都必须以这个前缀开头
LIB_PREFIX='lib_'

# “公共”库函数
lib_function1() {
    : 这个函数用两个参数做了一些复杂的事情。
    :
    : 参数:
    : 'arg1 - 第一个参数 ($1)'
    : 'arg2 - 第二个参数'
    :
    : 结果:
    : “ 情况很复杂”

    # 实际功能代码从这里开始
}

lib_function2() {
    : 函数文档

    #这里是函数代码
}

# 帮助功能
- 帮助() {
    回声 MyLib v0.0.1
    回声
    echo 用法:mylib.sh [function_name [args]]
    回声
    echo 可用功能:
    声明-f | sed -n -e '/^'$LIB_PREFIX'/,/^}$/{/\(^'$LIB_PREFIX'\)\|\(^[ \t]*:\)/{
        s/^\('$LIB_PREFIX'.*\) ()/\n=== \1 ===/;s/^[ \t]*: \?['\''"]\?/ / ;s/['\''"]\?;\?$//;p}}'
}

# 主要代码
如果 [ "${BASH_SOURCE[0]}" = "${0}" ]; 然后
    # 脚本被执行而不是 sourced
    # 调用请求的函数或显示帮助
    if [ "$(type -t - "$1" 2>/dev/null)" = function ]; 然后
        “$@”
    别的
        - 帮助
    菲
菲

关于代码的一些评论:

  1. 所有“公共”函数都有相同的前缀。只有这些是由用户调用的,并在帮助文本中列出。
  2. 自文档功能依赖于前一点,并用于declare -f枚举所有可用函数,然后通过 sed 过滤它们以仅显示具有适当前缀的函数。
  3. 将文档用单引号括起来是个好主意,以防止不必要的扩展和空格删除。在文本中使用撇号/引号时,您还需要小心。
  4. 您可以编写代码来内部化库前缀,即用户只需键入mylib.sh function1,它就会在内部翻译为lib_function1. 这是留给读者的练习。
  5. 帮助功能被命名为“--help”。这是一种方便(即惰性)的方法,它使用库调用机制来显示帮助本身,而无需编写额外的检查代码$1。同时,如果您获取库,它会弄乱您的命名空间。如果您不喜欢这样,您可以将名称更改为类似的名称,lib_help或者实际检查--help主代码中的 args 并手动调用帮助函数。
于 2016-03-28T15:28:39.710 回答
4

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

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

...这是代码的替代品:basetool=$(basename $0)

于 2016-04-29T23:29:59.717 回答
2

此处尚未提及的另一种方法是在无限 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
于 2020-07-20T09:30:35.830 回答