(冒号)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作为内置命令并没有false
。 true
,而不是简单的别名:
和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 message
types的语句现在打印消息两次。 冒号方法得到了。 请注意,您仍然必须像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.js
和node 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]; 然后 “$ @” 其他 - 帮帮我 科幻 科幻
关于代码的几点意见:
- 所有的“公共”function都有相同的前缀。 只有这些才能被用户调用,并被列在帮助文本中。
- 自loggingfunction依赖于前一点,并使用
declare -f
枚举所有可用的函数,然后通过sed过滤它们,只显示具有适当前缀的函数。 - 将文档放在单引号中是一个好主意,以防止不必要的扩展和空白的删除。 在文本中使用撇号/引号时,还需要小心。
- 您可以编写代码来内部化库前缀,即用户只需键入
mylib.sh function1
,然后将其内部翻译为lib_function1
。 这是一个留给读者的练习。 - 帮助function被命名为“ – 帮助”。 这是一种方便(即懒惰)的方法,它使用库调用机制来显示帮助本身,而不必为
$1
编写额外的检查。 同时,如果你来源库,它会混乱你的名字空间。 如果你不喜欢这个,你可以改名字为lib_help
或者在主代码中实际检查参数--help
并手动调用帮助函数。
我在一个脚本中看到了这个用法,并认为这是在脚本中调用基本名称的一个好替代。
oldIFS=$IFS IFS=/ for basetool in $0 ; do : ; done IFS=$oldIFS
…这是代码的替代品: basetool=$(basename $0)