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

我在 bash (:)中搜索 noop,但未能找到任何有用的信息。这个操作符的确切用途或用例是什么?

我试着跟着做,结果是这样的:

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

请让我知道,这个操作符的任何实时用例或任何地方,它是强制使用它。

69457 次浏览

因为历史的原因。结肠内在 :完全等同于 true。当返回值很重要时,通常使用 true,例如在无限循环中:

while true; do
echo 'Going on forever'
done

当 shell 语法需要一个命令,但是无需执行任何操作时,通常使用 :

while keep_waiting; do
: # busy-wait
done

内置的 :可以追溯到 汤普森弹壳,它是 现在:0。:是 Thompson shell 的 goto语句的标签指示符。这个标签可以是任何文本,所以 :作为一个注释指示符(如果没有 goto comment,那么 : comment实际上就是一个注释)。:1没有 goto,但保留了 :

使用 :的一个常见习惯用法是 : ${var=VALUE},如果未设置 var,则将其设置为 VALUE,如果已设置 var,则不执行任何操作。这个结构只以变量替换的形式存在,而且这个变量替换需要以某种方式成为命令的一部分: no-op 命令非常有用。

参见 结肠内置的作用是什么?

有时,no-op 子句可以使代码更具可读性。

这可能是一个观点问题,但这里有一个例子。让我们假设您已经创建了一个通过采用两个 unix 路径工作的函数。它计算 cd 从一个路径到另一个路径所需的“更改路径”。您对函数设置了一个限制,即路径都必须以“/”或“ both”开头,但不能以“/”或“ both”开头。

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

一些开发人员会想要删除 no-op,但这将意味着否定条件:

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

现在,在我看来,if 子句中并不是很清楚你想要跳过函数的条件。为了消除 no-op 并做得更清楚,你需要将 if 子句移出函数:

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

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

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

您可以使用 :提供一个成功但不执行任何操作的命令。在这个示例中,通过将“冗长”命令设置为 :,该命令在默认情况下被关闭。“ 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

如果您使用 set- e,那么如果发生故障(它显式地使其通过) ,|| :没有退出脚本的好方法。

我的两个人。

嵌入 POD 注释

:的一个非常时髦的应用程序是针对 在 bash 脚本中嵌入 POD 注释的,因此可以快速生成手册页。当然,最终会用 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

当我注释掉所有代码时,它用于 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 不能有空块(卧槽) ,所以你添加了一个 no-op:

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

或者你可以使用 no-op 来注释下面的行:

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

忽略 alias参数

有时你想要一个不需要任何参数的别名,你可以使用 ::

> alias alert_with_args='echo hello there'


> alias alert='echo hello there;:'


> alert_with_args blabla
hello there blabla


> alert blabla
hello there

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

: << 'EOF'


This part of the script is a commented out


EOF

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

这个答案有些相关的是,我发现这种无操作相当方便地破解 通晓多种语言脚本。例如,下面是 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")。更重要的是,它使移动或分发程序更加方便。

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

    #!/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"来完成。但是,这种设置非常重要,因此将其内联到一个字符串中会非常糟糕(如果可能的话)。因此,更好的解决方案是将其写入某个配置文件中的 全部,然后让 Vim 使用 -S "/path/to/file"读取该文件。因此,我得到了一个通晓多种语言的 bash/vimscript 文件。

假设你有一个命令,你希望把它与另一个命令的成功联系起来:

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

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


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


我有时会在 Docker 文件中使用它来保持 RUN 命令的对齐,比如:

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

对我来说,它比:

RUN somecommand1 \
&& somecommand2 \
&& somecommand3

当然,这只是个人喜好的问题

Null 命令 [:]实际上被认为是内置在 没错中的 shell 的同义词。":"命令本身是 Bash 内置的,其退出状态为 true (0)。 ` $ : “ Echo”

while :
do
operation-1
operation-2
...
operation-n
done


# Same as:
while true
do
...
done

如果/然后测试占位符:

if condition
then :   # Do nothing and branch ahead
else     # Or else ...
take-some-action
fi




$ : ${username=`whoami`}
$ ${username=`whoami`}   #Gives an error without the leading :

资料来源: TLDP