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

一个无所作为的命令的目的是什么,只不过是一个评论领袖,而实际上却是一个内在的壳呢?

比每次调用大概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 

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

历史上 ,Bourne shell作为内置命令并没有falsetrue ,而不是简单的别名:false的东西, let 0

:对于古代伯恩(Bourne)派生的炮弹的可移植性略胜一筹。 作为一个简单的例子,考虑既不! pipe道运营商也不|| 列表操作符(如一些古代Bourne shell的情况)。 这使if语句的else子句成为基于退出状态的唯一分支手段:

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

因为if需要一个非空then子句和注释不被视为非空, :作为一个空操作。

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

  • 需要在shell中内置特殊的内置插件; 普通的内置插件只是“典型”内置的,但并不是严格保证的。 通常不应该有一个常规程序命名为:在大多数系统的PATH中都具有true的function。

  • 或许最重要的区别是,使用特殊的内置插件,即使在简单的命令评估过程中,由内置设置的任何variables在命令完成之后仍然存在,如使用ksh93所示:

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

    请注意,Zsh忽略了这个要求,就像GNU Bash一样,除了在POSIX兼容模式下工作外,其他所有主要的“POSIX sh derived”shell都会观察这个包括短划线,ksh93和mksh。

  • 另一个区别是,定期的内置插件必须与exec兼容 – 在这里使用Bash演示:

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

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

 #!/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”存在但是长度最简单的方法之一。

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

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

 : << 'SKIP' your code block here SKIP 

它类似于pass Python。

一个用途是将一个函数写出来,直到它被写入:

 future_function () { :; } 

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

 :> file.log 

你可以使用它和反引号( `` )一起执行一个命令而不显示它的输出,如下所示:

 : `some_command` 

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

话虽如此,我不会推荐这样做,因为它只是混淆了人们。 它只是作为一个可能的用例而被想到。

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

logging

以此示例脚本:

 set -x : Logging message here example_command 

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

克朗职称

我已经看到它在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.jsnode filename.js都可以工作。

(绝对有点奇怪的用法,但有效。)


按照要求进行一些解释:

  • Shell脚本是逐行评估的; exec命令在运行时会终止shell,并生成的命令replace它的进程。 这意味着对于shell来说,程序看起来像这样:

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

  • …如上所述,第一行中的第一个命令是no-op(它转换为: // ,或者,如果您想引用单词':' '//'请注意//在这里没有什么特别的含义,就像它在JavaScript中所做的一样;这只是一个毫无意义的词语而已。

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

  • 同时,JavaScript中的第一行parsing为一个string( ':' ),然后是一个被删除的注释; 因此,JavaScript,程序看起来像这样:

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

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

自我loggingfunction

你也可以使用:在一个函数中embedded文档。

假设你有一个库脚本mylib.sh ,提供了各种function。 你可以从库( . mylib.sh )中获取资源,然后直接调用这个函数( lib_function1 arg1 arg2 ),或者避免lib_function1 arg1 arg2你的命名空间,并用一个函数参数( mylib.sh lib_function1 arg1 arg2 )来调用这个库。

如果你还可以inputmylib.sh --help并获得可用函数及其用法列表,而不必在帮助文本中手动维护函数列表?

 #!/斌/庆典

所有“公共”function必须以此前缀开头
 LIB_PREFIX = 'lib_'

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

     #实际function代码从这里开始
 }

 lib_function2(){
     :function文档

     #function代码在这里
 }

 #帮助function
 - 帮帮我() {
    回声MyLib v0.0.1
    回声
    回声用法:mylib.sh [function_name [args]]
    回声
    回声可用的function:
     declare -f |  sed -n -e'/ ^'$ LIB_PREFIX'/,/ ^} $ / {/ \(^'$ LIB_PREFIX'\)\ | \(^ [\ t] *:\)/ {
         s / ^ \('$ LIB_PREFIX'。* \)()/ \ n === \ 1 === /; s / ^ [\ t] *:\?['''“] ; S / [ '\' '“] \;?\ $ //; 2 p}}'
 }

 #主要代码
 if [“$ {BASH_SOURCE [0]}”=“$ {0}”]; 然后
     #脚本被执行,而不是来源
     #调用请求的函数或显示帮助
     if [“$(type -t  - ”$ 1“2> / dev / null)”= function]; 然后
         “$ @”
    其他
         - 帮帮我
    科幻
科幻

关于代码的几点意见:

  1. 所有的“公共”function都有相同的前缀。 只有这些才能被用户调用,并被列在帮助文本中。
  2. 自loggingfunction依赖于前一点,并使用declare -f枚举所有可用的函数,然后通过sed过滤它们,只显示具有适当前缀的函数。
  3. 将文档放在单引号中是一个好主意,以防止不必要的扩展和空白的删除。 在文本中使用撇号/引号时,还需要小心。
  4. 您可以编写代码来内部化库前缀,即用户只需键入mylib.sh function1 ,然后将其内部翻译为lib_function1 。 这是一个留给读者的练习。
  5. 帮助function被命名为“ – 帮助”。 这是一种方便(即懒惰)的方法,它使用库调用机制来显示帮助本身,而不必为$1编写额外的检查。 同时,如果你来源库,它会混乱你的名字空间。 如果你不喜欢这个,你可以改名字为lib_help或者在主代码中实际检查参数--help并手动调用帮助函数。

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

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

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