让我们想象一下我有script1.sh
以下script2.sh
来源:
脚本1:
#!/bin/bash
# script 1
echo "Doing some stuff..."
bash script2.sh
echo "Done"
和脚本2:
#!/bin/bash
# script 2
printf "\r [ \033[0;33m?\033[0m ] What is your name? "
read -e name
echo "$name"
并且调用bash script1.sh
工作也很优雅。
我正在尝试做类似的事情这里并执行wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install | bash
,但是当它执行script/bootstrap
脚本时,它会在read -e
.
我尝试使用该-i
标志强制交互,但没有成功。
有什么想法吗?
答案1
想法:您正在bash
使用标准输入的管道运行。然后你要求它从标准输入中读取。那是行不通的。
使用命名管道代替?
预计到达时间:或者也许不是……
mkfifo ~/tmp-pipe
wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install > ~/tmp-pipe &
bash ~/tmp-pipe
rm ~/tmp-pipe
此时,如果您还想确保不会破坏某些现有文件,那么您不妨使用临时文件:处理的混乱会更少:
P=$( mktemp )
wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install > $P &
bash $P
rm $P
是的。我不知道。
答案2
bash 进程的标准输入是 wget 下载的脚本。因此,调用会read
从该脚本中读取一行,或者如果 bash 已经完成了脚本,则不会读取任何内容。
wget -O - … | bash
也许是一句可爱的俏皮话,但这不是一个好主意。如果下载中途中断,bash 将执行可以下载的脚本部分,并且 wget 的错误可能很难检测到。最好先下载文件,然后执行它:
wget https://…/install
bash install
rm install
为了避免显式创建临时文件,您可以使用进程替换。这要求您的交互式 shell 支持进程替换:它必须是 bash、ksh93 或 zsh。这与管道具有相同的缺点,即使下载中断,脚本也会被执行,但它不会获取标准输入,而标准输入仍然是终端。
bash <(wget -O - …)
如果您希望脚本支持wget -O - … | bash
管道方法,那么您可以通过将exec </dev/tty
.然而,这是一个坏主意,会导致许多用户诅咒你,因为它使得不可能通过简单地在其标准输入上提供响应来自动运行脚本 - 相反,人们必须使用诸如expect
运行终端中的脚本甚至可以自动使用。
该-i
选项是无关紧要的:bash 不是交互式运行,因为它正在运行脚本,并且标准输入不是终端,因为它是从管道重定向的。打开该-i
选项会使 bash 假装它正在以交互方式使用,即使它在其标准输入上传递了一个脚本;这并没有改变标准输入不是终端的事实。
顺便说一下,这与脚本被分成两部分无关。如果脚本 2 中的代码位于主脚本中,您会看到完全相同的内容。
答案3
其他人提到您的标准输入正在被流脚本取代。这是一个常见问题,但很容易解决:
bash 3<&0 <<\SCRIPT #ref stdin in fd 3, replace stdin w/ SCRIPT
echo Doing some stuff... #do your stuff
printf "\r [ \033[0;33m?\033[0m ] What is your name? "
read -e name <&3 #read from original stdin
echo "$name"
echo Done
SCRIPT
在上面的序列中,我使用heredoc来代替您的管道脚本。因为管道尚未将上面bash
的 stdin 替换为之前的内容(通常是终端)以上只是一个小的比您的脚本所需的复杂程度要简单。但我确实想演示它是如何工作的,这是最简单的方法。
当bash
被调用时,它稍后运行的所有进程都将继承其所有 fd - 除非您显式>&-
关闭它们。因此,如果您只是在调用时执行简单的重定向,则该重定向将级联到所有子级。通过将 fd 0 复制到上面的 fd 3,可以稍后使用read <&3
相同的 fd,而不必做任何烦人的事情,例如</dev/tty
- 尽管如果您有充分的理由与用户进行交互,那么后者可能是一个好主意键盘,而不是用户可能向您发出的声音。
无论如何,标准输出仍然会转到同一个地方 - 因此所有内容echo/printf
都会打印到本来应该是的任何输出流,但将从流输入脚本中bash
获取stdin
,并且对它的任何其他引用都需要明确。
对于你的管道,你还需要一层:
{ wget ... | bash; } 3<&0
这将以大致相同的方式起作用。为此,您不必显式引用<&3
流read
(或任何其他命令):
{ { echo "script$$(){"
wget ...
printf "\n} <&3; script$$\n"
} | bash
} 3<&0
通过这种方式,您实际上定义了一个函数,其中包含整个脚本中的管道,并在读完整个内容后将保存的标准输入显式重定向到函数的标准输入,然后你称之为。因此,其中的所有命令都会将 视为<&3
您的标准输入,您不必以任何方式更改其内容来引用它,但您仍然可以通过标准输入读取整个内容。