我有一个脚本install.sh
基本上如下所示:
#!/bin/bash
if [[ ! -f "/usr/bin/jq" ]]; then
apt-get update && apt-get install -y jq
fi
echo $(date) >> install.log
如果我把它放在服务器上,然后像这样运行:
curl -s0 https://myserver.com/install.sh | bash
在疯狂的apt-get
输出之后,它不会运行最后echo
一行,而是将该行打印echo $(date) >> test.log
为输出。而如果我将 保存install.sh
在磁盘上并运行./install.sh
,它会按预期运行。执行 echo 行并install.log
附加 。
为了进一步重现这个问题,我在本地运行它,如下所示:
cat install.sh | bash
它也有同样的问题。
我apt-get remove jq
在每次测试之前手动运行。如果jq
安装了,那么两者都会按预期工作。那么为什么从下载运行它与从本地文件运行它不同呢?
我录制了一个短视频来演示这个问题:https://www.youtube.com/watch?v=lYvGXI7AibA
答案1
通常bash
不关心它是从文件(如bash foo
)还是从 STDIN(如bash <foo
)读取命令。但是现在您启动一个命令 ( apt-get
),该命令本身想要询问用户并使用 STDIN 来执行此操作。
因此奇怪的事情发生了:bash 从 STDIN 读取命令并一一执行它们。现在,其中一个命令从 STDIN 读取,并且读取文件的下一行。之后 bash 从 STDIN 读取下一行,因此跳过另一个命令已经读取的一个命令。
这个小脚本将演示正在发生的事情:
echo "hello"
read -p "how do you do? "
echo "I understand"
echo "you are '$REPLY'."
echo "bye"
将其保存在文件中,然后首先运行为 ,bash -x foo
然后运行为bash -x <foo
.
在你的例子中也会发生同样的事情。
一种解决方案是使用进程替换:
bash -x <(cat foo)
或者在你的情况下简单地:
bash <(curl -s0 https://myserver.com/install.sh)
一些备注:
每当某个 shell 脚本做了奇怪的事情时,请使用bash -x
来查看发生了什么。
写作cat foo| ...
被称为猫的无用用途。只需使用重定向即可。