コマンドを開始する必要があるスクリプトがあり、そのコマンドにいくつかの追加コマンドをコマンドとして渡します。私は試した
su
echo I should be root now:
who am I
exit
echo done.
...しかし、機能しません:su
は成功しますが、コマンドプロンプトが私を見つめています。プロンプトでexit
と入力すると、echo
とwho am i
etc実行を開始します!そしてその echo done.
はまったく実行されません。
同様に、これがssh
で機能する必要があります。
ssh remotehost
# this should run under my account on remotehost
su
## this should run as root on remotehost
whoami
exit
## back
exit
# back
どうすればこれを解決できますか?
これを一般的な方法で解決し、特に
su
またはssh
に固有ではない回答を探しています。この質問がこの特定のパターンの canonical になることを意図しています。
シェルスクリプトは一連のコマンドです。シェルはスクリプトファイルを読み取り、それらのコマンドを順番に実行します。
通常のケースでは、ここでの驚きはありません。しかし、頻繁な初心者エラーは、-someコマンドがシェルから引き継がれ、現在このスクリプトを実行しているシェルの代わりにスクリプトファイルで次のコマンドの実行を開始すると想定しています。しかし、それはそれが機能する方法ではありません。
基本的に、スクリプトはexactly対話型コマンドと同様に機能しますが、exactlyがどのように機能するかを正しく理解する必要があります。対話的に、シェルはコマンドを(標準入力から)読み取り、そのコマンドを(標準入力からの入力で)実行し、完了したら別のコマンドを(標準入力から)読み取ります。
スクリプトを実行すると、標準入力は依然としてターミナル(リダイレクトを使用しない限り)ですが、コマンドは標準入力からではなく、スクリプトファイルから読み取られます。 (実際にはその逆は非常に扱いにくくなります-read
はスクリプトの次の行を消費し、cat
はスクリプトの残りすべてを丸呑みにし、対話する方法はありません。 !)スクリプトファイルonlyには、それを実行するシェルインスタンスのコマンドが含まれています(もちろん、ヒアドキュメントなどを使用して、入力をコマンド引数として埋め込むこともできます)。
つまり、これらの「誤解されている」コマンド(su
、ssh
、sh
、Sudo
、bash
など)を単独で実行すると(引数なし)は対話型シェルを開始し、対話型セッションではそれで問題ありません。しかし、スクリプトから実行する場合、それはたいていの場合望んでいることではありません。
これらのすべてのコマンドには、対話型ターミナルセッション以外の方法でコマンドを受け入れる方法があります。通常、各コマンドは、コマンドをオプションまたは引数として渡す方法をサポートしています。
su root -c 'who am i'
ssh user@remote uname -a
sh -c 'who am i; echo success'
これらのコマンドの多くは、標準入力のコマンドも受け入れます。
printf 'uname -a; who am i; uptime' | su
printf 'uname -a; who am i; uptime' | ssh user@remote
printf 'uname -a; who am i; uptime' | sh
また、こちらのドキュメントを便利に使用できます。
ssh user@remote <<'____HERE'
uname -a
who am i
uptime
____HERE
sh <<'____HERE'
uname -a
who am i
uptime
____HERE
単一のコマンド引数を受け入れるコマンドの場合、そのコマンドはsh
またはbash
で、複数のコマンドを使用できます。
Sudo sh -c 'uname -a; who am i; uptime'
余談ですが、通常、明示的にexit
を指定する必要はありません。実行するために渡したスクリプト(コマンドのシーケンス)を実行すると、コマンドはとにかく終了します。
別のシェル用のヒアドキュメントとしてフォーマットされたスクリプトのセクションは、独自の環境を持つ別のシェルで(そしておそらく別のマシンでも)実行されることを覚えておくことが重要です。
スクリプトのそのブロックにパラメーター展開、コマンド置換、および/または算術展開が含まれている場合、展開を実行する場所に応じて、シェルのヒアドキュメント機能を少し異なる方法で使用する必要があります。
その場合、ヒアドキュメントの区切り文字はunquotedでなければなりません。
command <<DELIMITER
...
DELIMITER
例:
#!/bin/bash
a=0
mylogin=$(whoami)
Sudo sh <<END
a=1
mylogin=$(whoami)
echo a=$a
echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin
出力:
a=0
mylogin=leon
a=0
mylogin=leon
次に、ヒアドキュメントの区切り文字は引用符で囲まれている必要があります。
command <<'DELIMITER'
...
DELIMITER
例:
#!/bin/bash
a=0
mylogin=$(whoami)
Sudo sh <<'END'
a=1
mylogin=$(whoami)
echo a=$a
echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin
出力:
a=1
mylogin=root
a=0
mylogin=leon
その場合、ヒアドキュメントの区切り文字はunquotedである必要があり、また、子シェル。
例:
#!/bin/bash
a=0
mylogin=$(whoami)
Sudo sh <<END
a=1
mylogin=\$(whoami)
echo a=$a
echo mylogin=\$mylogin
END
echo a=$a
echo mylogin=$mylogin
出力:
a=0
mylogin=root
a=0
mylogin=leon
あらゆる種類のプログラムで機能する一般的なソリューションが必要な場合は、expect
コマンドを使用できます。
マニュアルページから抜粋:
Expect
は、スクリプトに従って他の対話型プログラムと「対話」するプログラムです。スクリプトに従って、Expect
プログラムから何が期待でき、正しい応答が何であるかを知っています。インタプリタ言語は、対話を指示するための分岐および高レベルの制御構造を提供します。さらに、ユーザーは必要に応じて制御を取り、直接対話することができます。その後、制御をスクリプトに戻します。
expect
を使用した実際の例を次に示します。
set timeout 60
spawn Sudo su -
expect "*?assword" { send "*secretpassword*\r" }
send_user "I should be root now:"
expect "#" { send "whoami\r" }
expect "#" { send "exit\r" }
send_user "Done.\n"
exit
その後、簡単なコマンドでスクリプトを起動できます。
$ expect -f custom.script
次のページで完全な例を表示できます。 http://www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands =
注:@tripleeeによって提案された回答は、コマンドの開始時に標準入力を1回読み取ることができる場合、またはttyが割り当てられている場合にのみ機能します、インタラクティブなプログラムでは機能しません。
パイプを使用する場合のエラーの例
echo "su whoami" |ssh remotehost
--> su: must be run from a terminal
echo "Sudo whoami" |ssh remotehost
--> Sudo: no tty present and no askpass program specified
SSHでは、複数の-t
パラメータを使用してTTY割り当てを強制する場合がありますが、Sudo
がパスワードを要求すると失敗します。
expect
のようなプログラムを使用しないと、stdinから情報を取得する可能性がある関数/プログラムを呼び出すと、次のコマンドが失敗します。
ssh use@Host <<'____HERE'
echo "Enter your name:"
read name
echo "ok."
____HERE
--> The `echo "ok."` string will be passed to the "read" command