chmod 755 與 u+x:數字與符號模式差異
了解 chmod 的八進位數字與 u/g/o 符號形式,為何遞迴使用數字模式會破壞目錄樹,以及大寫 X 如何只為目錄與原本可執行檔加上執行權限。
chmod 的變更內容
chmod 會變更檔案或目錄的權限位元,並接受兩種表示法的其中一種。數字表示法使用八進位數字,一次取代所有權限位元。符號表示法會指定使用者類別與運算子,只編輯所指定的位元。兩者最後都會呼叫相同的 system call。差異在於未指定的位元會如何處理。
讀取 mode 與變更 mode 是不同的工作。如果長格式列表中的字母尚不熟悉,請先閱讀 逐欄解析 drwxr-xr-x 的含義。本指南只會變更這些內容。
請以一般登入使用者執行這裡的所有操作。root 帳號會略過這些檢查,因此以 root 執行的權限示範沒有意義。如果您仍在新伺服器上以 root 工作,請先建立 具備最小權限的一般使用者,再返回本節。
建立可隨時刪除的 sandbox
cd "$(mktemp -d)"
touch notes.txtmktemp -d 會在 /tmp 下建立空目錄,並輸出所建立的路徑;命令替換 會直接將該路徑傳給 cd。以下操作都不會碰觸任何重要檔案。
有一個命令會回報每次變更的結果:
stat -c '%a %A %n' notes.txt%a 會以八進位數字輸出模式,%A 會以長格式清單使用的字母形式輸出相同模式,而 %n 會輸出名稱。在接下來的區塊中,以 $ 開頭的行是您輸入的內容,其下方的行是命令輸出的結果。請自行執行這些命令。您電腦上的輸出應逐行一致,因為數字模式不受環境中的任何設定影響。
數字形式:每個類別使用一位八進位數字
第一位代表檔案擁有者。第二位代表檔案所屬群組。第三位代表其他所有人。每一位數字都是各個位元值的總和,每個位元各有一個值。一次設定一個位元,並查看 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 個屬於其他所有人。將數值相加,即可在同一個類別中組合多個位元。
$ 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.txt第二個命令沒有指定群組或其他所有人,但它們的位元仍然變更。這就是數字形式的特性,也是數字形式不適合用於小幅修改的原因。(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每個指令只會處理一個類別,其他類別則維持原狀。使用逗號連接多個子句,中間不要加空格;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.txtgo= 等號後面沒有內容時,會移除群組和其他所有人的全部位元。這適合用於只有擁有者能讀取的檔案,例如 SSH 金鑰組中的私密部分。
chmod 也能回報自己的處理結果。在腳本中無人查看畫面時,這項功能很重要。
chmod -v u+x notes.txt
chmod -v u+x notes.txt-v 會為指定的每個檔案輸出一行。執行這兩個指令,並比較畫面上的兩行內容:第一行會列出檔案名稱,並回報變更前後的模式;第二行會回報模式維持不變,因為要求新增的位元原本就已設定。-c 只有在檔案實際變更時才會輸出一行,因此更適合保留在日誌中。
chmod 755 與 u+x:應使用哪種表示法
從同一個檔案開始執行兩次,並比較每種表示法留下的結果。
$ 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兩個指令都讓檔案的擁有者具備執行權限。只有其中一個保留了其他兩類的原有權限。當你知道想要的確切最終狀態,並且希望強制套用該狀態時,請使用數字模式。在必須於未知機器上產生相同結果的部署指令碼中,這是正確的做法。當你心中的描述包含「新增」或「移除」時,請使用符號模式,因為數字格式無法表示「保留該部分不變」。
新檔案的初始模式不是由 chmod 產生,而是由建立檔案之程序的 umask 決定。在自己的 shell 中執行 umask 和 umask -S,即可查看目前生效的值。請注意,這個值可能因發行版、登入 shell 與 systemd 服務,以及不同帳號而異。umask 會設定每個新檔案的模式,因此 chmod 執行前,umask 就決定了檔案的初始狀態。
目錄的 execute 位元作用
這是最常造成時間浪費的誤解。對一般檔案而言,execute 位元表示 kernel 可以執行該檔案。目錄不包含指令,因此沒有可供執行的內容。對目錄而言,execute 位元表示 traversal:允許解析其中的名稱。若要存取 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
helloshell 原樣輸出該模式,因為展開 vault/* 需要讀取目錄,而 比對不到任何項目的 glob 會以文字常值傳遞,不會產生錯誤。cat 仍然可以運作,因為你自行提供了名稱,且 traversal 權限已允許。現在將這兩個位元對調。
$ 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 會遍歷整個目錄樹,將指定的模式套用到遇到的所有項目。目錄與資料檔案需要不同的權限位元,因此同一個絕對數值不可能同時適用於兩者。先建立一個小型目錄樹,觀察錯誤如何發生。
$ 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表面上沒有任何問題,這正是危險所在。這類錯誤不會顯示明確症狀,因此可能在複製的操作指示中存在多年。現在有兩個資料檔案被標記為可執行檔。Git 會儲存執行位元,因此下一次 commit 會將這項變更帶給所有 clone 該 repository 的人,而 rsync 與 tar 也會在你將目錄樹複製到其他機器時,把這項變更帶到每台機器。
第二個遞迴操作錯誤會直接造成問題。若將不含執行位元的模式套用到目錄,該目錄就會關閉。
$ 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 denied檔名仍會列出,因為讀取位元仍存在;但通過該目錄的所有路徑都會被關閉,因為執行位元已不存在。chmod -R 644 site 會在一個命令中對目錄樹中的每個目錄執行相同操作。之後的結果也不一定完全一致,因為 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 而重建檔案 一樣緩慢且不確定,得到的教訓也相同:執行遞迴命令前,先確認命令的目標,再按下 Enter。
目錄使用大寫 X,而不是套用至每個檔案的 execute 權限
符號模式同時接受 X 與 x。X只會在適用的地方設定 execute bit:目錄,或已經針對某個類別設定 execute bit 的一般檔案。這正是你希望 -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.sh只需執行一個命令,目錄仍可進入,而資料檔案則不受影響。由於 script 原本已設定 execute bit,因此保留了該 bit;這是 X規則的另一半。X會讀取檔案目前的 mode,因此已經移除所有 execute bit 的檔案,無法透過它恢復 execute bit。
$ 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會將檔案已具備的 execute bit 延伸至另一個類別:
$ chmod go+X site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
755 -rwxr-xr-x site/deploy.sh因此,小寫 x適合用於指定要設為可執行的單一檔案,而 X適合用於遍歷整個樹狀目錄。若你需要依檔案類型設定確切的 mode,而不是根據目前 mode 套用規則,請改用 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會選取一般檔案,因此每種類型都能取得應有的 mode。-exec ... +會將多個路徑批次傳給一次 chmod 呼叫,而不是每個檔案各啟動一個 process;在包含數千個項目的樹狀目錄中,這點很重要。script 會在另一行單獨恢復自己的 bit,這是刻意的設計。
變更後離開前先確認結果
任何遞迴變更後,使用以下兩個指令即可找出重要錯誤。第一個指令會列出你已無法進入的目錄。
find . -type d ! -perm -u+x第二個指令會列出機器上的任何帳號都能編輯的檔案。
find . -type f -perm -o+w在 -perm -o+w 中,開頭的 - 表示「至少包含這些位元」,因此只要檔案的 o 類別具備寫入位元,不論其他位元如何設定,都會符合條件。兩個指令都沒有輸出,才是預期結果。對剛使用 -R 變更的任何目錄樹,請從頂層執行這兩個指令;再對你原本要變更的少數路徑執行 stat -c '%a %A %n',確認你認為已設定的模式,確實是 kernel 實際保留的模式。
FAQ
chmod 755 和 chmod u+x 有何不同?
數值模式是絕對設定。它會寫入檔案的所有權限位元,因此連你未特別考慮的類別也會一併改寫。符號模式則是編輯操作。u+x 會為擁有者新增一個位元,並保留其他位元的原狀。當你知道檔案需要的確切最終狀態時,使用數值形式。當你只想新增或移除一項權限時,使用符號形式。在執行任一命令前後執行 stat -c '%a %A %n' <file>,差異會顯示在輸出中。
為什麼對網頁檔案目錄執行 chmod -R 755 是不好的做法?
因為 -R 會將相同的絕對模式套用至目錄和一般檔案,而這兩者需要不同的位元。目錄需要 execute 位元才能遍歷。HTML 和 CSS 等資料檔案不需要此位元;將它們標記為可執行檔案後,git 會在下一次 commit 中記錄這項變更,rsync 也會將變更複製到下一台機器。使用 chmod -R u=rwX,go=rX <dir>,讓 execute 位元只套用至目錄;或者搭配 -type d 和 -type f,透過 find 分別設定兩種檔案的模式。
目錄上的 execute 位元有何作用?
它授予遍歷權限,而不是執行權限。目錄中沒有可供執行的內容。execute 位元代表解析其中名稱的權限,因此在開啟路徑末端的檔案前,路徑上的每個目錄都必須具備此權限。read 位元則獨立運作,授予列出名稱的權限。兩者可以只具備其中之一:只有 execute 時,你可以開啟已知名稱的檔案,但 shell 無法展開該目錄中的 glob;只有 read 時,你可以查看名稱,但每次嘗試開啟其中一個名稱都會失敗。
何時應使用大寫 X,而不是小寫 x?
只要命令會遞迴處理,就使用 X。X 會對目錄設定 execute 位元;對一般檔案則只有在其中一個 execute 位元已經設定時才會設定,因此單次處理即可讓整個樹狀結構可遍歷,又不會將資料檔案變成程式。當你只指定單一檔案,而且確實要處理該檔案時,使用小寫 x。有一項限制需要記住:X 無法復原檔案上已在所有位置清除的 execute 位元,因為已沒有可供比對的位元。請使用 chmod u+x <file> 將該位元加回。
chmod 會變更檔案的擁有者嗎?
不會。chmod 只會變更權限位元。擁有權分別由使用者的 chown 和群組的 chgrp 決定,而將檔案交給其他使用者需要 root 權限。這一點很重要,因為系統會根據檔案上記錄的擁有者和群組來解讀權限位元,而不是根據最後寫入檔案的人員。執行 stat -c '%U %G %a %n' <file>,即可同時查看擁有者、群組和模式,再決定實際需要使用哪一個命令。