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


9

実行中にユーザーを切り替えるスクリプトを作成し、標準のファイルリダイレクトを使用して実行しました。そう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
答えではありませんが、以下に関してsudo suunix.stackexchange.com/questions/218169/…–
Kusalananda

1
/ bin / sh =ダッシュはDebianですか?/ bin / sh = bashのRHELでは再現できません
Jeff Schaller

1
/bin/sh(私のコピー)のDebianはダッシュです。それが今まで何なのかさえ知りませんでした。私は今までbashにこだわっていました。
popedotninja 2017

1
バッシュ動作は、いずれかの任意の文書化の意味での動作が保証されていません。
Charles Duffy

今日誰かが、私が実行していたスクリプトがbashの使用方法のセマンティクスに違反していると指摘しました。この質問には2つの素晴らしい答えがありましたが、スクリプトの途中でユーザーを切り替えることはおそらくありません。私はもともとuser-switch.shJenkinsのジョブで試しましたが、スクリプトが実行されました。Jenkinsの外で同じスクリプトを実行すると、異なる結果が生成されたので、ファイルリダイレクトを使用して、Jenkinsで見た動作を取得しました。
popedotninja 2017

回答:


12

なしの同様のスクリプトですが、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()中にinput.c各バイトのための完全なシステムコールを行う)は、原則的に非常に効果的ではありません。実際には、読み取りが大きなオーバーヘッドになるとは思いません。たとえば、シェルが行うほとんどのことはまったく新しいプロセスの生成を伴うという事実とは異なります。

同様の状況はこれです:

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

サブシェルはread、最初の改行のみを読み取るようにしheadて、次の行が見えるようにする必要があります。(これも動作しdashます。)


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


1
率直に言って、私はそれがうまくいくことに少し驚いています。
ilkkachu 2017

素晴らしい答えをありがとう!後で両方のコマンドを実行straceして、出力を比較します。私はそのアプローチを使うつもりはありませんでした。
popedotninja 2017

2
ええ、質問を読んだときの私の反応、bash で機能するのには驚きでした。間違いなく機能しないものとしてファイルしてしまいました。私はちょうど半分正しかったと思います:)
ホッブズ

12

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

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

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

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は過去からのハックです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.