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


335

什么都不做,仅是注释领导者,但实际上是内置于其本身的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

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



68
@Caleb-我问这两年之前。
amphetamachine

我不会说一个返回特定值的命令“什么都不做”。除非函数式编程包括“什么都不做”。:-)
LarsH

Answers:


415

从历史上看,炮弹伯恩没有truefalse内置的命令。true取而代之的是简单地别名到:false诸如之类的东西let 0

:true可移植到古代伯恩衍生炮弹要好一些。举一个简单的例子,考虑既不使用!管道运算符也不使用||列表运算符(某些古老的Bourne壳就是这种情况)。这使该语句的else子句if成为基于退出状态进行分支的唯一方法:

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

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

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

  • 需要在外壳中内置特殊的内置插件。常规的内置程序只是“通常”内置的,但并不能严格保证。在大多数系统的PATH中,通常不应该有一个以:功能命名的常规程序true

  • 可能最关键的区别在于,使用特殊的内置程序,即使在简单的命令评估过程中,内置程序设置的任何变量(即使在环境中)也将在命令完成后仍然存在,如以下使用ksh93所示:

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

    请注意,除了在POSIX兼容模式下进行操作外,Zsh会与GNU Bash一样忽略此要求,但所有其他主要的“ POSIX sh派生”主外壳程序都遵守此要求,包括破折号,ksh93和mksh。

  • 另一个区别是常规的内置程序必须与exec- 兼容,此处使用Bash进行了演示:

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


您是说常规内置程序一定兼容exec吗?
Old Pro

1
@OldPro:不,他是正确的,因为它true是常规的内置函数,但是他不正确,因为它exec是在使用/bin/true而不是内置函数。
暂停,直到另行通知。

1
@DennisWilliamson我只是按照规范的措辞行事。当然,这意味着常规的内置程序也应该有一个独立的版本。
ormaaj

17
+1出色的答案。我仍然想指出初始化变量的用法,例如: ${var?not initialized}et al。
2014年

或多或少无关的后续行动。您说的:是内置的特殊功能,不应有以其命名的功能。但是,不是最常见的叉子炸弹示例为:(){ :|: & };:名称命名函数的例子:吗?
Chong

63

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

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


20
>afile更简单,达到相同的效果。
伯爵

2
太酷了,我将使用该$ vecho技巧来简化我正在维护的脚本。
BarneySchmale 2015年

5
引用冒号有vecho=":"什么好处?仅出于可读性?
leoj '16

56

对于:的一个有用的应用程序是:如果您只对使用参数扩展产生副作用,而不是将其结果实际传递给命令感兴趣。在这种情况下,根据要退出状态为0还是1,将PE用作:或false的参数。示例可能是: "${var:=$1}"。因为:是内置的,所以应该很快。


2
你也可以用它来算术扩展的副作用:: $((a += 1))++--不需要运营商根据POSIX实现)。在bash,ksh和可能的其他shell中,您也可以执行以下操作:((a += 1))或,((a++))但是POSIX未指定它。
pabouk

@pabouk是的,尽管(())被指定为可选功能,但没错。“如果以“((”“开头的字符序列在以'$'开头时将被shell解析为算术扩展,则实现扩展的shell会将”((expression))“视为算术表达式, “(((是作为算术评估而不是分组命令引入的。”)
ormaaj 2015年

50

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

: << 'SKIP'

your code block here

SKIP

3
馊主意。这里文档中的所有命令替换仍在处理中。
chepner 2014年

33
这不是一个坏主意。您可以在本文中通过单引号分隔符来避免变量的解析/替换::<<'SKIP'–
Rondo

1
IIRC,您也可以\ 转义任何分隔符以达到相同的效果:: <<\SKIP
yyny

31

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

:> file.log

16
> file.log更简单并达到相同的效果。
amphetamachine

55
是的,但是幸福的脸对我有什么帮助:>
Ahi Tuna

23
@amphetamachine::>更便携。某些shell(例如my zsh)会在当前shell中自动实例化一只猫,并在没有命令的情况下进行重定向时侦听stdin。而不是cat /dev/null:要简单得多。通常,这种行为在交互式外壳程序中而不是脚本中是不同的,但是如果您以也可以交互工作的方式编写脚本,则通过复制粘贴进行调试会容易得多。
卡勒布'02

2
如何: > file从不同true > file(除了字符数和笑脸)在现代壳(假设:true同样快速)?
亚当·卡兹


29

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

记录中

使用以下示例脚本:

set -x
: Logging message here
example_command

第一行,set -x使外壳程序在运行之前打印出命令。这是一个非常有用的构造。缺点是echo Log message现在通常的语句类型将消息打印两次。冒号方法可以解决这个问题。请注意,您仍然必须像转义特殊字符一样转义特殊字符echo

Cron职位

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

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

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


默认日志位置在哪里?我可以设置日志位置吗?目的是否更多是为了在脚本/后台进程期间在stdout中创建输出?
domdambrogia

1
@domdambrogia使用时set -x,打印出的命令(包括类似的命令: foobar)转到stderr。
Flimm

22

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

: `some_command`

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

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


25
如果命令要转储几兆字节的输出,这是不安全的,因为shell会缓冲输出,然后将其作为命令行参数(堆栈空间)传递给':'。
朱利诺

15

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

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

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

(肯定有点奇怪,但仍然有效。)


根据要求进行一些说明:

  • Shell脚本逐行评估;该exec命令在运行时会终止外壳并将其进程替换为结果命令。这意味着对于shell,该程序如下所示:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • 只要单词中没有任何参数扩展或混叠,shell脚本中的任何单词都可以用引号引起来,而无需更改其含义。这意味着':'等同于:(我们仅在此处用引号将其包装以实现下面描述的JavaScript语义)

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

  • 最后,第一行(在分号之后)的第二条命令是程序的真正内容:它是exec调用,它将调用的shell脚本替换为Node.js进程,以评估脚本的其余部分,从而代替了该脚本。

  • 同时,JavaScript中的第一行将解析为字符串型(':'),然后解析为注释,然后将其删除;因此,对于JavaScript,该程序如下所示:

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

    由于字符串字面量本身就是一行,因此它是一个no-op语句,因此已从程序中删除;这意味着将删除整行,保留您的程序代码(在此示例中为function(){ ... }正文)。


您好,您能解释一下: //;~function(){}做什么?谢谢:)
Stphane '16

1
@Stphane添加了细分!至于,则~function(){}稍微复杂一些。有 对夫妇在这里触摸它,虽然他们都没有真正解释给我满意......如果没有这些问题解释得很好,够你,随意张贴在这里作为一个问题,我会在其他的答案很乐意回答一个新问题。
ELLIOTTCABLE

1
我没有注意node。因此,功能部分全部与javascript有关!我对IIFE之前的一元运算符很满意。我以为这也是一开始就很actually跷,实际上并没有真正理解您帖子的含义。我现在很好,谢谢您花时间添加“故障”!
Stphane '16

~{ No problem. (= }
ELLIOTTCABLE

12

自我记录功能

您也可以:用来将文档嵌入功能中。

假设您有一个库脚本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
    回声
    回声用法: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}”];然后
    #脚本已执行而不是源
    #调用请求的函数或显示帮助
    如果[“ $(type -t-” $ 1“ 2> / dev / null)” =函数]; 然后
        “ $ @”
    其他
        - 帮帮我
    科幻
科幻

关于代码的一些注释:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.