【问题标题】:How to understand this line of Bash script?如何理解这一行 Bash 脚本?
【发布时间】:2018-12-12 12:03:24
【问题描述】:

我一直在尝试找出一个 Bash 脚本文件,它通过研究大量文档和在线搜索来启动一些 Docker 容器(我对 Bash 很陌生)。但是,我根本无法弄清楚以下行的作用:

which systemctl 2>&1 >/dev/null && systemctl stop firewalld && systemctl restart docker

我理解 2>&1 >/dev/null 部分。

我也明白“which”是一个实用程序,它基本上通过引用 PATH 信息将可执行文件的完整路径打印到 STDOUT(如果我错了,请纠正我),我相信它不是最推荐的实用程序,因为它不是在不同的 UNIX-y 系统中通用。

但是,我根本不理解那条线。

我尝试通过输入which nodewhich docker 之类的东西来玩它,它的行为与预期的一样,但我根本不明白上面一行发生了什么。

作为参考,整个代码及其输出如下:

PROVISION_PORT=3000
HLF_WORKSPACE=~/HLF_workspace

usage() {
    echo "Usage: $0 [-p provision_port] [-d HLF_workspace_path]"   # $0: 0th arg -> ./setupProvCont
}

if [ $# -eq 0 ]
then
   echo "No input parameter, will use default settings"
fi

while getopts "p:d:h" arg       #get options
do
    case $arg in
     h)
        usage
        exit        #exit 0 -> normal exit
        ;;
     p)
        PROVISION_PORT=$OPTARG  #optional arguments
        ;;
     d)
        HLF_WORKSPACE=$OPTARG
        ;;
     ?)
        usage
        exit 1     #exit 1 -> error exit, unknown flag used
        ;;
    esac
done

which systemctl 2>&1 >/dev/null && systemctl stop firewalld && systemctl restart docker
echo "Starting Provision..."
echo "docker run -it -u `id -u $USER` --network=host -e "PROVISION_PORT=$PROVISION_PORT" -e \"BCS_DOCKER_WORKSPACE=$HLF_WORKSPACE\" -e \"VM_HOSTNAME=`hostname`\" -v /var/run:/var/run -v $HLF_WORKSPACE:$HLF_WORKSPACE -d oracle/HLF-provision"
docker run -it -u `id -u $USER` --network=host -e "PROVISION_PORT=$PROVISION_PORT" -e "BCS_DOCKER_WORKSPACE=$HLF_WORKSPACE" -e "VM_HOSTNAME=`hostname`" -v /var/run:/var/run -v $HLF_WORKSPACE:$HLF_WORKSPACE -d oracle/HLF-provision 

输出:

[hlf@hlfdemo hlf_fabric]$ ./setupProvisionContainer.sh 
No input parameter, will use default settings
Starting Provision...
docker run -it -u 1000 --network=host -e PROVISION_PORT=3000 -e "BCS_DOCKER_WORKSPACE=/home/hlf/HLF_workspace" -e "VM_HOSTNAME=hlfdemo.internal" -v /var/run:/var/run -v /home/hlf/HLF_workspace:/home/hlf/HLF_workspace -d hlf/HLF-provision
b7f3073cc218b70525341c1770aaef17fc41c8e76f858807e7fd6e995594c60d
[hlf@hlfdemo hlf_fabric]$ 

【问题讨论】:

  • which systemctl 背后的一般想法是确定是否安装了 systemd,如果安装了,则调用两个下一个命令。除了返回给定命令的路径之外,当在$PATH 中找不到命令时,which 会以非零状态退出。
  • 如前所述,&& 条件使每个步骤都基于前一个步骤,因此如果which 失败,它不会停止firewalld 并重新启动docker。它也不会跳过该行 after 的任何内容,也不会检查 firewalld 是否正在运行,所以我猜它要么没那么重要,要么来自 @ 的错误987654333@ 在某些日志中足以让管理员解决问题并重试。
  • @PaulHodges 和 user3159253,非常感谢您的澄清,我终于明白了!整个命令是一个 AND 列表,仅当组成它的所有三个命令都退出且没有错误时才会执行,即退出 0,并且 which systemctl 2>&1 >/dev/null 将 STDERR 重定向到 STDOUT 然后丢弃它(dev/ null),从而检查以确保 systemctl 存在。如果没有,将显示错误并且此行不执行任何其他操作,但以下行确实会执行。如果我在某个地方仍然出错,请纠正我!再次感谢您!
  • 是的,虽然我们都需要注意它丢弃了错误,所以它甚至不会被记录,哈哈……但是是的,一般来说,你有它。
  • @PaulHodges 有道理,谢谢你的澄清!

标签: bash


【解决方案1】:

命令

which systemctl 2>&1 >/dev/null && systemctl stop firewalld && systemctl restart docker

是由布尔运算符 && 分隔的三个命令的 AND 列表。

它们从左到右执行,AND 运算符 (&&) 检查其左侧命令的退出代码,并仅在该退出代码为 0(表示“成功”)时调用右侧命令。

这基本上意味着命令一个接一个地执行,直到其中一个失败。

which 通常在这个例子中被用来检查一个程序是否可用,以确保它后面的命令可以被执行。如果程序不可用,则不会执行它们(如果找不到程序,which 以退出代码 1 完成)。
which 的输出和错误被重定向到/dev/null (它们基本上被丢弃了),因为这里我们不需要知道systemctl 的位置,但只要它存在(并且在直接调用时会被发现)列表中的下一个命令)。

你总是可以通过检查特殊shell变量$?的内容来检查上一个命令执行的退出代码:

$ which ls
/bin/ls
$ echo $?
0
$ which some-program-that-does-not-exist
$ echo $?
1

更新

重定向分别适用于每个简单命令,不会影响其他命令。

这个命令行中有三个简单的命令:

which systemctl 2>&1 >/dev/null
systemctl stop firewalld
systemctl restart docker

只有第一个重定向了 stdoutstderr,其他两个不受影响,它们将输出(和错误)发送到标准输出和错误流(默认连接到终端) .

重定向是从左到右处理的。

首先,2>&1 复制文件描述符 1 (stdout) 并将副本存储在文件描述符 2 (stderr) 下。用简单的英语来说,这意味着在2>&1 之后,stderr 将其内容发送到stdout 所做的同一流。

然后,>/dev/null 更改 stdout(并且只有 stdout)指向特殊文件 /dev/null
/dev/null 是一个特殊文件,不使用空间和写入的所有内容它被丢弃。将stdout 和/或stderr 重定向到它是丢弃输出和/或命令错误的标准Unix 方式。

请注意,2>&1 >/dev/null 之后,stdout 指向 /dev/nullstderr 指向 stdout 的前一个值 不是 /dev/null 而是控制台(终端窗户);这也是 stderr 的先前值。
换句话说,这会错误地使stderr 将其内容发送到终端,并且只重定向stdout

正确的做法是先重定向stdout,然后然后stdout复制到stderr

which systemctl >/dev/null 2>&1

或者您可以使用&> 重定向运算符将stdoutstderr 重定向到同一个文件:

which systemctl &>/dev/null

【讨论】:

  • 好吧,我终于明白了!!因此,正如您所说,这是三个命令的 AND 列表,在第一部分中,我们只是检查 systemctl 是否存在,如果不存在,我们将 STDERR 重定向到 STDOUT 然后丢弃它(这是正确的顺序,对吗?),然后跳过剩下的两个。如果哪个没有抛出错误,我们停止防火墙服务并重新启动 docker!非常感谢您的澄清!
  • 如果我可以冒昧地问另一个小问题,通过案例切换,我们看到正在处理配置端口和工作区路径的案例,以及处理未知标志输入的案例,但那是什么导致成功退出代码 0 的 case 'h'?下面的代码究竟意味着什么:getopts "p:d:h" arg ?
  • stdoutstderr/dev/null 的重定向是第一个命令的一部分。无论systemctl 是否存在,它都会发生,并且它不依赖于which 的退出代码。此外,它仅适用于第一个命令。第二个和第三个命令的标准输出和错误流不受影响,每个命令使用自己的重定向(这里第二个和第三个命令没有)。使用man bashgnu.org/software/bash/manual/html_node/index.html#SEC_Contents,在您的终端舒适地阅读有关 Bash 命令和重定向的更多信息
  • 关于getopt,您可以在此处阅读其帮助linux.die.net/man/1/getopt,或者您可以在终端中运行man getopt。无论哪种方式,在Stack Overflow 上最好再问一个关于它的问题(仅包括相关代码)。
  • c.f. This tutorialgetopts 是一个解析命令行参数的工具。如果用户使用-h(寻求帮助)提交了脚本,那么它会调用usage 函数以打印出预期的调用约定,然后以干净的返回码退出(因为它成功地完成了所要求的操作。)跨度>
猜你喜欢
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多