パイプで処理した結果をファイルにリダイレクトしたときに”Permission denied”と怒られたときの対処

Bash

はじめに

定期的に質問される問題について。

「パイプで繋いで処理した情報を別ファイルにリダイレクトしたいが、実行ユーザの書き込み権限がないために Permission denied が発生してしまい良い方法がないか?」

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

具体例

前提

  • 処理実行ユーザは root のような /etc ディレクトリ配下を編集する権限はない
  • ただし、 sudo を使えば変更が可能
  • /etc 配下にファイルを書き込みたい

実行したい内容

/etc/passwd ファイルをフィルタリングして、 /etc/passwd.bk を作りたいとします。

# "genzouw" さんのユーザだけをバックアップしておきたい
$ grep genzouw /etc/passwd
genzouw:x:1000:1000::/home/genzouw:/bin/zsh

# > で書き出ししたいが権限がないと怒られる
$ grep genzouw /etc/passwd > /etc/passwd.bk
bash: /etc/passwd.bk: Permission denied

余談ですが、上記メッセージは以下の言語設定のときのメッセージです。

$ expr $LC_ALL
en_US.UTF-8

言語設定を日本語に変更されている場合は以下のようなメッセージが表示されます。

$ LC_ALL=ja_JP.UTF-8
$ grep genzouw /etc/passwd > /etc/passwd.bk
bash: /etc/passwd.bk: 許可がありません

sudo grep では解決しない

すぐに思いつくのは sudo を使う方法です。

$ sudo grep genzouw /etc/passwd > /etc/passwd.bk
bash: /etc/passwd.bk: Permission denied

上記の通り、これでは問題が解決しません。
grep コマンドは sudo つまり root ユーザで実行していますが、リダイレクト処理(>)は一般ユーザで実行されるためです。

sudo tee コマンドを使う

sudo tee を利用すれば解決できます。
teeコマンドは標準入力の内容を指定されたファイルに書き出しつつ、引数に指定されたファイルに書き出します。
( 入力を2つの出力に流すフローが T字 に見えることからこの名前がつけられています。 )

$ grep genzouw /etc/passwd | sudo tee /etc/passwd.bk
genzouw:x:1000:1000::/home/genzouw:/bin/zsh

# 一般ユーザでは閲覧できません!
$ cat /etc/passwd.bk
cat: /etc/passwd.bk: Permission denied

# 閲覧にもsudoが必要
$ sudo cat /etc/passwd.bk
genzouw:x:1000:1000::/home/genzouw:/bin/zsh

ファイルを追記したいときには

> で上書きするような挙動だけでなく、 >> で追記するような挙動をさせたい場合はどうしたらよいでしょう?

これも sudo tee で実現可能です。
-a オプションを付与してやります。

# 先程のファイルに同じ内容を1行追加
$ grep genzouw /etc/passwd | sudo tee -a /etc/passwd.bk
genzouw:x:1000:1000::/home/genzouw:/bin/zsh

$ sudo cat /etc/passwd.bk
genzouw:x:1000:1000::/home/genzouw:/bin/zsh
genzouw:x:1000:1000::/home/genzouw:/bin/zsh

ひとこと

よく知られた方法ですが、質問されることが多いのでまとめてみました。

Bash