chmod 755とu+xの違い 数値・記号モード
chmodの8進数とu/g/o記号形式を比較し、再帰的な数値指定でディレクトリ構造が壊れる理由と、大文字Xでディレクトリだけに実行権限を付ける方法を説明します。
chmod で変更される内容
chmodは、ファイルまたはディレクトリの権限ビットを変更します。権限ビットは、2 種類の表記のいずれかで指定できます。数値表記では、すべての権限ビットを一度に置き換える 8 進数を指定します。シンボリック表記では、ユーザーの種類と演算子を指定し、指定したビットだけを編集します。どちらも最終的には同じシステムコールを実行します。違いは、指定しなかったビットがどう扱われるかです。
モードを読み取る作業と、モードを変更する作業は異なります。long listing の文字列がまだ分かりにくい場合は、先に drwxr-xr-x の意味をフィールドごとに確認する を読んでください。このガイドでは、モードの変更だけを扱います。
ここで実行する操作は、通常のログインユーザーで行ってください。root アカウントではこれらのチェックを回避できるため、root で実行した権限の確認は意味を持ちません。新しいサーバーでまだ root として作業している場合は、最小権限の通常ユーザーを作成する を参照してから戻ってください。
破棄できるサンドボックスを作成する
cd "$(mktemp -d)"
touch notes.txtmktemp -d は /tmp の下に空のディレクトリを作成し、そのパスを出力します。コマンド置換 は、そのパスを cd に直接渡します。以下の操作で、必要なファイルに触れることはありません。
すべての変更結果を確認するには、1つのコマンドを使用します。
stat -c '%a %A %n' notes.txt%a はモードを8進数で出力し、%A は長い一覧表示で使用される文字形式で同じモードを出力し、%n は名前を出力します。以下のブロックでは、$ で始まる行が入力する内容で、その下の行がコマンドの出力です。実際に実行してください。数値で指定したモードは環境に依存しないため、使用しているマシンでも出力は行単位で一致するはずです。
数値形式: 各クラスに 1 桁の 8 進数を割り当てる
1 桁目はファイルの所有者を表します。2 桁目はファイルのグループを表します。3 桁目はその他の全ユーザーを表します。各桁は、ビットごとに 1 つの値を加算したものです。1 ビットずつ設定し、stat が表示する内容を確認します。
$ chmod 400 notes.txt
$ stat -c '%a %A %n' notes.txt
400 -r-------- notes.txt
$ chmod 200 notes.txt
$ stat -c '%a %A %n' notes.txt
200 --w------- notes.txt
$ chmod 100 notes.txt
$ stat -c '%a %A %n' notes.txt
100 ---x------ notes.txt文字と数字は対応しています。先頭のファイル種別文字の後に続く 3 文字が所有者用、次の 3 文字がグループ用、最後の 3 文字がその他のユーザー用です。1 つのクラス内でビットを組み合わせるには、値を加算します。
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt
$ chmod 700 notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod 750 notes.txt
$ stat -c '%a %A %n' notes.txt
750 -rwxr-x--- notes.txt数値モードは絶対指定です。実行するたびに 9 ビットすべてを書き込むため、意識していなかったクラスの権限も書き換えられます。
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt2 つ目のコマンドでは、グループやその他のユーザーについて何も指定していません。それでも、それらのビットは変更されました。これが数値形式の特性であり、小さな変更には数値形式が適さない理由です。なお、chmod は setuid、setgid、sticky ビットも扱います。これらは上記の 3 桁の前にある 4 桁目で指定します。これは別のトピックであるため、このガイドでは 3 桁の形式を扱います。
記号形式: クラス、演算子、ビットの順序
最初にクラスを指定します。u は所有者、g はグループ、o はその他のユーザー、a はこれらすべてを同時に表します。次に演算子を指定します。+ はビットを追加し、- はビットを削除し、= はそのクラスを指定したビットだけに設定して、残りのビットをクリアします。最後に、ビットを r、w、または x として指定します。
$ chmod 640 notes.txt
$ stat -c '%a %A %n' notes.txt
640 -rw-r----- notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txt
$ chmod g-r notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod o=r notes.txt
$ stat -c '%a %A %n' notes.txt
704 -rwx---r-- notes.txt各コマンドは 1 つのクラスだけを変更し、他のクラスはそのままにします。節を組み合わせる場合は、スペースを入れずにコンマで区切ります。chmod は節を左から右に適用します。
$ chmod u=rw,go=r notes.txt
$ stat -c '%a %A %n' notes.txt
644 -rw-r--r-- notes.txt
$ chmod go= notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt等号の後に何も指定しない go= は、グループとその他のユーザーからすべてのビットを削除します。これは、所有者だけが読み取れるファイルに適した形式です。たとえば、SSH 鍵ペアの秘密鍵が該当します。
chmod は処理結果を出力することもできます。画面を監視している人がいないスクリプト内では、この機能が重要です。
chmod -v u+x notes.txt
chmod -v u+x notes.txt-v は、指定した各ファイルについて 1 行を出力します。両方のコマンドを実行し、画面に表示された 2 行を比較してください。1 行目はファイル名と、変更前および変更後のモードを出力します。2 行目は、追加を要求したビットがすでに設定されていたため、モードが維持されたことを出力します。-c は、ファイルが実際に変更された場合だけ 1 行を出力するため、ログに残すにはこちらが適しています。
chmod 755 と u+x: どちらの表記を使うべきか
同じファイルを 2 回用意し、それぞれの表記を適用した後に何が残るかを比較します。
$ chmod 640 notes.txt
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 640 notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txtどちらのコマンドでも、ファイルは所有者に対して実行可能になりました。その他 2 つのクラスを元の状態のまま残したのは、一方だけです。最終状態を正確に指定し、それを強制したい場合は数値モードを使います。見たことのないマシンでも同じ結果にする必要があるデプロイスクリプトでは、これが適切です。頭の中の説明に「追加」または「削除」が含まれる場合は、シンボリックモードを使います。数値形式では「その部分は変更しない」と指定できないためです。
新しいファイルが最初に持つモードは、chmod から決まるわけではありません。ファイルを作成したプロセスの umask によって決まります。自分のシェルで umask と umask -S を実行すると、そのシェルで有効な値を確認できます。値はディストリビューション、ログインシェルと systemd サービス、アカウントによって異なる場合があります。umask はすべての新しいファイルのモードを設定するため、chmod を実行する前の初期状態が umask によって決まります。
ディレクトリに対する execute ビットの役割
これは、最も多くの時間を失わせる誤解です。通常のファイルでは、execute ビットによって、カーネルがそのファイルを実行できます。ディレクトリには命令が格納されていないため、実行するものはありません。ディレクトリの execute ビットは、トラバーサル、つまり内部の名前を解決する権限を意味します。dir/fileに到達するには、dirにこの権限が必要です。read ビットは別の権限で、ディレクトリに含まれる名前の一覧表示を許可します。一方だけを持ち、もう一方を持たない状態も可能です。実際に確認すると、この違いを理解しやすくなります。
$ mkdir vault
$ printf 'hello\n' > vault/secret.txt
$ chmod 600 vault/secret.txt
$ chmod 100 vault
$ echo vault/*
vault/*
$ cat vault/secret.txt
helloシェルがパターンを変更せずに表示したのは、vault/*の展開にディレクトリの読み取りが必要だからです。また、一致するものがない glob はエラーを発生させず、リテラル文字列としてそのまま渡されます。catが機能したのは、名前を自分で指定し、トラバーサルが許可されていたためです。次に、2 つのビットを入れ替えます。
$ chmod 400 vault
$ echo vault/*
vault/secret.txt
$ cat vault/secret.txt
cat: vault/secret.txt: Permission denied名前は見えますが、内容は見えません。read は一覧を与えます。execute は中に入る手段を与えます。使用するほぼすべてのディレクトリには、両方が必要です。
$ chmod 500 vault
$ cat vault/secret.txt
hello数値モードで chmod -R を使うとディレクトリツリーが壊れる理由
-R はツリー全体を走査し、指定したモードを見つけたすべての対象に適用します。ディレクトリとデータファイルでは必要なビットが異なるため、1 つの絶対値で両方に正しい設定を適用することはできません。小さなツリーを作成し、どのように壊れるか確認します。
$ mkdir -p site/css
$ touch site/index.html site/css/main.css
$ printf '#!/bin/bash\necho hi\n' > site/deploy.sh
$ chmod 755 site site/css site/deploy.sh
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.shこれは静的ファイルを格納するディレクトリに適した構成です。まず、再帰的に変更する操作の誤りを確認します。
$ chmod -R 755 site
$ stat -c '%a %A %n' site/index.html site/css/main.css
755 -rwxr-xr-x site/index.html
755 -rwxr-xr-x site/css/main.css何も壊れていないように見えます。そこが問題です。この誤りは気付きにくいため、コピーされた手順の中で何年も残ります。2 つのデータファイルがプログラムとして扱われるようになりました。Git は実行ビットを保存するため、次のコミットにこの変更が含まれます。その結果、リポジトリを clone するすべての人に変更が伝わり、rsync と tar によってツリーをコピーしたすべてのマシンにも伝わります。
2 つ目の再帰的な誤りは、すぐに問題として現れます。実行ビットを含まないモードをディレクトリに適用すると、そのディレクトリを閉じることになります。
$ chmod 644 site
$ stat -c '%a %A %n' site
644 drw-r--r-- site
$ echo site/*
site/css site/deploy.sh site/index.html
$ cd site
bash: cd: site: Permission denied
$ cat site/index.html
cat: site/index.html: Permission deniedread ビットが残っているため名前は一覧表示されます。しかし execute ビットがないため、ディレクトリを通るすべてのパスが閉じられます。chmod -R 644 site は、ツリー内のすべてのディレクトリに対してこれを 1 つのコマンドで実行します。その後の状態は必ずしも一様ではありません。chmod は、走査権限を取り上げようとしている同じディレクトリを通って走査する必要があるためです。思い込みで判断せず、find site -type d -exec stat -c '%a %n' {} + で実際に適用された状態を確認します。続行する前に、この設定を元に戻します。
$ chmod 755 site
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/index.html
755 drwxr-xr-x site
644 -rw-r--r-- site/index.html再帰的な chmod に元に戻す機能はありません。chmod は置き換えた設定を記録しないため、以前の状態を推測しながらモードを再構築して修復する必要があります。これは 誤った rm -rf の後にファイルを再構築する作業と同じく、時間がかかり不確実です。教訓も同じです。再帰的なコマンドを実行する前に、対象を確認してください。
大文字の X: すべてのファイルではなく、ディレクトリに実行権限を付与する
シンボリックモードでは、Xだけでなくxも使用できます。Xは、実行権限が意味を持つ対象にだけ実行ビットを設定します。対象はディレクトリ、または何らかのクラスですでに実行ビットを持つ通常ファイルです。この規則は、-Rに実行してほしかった処理そのものです。
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.sh1つのコマンドで、ディレクトリは引き続き辿れる状態になり、データファイルは変更されませんでした。スクリプトにはすでに実行ビットがあったため、そのビットも維持されました。これがX規則の後半です。Xは、ファイルが現在持っているモードを読み取ります。そのため、すべての実行ビットをすでに失ったファイルを、Xで復元することはできません。
$ chmod 644 site/deploy.sh
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site/deploy.sh
644 -rw-r--r-- site/deploy.sh
$ site/deploy.sh
bash: site/deploy.sh: Permission denied
$ chmod u+x site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
744 -rwxr--r-- site/deploy.sh
$ site/deploy.sh
hi逆方向にも同じ規則が適用されます。Xは、ファイルがすでに持っている実行ビットを別のクラスにも拡張します。
$ chmod go+X site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
755 -rwxr-xr-x site/deploy.shしたがって、小文字のxは、指定した1つのファイルを実行可能にする場合に使用し、Xはツリー全体を走査する場合に使用します。現在のモードに基づく規則ではなく、対象の種類ごとに正確なモードを設定する場合は、findで走査対象を分けてください。
find site -type d -exec chmod 755 {} +
find site -type f -exec chmod 644 {} +
chmod u+x site/deploy.sh-type dはディレクトリを選択し、-type fは通常ファイルを選択するため、それぞれに適切なモードを設定できます。-exec ... +は多数のパスを1回の chmod 呼び出しにまとめます。ファイルごとにプロセスを起動するより効率的で、数千個のエントリがあるツリーでは特に重要です。スクリプトの実行ビットは、意図的に別の行で復元しています。
変更後に離れる前に結果を確認する
再帰的な変更を行った後、重要なミスを確認できるコマンドが2つあります。1つ目は、入れなくなったディレクトリを一覧表示します。
find . -type d ! -perm -u+x2つ目は、マシン上のすべてのアカウントが編集できるファイルを一覧表示します。
find . -type f -perm -o+w-perm -o+wでは、先頭の - が「少なくともこれらのビット」を意味します。そのため、o クラスに書き込みビットが設定されているファイルであれば、ほかの設定に関係なく一致します。どちらのコマンドも何も出力しない状態が望ましい結果です。変更したツリーの最上位から、-R を使って両方を実行してください。また、変更するつもりだった少数のパスには stat -c '%a %A %n' を実行し、設定したつもりのモードが、実際にカーネルで保持されているモードと一致することを確認してください。
FAQ
chmod 755 と chmod u+x の違いは何ですか?
数値モードは絶対指定です。ファイルのすべての権限ビットを書き込むため、意識していなかったクラスの権限も書き換わります。シンボリックモードは編集指定です。u+x は所有者に対して 1 つのビットを追加し、その他のビットはそのままにします。ファイルに必要な最終状態が明確な場合は数値形式を使用します。1 つの権限だけを追加または削除する場合はシンボリック形式を使用します。どちらのコマンドの前後でも stat -c '%a %A %n' <file> を実行すると、差分が出力に現れます。
Web ファイルのディレクトリに chmod -R 755 を実行すると問題になるのはなぜですか?
-R はディレクトリと通常ファイルに同じ絶対モードを設定するためです。ディレクトリと通常ファイルには異なるビットが必要です。ディレクトリは移動するために実行ビットを必要とします。HTML や CSS などのデータファイルには実行ビットは不要です。これらを実行可能にすると、次のコミットで git が変更として記録し、rsync が次のマシンへコピーします。chmod -R u=rwX,go=rX <dir> を使用すると、実行ビットはディレクトリだけに設定されます。または、find を -type d と -type f と組み合わせて使用し、種類ごとにモードを指定します。
ディレクトリの実行ビットは何をしますか?
実行ビットは実行ではなく、移動を許可します。ディレクトリの中に実行するものはありません。実行ビットは、その中にある名前を解決する権限です。したがって、パスの末尾にあるファイルを開くには、そのパス上のすべてのディレクトリに実行ビットが必要です。読み取りビットは別の権限で、名前の一覧表示を許可します。一方だけを持つこともできます。実行ビットだけがある場合、名前が既知のファイルは開けますが、shell はそのディレクトリ内で glob を展開できません。読み取りビットだけがある場合は名前を確認できますが、どのファイルを開こうとしても失敗します。
小文字の x ではなく大文字の X を使うのはどのような場合ですか?
再帰的にコマンドを実行する場合は必ず X を使用します。X はディレクトリに実行ビットを設定します。通常ファイルについては、実行ビットがすでに 1 つ設定されている場合にだけ設定します。そのため、データファイルをプログラムに変えずに、1 回の処理でツリー内を移動可能にできます。1 つのファイルを指定し、そのファイル自体に実行ビットを設定する場合は小文字の x を使用します。覚えておくべき制限が 1 つあります。ファイル上の実行ビットがすべてすでに削除されている場合、X では実行ビットを復元できません。一致させる実行ビットが残っていないためです。その場合は chmod u+x <file> で実行ビットを戻します。
chmod はファイルの所有者を変更しますか?
いいえ。chmod が変更するのは権限ビットだけです。所有者はユーザーについては chown、グループについては chgrp によって管理されます。ファイルを別のユーザーに割り当てるには root が必要です。権限ビットは、最後にファイルを書き込んだユーザーではなく、ファイルに記録された所有者とグループに基づいて評価されます。この点が重要です。どのコマンドが必要かを判断する前に stat -c '%U %G %a %n' <file> を実行すると、所有者、グループ、モードをまとめて確認できます。