---
title: 'shell 命令 - 裝置資訊、處理數據'
disqus: kyleAlien
---
shell 命令 - 裝置資訊、處理數據
===
## OverView of Content
這章演示如何透過指令來探查 Linux 裝置訊息
[TOC]
## 追蹤程序
追蹤程序通常有兩個命令 `stace`(系統呼叫追蹤)、`ltrace`(系統函式庫追蹤)
### 追蹤系統呼叫 - strace
* **`strace` 命令**:可以 **追蹤程序 `fork` 後的所有涉及系統呼叫的行為**;範例如下
```shell=
strace cat /dev/null
```
簡單分析以下系統呼叫
1. 使用 `execve` 命令去覆蓋 fork 命令後的記憶體區塊,去執行新的命令… 並使用 `brk` 來初始化記憶體
```shell=
execve("/usr/bin/cat", ["cat", "/dev/null"], 0x7fc339ef08 /* 50 vars */) = 0
brk(NULL)
```
2. 共享函式庫的載入,載入多個 so 庫(`ld.so.preload`、`ld.so.cache`、`libc.so.6`、``)
```shell=
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=79836, ...}) = 0
mmap(NULL, 79836, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f81b1e000
close(3) = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0`\17\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1455120, ...}) = 0
```
3. 開啟 `/dev/null` 裝置,如果核心開啟檔案成功,則會返回 3
```shell=
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
```
:::warning
* 如果核心開啟檔案失敗,則會返回 -1
```shell=
openat(AT_FDCWD, "/dev/hello", O_RDONLY) = -1 ENOENT (No such file or directory)
```
:::
4. 退出
```shell=
close(3) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
```
> 
### 追蹤系統函式庫 - ltrace
* **`ltrace` 命令**:它不追蹤核心即別的內容,只追蹤共享函式庫
```shell=
ltrace cat /dev/null
```
## 監看 Process、Thread
Process 就是一個應用的最小單位,每個應用都會有自己專屬的 Process ID
### 查看 Process 訊息 - ps
* 基礎使用如下;而 `ps` 這個命令有許多的參數,這邊這會列出幾個常用的參數,其餘的參數可以使用 `man ps` 去查詢
```shell=
## 查看當前用戶所使用的進程資訊
ps
```
> 
Linux 系統中的 GNU **ps 命令支持 3 種不同類型參數**
1. **Unix 風格**:單破折線 (`-<options>`),以下是常用參數
| 參數 | 功能 |
| - | - |
| **-A / -e** | 顯示所有的進程(不限用戶) |
| **-f** | 顯示完整格式輸出 |
| **-l** | 長列表 |
| -N | 顯示與指定參數不同的進程 |
| -a | 顯示除了 控制進程 (session leader) 和 無終端進程之外的所有進程 |
| -d | 顯示除了 控制進程 (session leader) 之外的所有進程 |
| -C <cmdlist\> | 顯示指定列表中的進程 |
| -G <grplist\> | 顯示列表中指定的 GID 進程 |
| -U <userlist\> | 顯示列表中指定的 UID 進程 |
```shell=
## Unix 風格常用組合
ps -ef
## 下表說明 -efl 輸出的欄位說明
ps -efl
```
> 
| 輸出欄位(前到後) | 說明 | 補充 |
| - | - | - |
| F | 內核分配給系統的標記 | |
| S | 進程當前狀態 | O:運行、S:睡眠、Z:僵化、R:等待中彈可運行、T:停止 |
| UID | 啟動該進程的的用戶 | |
| PID | 進程編號 | |
| PPID | 父進程編號 | |
| C | 進程的 CPU 使用率 | |
| PRI | 優先級 | 數字越大越優先 |
| NI | 謙讓度 | 與 PRI 共同決定最終的優先級 |
| ADDR | 進程的內存地址 | |
| SZ | 若進程被換出,所需要的交換空間大小 | Swap size |
| WCHAN | 進程休眠時的內存地址 | |
| TTY | 進程啟動的終端設備 | |
| TIME | 進程使用 CPU 的累積時間 | 格式:`hh:mm:ss` |
| CMD | 啟動的程序名稱 | 可能會更改 |
2. **BSD 風格**:無破折線(`<options>`);以下說明幾個常見的 options
:::info
BSD 風格的 ps 命令相對簡單
:::
| 參數 | 功能 |
| - | - |
| T | 跟蹤與當前終端有關的進程 |
| a | 所有終端的進程 |
| g | 顯示所有終端的進程(包括控制進程) |
| r | 僅顯示運行中的進程 |
| **x** | 顯示所有進程,包括未分配終端的進程 |
| l | 長模式 |
| s | 信號格式顯示 |
| **u** | 基於用戶的格式顯示 |
| v | 採用虛擬內存的方式顯示 |
| **w** | 顯示命令全名,而非簡易名稱 |
```shell=
## BSD 風格常用組合
ps s
ps xl
ps aux
```
:::info
加州伯克利分校開發的 Unix 版本,大部分與 Unix 風格可獲得的數據相差不大,它的特點是,它可以 **獲得更詳細的進程資訊** (如下表)
:::
| BSD 特色欄位 | 說明 | 補充 |
| - | - | - |
| VSZ | 進程在內存中的大小 | 大小為 KB |
| RSS | 物理內存的大小 | |
| STAT | 當前進程的狀態 | `<`:進程運行在高優先級上、`N`:進程運行在低優先級、`L`:進程有頁面鎖定在內存、`s`:控制進程、`l`:多線程進程、`+`:運行在前台、`S`:可休眠的進程 |
> 
:::success
* ps + `$$` 參數配合:可以獲得屬於當前程序的運行程序
```shell=
ps u$$
```
> 
:::
3. **GNU 風格**:雙破折線(`--<options>`);改進 ps 命令中加入了另外一些參數;
```shell=
## 查看進程之間的關係 (最後的 COMMAND 會有父子關係圖)
ps aux --forest
```
> 
### 查看 Thread
* 透過 ps 命令選項 `m`,可以查看到用戶下的程序都使用哪些進程
```shell=
ps am
```
> 下圖,PID 下的 `- -` 代表該進程下有幾個 Thread()
>
> 
### 實時監測 Process - top
* 當你下達 `ps` 命令時你所獲取的是當下的進程資訊,當有進程更新時(或有進程關閉、開啟)是不會實時更新的
* 使用 `top` 命令就可以實時監測進程
```shell=
top
```
> 
1. 第一行:當前時間、系統運行時間、登入用戶數量、平均負載(1min 5min 15min)
:::info
* 平均負載 15 分鐘仍在 2 以上說明系統在繁忙狀態
* 可以使用 `uptime` 命令來查看 相同效果
> 
:::
> 
2. 第二行:系統中進程的總數、狀態
:::info
* `top` 命令中將進程稱為 `task`
:::
> 
3. 第三行之後:
* CPU 訊息,根據進程的 owner & 進程的狀態將 CPU 利用率分成幾類輸出
* 記憶體分配、使用、剩餘
* 交換記憶體的訊息 (物理空間當記憶體的狀態)
> 
4. 內容欄位:
| 欄位 | 說明 |
| - | - |
| PID | 進程 ID |
| USER | 進程的 Owner |
| PR | 進程優先級 |
| NI | 進程謙讓度 |
| VIRT | 進程所佔用的虛擬記憶體量 |
| RES | 物理內存的總量 |
| SHR | 與其他進程共享的內存總量 |
| S | 進程狀態(D:可中斷、R:運行中、S睡眠、T:跟蹤或停止、Z:僵化) |
| %CPU | 進程使用的 CPU 時間百分比 |
| %MEM | 進程佔用的內存百分比 |
| TIME+ | 進程啟動到目前為止使用了多少 CPU 時間 |
| COMMAND | 進程名 |
* `top` 命令是一個互動式進程,可以透過以下字符來改變 `top` 設定
| 鍵入字符 | 功能 |
| - | - |
| `d` | 設定 `top` 更新時間 |
| `f` | 改變 `top` 的顯示順序(預設 %CPU 排序)、顯示內容 |
| `q` | 退出 |
> `f` 的設定頁面
>
> 
> 改變顯示順序為 `%MEM` 排序
>
> 
### 結束進程 - kill / killall
* Unix 進程之間的通訊其中一個辦法就是 **通過 ==信號== 通訊**,Linux 也沿用這個方法;其信號與意義如下表
| 信號 | 名稱 | 意義 |
| - | - | - |
| 1 | HUP | 掛起 |
| 2 | INT | 中斷 |
| 3 | QUIT | 結束運行 |
| 9 | KILL | 無條中終止 |
| 11 | SEGV | 段錯誤 |
| 15(Kill 默認) | TERM | 盡可能地終止(如果不能終止也不能怎麼辦) |
| 17 | STOP | 無條件停止運行(不等於終止) |
| 18 | TSTP | 停止或是暫停,再次啟動後繼續在後台運行 |
| 19 | CONT | 在 STOP、TSTP 之後恢復運行 |
* `kill` 命令:可以對指定進程傳入不同信號
```shell=
# 對 PID 為 1234 的進程下達強致終止的信號
kill -s 9 1234
```
* `killall` 命令:透過進程名(`Command` 欄位)來關閉進程
```shell=
## 關閉 http 開頭相關進程
killall http*
```
:::warning
要小心使用這個命令,否則可能會導致文件系統損壞
:::
<!--
## CPU、記憶體、IO 監控
### 測量 CPU 時間
* 除了使用 `top` 命令監控系統之外 -->
## 監看磁碟、檔案
在Linux系統上有幾個命令行命令可以用來幫助管理存儲媒體
### lsof - 開啟的檔案
* **`lsof` 命令**:`list open files` 會列出 **打開的檔案**、**使用它們的程序**,甚至包括 **網路資源、動態函數、管道**
```shell=
lsof | head
```
其中欄位意義如下
| 欄位 | 說明 |
| - | - |
| `COMMAND` | 描述擁有該檔案描述符的程序名稱 |
| `PID` | 檔案的進程 ID |
| `TID` | 檔案的線程 ID |
| `TASKCMD` | 任務名,通常跟 `COMMAND` 相同 |
| `USER` | 該進程所屬的 User name |
| `FD` | **包含兩種意義**;^1.^ 表明檔案的作用(像是 `cwd`)、^2.^ 檔案的描述符(一串數字) |
| `TYPE` | 檔案類型(像是目錄、socket... 等等) |
> 
1. **查看當前進程的所開啟的所有檔案**
```shell=
# `$$` 是當前進程 ID
ls -p $$
```
> 
2. **顯示 `/home/alien` 目錄中所有被開啟的檔案**
```shell=
ps /home/alien
```
> 
### 掛載設備 - mount
* 我們知道 Linux 是使用一個虛擬目錄來管理所有的硬碟,當使用新的硬碟時,就 **必須掛載 (mounting) 硬碟到虛擬目錄之下**
> 大部分狀況下移動式裝置都會自動掛載
* 單純使用 `mount` 命令可以查詢當前系統上已掛載的裝置;`mount` 輸出的格式代表意義如下表
```shell=
mount
## 輸出格式
## <設備文件> on <虛擬目錄掛載點> type (<訪問狀態>)
```
> `/dev/mmcblk0p2 on / type ext4 (rw,noatime)`
>
| 輸出 | 說明 | 補充 |
| - | - | - |
| `/dev/mmcblk0p2` | 媒體的設備文件 | - |
| `/` | 媒體掛載到虛擬目錄的掛載點 | - |
| `ext4` | 文件系統類型 | eg. `vfat`, `ntfs`, `iso9660` |
| `rw` | 已掛載的媒體訪問狀態 | `ro` 只能讀、`rw` 可寫可讀、`user` 普通用戶掛載文件系統、`loop` 掛載 **文件** |
> 
* 掛載:手動掛載設備
```shell=
## 格式:mount -t <type> <device> <directory>
## 掛載檔案類型 ext4
## 裝置在 /dev/test_device
## 掛載到虛擬目錄的 /mnt/my_device 資料夾中
sudo mount -t ext4 /dev/test_device /mnt/my_device
```
* **卸載**:移除已掛載設備 (如果設備 busy 就不能卸載)
```shell=
## umount [device]
## 卸載虛擬目錄對應的 /dev/test_device 裝置
sudo umount /dev/test_device
```
:::danger
* 設備 busy,可以使用 `lsof` 查看使用設備的進程,並將它停止
:::
### 文件系統空間 - df
* df (`report filesytem disk`) 命令:該命令可以透過檔案系統獲取當前硬體已經使用的空間
> 以檔案系統的角度去查看容量,一個硬碟中可能有多個分區,也就是會存在多個檔案系統,所以 `df` 並不代表硬碟總空間)
```shell=
df
## 輸出
## /dev/root 246108604 7232432 228818000 4% /
```
| df 欄位 | 輸出數值 | 說明 |
| - | - | - |
| FileSystem | /dev/root | 設備(device)位置 |
| 1K-blocks | 246108604 | 總容量(1K 為單位) |
| Used | 7232432 | 已使用(1K 為單位) |
| Available | 228818000 | 還可以使用(1K 為單位) |
| Use% | 4% | 已使用的百分比 |
| Mounted on | / | 設備(device)掛載到虛擬目錄的位置 |
:::info
* df 命令注意點
* 輸出顯示是 Linux 系統認為的當前值,並非實時值(尚未釋放的文件不會算數)
* 隱藏的預留區域也會算在百分比內(所以怎麼家都不會是 `100%`)
:::
> 
### 硬體使用空間 - du
* du (`estimate file space usage`) 命令:該命令可以判斷 **特定目錄** 的磁碟使用空間 (與 `df` 的差異是可以指定目錄)
> du 默認目錄為當前目錄
```shell=
## 由於輸出過長,這邊使用 head 限制輸出
du | head
## 輸出更容易讀的單位
du -h | head
```
> 輸出:4 ./Pictures
| du 左到右輸出數值 | 說明 |
| - | - |
| `4.0K` | 佔用的磁碟塊(單位預設為 1K) |
| `./Pictures` | 目錄 |
> 
`du` 其餘的 options
| options | 說明 |
| - | - |
| `-h` | 更方便檢視大小的格式 |
| `-c` | 已列出的文件總大小 |
| `-s` | 顯示每個輸出參數的總計(按資料夾) |
```shell=
## 查看當前目錄中的所有資料
du -sh *
```
> 
## 處理數據
### 排序 - sort
* sort 默認會按照默認語言的的排序規則對文本中的數據進行排序
```shell=
## 查看原先數據
cat test_Context
## sort 會按照字元(ASCII)順序排序
sort test_Context
```
> 
* 以下幾個 sort 常用的 options
| sort's options | 說明 | 其他 |
| - | - | - |
| -n | 將文本內容的排序改成透過數字順序排序 | - |
| -M | 按照月份排序 | Linux 日誌文件經常會再每行的起始位,會有一個時間戳 |
| -k POS1[, POS2]| 指定排序範圍;排序從某個位置(POS1) 到(POS2) | POS2 不指定則代表到尾 |
| -t | 按照某個值(由你設定),將文本區分 | - |
| -r | reverse 排序 | |
1. 按照月份排序內容
```shell=
## 查看原先數據
cat test_Context
## 將內容按照月份排序
sort -M test_Context2
```
> 
2. 按照用戶 ID 排序 `/etc/passwd`
```shell=
## -t 指定 ':' 區分 -> [root] [x] [0] [0] [root] [/root] [bin/bash]
## -k 3 排序範圍從 區分的第三個範圍開始到最後
## -n 用數字排序
sort -t ':' -k 3 -n /etc/passwd
```
> 
3. 按照大到小排序輸出內容
```shell=
## 排序 du -sh 的輸出(大到小)
du -sh * | sort -nr
```
> 
### 過濾數據 - grep
* `grep` 命令可以在數據海中找到你所需要的某些特定資料,格式如下
> grep [options] pattern [file]
```shell=
## 找尋 /etc/passwd 文件中,有 root 字的內容
grep root /etc/passwd
```
> 
* 以下幾個 grep 常用的 options
| grep's options | 說明 | 其他 |
| - | - | - |
| -v | 反向搜尋 | 顯示所有非 pattern 的內容 |
| -n | 添加行號 | - |
| -c | 查看符合 pattern 匹配數量 | - |
| -e | 多匹配模式 | 可設定多個條件 |
| -E | 正則表達式匹配 | `[]` 中括號內任意字串匹配,`''` 指定全符合才匹配 |
1. 查詢 `/etc/passwd` 內所有沒有 root 的內容
```shell=
grep -vn root /etc/passwd
```
> 
2. 同上,不過這邊輸出所有符合條件的數量
```shell=
grep -vc root /etc/passwd
```
> 
3. 多匹配模式,找尋 alien 或 root 關鍵字並輸出行數
```shell=
grep -e alien -e root -n /etc/passwd
```
> 
:::success
* 當然我們也可以使用正則表達式的方式去搜尋內容
```shell=
grep -E 'root|alien' -n /etc/passwd
```
> 
:::
### 壓縮數據 - gzip
* 壓縮數據可以讓文件縮小佔有空間,而 Linux 中有多種壓縮數據的工具,可以根據需求做選擇
| 工具 | suffix 文件擴展名 | 說明 |
| - | - | - |
| compress | .Z | Unix 最早期的壓縮工具 |
| bzip2 (主要用於壓縮文字) | .bz2 | Burrow-Wheeler 塊排序文本壓縮算法、霍夫曼編碼 |
| gzip | .gz | GNU 壓縮工具、Lempel-Ziv 編碼 |
| zip | .zip | PKZIP 工具 Unix 實現 |
* `gzip` 是用來替代 `compress` 的免費版工具,gzip 相關工具如下
| gzip 工具 | 說明 |
| - | - |
| gzip | 壓縮文件 |
| zcat | 查看壓縮過的文件 |
| gunzip | 解壓文件 |
1. 壓縮檔案,並查看壓縮過後的檔案
```shell=
## 壓縮 file.txt 並輸出到 done.gz 檔
gzip -c file.txt > done.gz
## 查看壓縮檔
zcat done.gz
```
:::info
如果沒有另外指定 `-c` 操作符,則會將原來的檔案直接替換成壓縮檔案
:::
> 
2. 解壓檔案
```shell=
gunzip -c done.gz > finishGunzip
cat finishGunzip
```
> 
### 數據歸檔 - tar
* **歸檔** 與 **壓縮** 是兩種不同的行為;**歸檔是將多個檔案放置匯聚成一個檔案,壓縮則是將原來的數據透過特別的算法減少其佔用空間**
> 這兩個行為常常會一起使用,所以會搞混是很正常的
* `tar` 命令最早上為了將數據寫到外部空間;格式如下
> `tar` function [options] object1 object2 ...
| tar's function | 說明 |
| - | - |
| -A | (--concatenate) 將已有的 tar 文件歸檔到另一個已有的 tar 文件 |
| -r | (--append) 將數據追加到已有的 tar 文件 |
| -c | (--create) 創建新的 tar 文件 |
| -t | (--list) 列出歸檔內容 |
| -u | (--update) 將已有的 tar 文件內的相同檔名的文件更新 |
| -x | (--extract) 從已有的 tar 歸檔文件中提取文件 |
| tar's options | 說明 |
| - | - |
| -C <\dir> | 切換到指定目錄 |
| -f <\file> | 將壓縮結果輸出到指定檔案 |
| -p | 保留所有文件的權限 |
| -v | 顯示處理時的文件 |
| -j | 將輸出內容重新定向給 bzip2 |
| -z | 將輸出內容重新定向給 gzip |
1. 歸檔文件
```shell=
## 將 .txt 結尾的檔案歸檔到 tarFinish.tar 檔案
tar -cvf tarFinish.tar *.txt
ls -laF
```
> 
2. 查看歸檔後的文件列表
```shell=
tar -tf tarFinish.tar
```
> 
3. 解壓文件
```shell=
## 解壓並輸出到指定資料夾中
tar -xvf ./tarFinish.tar -C ./myExtract
```
> 
:::success
* **後綴 `.tgz` 文件**?
`.tgz` 文件都是將 tar 檔案透過 gzip 算法壓縮過後的檔案,可以透過以下命令進行解壓並提取
```shell=
tar -zxvf <filename.tgz>
```
其實 suffix 結尾與 `.tar.gz` 相同,也可以使用兩段式命令解開
```shell=
gunzip <filename.tar.gz>
tar xvf <filename.tar>
```
:::
## Appendix & FAQ
:::info
:::
###### tags: `Linux Shell`