LoginSignup
28

More than 5 years have passed since last update.

sudo で書き込みをしたい

Last updated at Posted at 2016-02-18

以前 Rake で Docker コンテナを管理するためのテンプレート権ツールを作成した。

その機能のひとつとして Docker のホスト側に Nginx 用のリバースプロキシを設定するものがあった。設定後はコンフィグを自動的に読み直す。

これらの処理には root 権限が必要だった。権限はスクリプト中の sudo コマンドで得ていた。

failed_sudo_write.rb
sh "sudo echo -e \"$data\" > /path/to/nginx/config"
sh 'sudo service nginx reload'

実行するとエラーが起きた。

権限エラーだ。

Rake 経由だと sudo が使えないのだろうか。だが、一応 1 行目を消して実行してみたら無事に Nginx コンフィグがリロードされた。

なぜ。

この問題はスクリプトの中で sudo を使わないようにして Rake 自体を sudo で実行すれば解決する。

しかし今回のツールでは、全ての処理に管理者権限が必要なわけではない。普段権限なしで実行していて特定の場面でだけエラーが起きるのは、たぶん数ヵ月後に出会うと小さなストレスだ。

必要になればパスワードを求める仕様にしたかった。

sudo 経由で書き込むことが今回の目的だ。

環境

  • Ubuntu 15.04
  • sudo 1.8.9p5
  • Ruby 2.2.3p173
  • Rake 10.5.0

状況と目的の再定義

序文が不必要に長くなった上に Rake 依存の問題のように読めなくもないので、改めて状況を説明する。

Bash で再現するとこういう状況。

$ touch rootfile
$ sudo chown root:root rootfile
$ sudo echo 1 > rootfile
bash: rootfile: Permission denied

sudo を使って root 権限で書き込むのが本記事の目的。

解決方法

いくつか試して、目的を達成できた方法を書いていく。

1. mv

最初にやったのはこの方法。

sudo_write1.sh
echo 1 > rootfile.temp
sudo mv rootfile.temp rootfile

書き込みたい内容を一時ファイルに保存して、それを mv によって目的の場所に移動、または上書きする。

でもこれだと所有者情報に違和感があるし (sudo 付きで echo で実行しても、書き出したファイルの所有者は sudo を実行した元ユーザーのものになる) 、既存のファイルに上書きする場合は inode も変わってしまう。

どちらも今回の処理では問題にならないが、別の処理では問題になる可能性がある。余計な変化はできるだけ避けたい。

2. tee

sudo に頼った書き込みをする場合、必ず一度はシェル用のコマンドを使うことになる。

そこでまず思いついたのは echocat とリダイレクト記号 > を組み合わせたものであり、そのつまずきが序文での出来事だ。

他にビルトインで書き込みができるものは何かないか、と探してみると tee があった。

sudo_write2.sh
echo 1 | sudo tee rootfile

標準出力が気になる場合は /dev/null に捨てる。

3. sh -c

どうやら sudo ではリダイレクトを使った書き込みが出来ないらしい。

先日の記事「sudo 時に root 権限で実行されてるかを確認する」を書いたときに知ったのだが、この仕様は man sudo でも触れられている。

To make a usage listing of the directories in the /home partition. Note that this runs the commands in a sub-shell to make the cd and file redirection work.
$ sudo sh -c "cd /home ; du -s * | sort -rn > USAGE"

この例のように sudo sh -c の中にリダイレクト処理を含めれば、問題なく書き込みができる。

sudo_write3-1.sh
sudo sh -c 'echo 1 > rootfile'

標準入力も扱える。

sudo_write3-2.sh
echo 1 | sudo sh -c 'cat - > rootfile'

4. ruby -e

おまけ。

今回自分がぶつかったケースの場合 Rake を使っているということで、当然そこには Ruby の実行環境がある。

以下のようにすれば sudo 経由で Ruby の機能を使って書き込みができる。

sudo_write4.sh
echo 1 | sudo ruby -e "File.write('rootfile', STDIN.read)"

Ruby で記述する場合はこう。

sudo_write4.rb
IO.popen("sudo ruby -e \"File.write('rootfile', STDIN.read)\"", 'w') do |io|
  io.puts('1')
end

ただし sudo は基本的に環境変数を引き継がない。引き継ぐようにもできるが、面倒くさい。

rbenv などでユーザーローカルに Ruby をインストールしている場合はその面倒事をやるか、 sudo 経由で呼び出している ruby の部分をフルパスに書き換える必要がある。

開発環境や、開発環境をまたいで使う場合には向かない。

まとめ

使うとしたら 2 か 3 だろうか。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28