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
標準入力でパイプを使用して実行しています。その後、標準入力から読み込むように要求します。それは動作しません。
代わりに名前付きパイプを使用しますか?
ETA:それとも違うかも…
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
スクリプトから1行を読み取るか、bashがすでにスクリプトを完了している場合は何も読みません。
wget -O - … | bash
たぶんかわいいジョークかもしれませんが、良い考えではありません。ダウンロードが中断されると、bashはダウンロード可能なスクリプト部分を実行するため、wgetのエラーは検出しにくい場合があります。まず、ファイルをダウンロードして実行することをお勧めします。
wget https://…/install
bash install
rm install
一時ファイルを明示的に生成したくない場合は、プロセス置換を使用できます。これを行うには、対話型シェルがプロセス置換をサポートする必要があります。つまり、bash、ksh93、またはzshでなければなりません。これはパイプと同じ欠点がある。ダウンロードが中断されてもスクリプトは実行されますが、まだ端末である標準入力を取得できません。
bash <(wget -O - …)
スクリプトがwget -O - … | bash
パイプメソッドをサポートできるようにするには、渡しますexec </dev/tty
。しかし、これは悪い考えであり、単に応答を提供するだけではスクリプトを自動化できないため、多くのユーザーがあなたを呪います。標準入力 - 代わりに、端末でスクリプトを実行するのと同じものを使用する必要があるexpect
か、自動的に使用することもできます。
この-i
オプションは関係ありません。 bashはスクリプトを実行するため、対話的に実行されず、標準入力はパイプからリダイレクトされるため、端末ではありません。この-i
オプションをオンにすると、標準入力にスクリプトが渡されますが、bashは対話式に使用されるふりをします。これは、標準入力が端末ではないという事実を変えない。
ちなみに、これはスクリプトが2つの部分に分割されることとは何の関係もありません。スクリプト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
上記の標準入力を以前の内容に置き換えていないため(通常ターミナル)上記は1つだけです。小さいスクリプトが必要以上に簡単です。しかし、私はそれがどのように機能するかを見せたいのですが、これが最も簡単な方法です。
呼び出されると、bash
後で実行されるすべてのプロセスは、明示的に閉じない限り、すべてのfdを継承します>&-
。したがって、通話中に単純なリダイレクトを実行すると、そのリダイレクトはすべての子に渡されます。上記のfd 0をfd 3にコピーすると、read <&3
後で同じ迷惑な操作を行わなくても同じfdを使用できます</dev/tty
。ユーザーがキーボードを操作するよりも、ユーザーのキーボードと対話するための妥当な理由がある場合、後者はおそらく良い考えです。あなたに話すこともできます。
それにもかかわらず、標準出力はまだ同じ位置に移動します。したがって、すべてのecho/printf
出力ストリームは任意の出力ストリームに印刷されますが、ストリーム入力スクリプトからbash
取得され、stdin
他の参照は明示的でなければなりません。
パイプラインにはもう1つのレイヤーが必要です。
{ wget ... | bash; } 3<&0
これはほぼ同じように機能します。これを行うには、<&3
ストリームを明示的に参照する必要はありません。read
(または他のコマンド):
{ { echo "script$$(){"
wget ...
printf "\n} <&3; script$$\n"
} | bash
} 3<&0
これにより、実際にスクリプト全体をパイプして内容全体を読み取った後、保存された標準入力を関数の標準入力に明示的にリダイレクトする関数を定義できます。それからあなたはそれを呼びます。したがって、ここに含まれるすべてのコマンドは標準入力として扱われ、<&3
それを参照するために内容を変更する必要はありませんが、標準入力を介して内容全体を読み続けることができます。