web-dev-qa-db-ja.com

Linuxでの標準へのbashファイルのリダイレクトはシェル( `sh`)とどう違うのですか?

実行中にユーザーを切り替えるスクリプトを作成し、標準のファイルリダイレクトを使用して実行しました。したがって、user-switch.shは...

#!/bin/bash

whoami
Sudo su -l root
whoami

bashで実行すると、期待した動作が得られます

$ bash < user-switch.sh
vagrant
root

ただし、shを使用してスクリプトを実行すると、異なる出力が得られます

$ sh < user-switch.sh 
vagrant
vagrant

なぜbash < user-switch.shとは異なる出力を与えるsh < user-switch.sh

ノート:

  • debianJessieを実行している2つの異なるボックスで発生します
9
popedotninja

Sudoなしの同様のスクリプトですが、結果は同様です。

_$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta
_

bashを使用すると、スクリプトの残りの部分はsedへの入力として送信されます。dashを使用すると、シェルはスクリプトを解釈します。

これらに対してstraceを実行すると、dashはスクリプトのブロックを読み取り(ここでは8 kB、スクリプト全体を保持するのに十分な量を超えます)、次にsedを生成します。

_read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...
_

これは、ファイルハンドルがファイルの最後にあり、sedが入力を認識しないことを意味します。 dash内でバッファリングされている残りの部分。 (スクリプトが8 kBのブロックサイズよりも長い場合、残りの部分はsedによって読み取られます。)

一方、bashは最後のコマンドの最後までシークします。

_read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...
_

次のように、入力がパイプからのものである場合:

_$ cat script.sh | bash
_

パイプとソケットがシークできないため、巻き戻しを実行できません。この場合、Bashは、一度に1文字ずつ入力を読み取るようにフォールバックして、過剰読み取りを回避します。 ( fd_to_buffered_stream() in _input.c_ )各バイトに対して完全なシステムコールを行うことは、原則としてあまり効果的ではありません。実際には、読み取りが大きなオーバーヘッドになるとは思いません。シェルが行うほとんどのことは、まったく新しいプロセスの生成を伴うという事実に。

同様の状況は次のとおりです。

_echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'
_

サブシェルは、readが最初の改行のみを読み取るようにして、headが次の行を参照できるようにする必要があります。 (これはdashでも機能します。)


言い換えると、Bashは、スクリプト自体とそれから実行されるコマンドの同じソースの読み取りをサポートするために、追加の長さを使用します。 dashはしません。 Debianにパッケージ化されているzshと_ksh93_は、Bashでこれに対応しています。

12
ilkkachu

シェルは標準入力からスクリプトを読み取っています。スクリプト内で、標準入力も読み取りたいコマンドを実行します。どの入力がどこに行くのですか? 確実に伝えることはできません

シェルが機能する方法は、ソースコードのチャンクを読み取って解析し、完全なコマンドを見つけた場合はコマンドを実行して、残りのチャンクと残りのファイルを処理することです。チャンクに完全なコマンドが含まれていない場合(末尾に終了文字がある場合-すべてのシェルが行の最後まで読み取ったと思います)、シェルは別のチャンクを読み取り、以下同様に続きます。

スクリプト内のコマンドが、シェルがスクリプトを読み取っているのと同じファイル記述子から読み取ろうとすると、コマンドは、最後に読み取ったチャンクの後にあるものをすべて検索します。この場所は予測できません。シェルが選択したチャンクサイズによって異なり、シェルとそのバージョンだけでなく、マシン構成、使用可能なメモリなどによっても異なります。

Bashは、コマンドを実行する前に、スクリプト内のコマンドのソースコードの最後を探します。これは、他のシェルがそれを行わないだけでなく、シェルが通常のファイルから読み取っている場合にのみ機能するため、信頼できるものではありません。シェルがパイプから読み取っている場合(例:ssh remote-Host.example.com <local-script-file.sh)、読み取られたデータは読み取られ、読み取られないようにすることはできません。

スクリプト内のコマンドに入力を渡したい場合は、明示的に行う必要があります。通常は、 ヒアドキュメント を使用します。 (ヒアドキュメントは通常、複数行の入力に最も便利ですが、どの方法でもかまいません。)スクリプトが通常のファイルからシェルへの入力として渡される場合にのみ、作成したコードはいくつかのシェルでのみ機能します。 2番目のwhoamiSudo …への入力として渡されると予想した場合は、ほとんどの場合、スクリプトがシェルの標準入力に渡されないことに注意してください。

#!/bin/bash
whoami
Sudo su -l root <<'EOF'
whoami
EOF

この10年間は​​、Sudo -i rootを使用できることに注意してください。 Sudo suの実行は過去からのハックです。