在 Raspberry Pi 或一般 Linux 系統中,有時候會遇到某個 .sh 腳本在開機後自動執行,但不清楚究竟是誰啟動它的。
例如:
不知道是哪個程序啟動了某個 Shell Script。
想確認
.sh是由systemd、rc.local還是其他腳本啟動。想知道 Shell Script 又啟動了哪些程式。
想追查 Qt/C++ 程式是否由某個 Shell Script 啟動。
想確認
tee、背景程序及其他子程序之間的關係。
Linux 提供了 ps、pstree、pgrep 等工具,可以用來追查程序(Process)的父子關係。
1. 查詢目前正在執行的 Shell Script
假設要追查的腳本名稱是:
run.sh使用以下指令:
ps -ef | grep run.sh也可以使用:
pgrep -af run.sh第二種方式比較簡潔,可以直接顯示符合條件的 PID 和完整執行命令。
例如:
root 488 1 0 09:00 ? 00:00:00 /bin/sh /home/pi/prj1/run.shps -ef 的主要欄位:
| 欄位 | 意義 |
|---|---|
| UID | 執行程序的使用者 |
| PID | Process ID,程序自己的 ID |
| PPID | Parent Process ID,父程序的 ID |
| C | CPU 使用率相關資訊 |
| STIME | 程序啟動時間 |
| TTY | 所屬終端機 |
| TIME | 累計 CPU 使用時間 |
| CMD | 執行命令 |
其中最重要的是:
PID:用來識別目前要追查的程序。
PPID:用來找出直接啟動它的父程序。
例如 PID 為 488、PPID 為 1,代表它的直接父程序是 PID 1。
2. 使用 pstree 追查程序的啟動來源
pstree 可以用樹狀結構顯示程序之間的父子關係。
基本語法:
pstree -aps PID例如:
pstree -aps 488參數說明:
| 參數 | 意義 |
|---|---|
| -a | 顯示程序的命令列參數 |
| -p | 顯示程序 PID |
| -s | 顯示指定程序的祖先程序(從根部往下追查) |
假設輸出:
systemd,1 splash
└─run.s,488 /home/pi/prj1/run.sh
├─appTest,499
│ ├─{DvipServ},649
│ ├─{HCEC Notify},513
│ ├─{HDispmanx Notif},511
│ ├─{HTV Notify},512
│ ├─{QDBusConnection},517
│ ├─{QQmlThread},515
│ ├─{QSGRenderThread},620
│ └─...
└─run.s,490 /home/pi/prj1/run.sh
└─tee,494 /home/home/pi/prj1/f.log從這個結果可以判斷:
PID 1:systemd,Linux 系統的初始化程序。
PID 488:run.sh,由 systemd 的程序樹啟動。
PID 499:appTest,是 run.sh 啟動的 Qt 應用程式。
PID 490:另一個 run.sh 子程序。
PID 494:tee,負責將輸出寫入 f.log。
程序關係可以簡化為:
systemd (PID 1)
|
└── run.sh (PID 488)
|
├── appTest (PID 499)
| ├── DvipServ Thread
| ├── QQmlThread
| ├── QSGRenderThread
| └── 其他 Qt Threads
|
└── run.sh (PID 490)
|
└── tee (PID 494)注意:看到 systemd 並不代表已經知道是哪個 systemd service 啟動腳本,只能確定程序的祖先鏈追溯到了 systemd。
3. 查詢特定程序的父程序
如果只想知道某個 PID 的直接父程序,可以使用 ps。
例如:
ps -o pid,ppid,user,args -p 488輸出:
PID PPID USER COMMAND
488 1 root /bin/sh /home/home/pi/prj1/run.sh這代表:
PID 488:目前的 Shell Script。
PPID 1:直接父程序是 systemd。
USER root:由 root 使用者執行。
如果想進一步查看父程序:
ps -fp $(ps -o ppid= -p 488)這個指令會先取得 PID 488 的 PPID,再查詢該父程序的詳細資訊。
4. 為什麼會出現兩個相同的 Shell Script?
有時候使用 pstree 會看到同一個 .sh 出現兩次。
例如:
run.sh (PID 488)
|
└── run.sh (PID 490)
|
└── tee (PID 494)這不一定代表腳本被執行了兩次。
常見原因是 Shell Script 使用了管線(Pipe)。
例如:
./appTest2>&1 | tee /home/home/pi/prj1/f.log
這行命令的用途:
./appTest:執行應用程式。2>&1:將標準錯誤輸出重新導向至標準輸出。|:將前一個程序的輸出傳給下一個程序。tee:同時將收到的輸出顯示在終端機,並寫入指定檔案。
因此,Shell 可能建立額外的子程序來處理管線。
這也是為什麼程序樹中可能看到兩個相同名稱的 Shell Script。
5. 如何確認 systemd 是透過哪個機制啟動腳本?
如果 pstree 顯示:
systemd,1
└── run.sh,488只能確認它的祖先是 systemd,還需要進一步查找啟動設定。
5.1 檢查 systemd service
先查看目前執行中的服務:
systemctl list-units --type=service --state=running也可以查看服務設定中是否有指定腳本:
grep -R "run.sh" /etc/systemd /lib/systemd /usr/lib/systemd 2>/dev/null如果找到相關 .service 檔案,再查看:
systemctl cat 服務名稱.service特別注意:
ExecStart=這個設定通常用來指定服務啟動時執行的命令。
5.2 檢查 /etc/rc.local
有些 Raspberry Pi 系統會透過 /etc/rc.local 執行開機腳本。
查看內容:
cat /etc/rc.local搜尋是否有:
/home/home/pi/prj1/run.sh5.3 搜尋其他啟動設定
也可以搜尋常見的啟動設定目錄:
grep -R "run.sh" \
/etc/systemd \
/lib/systemd \
/usr/lib/systemd \
/etc/init.d \
/etc/rc.local \
/etc/xdg/autostart \
2>/dev/null注意:搜尋結果只能協助找出設定檔中的文字,不能單憑搜尋結果就證明該設定實際啟動了目前的程序。
6. 查看開機期間的系統日誌
如果程序已經執行過,但目前已經結束,可以嘗試從系統日誌找線索。
查看本次開機的日誌:
journalctl -b搜尋腳本名稱:
journalctl -b | grep -i run如果系統有使用 syslog,也可以查詢:
grep -i run /var/log/syslog不過 Linux 預設不一定會記錄每一次程序啟動,因此找不到紀錄不代表腳本沒有執行。
7. 在 Shell Script 裡記錄啟動資訊
如果需要長期追蹤腳本是由誰啟動的,可以在 .sh 開頭加入以下內容:
LOG="/tmp/script_start.log"
{
echo "================================"
echo "Time: $(date)"
echo "PID: $$"
echo "PPID: $PPID"
echo "User: $(whoami)"
echo "Parent process:"
ps -fp "$PPID"
echo "Process tree:"
pstree -aps $$
} >> "$LOG" 2>&1每次執行腳本時,就會將以下資訊記錄到 /tmp/script_start.log:
執行時間。
自己的 PID。
父程序的 PID。
執行使用者。
父程序資訊。
完整的程序祖先與子程序關係。
如果需要在正式產品中長期保存,建議將 LOG 路徑改成不會隨暫存空間清除的位置,例如:
/home/home/pi/prj1/script_start.log8. 常用指令快速查詢
| 目的 | 指令 |
|---|---|
| 搜尋執行中的腳本 | pgrep -af run.sh |
| 查看程序詳細資訊 | ps -ef |
| 查看指定 PID 的父程序 | ps -o pid,ppid,user,args -p PID |
| 查看父程序詳細資訊 | ps -fp $(ps -o ppid= -p PID) |
| 查看完整程序樹 | pstree -ap |
| 往上追查祖先程序 | pstree -aps PID |
| 查看開機日誌 | journalctl -b |
| 搜尋 systemd 啟動設定 | grep -R "腳本名稱" /etc/systemd /lib/systemd /usr/lib/systemd 2>/dev/null |
| 查看 rc.local | cat /etc/rc.local |
9. 實際排查建議流程
遇到不明原因自動執行的 Shell Script,可以依照以下順序排查:
Step 1:確認腳本是否正在執行
pgrep -af run.shStep 2:取得 PID,追查程序樹
pstree -aps PIDStep 3:確認直接父程序
ps -o pid,ppid,user,args -p PIDStep 4:如果祖先是 systemd,搜尋啟動設定
grep -R "run.sh" /etc/systemd /lib/systemd /usr/lib/systemd /etc/init.d /etc/rc.local 2>/dev/nullStep 5:檢查開機日誌
journalctl -b | grep -i runStep 6:必要時在腳本中加入啟動紀錄
透過 $$、$PPID、ps 和 pstree 記錄程序資訊。
10. 重要觀念
PID:代表程序本身的識別碼。
PPID:代表直接父程序的識別碼。
systemd(PID 1):Linux 系統的主要初始化與服務管理程序。
pstree:用來查看程序之間的父子關係。
Shell Pipeline:使用
|時可能建立額外程序,因此程序樹不一定只有一層。程序祖先不等於真正的啟動設定:看到 systemd 並不能直接判斷是由哪個 service 或開機腳本啟動,還需要檢查設定檔及日誌。
程序結束後不一定能追查:如果沒有事先啟用程序稽核或留下日誌,Linux 預設不保證保留完整的程序啟動紀錄。
這些工具適用於 Raspberry Pi、Debian、Ubuntu 及其他常見 Linux 發行版。
No comments:
Post a Comment