web-dev-qa-db-ja.com

コマンドを別のコマンド(su、ssh、shなど)への入力として渡す

コマンドを開始する必要があるスクリプトがあり、そのコマンドにいくつかの追加コマンドをコマンドとして渡します。私は試した

su
echo I should be root now:
who am I
exit
echo done.

...しかし、機能しません:suは成功しますが、コマンドプロンプトが私を見つめています。プロンプトでexitと入力すると、echowho 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 になることを意図しています。

19
tripleee

シェルスクリプトは一連のコマンドです。シェルはスクリプトファイルを読み取り、それらのコマンドを順番に実行します。

通常のケースでは、ここでの驚きはありません。しかし、頻繁な初心者エラーは、-someコマンドがシェルから引き継がれ、現在このスクリプトを実行しているシェルの代わりにスクリプトファイルで次のコマンドの実行を開始すると想定しています。しかし、それはそれが機能する方法ではありません。

基本的に、スクリプトはexactly対話型コマンドと同様に機能しますが、exactlyがどのように機能するかを正しく理解する必要があります。対話的に、シェルはコマンドを(標準入力から)読み取り、そのコマンドを(標準入力からの入力で)実行し、完了したら別のコマンドを(標準入力から)読み取ります。

スクリプトを実行すると、標準入力は依然としてターミナル(リダイレクトを使用しない限り)ですが、コマンドは標準入力からではなく、スクリプトファイルから読み取られます。 (実際にはその逆は非常に扱いにくくなります-readはスクリプトの次の行を消費し、catはスクリプトの残りすべてを丸呑みにし、対話する方法はありません。 !)スクリプトファイルonlyには、それを実行するシェルインスタンスのコマンドが含まれています(もちろん、ヒアドキュメントなどを使用して、入力をコマンド引数として埋め込むこともできます)。

つまり、これらの「誤解されている」コマンド(susshshSudobashなど)を単独で実行すると(引数なし)は対話型シェルを開始し、対話型セッションではそれで問題ありません。しかし、スクリプトから実行する場合、それはたいていの場合望んでいることではありません。

これらのすべてのコマンドには、対話型ターミナルセッション以外の方法でコマンドを受け入れる方法があります。通常、各コマンドは、コマンドをオプションまたは引数として渡す方法をサポートしています。

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を指定する必要はありません。実行するために渡したスクリプト(コマンドのシーケンス)を実行すると、コマンドはとにかく終了します。

17
tripleee

tripleeeanswer に追加:

別のシェル用のヒアドキュメントとしてフォーマットされたスクリプトのセクションは、独自の環境を持つ別のシェルで(そしておそらく別のマシンでも)実行されることを覚えておくことが重要です。

スクリプトのそのブロックにパラメーター展開、コマンド置換、および/または算術展開が含まれている場合、展開を実行する場所に応じて、シェルのヒアドキュメント機能を少し異なる方法で使用する必要があります。

1.すべての展開は、親シェルのスコープ内で実行する必要があります。

その場合、ヒアドキュメントの区切り文字は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

2.すべての展開は、子シェルのスコープ内で実行する必要があります。

次に、ヒアドキュメントの区切り文字は引用符で囲まれている必要があります

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

3.一部の拡張は子シェルで実行する必要があり、一部は親で実行する必要があります。

その場合、ヒアドキュメントの区切り文字は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
15
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
8
Adam