bash中的noop [:]用例是什么?

181

我在bash中搜索了noop (:), 但没有找到任何有用的信息。这个运算符的确切目的或用例是什么?

我尝试了以下内容,对我来说它起作用了:

[mandy@root]$ a=11
[mandy@root]$ b=20
[mandy@root]$ c=30
[mandy@root]$ echo $a; : echo $b ; echo $c
10
30

请让我知道,在实时中这个运算符的任何使用情况,或者任何必须使用它的地方。


请查看https://dev59.com/sGs05IYBdhLWcg3wG-NR。 - Stephane Rouberol
请注意,: 内置命令在 Bourne Shell、KSH 和 Bash 中都存在。 - ghoti
请参阅 http://tldp.org/LDP/abs/html/special-chars.html 中的 : - choroba
6
这不是一个真正的问题吗?我认为这是一个非常好的问题。我甚至有一个不错的用途,但我不能发布答案。 - Steven Lu
14个回答

250

出于历史原因,它还在那里。 冒号内置 :true 完全等效。 在返回值很重要的情况下,例如在无限循环中,使用 true 是传统的做法:

while true; do
  echo 'Going on forever'
done

在 shell 语法要求有命令但你没有要执行的操作时,使用 : 是传统的做法。

while keep_waiting; do
  : # busy-wait
done

:内置命令可以追溯到汤普森壳层现在Unix v6中存在。 :是汤普森shell的goto语句的标签指示器。标签可以是任何文本,因此:也充当注释指示器(如果没有goto comment,那么: comment实际上是注释)。Bourne shell没有goto但保留了:

一个常用的惯用语使用了:,即: ${var=VALUE},如果未设置,则将var设置为VALUE,如果var已经设置,则不做任何操作。这个结构只存在于变量替换的形式中,并且这个变量替换需要以某种方式成为命令的一部分:一个无操作的命令可以很好地服务。
另请参见冒号内置命令有什么用途?

14
很好的总结。此外,原始的Bourne shell没有使用作为注释(或者#!/bin/sh shebangs); :命令引入了注释,并且对于在其注释中制作了一个漂亮的星星盒子的天真程序员来说是个不幸的事情:: ****(或者更糟糕的是,: * * *)。 - Jonathan Leffler
3
抱歉,但我需要更多的上下文才能准确理解这句话的意思并进行翻译。请提供更多信息。 - DevSolar
11
因为 : 是一个命令,所以在发现 : 忽略它们之前,shell 仍然需要处理它的参数。大多数情况下,你只是让 shell 在将 * 扩展成当前目录中的文件列表方面做了额外的工作;这实际上不会影响脚本的运行方式。 - chepner
如果你恰好在一个有很多文件的目录中运行脚本,使得glob扩展超过命令长度限制,那么我想这可能会导致你的脚本失败。也许只有当你打开了set -e时才会出现这种情况。无论如何,希望我们都可以同意使用# :) - Jack O'Connor
3
有时候我会使用 while : ; do ... ; done 来创建一个快速而简单的无限循环。(通常在循环中会有一个 sleep,我会使用 Ctrl-C 来终止它。) - Keith Thompson
显示剩余2条评论

43

我在注释掉所有代码时使用它来进行if语句。例如,您有一个测试:

if [ "$foo" != "1" ]
then
    echo Success
fi

但是您想临时注释掉其中包含的所有内容:

if [ "$foo" != "1" ]
then
    #echo Success
fi

导致bash显示语法错误的原因:

Which causes bash to give a syntax error:
line 4: syntax error near unexpected token `fi'
line 4: `fi'
Bash无法有空块(WTF)。 因此,您需要添加一个空操作符:

if [ "$foo" != "1" ]
then
    #echo Success
    :
fi

或者您可以使用no-op来注释掉这些行:

if [ "$foo" != "1" ]
then
    : echo Success
fi

1
没有 Stack Overflow 和像你这样好的回答,我该怎么办呢 :) 非常感谢! - danday74

19
如果你使用set -e,那么|| :是一个很好的方法,可以在脚本发生失败时不会退出(它明确地使其通过)。

2
我发现这对于某些调用shell脚本的应用程序非常有用。例如,Mac上的Automator会在出错时退出而不告诉您原因。但是使用||:,然后稍后使用exit 0自己处理错误,将允许您在调试期间将所有stdout和stderr显示到应用程序窗口中。考虑{ foo ... 2>&1; } || :,它比设置更复杂的陷阱和if-then要简单得多。 - Beejor

9

忽略alias参数

有时您想要一个不带任何参数的别名。您可以使用:来实现:

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there

3
问题不在于它如何工作,而在于“有什么用途”。我的答案与 https://dev59.com/Pmct5IYBdhLWcg3wCJF-#37170755 有些相似,但实际上使用场景并不完全相同。我很乐意改进我的答案,但我真的不知道该怎么做…… - Ulysse BN
1
这有点奇怪——一个边角情况,如果你愿意的话——但仍然非常有趣,可能是一个聪明的技巧,可以放在某人的后袋里。 - Mike S
2
可以使用 # 来实现相同的效果。 - Zoey Hewll
3
@ZoeyHewll,不完全正确。由于别名在任何执行之前都是被 文本替换 的,所以使用别名来代替 # 将会使得在同一行中语法上后面的所有内容都被忽略。这与其他情况(例如考虑 my_alias arg ; other_command 或者 my_alias arg1 {BACKSLASH}{NEWLINE} arg2)有很大区别,并且可能会导致语法错误(猜猜 while my_alias ; do true ; done 或者 while true ; do my_alias ; done 会做什么)。 - Maëlan
1
@ZoeyHewll,不完全是第二点。当使用set -x运行shell时,我们永远不会看到#后面的内容。 - Ярослав Рахматуллин
显示剩余2条评论

8

您可以使用:来提供一个成功但不执行任何操作的命令。在这个例子中,“verbosity”命令默认被关闭,因为它被设置为:。选项“v”将其打开。

#!/bin/sh
# example
verbosity=:                         
while getopts v OPT ; do          
   case $OPT in                  
       v)        
           verbosity=/bin/realpath 
       ;;
       *)
           exit "Cancelled"
       ;;             
   esac                          
done                              

# `$verbosity` always succeeds by default, but does nothing.                              
for i in * ; do                   
  echo $i $($verbosity $i)         
done                              

$ example
   file

$ example -v
   file /home/me/file  

严格来说,给变量赋值是“做某事”,因此在您的情况语句中不会抛出错误。 - qodeninja
1
@qodeninja:没有人声称变量赋值“什么也没做”;关键是后面的 $(verbosity $i) 在某些情况下什么也不做。 - Lightness Races in Orbit

8

其中一种用途是作为多行注释,或者与此文件结合使用,将代码的一部分注释掉以进行测试。

: << 'EOF'

This part of the script is a commented out

EOF

别忘了在EOF周围使用引号,这样里面的任何代码都不会被执行,比如$(foo)。也许值得使用一个直观的终止符名称,比如NOTESSCRATCHPADTODO


很酷的技巧!特别适用于草稿笔记和需要大量 "#" 硬换行的代码。其中一个副作用是,与注释行不同,一些语法高亮器将忽略 heredocs,并将其着色为普通代码(或至少是纯文本)。这对于检查被注释的代码或避免眼睛看到霓虹灯颜色的段落非常有用。唯一的注意事项是,在共享代码中应将这样的块注明为注释,因为语法是独特的,可能会引起混淆。再次强调,这是我们谈论的 shell,混淆的潜力是系统性的。 - Beejor
我本以为这是一件人们不喜欢的事情(混淆)。 很高兴看到有些人点赞 :) - Ярослав Рахматуллин

3
假设您有一个希望链接到另一个成功命令的命令:
cmd="some command..."
$cmd
[ $? -eq 0 ] && some-other-command

但是现在您希望有条件地执行命令,并且您想显示将被执行的命令(干运行):

cmd="some command..."
[ ! -z "$DEBUG" ] && echo $cmd
[ -z "$NOEXEC" ] && $cmd
[ $? -eq 0 ] && {
    cmd="some-other-command"
    [ ! -z "$DEBUG" ] && echo $cmd
    [ -z "$NOEXEC" ] && $cmd
}

如果您设置了DEBUG和NOEXEC,第二个命令将永远不会显示。这是因为第一个命令从未执行(因为NOEXEC不为空),但该事实的评估使您得到返回值1,这意味着下属命令从未执行(但您希望它执行因为它是一次模拟运行)。因此,为了解决这个问题,您可以使用noop重置堆栈上留下的退出值:
[ -z "$NOEXEC" ] && $cmd || :

3
有时候,无操作语句可以让你的代码更易读。这可能是个人意见,不过我们来看一个例子。假设你创建了一个函数,它通过获取两个 Unix 路径来工作。它计算从一个路径到另一个路径所需的“更改路径”。你对该函数施加了限制,即路径必须同时以 '/' 开始或同时不以 '/' 开始。
function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        true      # continue with function
    else
        return 1  # Skip function.
    fi

一些开发者可能想要移除无操作,但这意味着需要否定条件:

function chgpath() {
    # toC, fromC are the first characters of the argument paths.
    if [[ "$toC" != / || "$fromC" == / ]] && [[ "$toC" == / || "$fromC" != / ]]
    then
        return 1  # Skip function.
    fi

在我看来,从if子句中并不清楚你想要跳过执行函数的条件。为了消除无操作并使其更加明确,您需要将if子句移出函数:

    if [[ "$toC" == / && "$fromC" == / ]] || [[ "$toC" != / && "$fromC" != / ]]
    then
        cdPath=$(chgPath pathA pathB)   # (we moved the conditional outside)

看起来好多了,但很多时候我们不能这样做;我们希望检查在函数内部完成。

那么这种情况有多常见?并不是很常见。也许一年或两年才会出现一次。它发生的足够频繁,您应该意识到它。我在认为它可以提高代码的可读性时(无论使用何种语言),都不会回避使用它。


3
如果您正在回答有关“ :”用途的问题,则应在答案中使用“ :”,而不是“true”。 话虽如此,在此处否定条件语句的最简单方法是使用一个[[...]]命令,并在前面加上“!”:“if![[(... && ...)||(... && ...)]]; then”。 - chepner
1
啊,非常好,我应该给我的答案点个踩。嗯...我还在想我曾经使用过无操作子句的例子。这是我能想到的最好的一个。 - Bitdiot
这只是为了向后兼容而保留的,尽管 : ${...=...} 可以说比 true ${...=...} 看起来更不拘束。有些人可能也更喜欢使用 while :; do 而不是 while true; do,因为它更简洁。 - chepner

3

这个答案有些相关,我发现这个 no-op 对于编写多语言脚本非常方便。例如,以下是一个适用于bash和vimscript的有效注释:

":" #    this is a comment
":" #    in bash, ‘:’ is a no-op and ‘#’ starts a comment line
":" #    in vimscript, ‘"’ starts a comment line

当然,我们同样可以使用true,但是作为一个标点符号而不是一个无关紧要的英语单词,:清楚地表明它是一个语法标记。
至于为什么会有人做出这样一个棘手的事情,即编写一个多语言脚本(除了很酷之外),是因为它在我们通常需要编写几个不同语言的脚本文件的情况下非常有用,其中文件X引用了文件Y
在这种情况下,将两个脚本合并到一个多语言文件中,避免了在文件X中确定路径Y的任何工作(它只是"$0")。更重要的是,它使程序移动或分发更加方便。
  • A common example. There is a well-known, long-standing issue with shebangs: most systems (including Linux and Cygwin) allow only one argument to be passed to the interpreter. The following shebang:

    #!/usr/bin/env interpreter --load-libA --load-libB
    

    will fire the following command:

    /usr/bin/env "interpreter --load-libA --load-libB" "/path/to/script"
    

    and not the intended:

    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"
    

    Thus, you would end up writing a wrapper script, such as:

    #!/usr/bin/env sh
    /usr/bin/env interpreter --load-libA --load-libB "/path/to/script"
    

    This is where polyglossia enters the stage.

  • A more specific example. I once wrote a bash script which, among other things, invoked Vim. I needed to give Vim additional setup, which could be done with the option --cmd "arbitrary vimscript command here". However, that setup was substantial, so that inlining it in a string would have been terrible (if ever possible). Hence, a better solution was to write it in extenso in some configuration file, then make Vim read that file with -S "/path/to/file". Hence I ended up with a polyglot bash/vimscript file.


3

我的两个。

嵌入POD注释

:的一个相当奇特的应用是在bash脚本中嵌入POD注释,以便可以快速生成man页面。当然,最终会重写整个Perl脚本 ;-)

运行时函数绑定

这是一种在运行时绑定函数的代码模式。例如,有一个调试函数只在设置了某个标志时才执行某些操作:

#!/bin/bash
# noop-demo.sh 
shopt -s expand_aliases

dbg=${DBG:-''}

function _log_dbg {
    echo >&2 "[DBG] $@"
}

log_dbg_hook=':'

[ "$dbg" ] && log_dbg_hook='_log_dbg'

alias log_dbg=$log_dbg_hook


echo "Testing noop alias..."
log_dbg 'foo' 'bar'

您将获得:

$ ./noop-demo.sh 
Testing noop alias...
$ DBG=1 ./noop-demo.sh 
Testing noop alias...
[DBG] foo bar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接