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


123

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

我尝试了以下操作,它对我来说是这样的:

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

请让我知道,该操作员的实时使用情况或在强制使用它的任何地方。



请注意,:内置文件存在于bourne shell和ksh以及bash中。
ghoti 2012年


6
这怎么不是一个真正的问题?我认为这是一个非常好的问题。我什至可以很好地利用它,但是我无法发布答案。
史蒂文·卢

Answers:


175

由于历史原因,这里更多。内建的冒号:完全等于truetrue当返回值很重要时,例如在无限循环中,通常会使用:

while true; do
  echo 'Going on forever'
done

:当shell语法需要命令但您无事可做时,通常会使用它。

while keep_waiting; do
  : # busy-wait
done

:内置日期全部回到原来的老路汤普森壳,它是目前Unix的V6发动机:是Thompson shell goto声明的标签指示符。标签可以是任何文本,因此:可以用作注释指示符(如果没有goto comment,则: comment实际上是注释)。在Bourne shell中没有goto但保留:

常用的惯用法:: ${var=VALUE},它设置varVALUEunset,如果不设置则不执行任何操作var已被设置。此构造仅以变量替换的形式存在,并且此变量替换需要以某种方式成为命令的一部分:no-op命令可以很好地发挥作用。

另请参阅内建冒号的目的是什么?


11
好总结。另外,原始的Bourne外壳不#用于注释(或#!/bin/shshebangs)。该:命令引入了注释,并祸害天真的程序员,他们在注释中打出了一个漂亮的星星:(: ****或者更糟的是,: * * *)。
乔纳森·勒夫勒

3
@JonathanLeffler:对我感到羞耻,但我不明白。会发生什么: * * *
DevSolar 2014年

7
因为:是命令,所以外壳程序仍必须先处理其参数,然后才能发现:忽略它们的参数。通常,您只是使shell在扩展*到当前目录中的文件列表方面做了额外的工作。它实际上不会影响脚本的工作方式。
chepner 2014年

我想如果您碰巧在包含很多文件的目录中运行,可能会使您的脚本失败,从而使全局扩展超出命令长度限制?也许只有当你set -e继续。无论如何,希望我们都同意使用它#:)
Jack O'Connor

2
while : ; do ... ; done如果我想要快速且肮脏的无限循环,有时会使用。(通常sleep在循环中有一个,然后键入Ctrl-C将其杀死。)
Keith Thompson

21

当我注释掉所有代码时,将它用于if语句。例如,您有一个测试:

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

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

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

这导致bash给出语法错误:

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

12

如果您使用,set- e那么这|| :是一种在失败发生时退出脚本(显式使其通过)的好方法。


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

7

您将使用:提供成功但不执行任何操作的命令。在此示例中,“ 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  

严格来说,给变量赋值就是“做某事”,因此为什么它不会在case语句中引发错误。
qodeninja

@qodeninja:没有人声称变量赋值“没有做任何事情”;关键是$(verbosity $i)后面的(有条件的)什么都不做。
Lightness Races in Orbit

6

忽略alias参数

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

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there

而不是拒绝投票的人,但是:不幸的是,这个答案并没有说明其工作原理。您提供的示例似乎多余。
qodeninja

2
问题不是它如何工作,而是用例是什么。的确,我的回答有点类似于stackoverflow.com/a/37170755/6320039。但这确实不是同一用例。我很高兴来提高我的答案,但我真的不krnow怎么在这里..
雅典BN

1
这有点时髦-如果可以的话,可以算是一个小巧的选择-但还是很有趣的,这可能是一个巧妙的技巧。
Mike S

2
同样可以实现#
佐伊Hewll

2
@ZoeyHewll,不完全是。由于别名是在执行任何文本之前替换的,因此使用别名#会使在同一行上语法之后出现的所有内容都被忽略。这确实是不同的(考虑my_alias arg ; other_command或相反 my_alias arg1 {BACKSLASH}{NEWLINE} arg2),并且可能不是您想要的,因为它可能会导致语法错误(猜测要做什么while my_alias ; do true ; donewhile true ; do my_alias ; done将要做什么)。
马兰(Maëlan)

4

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

: << 'EOF'

This part of the script is a commented out

EOF

别忘了在引号周围使用引号,EOF这样里面的任何代码都不会被评估,例如$(foo)。它也可能会使用一个直观的终结名字一样值得NOTESSCRATCHPADTODO


酷把戏!特别是对于草稿笔记和硬包装时需要数十个“#”的代码。副作用是,与注释行不同,某些语法突出显示工具将忽略heredocs,将它们像普通代码(或至少纯文本)一样着色。这对于检查已注释掉的代码或以霓虹注释颜色使您的眼睛免于段落非常有用。唯一需要注意的是,由于语法是唯一的并且可能引起混淆,因此应该将此类块标记为共享代码中的注释。再说一遍,这就是我们正在谈论的shell,其中混乱的可能性是系统性的。
Beejor

3

我的两个。

嵌入POD注释

一个非常时髦的应用程序:将POD注释嵌入bash脚本中,以便可以快速生成手册页。当然,最终还是会在Perl中重写整个脚本;-)

运行时函数绑定

这是一种用于在运行时绑定功能的代码模式。Fi,只有在设置了特定标志的情况下,才具有调试功能以执行某些操作:

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

2

有时,无操作子句可使您的代码更具可读性。

这可能是一个见解,但这是一个示例。假设您已经创建了一个可以通过两个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 2014年

1
啊,非常好,我应该拒绝我的回答。嗯....我仍在考虑必须使用no-op子句的示例。这是我想出的最好的。
Bitdiot 2014年

它实际上只是为了向后兼容而保留的,尽管: ${...=...}可以说看起来不那么尴尬true ${...=...}。有些人可能更喜欢while :; dowhile true; do了简洁为好。
chepner 2014年

2

此答案有些相关,我发现此无操作非常容易破解多语种脚本。例如,这是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引用file)的情况下,这被证明是有帮助的Y

在这种情况下,将两个脚本合并到一个多语言文件中可以避免X确定路径的工作Y(很简单"$0")。更重要的是,它使四处移动或分发程序更加方便。

  • 一个常见的例子。shebangs有一个众所周知的长期存在的问题:大多数系统(包括Linux和Cygwin)仅允许将一个参数传递给解释器。以下shebang:

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

    将触发以下命令:

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

    而不是预期的:

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

    因此,您最终将编写包装器脚本,例如:

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

    这就是多语症进入阶段的地方。

  • 一个更具体的例子。我曾经写过一个bash脚本,其中包括Vim。我需要给Vim附加设置,这可以通过option来完成--cmd "arbitrary vimscript command here"。但是,该设置非常重要,因此将其内联到字符串中将是很糟糕的(如果可能的话)。因此,更好的解决方案是将它扩展方式写入一些配置文件,然后使Vim使用读取该文件-S "/path/to/file"。因此,我最终得到了一个多语言的bash / vimscript文件。


1

我还在其中使用了脚本来定义默认变量。


: ${VARIABLE1:=my_default_value}
: ${VARIABLE2:=other_default_value}
call-my-script ${VARIABLE1} ${VARIABLE2}

0

假设您有一条希望链接到另一个成功的命令:

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 || :
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.