:(冒号)GNU Bash 内置函数的用途是什么?

发布于 2024-09-09 04:28:43 字数 415 浏览 3 评论 0原文

一个什么也不做的命令的目的是什么,它只不过是一个注释引导者,但实际上它本身就是一个内置的 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

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

What is the purpose of a command that does nothing, being little more than a comment leader, but is actually a shell builtin in and of itself?

It's slower than inserting a comment into your scripts by about 40% per call, which probably varies greatly depending on the size of the comment. The only possible reasons I can see for it are these:

# 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

I guess what I'm really looking for is what historical application it might have had.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

溺孤伤于心 2024-09-16 04:28:44

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

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

I use it to easily enable/disable variable commands:

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

$vecho "Verbose echo is ON"

Thus

$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON

This makes for a clean script. This cannot be done with '#'.

Also,

: >afile

is one of the simplest ways to guarantee that 'afile' exists but is 0 length.

网名女生简单气质 2024-09-16 04:28:44

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

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

: "${var:=$1}"

since < code>: 是一个内置函数,它应该很快。

A useful application for : is if you're only interested in using parameter expansions for their side-effects rather than actually passing their result to a command.

In that case, you use the parameter expansion as an argument to either : or false depending upon whether you want an exit status of 0 or 1. An example might be

: "${var:=$1}"

Since : is a builtin, it should be pretty fast.

水波映月 2024-09-16 04:28:44

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

: << 'SKIP'

your code block here

SKIP

: can also be for block comment (similar to /* */ in C language). For example, if you want to skip a block of code in your script, you can do this:

: << 'SKIP'

your code block here

SKIP
苦行僧 2024-09-16 04:28:44

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

日志记录

以这个示例脚本为例:

set -x
: Logging message here
example_command

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

Cron 作业标题

我见过它在 cron 作业中使用,如下所示:

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

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

Two more uses not mentioned in other answers:

Logging

Take this example script:

set -x
: Logging message here
example_command

The first line, set -x, makes the shell print out the command before running it. It's quite a useful construct. The downside is that the usual echo Log message type of statement now prints the message twice. The colon method gets round that. Note that you'll still have to escape special characters just like you would for echo.

Cron job titles

I've seen it being used in cron jobs, like this:

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

This is a cron job that runs the script /opt/backup.sh every day at 10:45. The advantage of this technique is that it makes for better looking email subjects when the /opt/backup.sh prints some output.

笛声青案梦长安 2024-09-16 04:28:44

它类似于Python 中的pass

一种用途是在函数被写入之前将其存根:

future_function () { :; }

It's similar to pass in Python.

One use would be to stub out a function until it gets written:

future_function () { :; }
╰つ倒转 2024-09-16 04:28:44

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

:> file.log

If you'd like to truncate a file to zero bytes, useful for clearing logs, try this:

:> file.log
旧街凉风 2024-09-16 04:28:44

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

: `some_command`

当然,您可以只执行 some_command >; /dev/null,但 :-版本稍微短一些。

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

You could use it in conjunction with backticks (``) to execute a command without displaying its output, like this:

: `some_command`

Of course you could just do some_command > /dev/null, but the :-version is somewhat shorter.

That being said I wouldn't recommend actually doing that as it would just confuse people. It just came to mind as a possible use-case.

末が日狂欢 2024-09-16 04:28:44

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

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

现在它既是一个可执行的 shell 脚本,又是一个 JavaScript 程序:意思是 ./filename.jssh filename.js< /code> 和 node filename.js 都可以工作。

(确实有点奇怪的用法,但仍然有效。)


根据要求进行一些解释:

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

    #!/usr/bin/env sh
    ':' //;执行“$(命令-v节点)”“$0”“$@”
    
  • 只要单词中没有出现参数扩展或别名,shell 脚本中的任何单词都可以用引号括起来,而无需引号。改变其含义;这意味着 ':' 等同于 : (我们只是将其用引号括起来,以实现下面描述的 JavaScript 语义)

  • ...如上所述,第一行的第一个命令是无操作(它翻译为 : //,或者如果您更喜欢引用这些单词,则 ':' '//'<请注意,// 在这里没有特殊含义,就像在 JavaScript 中一样,它只是一个被丢弃的无意义单词。)

  • 最后,第一行的第二个命令(分号之后),是程序的真正核心:它是 exec 调用,它取代了被调用的 shell 脚本,并调用 Node.js 进程来评估< em>脚本的其余部分。

  • 同时,JavaScript 中的第一行解析为字符串文字 (':'),然后是注释,该注释被删除;因此,对于 JavaScript,程序看起来像这样:

    <前><代码>':'
    〜函数(){...}

    由于字符串文字本身位于一行,因此它是一条无操作语句,因此会从程序中删除;这意味着整行都被删除,仅留下您的程序代码(在本例中为 function(){ ... } 主体。)

It's also useful for polyglot programs:

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

This is now both an executable shell-script and a JavaScript program: meaning ./filename.js, sh filename.js, and node filename.js all work.

(Definitely a little bit of a strange usage, but effective nonetheless.)


Some explication, as requested:

  • Shell-scripts are evaluated line-by-line; and the exec command, when run, terminates the shell and replaces it's process with the resultant command. This means that to the shell, the program looks like this:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
    
  • As long as no parameter expansion or aliasing is occurring in the word, any word in a shell-script can be wrapped in quotes without changing its' meaning; this means that ':' is equivalent to : (we've only wrapped it in quotes here to achieve the JavaScript semantics described below)

  • ... and as described above, the first command on the first line is a no-op (it translates to : //, or if you prefer to quote the words, ':' '//'. Notice that the // carries no special meaning here, as it does in JavaScript; it's just a meaningless word that's being thrown away.)

  • Finally, the second command on the first line (after the semicolon), is the real meat of the program: it's the exec call which replaces the shell-script being invoked, with a Node.js process invoked to evaluate the rest of the script.

  • Meanwhile, the first line, in JavaScript, parses as a string-literal (':'), and then a comment, which is deleted; thus, to JavaScript, the program looks like this:

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

    Since the string-literal is on a line by itself, it is a no-op statement, and is thus stripped from the program; that means that the entire line is removed, leaving only your program-code (in this example, the function(){ ... } body.)

归属感 2024-09-16 04:28:44

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

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

You can also use : to embed documentation in a function.

Assume you have a library script mylib.sh, providing a variety of functions. You could either source the library (. mylib.sh) and call the functions directly after that (lib_function1 arg1 arg2), or avoid cluttering your namespace and invoke the library with a function argument (mylib.sh lib_function1 arg1 arg2).

Wouldn't it be nice if you could also type mylib.sh --help and get a list of available functions and their usage, without having to manually maintain the function list in the help text?

#!/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

A few comments about the code:

  1. All "public" functions have the same prefix. Only these are meant to be invoked by the user, and to be listed in the help text.
  2. The self-documenting feature relies on the previous point, and uses declare -f to enumerate all available functions, then filters them through sed to only display functions with the appropriate prefix.
  3. It is a good idea to enclose the documentation in single quotes, to prevent undesired expansion and whitespace removal. You'll also need to be careful when using apostrophes/quotes in the text.
  4. You could write code to internalize the library prefix, i.e. the user only has to type mylib.sh function1 and it gets translated internally to lib_function1. This is an exercise left to the reader.
  5. The help function is named "--help". This is a convenient (i.e. lazy) approach that uses the library invoke mechanism to display the help itself, without having to code an extra check for $1. At the same time, it will clutter your namespace if you source the library. If you don't like that, you can either change the name to something like lib_help or actually check the args for --help in the main code and invoke the help function manually.
萌辣 2024-09-16 04:28:44

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

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

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

I saw this usage in a script and thought it was a good substitute for invoking basename within a script.

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

...
this is a replacement for the code: basetool=$(basename $0)

岛歌少女 2024-09-16 04:28:44

这里尚未提及的另一种方法是在无限 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

Another way, not yet mentioned here is the initialisation of parameters in infinite while-loops. Below is not the cleanest example, but it serves it's purpose.

#!/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
国际总奸 2024-09-16 04:28:43

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

: 在可移植性方面比 true 略好于源自 Bourne 的古老 shell。作为一个简单的示例,请考虑既没有 ! 管道运算符,也没有 || 列表运算符(就像某些古老的 Bourne shell 的情况一样)。这使得 if 语句的 else 子句成为基于退出状态进行分支的唯一方法:

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

因为 if 需要非空 >then 子句和注释不算为非空,: 用作无操作。

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

  • 需要在shell中内置特殊的内置函数;常规内置函数只是“通常”内置的,但并不能严格保证。大多数系统的 PATH 中通常不应该有一个名为 : 且具有 true 功能的常规程序。

  • 可能最重要的区别是,对于特殊的内置程序,内置程序设置的任何变量(即使是在简单命令评估期间的环境中)在命令完成后仍然存在,如使用 ksh93 所示:

    $ 取消设置 x; ( x=hi :; 回显 "$x" )
    你好
    $ ( x=hi true; echo "$x" )
    
    $
    

    请注意,Zsh 会忽略此要求,GNU Bash 也会忽略此要求,除非在 POSIX 兼容模式下运行,但所有其他主要的“POSIX sh 派生”shell 都会遵守此要求,包括 dash、ksh93 和 mksh。

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

    <预置><代码>$ (执行:)
    -bash: 执行::: 未找到
    $(执行真)
    $

  • POSIX 还明确指出 :可能比 true 更快,尽管这当然是特定于实现的细节。

Historically, Bourne shells didn't have true and false as built-in commands. true was instead simply aliased to :, and false to something like let 0.

: is slightly better than true for portability to ancient Bourne-derived shells. As a simple example, consider having neither the ! pipeline operator nor the || list operator (as was the case for some ancient Bourne shells). This leaves the else clause of the if statement as the only means for branching based on exit status:

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

Since if requires a non-empty then clause and comments don't count as non-empty, : serves as a no-op.

Nowadays (that is: in a modern context) you can usually use either : or true. Both are specified by POSIX, and some find true easier to read. However there is one interesting difference: : is a so-called POSIX special built-in, whereas true is a regular built-in.

  • Special built-ins are required to be built into the shell; Regular built-ins are only "typically" built in, but it isn't strictly guaranteed. There usually shouldn't be a regular program named : with the function of true in PATH of most systems.

  • Probably the most crucial difference is that with special built-ins, any variable set by the built-in - even in the environment during simple command evaluation - persists after the command completes, as demonstrated here using ksh93:

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

    Note that Zsh ignores this requirement, as does GNU Bash except when operating in POSIX compatibility mode, but all other major "POSIX sh derived" shells observe this including dash, ksh93, and mksh.

  • Another difference is that regular built-ins must be compatible with exec - demonstrated here using Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
    
  • POSIX also explicitly notes that : may be faster than true, though this is of course an implementation-specific detail.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文