以下の5つのコマンドのシーケンスでは、すべてが一重引用符に依存して、呼び出されたbash
に可能な変数置換を渡します。 呼び出し元のシェルではなくシェル。呼び出し元のユーザーはxx 、ただし、呼び出されたシェルはユーザー yyとして実行されます 。呼び出されたシェルはログインシェルではないため、最初のコマンドは$HOMEを呼び出し元のシェルの値に置き換えます。 2番目のコマンドは、ログインシェルによってロードされた$ HOMEの値を置き換えるため、ユーザー yyに属する値になります。 。 3番目のコマンドは、$ HOME値に依存せず、ユーザー yyの推測されたホームディレクトリにファイルを作成します。 。
4番目のコマンドが失敗するのはなぜですか?同じファイルを書き込むことを目的としていますが、ユーザー yyに属する$HOME変数に依存しています。 それが実際に彼女のホームディレクトリにあることを確認するため。ログインシェルが、静的な一重引用符で囲まれた文字列として渡されたヒアドキュメントコマンドの動作を壊す理由がわかりません。 5番目のコマンドの失敗は、この問題が変数の置換に関するものではないことを確認します。
[email protected] ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
[email protected] ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
[email protected] ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
[email protected] ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
[email protected] ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
これらのコマンドは、Ubuntu 16.04(Xenial Xerus)に基づくLinux Mint 18.3Cinnamon64ビットシステムで発行されました。
更新: ヒアドキュメントの側面は問題を曇らせているだけです。問題の簡略化は次のとおりです。
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
これら2つのコマンドの最初のコマンドが改行を保持し、2番目のコマンドが保持しないのはなぜですか? sudo
は両方のコマンドに共通ですが、「-i」オプション以外に依存して、エスケープ/フィルタリング/補間が異なるようです。
承認された回答:
-i
のドキュメント 状態:
-i
(初期ログインのシミュレーション)オプションは、ターゲットユーザーのパスワードデータベースエントリで指定されたシェルをログインシェルとして実行します。これは、.profileや.loginなどのログイン固有のリソースファイルがシェルによって読み取られることを意味します。 コマンドが指定されている場合、コマンドはシェルの-cオプションを介して実行するためにシェルに渡されます。
つまり、ユーザーのログインシェルを実際に実行し、sudo
で指定したコマンドをすべて渡します。 -c
を使用してそれに –とは異なり 何sudo cmd arg arg
通常、-i
なしで実行します オプション。通常、sudo
exec*
の1つを使用するだけです プロセス自体を開始するために直接機能し、中間シェルはなく、すべての引数はそのまま渡されます。
-i
を使用 、環境をセットアップし、ユーザーのシェルをログインシェルとして実行し、実行を要求したコマンドをbash -c
の引数として再構築します。 。あなたの場合、それは(例えば、おおよそ)/bin/bash -c "bash -c ' ... '"
を実行します (うまくいく引用を想像してください。)
問題はsudo
にあります 書き込んだコマンドを-c
のようなものに変換します 対処できる 、次のセクションで説明します。最後のセクションにはいくつかの可能な解決策があり、その間にいくつかのデバッグと検証の手法があります
なぜこれが起こるのですか?
コマンドを-c
に渡す場合 、正しいことを行うには、いくつかの前処理が必要です。sudo
シェルを実行する前に行います。たとえば、コマンドが次の場合:
sudo -iu yy echo 'three spaces'
次に、コマンドが同じことを意味するために(つまり、単一の引数が2つの単語に分割されないようにするために)、これらのスペースをエスケープする必要があります。最終的に実行されるのは:
/bin/bash -c 'echo three spaces'
簡略化したコマンドから始めましょう:
sudo bash -c 'echo 1
echo 2'
この場合、sudo
ユーザーを変更してから、execvp("bash", ["bash", "-c", "echo 1necho 2"])
を実行します。 (発明された配列リテラル構文の場合)。
-i
を使用 :
sudo -i bash -c 'echo 1
echo 2'
代わりに、ユーザーを変更してから、execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
を実行します。 、ここで\
リテラルのに相当します および
n
改行です。メインコマンドのスペースと改行の前にバックスラッシュを付けることで、スペースと改行をエスケープしました。
つまり、2つの引数を持つ外部ログインシェルがあります:-c
そして、シェルが正しく理解することが期待される形式に再構築されたコマンド全体。残念ながら、そうではありません。内側のbash
コマンドは最終的に実行を試みます:
echo 1
echo 2
ここで、最初の物理行は行の継続(バックスラッシュの後に改行が続く)で終わり、完全に削除されます。その場合、論理行はecho 1echo 2
になります。 、それはあなたが望んでいたことをしません。
これはsudo
の欠陥であるという議論があります バックスラッシュと改行のペアの標準的な動作を前提として、のエスケープ。ここでエスケープしないでおくのは安全だと思います。
ヒアドキュメントを使用したコマンドでも同じことが起こります。大まかに次のように実行されます:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
ここで