lsコマンドで表示されるファイルのモード(drwxr-xr-x) 〜RubyのFile::Stat#modeとは〜
最初に
フィヨルドブートキャンプ Part 2 Advent Calendar 2020の2日目の記事です。
昨日は、fukindesuさんによる「FJORD BOOT CAMPのカリキュラムで必要な書籍・参考書籍」の補助輪でした。
そして、今のところ明日・明後日だけが、まだ埋まっていません。3日後がsasaboさんです。
また、Part1について、昨日がshita1112さんのDevise入門64のレシピ その1で、今日がbecolomochiさんで、明日がおぐさんです。
本記事の主な目的
- Unix系の
lsコマンドの-lオプションでの詳細表示(long format)で表示される左端の記号 - Rubyの
File::Stat#modeの数字
これら2つはどちらもファイルタイプ・アクセス権限を示すもので、これら2つの対応関係を示すことを目的とします。
$ ls -l
-rw-r--r-- 1 uname staff 0 11 28 12:31 default.txt
drwxr-xr-x 2 uname staff 64 11 28 11:55 default_dir
$ irb
irb(main):001:0> File.lstat("default.txt").mode.to_s(8)
=> "100644"
irb(main):002:0> File.lstat("default_dir").mode.to_s(8)
=> "40755"
-rw-r--r--と"100644", drwxr-xr-xと"40755"が対応してます。
背景として、lsコマンドをRubyで再現するにあたり、RubyリファレンスマニュアルのFile::Stat#modeの説明だけだとシンプルで十分な理解が得られなかったため(別途Unixについての知識が必要だったため)、この記事を書くことにしました。
読者対象
- Unixのlsコマンド(特にファイル権限)について、学習してこなかったがザックリ理解したい人
- Unixのlsコマンド(特にファイル権限)について、改めて理解したい人
用語の整理
混乱のないよう、簡単にですがまず用語を整理します。
ディレクトリとは、Windowsでいうフォルダです。
ディレクトリは、ディレクトリ、ファイル、シンボリックリンク等を格納します。
シンボリックリンクは、ファイルのショートカットで、別のディレクトリやファイル等を指すものです。
さらに、ディレクトリ、ファイル、シンボリックリンク等をまとめてファイルともいいます。狭い意味でのファイルを特に強調したい場合は、「通常ファイル(regular file)」と言います。ただ、一般にどちらも単に「ファイル」と書くことも少なくなく、本記事でも特に区別しないので適宜どの意味か読み取って下さい。
数の表記について
ファイル権限は8進数(octal digit), 2進数(binary digit)と結びついているため、本記事では10進数ではなく主に8進数, 2進数を扱います。
Ruby等の多くのプログラミング言語などで、2進数は0b始まりで表記し、8進数は0oまたは0始まりで表記します。それにならい、本記事でも0b始まりは2進数、0o始まりは8進数とします。逆に、必ずしも8進数は0oや0始まりで表記されているわけではないので注意してください。
なお、Rubyでは以下のように2進数や8進数で直接書くことができます。
p 0b101 #=> 5 = (2**2) * 1 + (2**1) * 0 + (2**0) * 1
p 0o12 #=> 9 = (8**1) * 1 + (8**0) * 2
p 0170 #=> 120 = (8**2) * 1 + (8**1) * 7 + (8**0) * 0
**は、累乗です。
(内部的にはどれも同じIntegerであり、10進数や8進数のデータ型が存在するわけではなく、そのまま出力した場合は10進数で表示されます)
動作環境
- MacOS Catalina Version 10.15.7
- Ruby 2.7.1 (今月のクリスマス前後にRuby 3.0が新しくでる予定)
RubyリファレンスマニュアルでのFile::Stat#mode
File::Stat#mode (Ruby 2.7.0 リファレンスマニュアル)

Rubyリファレンスマニュアル(るりま)の説明は、とてもシンプルです。
周辺知識もないと厳しい気がするので、本記事で補足します。
特殊変数$0とは
まず「$0とは何なのか」となる人も少なくないでしょう。
$0は、まさに自身のファイル名・パス(文字列)が入っている特殊変数です。
説明だけだと理解しにくいと思うので、実際にp $0と中身を出力してみるといいでしょう。
main.rbというファイルにp $0が書かれ、これを実行したなら、"main.rb"等と表示されるはずです。
現在実行中の Ruby スクリプトの名前を表す文字列です。
File::Stat#modeの返り値の注意
また注意しておきたいのが、File::Statインスタンスのmodeメソッドの返り値です。リファレンスマニュアルに「mode -> Integer」とあるように、Integerクラスの数値が返ります。
しかし、この数値は8進数(or 2進数)の表記で見たときに初めて意味のわかるもので、そのまま単純に数値を出力すると10進数で表示され意味がわかりません。このあたり勘違いされる方が、少なくないです。
実際、リファレンスマニュアルの例ではprintf "%o\n", fs.modeと書き、8進数表記で出力するように%oで指定しています(\nは改行)。他に、fs.mode.to_s(8)と書くことで、8進数表記した文字列を返してくれます。
puts fs.mode #=> 33188
puts fs.mode.to_s(8) #=> 100644
printf "%o\n", fs.mode #=> 100644
0oや0始まりではないですが、ここでの100644は8進数表記であることに注意です。
後述になりますが、100644という8進数表記で、それぞれの数字が意味を持っています。
File::Statクラスのインスタンスを作成する方法
なお、File::StatクラスのインスタンスをFile::Stat.newで作成していますが、これ以外にFile::Statクラスのインスタンスを作成する方法が存在します。
-
File.stat……File::Stat.newと同じ。 File.lstat
名称が異なるように、前者と後者は挙動が異なることがあるので、注意が必要です。
具体的には、引数がシンボリックリンクのときの挙動が異なっており、前者はシンボリックリンク先のファイル情報を取得しますが、後者はシンボリックリンクそのもののファイル情報を取得します。目的に応じて、使い分けましょう。
【参考】
実際のファイルの数字と記号について
さて、次の記号と数字の対応関係を明らかにすることが目的でした。
| 8進数(mode) | lsコマンドの記号 |
|---|---|
| 0o100644 | -rw-r--r-- |
| 0o040755 | drwxr-xr-x |
0o40755と書くと説明が不便になるので、6桁になるように0o040755とします。
この0o040755とdrwxr-xr-xは、次のように対応しています。
| ファイル種類 | 特殊な権限 | 所有者の権限 | 所有グループ権限 | その他ユーザーの権限 |
|---|---|---|---|---|
| 04 | 0 | 7 | 5 | 5 |
| d | 各xの部分 | rwx | r-x | r-x |
これから、大筋として、ファイルの種類(タイプ)、通常の権限、特殊な権限の順に書いていきます。
1文字目のファイルの種類(タイプ)について
8進数での頭2桁の数値とlsコマンドの記号の頭1文字目は、ファイルの種類(タイプ)です。
以下のように対応しています。
| 8進数 | 記号 | 意味 | RubyのFile::Stat#ftype |
|---|---|---|---|
| 0o010000 | p | FIFO | "fifo" |
| 0o020000 | c | Character special file | "characterSpecial" |
| 0o040000 | d | Directory(ディレクトリ) | "directory" |
| 0o060000 | b | Block special file | "blockSpecial" |
| 0o100000 | - | Regular file(通常ファイル) | "file" |
| 0o120000 | l | Symbolic link(シンボリックリンク) | "link" |
| 0o140000 | s | Socket link | "socket" |
列挙しましたが、最初は-とd、次にlがわかれば、きっと十分でしょう。
なお、File::Stat#modeでなくとも、File::Stat#ftypeで識別可能だったため、これも表に記載しました。
fs = File.lstat($0)
p fs.mode.to_s(8) #=> "100644" (8進数の文字列)
p fs.ftype #=> "file"
【参考】
File.ftype (Ruby リファレンスマニュアル)
アクセス権と数値の関係
ファイルに対する通常の権限は、独立して3種類あります。
readable (読み込み権限)、writable (書き込み権限)、executable (実行権限)です。
そして、このそれぞれの権限に、3桁の2進数でのそれぞれの位に対応するよう紐付けられています。
権限があれば1, 権限がなければ0です。
つまり、読み込み権限があれば1番下の位の1が立つし、なければ立たないように決まっています。
| 10進数 | 8進数 | 2進数 | 記号 | 意味する単語 (権限) |
|---|---|---|---|---|
| 1 | 0o1 | 0b001 | x | executable (実行権限) |
| 2 | 0o2 | 0b010 | w | writable (書き込み権限) |
| 4 | 0o4 | 0b100 | r | readable (読み込み権限) |
これら通常の権限はそれぞれ「権限がある」「権限がない」の2通りで、権限の種類が独立に3種類なので、8通り(= 2の3乗)となります。
これらの8通りをまとめると、以下のようになります。
| 10進数 | 8進数 | 2進数 | lsコマンドの記号 |
|---|---|---|---|
| 0 | 0o0 | 0b000 | --- |
| 1 | 0o1 | 0b001 | --x |
| 2 | 0o2 | 0b010 | -w- |
| 3 | 0o3 | 0b011 | -wx |
| 4 | 0o4 | 0b100 | r-- |
| 5 | 0o5 | 0b101 | r-x |
| 6 | 0o6 | 0b110 | rw- |
| 7 | 0o7 | 0b111 | rwx |
ディレクトリの権限について
ディレクトリは実行するものではないので、ディレクトリにxが立っているときの意味は、searchable(探索権限=移動権限)の意味で、cdコマンドでそのディレクトリに
移動できるようになります。
また、ディレクトリに読み込み権限があると、lsコマンドでディレクトリの中身を見ることが可能になります。
感覚的にディレクトリの読み込み権限・移動権限は、あまり区別する必要がなさそうに感じます。
実際のファイルの権限の表示
さらに、ファイルの権限は、所有者(owner)、所有グループ(group)、その他のユーザー(other)にわけて、管理されます。
それぞれの権限を、owner permission, group permission, other permissionといいます。
それぞれのユーザーのセグメントについて、前述の8通りのどれかの権限が付与されます。
$ ls -l
-rw-r--r-- 1 uni staff 0 11 28 12:31 default.txt
drwxr-xr-x 2 uni staff 64 11 28 11:55 default_dir
2文字目から3文字区切りで、所有者の権限rwx、グループの権限rwx、その他のユーザーの権限rwxを表し、権限がない部分は-となります。
ここで説明を加えて、対応関係を明記します。
| 8進数(mode) | lsコマンドの記号 | 意味 |
|---|---|---|
| 0o100644 | -rw-r--r-- | 通常ファイルで誰でも閲覧できるが、所有者のみ書き込める。プログラムの実行は誰もできない。 |
| 0o040755 | drwxr-xr-x | ディレクトリで閲覧等は全員できるが、中でのファイル新規作成・名前変更・削除はwのある所有者のみ |
実際に数値でファイル権限を変更できる
File::Stat#modeの返り値の8進数はRubyで使われているだけでなく、Unix内で使われています。
chmod 755 file_name
上記のようにコマンドをうつことで、アクセス権限をrwxr-xr-xに変更できます。
特殊な権限の設定
File::Stat#modeの返り値の8進数表記の"0o040755"を例に説明すると、
- 先頭から2桁
04が、ファイルのタイプ。 - 先頭から4桁目
7が、所有者の権限。 - 先頭から5桁目
5が、所有グループの権限。 - 先頭から6桁目
5が、その他のユーザーの権限。
となりました。
これから、先頭から3桁目の特殊権限について、これから説明します。これで最後です。
先頭から3桁目の位の数について、2進数表記で1が立っていれば、特殊な権限が設定されています。
ビットの立っている位置によって、次のような特殊な権限がつきます。
| 8進数 | 2進数 | 記号 | 特殊な権限の設定 |
|---|---|---|---|
| 0o0000 | 0b000000000000 | (特殊な権限なし) | |
| 0o1000 | 0b001000000000 | t / T | スティッキービット |
| 0o2000 | 0b010000000000 | s / S | SGID(Set Group ID) |
| 0o4000 | 0b100000000000 | s / S | SUID(Set User ID) |
これらの特殊な権限の設定があると、lsコマンドの詳細で表示される権限のxの部分がsやtに上書きされます。
次から、「スティッキービット」そして「SUID、GUID」について簡単に説明して、終わりにします。
スティッキービット
スティッキービットが立っているディレクトリの中では、(自分が所有者のファイルでない限り)たとえother permission(その他のユーザー権限)にwの権限があるファイルであっても、出来るのは書き込みまでで、ファイル名の変更や削除が出来なくなります。一応いうと、スティッキービットの配下であっても、自分が所有者のファイルやrootは名称変更や削除できるので注意です。
$ mkdir sticky_dir
$ chmod 1755 sticky_dir
$ ls -l
drwxr-xr-t 2 uname staff 64 11 28 12:15 sticky_dir
$ chmod 1754 sticky_dir
$ ls -l
drwxr-xr-T 2 uname staff 64 11 28 12:15 sticky_dir
ディレクトリにスティッキービットがたっているのに、other permission(その他のユーザー権限)に移動権限xが立ってない場合は、大文字のTになります。
SUID(Set User ID), SGID(Set Group ID)
owner permissionやgroup permissionのxにs(またはS)が立つことがあります。
例えば、ファイルの所有者権限にsが立っている場合は、SUIDが機能することになり、所有者以外が実行した場合でも所有者が実行したものとして機能します。
なお、スティッキービットと同様に、ファイルの所有者の実行権限xがないにもかかわらず、SUIDを立たせると大文字のSが立ちます。
最後に
以上で説明は終わりです。
文章を構成するのは難しいと改めて実感しました。
ソース・参考書籍
lsコマンドの説明は、man lsとコマンドを打つとlsコマンドの説明が表示されます。
これが仕様であり正確な説明のはずなので、こちらを読むといいでしょう。
- 『新しいLinuxの教科書』
- 『Linux入門講座』
- 図解でわかりやすく読みやすいです。スティッキービット等の説明もあります。
- Linux: SUID、SGID、スティッキービットまとめ - Qiita
- RubyのFile::Stat#modeで返ってくる数字について - yupaがエンジニアになるまでを記録するブログ
Discussion