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

amp*_*ine 313 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
Run Code Online (Sandbox Code Playgroud)

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

ear*_*arl 392

从历史上看,炮弹伯恩没有truefalse内置的命令.true而是简单地别名:,和false类似的东西let 0.

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

if command; then :; else ...; fi
Run Code Online (Sandbox Code Playgroud)

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

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

  • 外壳中需要安装特殊的内置插件; 常规内置插件仅"内置",但并未严格保证.通常不应该有一个以大多数系统的PATH :功能命名的常规程序true.

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

    $ unset x; ( x=hi :; echo "$x" )
    hi
    $ ( x=hi true; echo "$x" )
    
    $
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,Zsh忽略了这个要求,GNU Bash也是如此,除非在POSIX兼容模式下运行,但所有其他主要的"POSIX sh派生"shell都会观察到这一点,包括dash,ksh93和mksh.

  • 另一个区别是常规内置函数必须兼容exec- 在这里使用Bash演示:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
    
    Run Code Online (Sandbox Code Playgroud)
  • POSIX也明确指出:可能比true这更快,尽管这当然是一个特定于实现的细节.

  • +1优秀的答案.我还是要注意初始化变量的用法,比如`:$ {var?not initialized}`等. (15认同)
  • @OldPro:不,他是正确的,因为 `true` 是一个常规的内置函数,但他是错误的,因为 `exec` 使用的是 `/bin/true` 而不是内置函数。 (2认同)
  • @DennisWilliamson 我只是顺便看看规范的措辞方式。当然,这意味着常规内置程序也应该有一个独立版本。 (2认同)
  • 或多或少不相关的后续行动。你说 `:` 是一个特殊的内置函数,不应该有一个由它命名的函数。但是,最常见的 fork 炸弹示例不就是 `:(){ :|: &amp; };:` 用名称 `:` 命名函数吗? (2认同)

Kev*_*tle 61

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

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

$vecho "Verbose echo is ON"
Run Code Online (Sandbox Code Playgroud)

从而

$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON
Run Code Online (Sandbox Code Playgroud)

这使得一个干净的脚本.这不能用'#'来完成.

也,

: >afile
Run Code Online (Sandbox Code Playgroud)

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

  • `> afile`甚至更简单,并达到同样的效果. (20认同)
  • 在`vecho ="中引用冒号有什么好处:"` 只是为了可读性? (5认同)
  • 太酷了,我将使用该$ vecho技巧来简化我正在维护的脚本。 (2认同)

orm*_*aaj 51

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

  • 您还可以将其用于算术扩展的副作用:`:$((a + = 1))`(++和`--`运算符不需要根据POSIX实现。)。在bash,ksh和可能的其他shell中,您也可以执行:(((a + = 1)))或(((a ++))),但POSIX未指定。 (2认同)

小智 44

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

: << 'SKIP'

your code block here

SKIP
Run Code Online (Sandbox Code Playgroud)

  • 这不是一个坏主意.您可以通过单引号分隔符来避免此处文档中的变量解析/替换:: <<'SKIP' (28认同)
  • 馊主意.仍处理此处文档中的任何命令替换. (3认同)
  • IIRC 您还可以“\”转义任何分隔符以获得相同的效果:“: &lt;&lt;\SKIP”。 (2认同)

Ahi*_*una 31

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

:> file.log
Run Code Online (Sandbox Code Playgroud)

  • 是的,但幸福的脸对我来说是什么:> (52认同)
  • @amphetamachine:`:>`更便携.一些shell(比如我的`zsh`)在当前shell中自动实例化一个cat,并在给定没有命令的重定向时监听stdin.而不是`cat/dev/null`,`:`要简单得多.通常,这种行为在交互式shell而不是脚本中是不同的,但如果您以一种也可以交互的方式编写脚本,则通过复制粘贴进行调试会更加容易. (23认同)
  • `> file.log`更简单,并实现相同的效果. (16认同)
  • 在现代shell中,`:> file`与`true> file`(除了字符数和幸福面)有什么不同(假设`:`和`true`同样快)? (2认同)

Pau*_*ce. 29

它与passPython 类似.

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

future_function () { :; }
Run Code Online (Sandbox Code Playgroud)


Fli*_*imm 28

在其他答案中没有提到的另外两个用途:

记录

以此示例脚本为例:

set -x
: Logging message here
example_command
Run Code Online (Sandbox Code Playgroud)

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

Cron职称

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

45 10 * * * : Backup for database ; /opt/backup.sh
Run Code Online (Sandbox Code Playgroud)

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

  • @domdambrogia 当使用 `set -x` 时,打印出的命令(包括类似 `: foobar` 的命令)会转到 stderr。 (2认同)

sep*_*p2k 21

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

: `some_command`
Run Code Online (Sandbox Code Playgroud)

当然你可以这么做some_command > /dev/null,但是:-version有点短.

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

  • 如果命令要转储几兆字节的输出,这是不安全的,因为shell缓冲输出,然后将它作为命令行参数(堆栈空间)传递给':'. (24认同)
  • 顺便说一句,这引出了一个问题,有没有一种方法可以在不使用“/dev/null”的情况下丢弃管道的输出?假设 `/dev/null` 不存在。毕竟它可以从系统中删除...... (2认同)
  • @TrippKinetics 很难想象一个系统可以在没有“/dev/null”的情况下运行。不值得浪费时间去担心。 (2认同)

ELL*_*BLE 14

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

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

现在,这既是一个可执行的shell脚本 JavaScript程序:含义./filename.js,sh filename.jsnode filename.js所有的工作.

(绝对有点奇怪的用法,但仍然有效.)


根据要求进行一些解释:

  • Shell脚本逐行评估; 并且该exec命令在运行时终止shell并结果命令替换它的进程.这意味着对于shell,程序看起来像这样:

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

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

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

  • 同时,JavaScript中的第一行解析为string-literal(':'),然后删除注释,删除; 因此,对于JavaScript,程序看起来像这样:

    ':'
    ~function(){ ... }
    
    Run Code Online (Sandbox Code Playgroud)

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

  • @Stphane 添加了故障!至于`~function(){}`,那就有点复杂了。有[a](http://stackoverflow.com/questions/8305915/function-vs-function) [夫妇](http://stackoverflow.com/questions/26078006/iife-with-unary-operator-real-world -use-case)这里的其他答案都涉及到它,尽管他们都没有真正让我满意地解释它......如果这些问题都没有对你解释得足够好,请随意将其作为一个问题发布在这里,我'我们很乐意深入回答新问题。 (2认同)
  • 我没有注意“节点”。所以函数部分都是关于javascript的!我对 IIFE 前面的一元运算符很满意。我一开始也以为这也是 bash,实际上并没有真正理解你的帖子的含义。我现在没事了,感谢您花时间添加“故障”! (2认同)

Sir*_*hos 12

自我记录功能

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

假设您有一个库脚本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

关于代码的一些评论:

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


小智 5

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

oldIFS=$IFS  
IFS=/  
for basetool in $0 ; do : ; done  
IFS=$oldIFS  
Run Code Online (Sandbox Code Playgroud)

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

  • 我更喜欢 `basetool=${0##*/}` (4认同)