【问题标题】:What is the purpose of the : (colon) GNU Bash builtin?:(冒号)GNU Bash 内置的目的是什么?
【发布时间】:2011-03-14 14:01:26
【问题描述】:

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

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

【问题讨论】:

标签: bash shell built-in


【解决方案1】:

历史上,Bourne shell 没有 truefalse 作为内置命令。 true 只是简单地别名为:,而false 则别名为let 0

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

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

由于if 需要一个非空的then 子句并且cmets 不能算作非空,所以: 用作无操作。

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

  • 需要在 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 更快,尽管这当然是特定于实现的细节。

【讨论】:

  • 您的意思是常规内置插件必须exec兼容吗?
  • @OldPro: 不,他的正确之处在于true 是一个常规的内置函数,但他的错误在于exec 使用的是/bin/true 而不是内置函数。
  • @DennisWilliamson 我只是按照规范的措辞进行。这当然意味着常规的内置函数也应该有一个独立的版本。
  • +1 很好的答案。我仍然想注意初始化变量的用法,例如 : ${var?not initialized} 等。
  • 一个或多或少无关的后续行动。你说: 是一个特殊的内置函数,不应该有一个由它命名的函数。但是最常见的 fork 炸弹示例不是 :(){ :|: &amp; };: 以名称 : 命名函数吗?
【解决方案2】:

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

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

【讨论】:

  • &gt;afile 更简单,达到同样的效果。
  • 酷,我将使用 $vecho 技巧来简化我正在维护的脚本。
  • vecho=":" 中引用冒号有什么好处?只是为了可读性?
【解决方案3】:

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

在这种情况下,您可以使用参数扩展作为 :false 的参数,具体取决于您希望退出状态为 0 还是 1。例如

: "${var:=$1}"

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

【讨论】:

  • 您也可以将其用于算术扩展的副作用:: $((a += 1))++-- 运算符不需要根据 POSIX 实现。)。在 bash、ksh 和其他可能的 shell 中,您也可以这样做:((a += 1))((a++)),但 POSIX 未指定。
  • @pabouk 没错,尽管(()) 被指定为可选功能。 “如果一个以“((”开头的字符序列如果前面有一个“$”)将被shell解析为算术扩展,那么实现扩展的shell可以将“((表达式))”作为算术表达式评估"((" 作为算术评估而不是分组命令引入。"
  • 请注意,任何扩展都可以用于其副作用(即使它没有显式设置变量),因为它可以通过下一行的$_ 访问。因此可以使用一系列: 命令来连续转换一个值。
【解决方案4】:

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

: << 'SKIP'

your code block here

SKIP

【讨论】:

  • 坏主意。仍然会处理此处文档中的任何命令替换。
  • 不是一个坏主意。您可以通过单引号分隔符来避免此处文档中的变量解析/替换::
  • IIRC 你也可以\ 转义任何分隔符以获得相同的效果:: &lt;&lt;\SKIP
  • @zagpoint 这是 Python 将文档字符串用作多行 cmets 的地方吗?
【解决方案5】:

类似于 Python 中的pass

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

future_function () { :; }

【讨论】:

    【解决方案6】:

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

    :> file.log
    

    【讨论】:

    • &gt; file.log 更简单,达到同样的效果。
    • 是的,但快乐的脸对我来说是什么:>
    • @amphetamachine: :&gt; 更便携。一些 shell(比如我的zsh)会在当前 shell 中自动实例化一只猫,并在没有命令的情况下监听标准输入。 :cat /dev/null 简单得多。通常这种行为在交互式 shell 而不是脚本中是不同的,但是如果您以一种也可以交互式工作的方式编写脚本,那么通过复制粘贴进行调试会容易得多。
    • : &gt; file 与现代 shell 中的 true &gt; file 有何不同(除了字符数和笑脸)(假设 :true 速度一样快)?
    • @AdamKatz 假设 :true 速度一样快,它们是一样的。但这不是一个有效的假设。
    【解决方案7】:

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

    记录

    以这个示例脚本为例:

    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 打印一些输出时,它可以使电子邮件主题看起来更好。

    【讨论】:

    • 默认日志位置在哪里?我可以设置日志位置吗?在脚本/后台进程期间在标准输出中创建输出的目的更多吗?
    • @domdambrogia 使用set -x 时,打印出的命令(包括: foobar 之类的内容)会转到stderr。
    【解决方案8】:

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

    : `some_command`
    

    当然你可以只使用some_command &gt; /dev/null,但:-版本会短一些。

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

    【讨论】:

    • 如果命令要转储几兆字节的输出,这是不安全的,因为 shell 会缓冲输出,然后将其作为命令行参数(堆栈空间)传递给 ':'。
    • 顺便说一句,这导致了一个问题,有没有办法不使用使用/dev/null来丢弃管道的输出?假设/dev/null 不存在。毕竟,它可以从系统中删除......
    【解决方案9】:

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

    #!/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 中一样;它只是一个被丢弃的无意义词。)

    • 1234563 ,调用一个 Node.js 进程来评估脚本的其余部分
    • 同时,在 JavaScript 中,第一行解析为字符串文字 (':'),然后是注释,该注释被删除;因此,对于 JavaScript,程序如下所示:

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

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

    【讨论】:

    • 你好,你能解释一下: //;~function(){}做什么吗?谢谢:)
    • @Stphane 添加了故障!至于~function(){},那就有点复杂了。这里有 a couple 其他答案,虽然没有一个能真正让我满意......我很乐意深入回答一个新问题。
    • 我没有关注node。所以功能部分都是关于 javascript 的!我对 IIFE 前面的一元运算符很满意。我认为这也是 bash 首先,实际上并没有真正理解您的帖子的含义。我现在很好,感谢您花时间添加«break-down»!
    • ~{ No problem. (= }
    【解决方案10】:

    自记录功能

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

    假设你有一个库脚本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 的参数并手动调用帮助函数。

    【讨论】:

      【解决方案11】:

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

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

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

      【讨论】:

      • 我更喜欢basetool=${0##*/}
      【解决方案12】:

      这里还没有提到的另一种方法是在无限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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-11
        • 2016-10-16
        • 2011-03-01
        • 1970-01-01
        • 2012-09-25
        • 2019-09-14
        • 2016-05-16
        • 1970-01-01
        相关资源
        最近更新 更多