「sudo -i」ログインシェルがhere-docコマンド文字列引数を中断するのはなぜですか?

「sudo -i」ログインシェルがhere-docコマンド文字列引数を中断するのはなぜですか?

以下の5つのコマンドシーケンスでは、すべてのコマンドは単一引用符を使用して、呼び出しbashシェルではなく呼び出しシェルに可能な変数置換を渡します。通話ユーザーはダブルXただし、呼び出されるシェルはユーザーとして実行されます。うん。呼び出しシェルはログインシェルではないため、最初のコマンドは$ HOMEを呼び出しシェルの値に置き換えます。 2番目のコマンドはログインシェルによってロードされた$ HOME値を置き換えるため、これはユーザーに属する値です。うん。 3番目のコマンドは、$ HOME値に依存せずに推測されたユーザーのホームディレクトリにファイルを生成します。うん

4番目のコマンドが失敗するのはなぜですか?意図は、同じファイルに書きますが、ユーザーに属する$ HOME変数に依存することです。うんそれが彼女のホームディレクトリにあることを確認してください。ログインシェルが静的一重引用符文字列として渡されたhere-docコマンドの動作を停止する理由はわかりません。 5 番目のコマンドが失敗すると、問題が変数置換に関連していないことが確認されます。

xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ 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')
xx@host ~ $ 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.3 Cinnamon 64ビットシステムで実行されました。

修正する:ここで、文書化の側面は問題をあいまいにします。これは問題を単純化したものです。

$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2

これら2つのコマンドのうち、最初のコマンドは改行文字を保持し、2番目のコマンドは維持しないのはなぜですか?sudoどちらのコマンドも共通ですが、「-i」オプションによってエスケープ/フィルタリング/補間が異なって実行されるようです。

答え1

これ出荷書類ビーチ-i状態:

(初期ログインシミュレーション)オプションは、ターゲット-iユーザーのパスワードデータベースエントリで指定されたシェルをログインシェルとして実行します。これは、シェルが.profileや.loginなどのログイン固有のリソースファイルを読み取ることを意味します。コマンドを指定すると、シェルの -c オプションで実行できるようにシェルに渡されます。

つまり、実際にユーザーのログインシェルを実行してから、次のコマンドを使用して提供するsudoすべてのコマンドを渡します-c同じではないsudo cmd arg argこれはオプションなしで通常行われます-i。通常、sudoこれらのうちの1つだけが使用されます。exec*機能プロセス自体は中間シェルなしで直接起動され、すべてのパラメータはそのまま渡されます。

を使用すると、-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 1\necho 2"\])(発明された配列リテラル構文の場合)

そして-i

sudo -i bash -c 'echo 1
echo 2'

代わりにユーザーを変更して実行しますexecv("/bin/bash", ["-bash", "-c", "bash -c echo\\ 1\\\necho\\ 2"])。ここで\\はリテラルと同じで\改行\nです。メインコマンドにバックスラッシュを追加して、スペースと改行をエスケープします。

-cつまり、2つの引数、つまりシェルが正しく理解することを期待する形式に再構成された完全なコマンドを使用する外部ログインシェルがあります。残念ながらそうではありません。内部bashコマンドは最終的に実行を試みます。

echo 1\
echo 2

最初の実際の行が行連続文字(バックスラッシュの後に改行文字が続く文字)で終わると、その行は完全に削除されます。論理的な行は、echo 1echo 2あなたが望むことをしないということです。

sudoこれはsの脱出に欠陥があるという見方があります。バックスラッシュと改行の標準動作右。私はそれらをここに置くのは安全だと思いました。


ここでドキュメントを使用するコマンドでも同様です。おおよそ次のように動作します。

/bin/bash -c 'bash -c cat\ \<\<\ \"EOF\"\\012script-content\\012EOF\\012'

ここで\012実際の改行文字を表します。sudo各改行文字の前にスペースのようにバックスラッシュを挿入します。二重エスケープ on に注意してください\\012。これはps実際のバックスラッシュと改行文字を表現したものです。ここではこれを使用します(下記参照)。最終的に実行された内容は次のとおりです。

bash -c 'cat << "EOF"\
script-content\
EOF\
'

そして行連続\+改行あちこちにありますが、ちょうど削除されました。これにより、実際の改行文字がなく、無効なheredocがない1つの長い行になります。

bash -c 'cat << "EOF"script-contentEOF'

問題は次のとおりです。内部bashプロセスは1行のコマンドのみを取得するため、ここでは文書を終了または開始する機会はありません。内部的にはやや不完全な解決策がありますが、これが根本的な原因です。


何が起こっているのか、どうやって確認しますか?

このコマンドを正しく引用し、何が起こっているのかを確認するために、ログインシェル(.profile、、.bash_profileなど.zprofile)の設定ファイルを変更して、次のように簡単に説明しました。

ps awx|grep $$

これは、当時実行中のシェルのコマンドラインを示し、警告の前に数行の追加の出力行を提供します。

hexdump -C /proc/$$/cmdlineLinuxにも役立ちます。


これについて何ができますか?

私はこれからあなたが望むものを得るための明確で信頼できる方法を見つけることができませんでした。sudo可能であれば、コマンドに全く触れたくありません。単純な場合に主に機能する1つのオプションは、コマンドラインでコマンドを指定するのではなく、コマンドをシェルにパイプすることです。

printf 'cat > ... << ... \n ...' | sudo -iu yy

これにはまだ慎重な内部脱出が必要です。

おそらくより良いアプローチは、一時スクリプトファイルに入れて、次のように実行することです。

f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"

設定したファイル名も機能します。 sudoersの設定によっては、/dev/fd/3実際にディスクに保存したくない場合は、ファイルディスクリプタを開いたままにしてfile()として読み取ることができますが、実際のファイルは簡単になる可能性があります。

答え2

これを明らかにするためのより一般的なニーズは、端末セッションに直接コピーして貼り付けることができる一連のコマンドを記録することです。これらのコマンドのいくつかは、人間が読めるように小さなファイルを作成しながら、各ステップを文書化します。一時ファイルを作成するか、読者に「エディタを使用」するように指示することは、本質的に100%の再生可能性という目標から逸脱することです。 –4分前

もしそれは必要なタスクを実行するには、次を使用します。

sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF

bash -c '...'はるかに簡単です。内容全体を構文に貼り付ける参照の問題を解決する必要はありません。特に、スクリプトに引用符が含まれていると、操作が簡単になります。

(ここで参照されている区切り文字の定義は次のとおりです。'EOF'スクリプトの内容に使用するすべての変数は、作るはいtest.sh、これはあなたの意図ではないかもしれません。 )

yyまた、ユーザーのホームディレクトリを取得するためにチルダ拡張を使用することに注意してください~yyLESS='+/^ *Tilde Expansion' man bash詳しくは参考資料をご覧ください。だからそうする必要はありませんsudo -i

関連情報