Learning Objectives
- 用 which 依 PATH 找出指令的完整路徑,用 type 分辨內建指令與別名,用 whereis 一併找出說明文件
- 用萬用字元 *、?、[ ] 比對檔名,了解萬用字元是由 shell 先展開
- 用 find 依名稱、類型、大小與修改時間搜尋檔案
- 看懂 find 的 Permission denied 訊息並用 2>/dev/null 隱藏,知道 locate 的用法與前置步驟
檔案在哪裡?
Linux 的檔案系統從 / 開始,底下有數十萬個檔案。要找東西時,先想清楚找的是哪一種:
| 想找的 | 工具 | 怎麼找 |
|---|---|---|
| 某個指令的執行檔在哪裡 | which、type、whereis |
只看幾個固定的目錄,很快 |
| 檔名符合某個樣式的檔案(目前目錄或指定目錄) | 萬用字元 *、?、[ ] |
由 shell 比對一層目錄裡的檔名 |
| 符合條件的檔案(名稱、類型、大小、時間),可以往下搜尋整個目錄樹 | find |
實際走過每一個目錄,最完整也最慢 |
| 檔名含有某段文字的檔案(全系統) | locate |
查事先建好的資料庫,很快,但資料可能不是最新 |
which、type 與 whereis:找指令
輸入 top 時,shell 會依照環境變數 PATH 列出的目錄,一個一個找名為 top 的執行檔:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
$ which top
/usr/bin/top
$ which history
$
which 只在 PATH 的目錄裡找執行檔。history 沒有任何輸出,因為它不是一個檔案,而是 bash 的內建指令(builtin)。
which -a 會列出 PATH 裡所有同名的執行檔,而不是只列第一個。
type 是 bash 的內建指令,連別名與內建指令都認得,是確認「這個指令到底是什麼」最直接的方法:
$ type history
history is a shell builtin
$ type ll
ll is aliased to `ls -alF'
$ type ls
ls is /usr/bin/ls
whereis 除了執行檔,還會找出說明文件(man page)等相關檔案:
$ whereis top
top: /usr/bin/top /usr/share/man/man1/top.1.gz
whereis 只查幾個固定的系統目錄(whereis -l 會列出它查哪些目錄),不會搜尋整個系統;-b 只找執行檔、-m 只找說明文件。
萬用字元:比對檔名
| 符號 | 意思 | 例子 | 符合 |
|---|---|---|---|
* |
0 個到任意多個字元 | report*.txt |
report.txt、report1.txt、report10.txt |
? |
剛好 1 個任意字元 | report?.txt |
report1.txt、report2.txt(不含 report10.txt) |
[abc] |
括號內任一個字元 | [rR]eport* |
report1.txt、Report.txt |
[0-9] |
範圍內任一個字元 | *[0-9]* |
檔名含有數字的 |
[!a-z] |
不在括號內的任一個字元 | [!a-z]* |
不是以小寫字母開頭的 |
投影片寫的 [^a-z] 在 bash 裡也代表「不在括號內」;POSIX 標準的寫法是 [!a-z],在各種 shell 都通用。
萬用字元由 shell 展開,不是由指令處理。 執行 ls -d /etc/cron* 時,bash 先把 /etc/cron* 換成所有符合的檔名,
ls 收到的其實是 ls -d /etc/cron.d /etc/cron.daily /etc/cron.weekly /etc/crontab。用 echo 就看得到展開的結果:
$ echo *.txt
Report.txt report1.txt report10.txt report2.txt
$ echo '*.txt'
*.txt
加上引號,shell 就不展開,原樣交給指令。沒有任何檔名符合時,bash 預設也會原樣保留,
所以投影片才會看到 ls: /etc/[^a-z]*: No such file or directory 這種訊息。
投影片的
ll -d是ls -alF的別名再加上-d;-d讓ls列出目錄本身,而不是目錄裡面的內容。
投影片也提到三種引號:單引號 '…' 裡的內容完全原樣;雙引號 "…" 裡的 $變數 會被替換;
反引號 `指令` 會先執行裡面的指令、再把輸出放回原處,例如 echo "現在時間 `date`"。
反引號在 bash 裡建議改寫成 $(date),可以巢狀使用、也比較不容易和單引號看錯。
find:依條件搜尋整個目錄樹
find 起點目錄 條件…
find 從起點目錄開始,走過底下的每一層目錄,列出符合所有條件的項目;不給條件就列出全部。
| 條件 | 意思 | 例子 |
|---|---|---|
-name '樣式' |
檔名符合樣式(可用 *、?、[ ],區分大小寫) |
find /etc -name 'cron*' |
-iname '樣式' |
同上,但不分大小寫 | find . -iname '*.txt' |
-type f/d/l |
一般檔案/目錄/符號連結 | find /etc -type d -name 'cron*' |
-size +5M |
大於 5 MB(-5M 小於;單位有 k、M、G) |
find /usr/bin -size +5M |
-mtime -7 |
內容在 7 天內修改過(+7=超過 7 天前) |
find ~ -type f -mtime -7 |
-atime -7 |
7 天內被讀取過(投影片的例子) | find /usr/bin -type f -atime -7 |
-maxdepth 1 |
最多往下找幾層 | find /etc -maxdepth 1 -name '*.conf' |
-name 後面的樣式一定要加引號。 不加引號時,shell 會先用目前目錄的檔名把 *.txt 展開。
如果目前目錄剛好只有一個 todo.txt,find . -name *.txt 實際執行的是 find . -name todo.txt,
只會找到叫 todo.txt 的檔案,而且不會有任何錯誤訊息提醒你;有好幾個符合時則會出現 paths must precede expression 錯誤。
Permission denied
一般使用者沒有權限進入某些目錄(例如 /root、/etc/ssl/private),find 走到那裡就會印出錯誤訊息:
$ find /etc -name '*.key'
find: '/etc/ssl/private': Permission denied
這些訊息走的是標準錯誤,可以用上一課的 2>/dev/null 丟掉,只留下找到的結果;需要完整結果時,改用 sudo find …。
延伸:locate
locate 不走訪目錄,而是查一個事先建好的檔名資料庫,所以速度很快。Ubuntu 提供的實作是 plocate,Server 版預設沒有安裝:
sudo apt install plocate # 安裝
sudo updatedb # 建立/更新資料庫
locate crontab # 列出路徑中含有 crontab 的檔案
資料庫是定期更新的,剛建立的檔案要等下一次 updatedb 之後才找得到;要找最新的狀態,還是用 find。
想一想:找指令與萬用字元
which cd 沒有任何輸出,最可能的原因是?
目錄裡有 a1.log、a2.log、a10.log、b1.log。ls a?.log 會列出哪些?
目前目錄有 x.txt 與 y.txt。執行 echo *.txt 時,echo 實際收到的參數是?
想一想:find 與 locate
要找出 /var/log 底下所有副檔名是 .log 的一般檔案,哪一個寫法正確?
find / -name passwd 印出很多 Permission denied,只想看找到的結果,應該怎麼改?
剛建立 report.pdf,馬上執行 locate report.pdf 卻找不到,原因是?
重點精華回顧
which依PATH找執行檔,找不到內建指令;type能分辨別名、內建指令與執行檔;whereis同時找執行檔與說明文件。- 萬用字元:
*任意多個字元、?剛好一個字元、[abc]其中一個、[0-9]範圍、[!a-z]不在範圍內。 - 萬用字元由 shell 先展開,指令收到的是展開後的檔名;加上引號就不展開。
find 起點 條件:-name/-iname名稱、-type f/d/l類型、-size +5M大小、-mtime -7修改時間、-maxdepth層數;-name的樣式要加引號。- 沒有權限的目錄會出現
Permission denied,用2>/dev/null隱藏,或用sudo find。 locate查資料庫很快,Ubuntu Server 要先sudo apt install plocate再sudo updatedb。