Linux 的 bash 找不到指令時,如何作到自動提示安裝指令?

實例演練使用系統工具理解程式運行的邏輯

fcamel
fcamel的程式開發心得
12 min readAug 15, 2018

--

先前提過一些工具:

這篇以一個簡單的例子,說明如何用這些工具快速找出程式是如何運作的。這是除錯時的重要技能之一。

本篇的目的不是說明問題的答案,而是說明遇到各種狀況時,如何用工具有效率地找到答案。

問題描述

在 Ubuntu 的 bash 下執行指令後,若沒有安裝指令的話,會出現提示告訴你怎麼裝:

但若直接用 bash 執行,卻不會有這效果:

這背後是怎麼運作的呢?

步驟一: 用 strace 找線索

先找出目前 bash 的 PID:

開另一個 terminal 用 strace 追踪資訊,再回到原本 terminal 執行 apt-rdepends,然後看 strace 輸出。

一開始我只有用 strace -e open,看有無讀特別的檔案,但沒看出什麼線索,再來看全部的 system call,還是沒看到什麼線索,但有看到呼叫 clone(): 表示有 child process 作事。再來就連 child process 的一起看:

/tmp/t 的部份內容如下:

由此可知是透過 /usr/lib/command-not-found 處理的。

看一下它的介紹:

有興趣了解它怎麼找的,可以用 apt-get source command-not-found 取得原始碼研究。再來的問題是: bash 為什麼會呼叫它?

相信以 /usr/lib/command-not-found為關鍵字上網搜尋,滿有機會找到答案。不過我想演練如何自行找到答案,就繼續用「硬漢」的作法往下找。

步驟二: 用 gdb 找線索

我想找出呼叫 /usr/lib/command-not-found 當下 bash 的 backtrace,這裡可能會有些線索。

前置作業:

  1. 寫一個程式讓它 sleep(3600),然後暫時換掉 /usr/lib/command-not-found,這樣執行 command-not-found 後會卡住不動 。

2. 安裝 bash debug symbol:

3. 取得 bash 原始碼:

4. 回到原本的 bash 執行:

5. 開新的 terminal 用 gdb attach 剛才的 bash。

這裡我用 cgdb,會切上下兩個視窗,下面是 gdb,上面是原始碼。下面只貼 gdb 視窗的部份:

所以要再看 process 30479 在作什麼:

一樣用 gdb 看 30480,發現是自己替換的程式 (或用 ps axuw | grep 30480 驗證 )。

回頭看 30479。frame 12 的 execute_shell_function 看來可能會有線索:

獲得新的線索 command_not_found_handle。顧名思義,看起來像是 bash 提供找不到命令時的 hook 。

步驟三: 查文件

知道更精確的關鍵字後,查看看 man bash 有沒有說明。文內搜尋 command_not_found_handle 就找到答案了:

COMMAND EXECUTION

...

If the name is neither a shell function nor a builtin, and contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name. Bash uses a hash table to remember the full pathnames of executable files (see hash under SHELL BUILTIN COMMANDS below). A full search of the directories in PATH is performed only if the command is not found in the hash table. If the search is unsuccessful, the shell searches for a defined shell function named command_not_found_handle. If that function exists, it is invoked with the original command and the original command’s arguments as its arguments, and the function’s exit status becomes the exit status of the shell. If that function is not defined, the shell prints an error message and returns an exit status of 127.

所以這是 bash 內建的功能,找不到命令時會看有沒有定義它,有定義就呼叫。可見系統的 bashrc 有定義 command_not_found_handle/usr/lib/command-not-found。稍微找找,會發現寫在 /etc/bash.bashrc

若文件裡沒說明,再回去看 bash 原始碼和 command_not_found_handle 相關程式,有了一些想法後回頭用 gdb 設中斷點觀察以取得更多資訊。必要時也可重編 bash,在自己的 bash 裡加 log 獲得更多資訊。

--

--