Raspberry Pi / Linux:如何追查 Shell Script(.sh)的啟動來源與程序關係

在 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.sh

ps -ef 的主要欄位:

欄位意義
UID執行程序的使用者
PIDProcess ID,程序自己的 ID
PPIDParent Process ID,父程序的 ID
CCPU 使用率相關資訊
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

從這個結果可以判斷:

  1. PID 1:systemd,Linux 系統的初始化程序。

  2. PID 488:run.sh,由 systemd 的程序樹啟動。

  3. PID 499:appTest,是 run.sh 啟動的 Qt 應用程式。

  4. PID 490:另一個 run.sh 子程序。

  5. 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)。

例如:

./appTest 2>&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.sh

5.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.log

8. 常用指令快速查詢

目的指令
搜尋執行中的腳本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.localcat /etc/rc.local

9. 實際排查建議流程

遇到不明原因自動執行的 Shell Script,可以依照以下順序排查:

Step 1:確認腳本是否正在執行

pgrep -af run.sh

Step 2:取得 PID,追查程序樹

pstree -aps PID

Step 3:確認直接父程序

ps -o pid,ppid,user,args -p PID

Step 4:如果祖先是 systemd,搜尋啟動設定

grep -R "run.sh" /etc/systemd /lib/systemd /usr/lib/systemd /etc/init.d /etc/rc.local 2>/dev/null

Step 5:檢查開機日誌

journalctl -b | grep -i run

Step 6:必要時在腳本中加入啟動紀錄

透過 $$、$PPID、ps 和 pstree 記錄程序資訊。


10. 重要觀念

  1. PID:代表程序本身的識別碼。

  2. PPID:代表直接父程序的識別碼。

  3. systemd(PID 1):Linux 系統的主要初始化與服務管理程序。

  4. pstree:用來查看程序之間的父子關係。

  5. Shell Pipeline:使用 | 時可能建立額外程序,因此程序樹不一定只有一層。

  6. 程序祖先不等於真正的啟動設定:看到 systemd 並不能直接判斷是由哪個 service 或開機腳本啟動,還需要檢查設定檔及日誌。

  7. 程序結束後不一定能追查:如果沒有事先啟用程序稽核或留下日誌,Linux 預設不保證保留完整的程序啟動紀錄。

這些工具適用於 Raspberry Pi、Debian、Ubuntu 及其他常見 Linux 發行版。

No comments:

Post a Comment

半導體產業簡介

半導體產業簡介: 所謂微電子產業,就是生產 積體電路 ( 大陸稱為 集成電路 ) 的產業 。 積體電路 (Integrated Circuit ,簡稱 IC) 在電子學中是一種把電路(包括半導體裝置、元件)小型化的方式、並製造在半導體晶圓表面上。所以半導體只是製作「 ...