bash中noop [:]的用例是啥?

Posted

技术标签:

【中文标题】bash中noop [:]的用例是啥?【英文标题】:What is the use case of noop [:] in bash?bash中noop [:]的用例是什么? 【发布时间】:2012-09-06 10:42:59 【问题描述】:

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

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

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

请让我知道,这个算子的任何实时用例或任何强制使用它的地方。

【问题讨论】:

看看***.com/questions/7444504/… 请注意,: 内置存在于 bourne shell 和 ksh 以及 bash 中。 另见: in tldp.org/LDP/abs/html/special-chars.html 这不是一个真正的问题吗?我认为这是一个非常好的问题。我什至有一个很好的用途,但我无法发布答案。 【参考方案1】:

更多是出于历史原因。内置的冒号: 完全等同于true。当返回值很重要时,例如在无限循环中,通常使用true

while true; do
  echo 'Going on forever'
done

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

while keep_waiting; do
  : # busy-wait
done

内置的: 可以追溯到Thompson shell,在Unix v6 中是present。 : 是 Thompson shell 的 goto 语句的标签指示符。标签可以是任何文本,所以: 加倍作为评论指示符(如果没有goto comment,则: comment 实际上是评论)。 Bourne shell 没有goto,但保留了:

使用: 的常见习语是: $var=VALUE,如果未设置var 将设置为VALUE,如果var 已设置则不执行任何操作。这种结构仅以变量替换的形式存在,并且这种变量替换需要以某种方式成为命令的一部分:无操作命令很好地服务。

另见What purpose does the colon builtin serve?。

【讨论】:

很好的总结。此外,原始的 Bourne shell 没有将 # 用于 cmets(或 #!/bin/sh shebangs); : 命令引入了 cmets,让那些在他们的 cmets 中制作了一个漂亮的星星盒子的天真的程序员有祸了:: ****(或者,更糟糕的是,: * * *)。 @JonathanLeffler:真丢脸,但我不明白。 : * * * 会发生什么? 因为: 是一个命令,所以shell 在发现: 忽略它们之前仍然必须处理它的参数。大多数情况下,您只是让外壳程序在将* 扩展为当前目录中的文件列表时做额外的工作;它实际上不会影响脚本的工作方式。 我想如果你碰巧在一个有很多文件的目录中运行它可能会使你的脚本失败,从而使 glob 扩展超过命令长度限制?也许只有当你有set -e 时。无论如何,希望我们都同意使用# :) 如果我想要一个快速而肮脏的无限循环,我有时会使用while : ; do ... ; done。 (通常循环中有一个sleep,我按Ctrl-C 来杀死它。)【参考方案2】:

当我注释掉所有代码时,我将它用于 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

【讨论】:

没有 SO 和像你这样的好答案我该怎么办:) 很多谢谢【参考方案3】:

如果您使用set- e,那么|| : 是一个很好的方式,可以在发生故障时退出脚本(它明确地使其通过)。

【讨论】:

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

您可以使用: 来提供一个成功但不执行任何操作的命令。在本例中,“详细”命令默认关闭,将其设置为:。 '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语句中抛出错误。 @qdeninja:没有人声称变量赋值“什么也没做”;关键是$(verbosity $i) 稍后(并且有条件地)什么都不做。【参考方案5】:

忽略 alias 参数

有时您希望有一个不带任何参数的别名。你可以使用:

> alias alert_with_args='echo hello there'

> alias alert='echo hello there;:'

> alert_with_args blabla
hello there blabla

> alert blabla
hello there

【讨论】:

不是反对者,但不幸的是,这个答案并没有说明: 的工作原理。您提供的示例似乎是多余的。 问题不在于它是如何工作的,而在于用例是什么。确实,我的回答与***.com/a/37170755/6320039 有点相似。但这确实不是同一个用例。我很乐意改进我的答案,但我真的不知道如何在这里.. 这有点时髦——如果你愿意的话,这是一个角落的案例——但仍然很有趣,可能是一个聪明的小把戏。 同样可以用#实现 @ZoeyHewll,不完全是。由于别名在任何类型的执行之前被以文本方式替换,因此使用# 的别名将使同一行中语法上出现的所有内容都被忽略。这确实是不同的(考虑my_alias arg ; other_command 或相反的my_alias arg1 BACKSLASHNEWLINE arg2),并且可能不是您想要的,因为它可能会导致语法错误(猜猜while my_alias ; do true ; donewhile true ; do my_alias ; done 会做什么)。【参考方案6】:

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

: << 'EOF'

This part of the script is a commented out

EOF

不要忘记在EOF 周围使用引号,这样内部的任何代码都不会被评估,例如$(foo)。也可能值得使用直观的终止符名称,例如 NOTESSCRATCHPADTODO

【讨论】:

很酷的把戏!特别是对于硬包装时需要数十个“#”的草稿笔记和代码。副作用是,与注释行不同,一些语法高亮显示将忽略 heredocs,像普通代码(或至少纯文本)一样着色。这对于检查注释掉的代码或将您的眼睛从霓虹注释颜色的段落中拯救出来非常有用。唯一需要注意的是,在共享代码中应将此类块标记为 cmets,因为语法是唯一的并且可能导致混淆。再说一次,这是我们正在谈论的外壳,其中潜在的混乱是系统性的。【参考方案7】:

我的两个。

嵌入 POD cmets

: 的一个非常时髦的应用程序是用于embedding POD comments in bash scripts,以便可以快速生成手册页。当然,最终会用 Perl 重写整个脚本 ;-)

运行时函数绑定

这是一种在运行时绑定函数的代码模式。 F.i.,只有在设置了某个标志时才具有调试功能:

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

【讨论】:

【参考方案8】:

与this answer 有点相关,我发现这个无操作很方便破解polyglot 脚本。例如,下面是 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")。更重要的是,它使移动或分发程序更加方便。

一个常见的例子。 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 额外的设置,这可以通过选项 --cmd "arbitrary vimscript command here" 来完成。但是,该设置非常重要,因此将其内联在字符串中会很糟糕(如果可能的话)。因此,一个更好的解决方案是将它extenso 写入某个配置文件中,然后让 Vim 使用-S "/path/to/file" 读取该文件。因此,我最终得到了一个多语言 bash/vimscript 文件。

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

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

这可能是一个见仁见智的问题,但这里有一个例子。假设您创建了一个通过两条 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)

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

那么这种情况多久发生一次?不经常。也许一年一次或两次。它经常发生,你应该意识到它。当我认为它提高了我的代码的可读性(不管是什么语言)时,我不会回避使用它。

【讨论】:

如果您要回答有关: 用途的问题,您应该在答案中使用:,而不是true。也就是说,在这里否定条件的最简单方法是使用一个[[ ... ]] 命令并在其前面加上!: if ! [[ ( ... &amp;&amp; ... ) || ( ... &amp;&amp; ... ) ]]; then 啊,非常好,我应该否决我的答案。嗯....我仍在考虑必须使用无操作子句的示例。这是我想出的最好的。 它实际上只是为了向后兼容而保留的,尽管: $...=... 可以说比true $...=... 看起来不那么尴尬。有些人可能更喜欢while :; do 而不是while true; do,因为它更简洁。【参考方案11】:

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


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

【讨论】:

这比VARIABLE1=$VARIABLE1:-my_default_value 有什么好处?只是它更短,还是有别的原因?【参考方案12】:

我有时在 Docker 文件上使用它来保持 RUN 命令对齐,如下所示:

RUN : \
    && somecommand1 \
    && somecommand2 \
    && somecommand3

对我来说,它读起来比:

RUN somecommand1 \
    && somecommand2 \
    && somecommand3

当然,这只是偏好问题

【讨论】:

以上是关于bash中noop [:]的用例是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Python中“通过”的用例是啥? [复制]

NuxtJs asyncData 的用例是啥?

IEEE 754 中 Infinity 的用例是啥

Streams 和 Firehose 的用例是啥?

TypeScript 中的“as const”是啥意思,它的用例是啥?

内存数据库的用例是啥? [关闭]