目的是什么:(冒号)GNU Bash内置?

时间:2010-07-11 22:30:25

标签: bash shell built-in

一个什么都不做的命令的目的是什么,仅仅是一个评论领导者,但实际上是一个内置的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' (lazy programming)
while : ; do command ; done

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

12 个答案:

答案 0 :(得分:362)

历史,Bourne shell没有truefalse作为内置命令。 true只是简称为:falselet 0

:稍微好于true,可以移植到古代Bourne派生的shell。举个简单的例子,考虑既没有!管道运算符也没有||列表运算符(就像一些古老的Bourne shell一样)。这使得else语句的if子句成为基于退出状态进行分支的唯一方法:

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

由于if需要非空then子句且注释不计为非空,:将作为无操作。

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

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

  • 可能最重要的区别在于,使用特殊的内置函数,内置的任何变量 - 即使在简单命令评估期间的环境中 - 在命令完成后仍然存在,如此处使用ksh93所示: / p>

    $ 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更快,但这当然是特定于实现的细节。

答案 1 :(得分:57)

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

#!/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的最简单方法之一。

答案 2 :(得分:50)

一个有用的应用程序:如果你只对参数扩展的副作用感兴趣,而不是实际将结果传递给命令。在这种情况下,您使用PE作为参数:或者取决于您是否希望退出状态为0或1.示例可能是: "${var:=$1}"。由于:是内置的,因此应该非常快。

答案 3 :(得分:39)

:也可以用于块注释(类似于C语言中的/ * * /)。例如,如果要跳过脚本中的代码块,可以执行以下操作:

: << 'SKIP'

your code block here

SKIP

答案 4 :(得分:30)

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

:> file.log

答案 5 :(得分:27)

它类似于Python中的pass

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

future_function () { :; }

答案 6 :(得分:25)

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

登录

以此示例脚本:

set -x
: Logging message here
example_command

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

Cron职称

我已经看到它被用于cron工作,如下:

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

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

答案 7 :(得分:21)

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

: `some_command`

当然你可以some_command > /dev/null,但: - 版本稍微短一些。

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

答案 8 :(得分:13)

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

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

现在这是一个可执行的shell脚本一个JavaScript程序:意味着./filename.jssh filename.jsnode 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(){ ... }
    

    由于string-literal本身就是一行,所以它是一个无操作语句,因此从程序中删除;这意味着删除整行,只留下 您的程序代码(在本例中为function(){ ... }正文。)

答案 9 :(得分:11)

自我记录功能

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

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

如果您还可以输入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

关于代码的一些评论:

  1. 所有&#34;公众&#34;函数具有相同的前缀。只有这些意味着由用户调用,并列在帮助文本中。
  2. 自我记录功能依赖于前一点,并使用declare -f枚举所有可用功能,然后通过sed过滤它们,仅显示具有相应前缀的功能。
  3. 最好将文档用单引号括起来,以防止意外扩展和删除空格。在文中使用撇号/引号时,您还需要小心。
  4. 您可以编写代码来内化库前缀,即用户只需键入mylib.sh function1并将其内部翻译为lib_function1。这是一个留给读者的练习。
  5. 帮助功能名为&#34; - help&#34;。这是一种方便(即懒惰)的方法,它使用库调用机制来显示帮助本身,而无需为$1编写额外的检查。同时,如果您获取库,它将使您的命名空间变得混乱。如果您不喜欢,可以将名称更改为lib_help,或者在主代码中检查--help的args并手动调用帮助功能。

答案 10 :(得分:3)

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

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

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

答案 11 :(得分: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