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

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

実行時にユーザーを切り替えるスクリプトを作成し、ファイルリダイレクトをstdinとして使用して実行しました。だから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

メモ:

  • Debian Jessieを実行している2つの異なるコンピュータで発生しました。

答え1

同様のスクリプト、no sudo、しかし同様の結果:

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

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

使用すると、bashスクリプトの残りの部分が入力として使用され、シェルによって解釈されます。seddash

strace次から実行:dashスクリプトの塊(ここではスクリプト全体を入れるのに十分な8kB)を読み、次のように生成します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。 (スクリプト長がブロックサイズ8kBを超えると、残りは読み取られます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()存在するinput.c)原則として、すべてのバイトに対して完全なシステムコールを実行することはそれほど効率的ではありません。実際には、シェルが実行する作業の大部分がまったく新しいプロセスを作成することに関連しているという事実に比べて、読み取りが大きなオーバーヘッドになるとは思いません。

同様の状況はこうです。

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

サブシェルは、次の行を表示するにはread最初の改行文字のみを読み取る必要があります。head(これも適用されますdash。)


つまり、Bash はスクリプト自体とスクリプトで実行されるコマンドと同じソース読み取りをサポートするためにさらに努力します。dashいいえ。 Debian にパッケージされておりzshksh93Bash と一緒に使用されます。

答え2

シェルは標準入力からスクリプトを読み込んでいます。スクリプト内で標準入力を読み取るコマンドを実行します。どの入力がどこに行きますか?確かに言えない

シェルがどのように機能するかは、ソースコードブロックを読み、解析し、見つけたらコマンドを実行してから、ブロックの残りの部分とファイルの残りの部分を進めることです。ブロックに完全なコマンドが含まれていない場合(最後に終了文字があります。すべてのシェルは行の最後まで読み取るようです)、シェルは他のブロックを読み取るように進みます。

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

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

スクリプトのコマンドに入力を渡すには、通常、次のように明示的にこれを実行する必要があります。ここのドキュメント。 (ここのドキュメントは通常複数行の入力に最も便利ですが、どのようなアプローチでも可能です。)作成するコードはいくつかのシェルでのみ実行され、スクリプトが通常のファイルからシェルに入力として渡される場合にのみ実行されます。予想される2番目はwhoami入力に渡されますsudo …。もう一度考えてみてください。ほとんどの場合、スクリプトはシェルの標準入力に渡されません。

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

今回の10年間sudo -i root走るのはsudo su過去の技術です。

関連情報