內容目錄
這裡我給出一些 Debian 系統中的資訊,幫助學習程式設計的人找出打包的原始碼。下面是值得關注的軟體包和與之對應的文件。
安裝 manpages 和 manpages-dev
包之後,可以通過運行“man 名稱”查看手冊頁中的參考資訊。安裝了 GNU
工具的相關文檔包之後,可以通過運行“info 程序名稱”查看參考文檔。某些 GFDL 協議的文檔與 DFSG
並不兼容,所以你可能需要在 main 倉庫中包含 contrib 和
non-free 才能下載並安裝它們。
請考慮使用版本控制系統工具。參見 節 10.5, “Git”。
| ![[警告]](images/warning.png)  | 警告 | 
|---|---|
| 不要用“ | 
| ![[注意]](images/caution.png)  | 注意 | 
|---|---|
| 你可以把從源代碼編譯得到的程序直接放到“ | 
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| “歌曲:99瓶啤酒”的代碼示例可以給你提供實踐各種語言的好範本。 | 
Shell 腳本 是指包含有下面格式的可執行的文本文件。
#!/bin/sh ... command lines
第一行指明瞭讀取並執行這個文件的 shell 解釋器。
讀懂 shell 指令碼的最好 辦法是先理解類 UNIX 系統是如何工作的。這裡有一些 shell 程式設計的提示。看看“Shell 錯誤”(https://www.greenend.org.uk/rjk/2001/04/shell.html),可以從錯誤中學習。
不像 shell 交互模式(參見節 1.5, “簡單 shell 指令” 和 節 1.6, “類 Unix 的文本處理”),shell 腳本會頻繁使用參數、條件和循環等。
系統中的許多指令碼都可以透過任意 POSIX shell(參見 表格 1.13, “shell 程式列表”)來執行。
預設的非互動 POSIX shell "/usr/bin/sh" 是一個指向到
/usr/bin/dash 的符號連結,並被許多系統程式使用。 
預設的互動式 POSIX shell 是 /usr/bin/bash。 
避免編寫具有 bashisms(bash 化)或者 zshisms(zsh 化)語法的 shell 指令碼,確保指令碼在所有 POSIX shell
之間具有可移植性。你可以使用 checkbashisms(1) 對其進行檢查。
表格 12.1. 典型 bashism 語法列表
| 好的:POSIX | 應該避免的:bashism | 
|---|---|
| if [ "$foo" = "$bar" ] ; then … | if [ "$foo" == "$bar" ] ; then … | 
| diff -u file.c.orig file.c | diff -u file.c{.orig,} | 
| mkdir /foobar /foobaz | mkdir /foo{bar,baz} | 
| funcname() { … } | function funcname() { … } | 
| 八進位制格式:" \377" | 十六進位制格式:" \xff" | 
使用 "echo" 指令的時候需要注意以下幾個方面,因為根據內建 shell 和外部指令的不同,它的實現也有差別。
 避免使用除“-n”以外的任何指令列選項。  
避免在字串中使用轉義序列,因為根據 shell 不同,計算後的結果也不一樣。
| ![[注意]](images/note.png)  | 注意 | 
|---|---|
| 儘管“ | 
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 如果你想要在輸出字串中嵌入轉義序列,用 " | 
特殊的 shell 參數經常在 shell 腳本里面被用到。
表格 12.2. shell 參數列表
| shell 參數 | 值 | 
|---|---|
| $0 | shell 或 shell 指令碼的名稱 | 
| $1 | 第一個 shell 參數 | 
| $9 | 第 9 個 shell 參數 | 
| $# | 位置參數數量 | 
| "$*" | "$1 $2 $3 $4 … " | 
| "$@" | "$1" "$2" "$3" "$4" … | 
| $? | 最近一次指令的退出狀態碼 | 
| $$ | 這個 shell 指令碼的 PID | 
| $! | 最近開始的後臺任務 PID | 
如下所示是需要記憶的基本的參數展開。
表格 12.3. shell 參數展開列表
| 參數表示式形式 | 如果 var變數已設定那麼值為 | 如果 var變數沒有被設定那麼值為 | 
|---|---|---|
| ${var:-string} | " $var" | " string" | 
| ${var:+string} | " string" | " null" | 
| ${var:=string} | " $var" | " string" (並執行 "var=string") | 
| ${var:?string} | " $var" | 在 stderr 中顯示 " string"
(出錯退出) | 
以上這些操作中 ":" 實際上都是可選的。
有 ":" 等於測試的 var
值是存在且非空 
沒有 ":" 等於測試的 var
值只是存在的,可以為空 
表格 12.4. 重要的 shell 參數替換列表
| 參數替換形式 | 結果 | 
|---|---|
| ${var%suffix} | 刪除位於 var 結尾的 suffix 最小匹配模式 | 
| ${var%%suffix} | 刪除位於 var 結尾的 suffix 最大匹配模式 | 
| ${var#prefix} | 刪除位於 var 開頭的 prefix 最小匹配模式 | 
| ${var##prefix} | 刪除位於 var 開頭的 prefix 最大匹配模式 | 
每個指令都會回傳 退出狀態,這可以被條件語句使用。
成功:0 ("True")
失敗:非0 ("False")
| ![[注意]](images/note.png)  | 注意 | 
|---|---|
| "0" 在 shell 條件語句中的意思是 "True",然而 "0" 在 C 條件語句中的含義為 "False"。 | 
| ![[注意]](images/note.png)  | 注意 | 
|---|---|
| " | 
如下所示是需要記憶的基礎 條件語法。
 "command &&
if_success_run_this_command_too || true" 
 "command ||
if_not_success_run_this_command_too || true" 
如下所示是多行指令碼片段
if [ conditional_expression ]; then if_success_run_this_command else if_not_success_run_this_command fi
這裡末尾的“|| true”是需要的,它可以保證這個 shell
指令碼在不小心使用了“-e”選項而被呼叫時不會在該行意外地退出。
表格 12.5. 在條件表示式中進行檔案比較
| 表示式 | 回傳邏輯真所需的條件 | 
|---|---|
| -e file | file 存在 | 
| -d file | file 存在並且是一個目錄 | 
| -f file | file 存在並且是一個普通檔案 | 
| -w file | file 存在並且可寫 | 
| -x file | file 存在並且可執行 | 
| file1 -nt file2 | file1 是否比 file2 新 | 
| file1 -ot file2 | file1 是否比 file2 舊 | 
| file1 -ef file2 | file1 和 file2 位於相同的裝置上並且有相同的 inode 編號 | 
表格 12.6. 在條件表示式中進行字串比較
| 表示式 | 回傳邏輯真所需的條件 | 
|---|---|
| -z str | str 的長度為零 | 
| -n str | str 的長度不為零 | 
| str1 = str2 | str1 和 str2 相等 | 
| str1 != str2 | str1 和 str2 不相等 | 
| str1 < str2 | str1 排列在 str2 之前(取決於語言環境) | 
| str1 > str2 | str1 排列在 str2 之後(取決於語言環境) | 
算術整數的比較在條件表示式中為
"-eq","-ne","-lt","-le","-gt"
和 "-ge"。
這裡有幾種可用於 POSIX shell 的迴圈形式。
 "for x in foo1 foo2 … ; do command ; done",該迴圈會將
"foo1 foo2 …" 賦予變數 "x" 並執行
"command"。  
 "while condition ; do command ; done",當
"condition" 為真時,會重複執行 "command"。  
 "until condition ; do command ; done",當
"condition" 為假時,會重複執行 "command"。  
 "break" 可以用來退出迴圈。  
 "continue" 可以用來重新開始下一次迴圈。  
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| C 語言中的數值迭代可以用  | 
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
普通的 shell 命令列提示下的一些常見的環境變數,可能在你的指令碼的執行環境中不存在。
 對於 "$USER", 使用 "$(id -un)"  
 對於 "$UID", 使用 "$(id -u)"  
 對於 "$HOME",使用"$(getent passwd "$(id -u)"|cut -d
":" -f 6)" (這個也在 節 4.5.2, “現代的集中式系統管理” 下工作) 
shell 大致以下列的順序來處理一個指令碼。
shell 讀取一行。
 如果該行包含有"…" 或 '…',shell 對該行各部分進行分組作為
一個標識(one token) (譯註:one token 是指 shell
識別的一個結構單元).  
shell 通過下列方式將行中的其它部分分隔進 標識(tokens)。
 空白字元:空格 tab
換行符 
 元字元:< > | ; & ( ) 
 shell 會檢查每一個不位於 "…" 或 '...' 的 token 中的
保留字 來調整它的行為。  
保留字:if then elif else fi for in
while unless do done case esac
 shell 展開不位於 "…" 或 '...' 中的 別名。  
 shell 展開不位於 "…" 或 '...' 中的 波浪線。  
 "~" → 當前使用者的家目錄 
 "~user" →
user 的家目錄 
 shell 將不位於 '...' 中的 變數
展開為它的值。  
變數:"$PARAMETER" 或
"${PARAMETER}" 
 shell 展開不位於 '...' 中的 指令替換。  
 "$( command )" → "command" 的輸出 
 "` command `" → "command" 的輸出 
 shell 將不位於 "…" 或 '...' 中的 glob 路徑 展開為匹配的檔名。  
* → 任何字元 
? → 一個字元 
[…] → 任何位於 "…" 中的字元 
shell 從下列幾方面查詢 指令 並執行。
函式 定義
內建指令
“$PATH” 中的可執行檔案 
shell 前往下一行,並按照這個順序從頭再次進行處理。
雙引號中的單引號是沒有效果的。
在 shell 中執行 “set -x” 或使用 “-x” 選項啟動
shell 可以讓 shell 顯示出所有執行的指令。這對除錯來說是非常方便的。
為了使你的 shell 程式在 Debian 系統上儘可能地具有可移植性,你應該只使用 必要的 軟體包所提供的應用程式。
 "aptitude search ~E",列出 必要的 軟體包。  
 "dpkg -L package_name |grep
'/man/man.*/'",列出
package_name 軟體包所提供的 man 手冊。  
表格 12.7. 包含用於 shell 指令碼的小型應用程式的軟體包
| 軟體包 | 流行度 | 大小 | 說明 | 
|---|---|---|---|
| dash | V:884, I:997 | 191 | 小和快的 POSIX 相容 shell,用於 sh | 
| coreutils | V:880, I:999 | 18307 | GNU 核心工具 | 
| grep | V:782, I:999 | 1266 | GNU grep、egrep和fgrep | 
| sed | V:790, I:999 | 987 | GNU sed | 
| mawk | V:442, I:997 | 285 | 小和快的 awk | 
| debianutils | V:907, I:999 | 224 | 用於 Debian 的各種工具 | 
| bsdutils | V:519, I:999 | 356 | 來自 4.4BSD-Lite 的基礎工具 | 
| bsdextrautils | V:596, I:713 | 339 | 來自 4.4BSD-Lite 的額外的工具 | 
| moreutils | V:15, I:38 | 231 | 額外的 Unix 工具 | 
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 儘管  | 
參見 節 1.6, “類 Unix 的文本處理” 的例子。
表格 12.8. 直譯器相關軟體包列表
| 軟體包 | 流行度 | 大小 | 包 | 
|---|---|---|---|
| dash | V:884, I:997 | 191 | sh: 小和快的 POSIX 相容的 shell,用於 sh | 
| bash | V:838, I:999 | 7175 | sh: 由 bash-doc包提供的“info bash” | 
| mawk | V:442, I:997 | 285 | AWK: 小和快的 awk | 
| gawk | V:285, I:349 | 2906 | AWK: 由 gawk-doc包提供的“info gawk” | 
| perl | V:707, I:989 | 673 | Perl: perl(1) 以及透過perl-doc和perl-doc-html提供的 html 文件 | 
| libterm-readline-gnu-perl | V:2, I:29 | 380 | GNU ReadLine/History 庫的 Perl 擴充套件: perlsh(1) | 
| libreply-perl | V:0, I:0 | 171 | Perl 的 REPL : reply(1) | 
| libdevel-repl-perl | V:0, I:0 | 237 | Perl 的 REPL : re.pl(1) | 
| python3 | V:718, I:953 | 81 | Python: python3(1) 以及透過python3-doc包提供的 html 文件 | 
| tcl | V:25, I:218 | 21 | Tcl: tcl(3) 以及透過tcl-doc包提供的更詳細的手冊頁文件 | 
| tk | V:20, I:211 | 21 | Tk: tk(3) 以及透過tk-doc包提供的更詳細的手冊頁文件 | 
| ruby | V:86, I:208 | 29 | Ruby: ruby(1),erb(1),irb(1),rdoc(1),ri(1) | 
當你希望在 Debian 上自動化執行一個任務,你應當首先使用解釋性語言指令碼。選擇解釋性語言的準則是:
 使用 dash,如果任務是簡單的,使用 shell 程式聯合 CLI 命令列程式。 
 使用 python3,如果任務不是簡單的,你從零開始寫。 
 使用
perl、tcl、ruby……,如果在
Debian 上有用這些語言寫的現存程式碼,需要為完成任務進行調整。 
如果最終程式碼太慢,為提升執行速度,你可以用編譯型語言重寫關鍵部分,從解釋性語言呼叫。
大部分直譯器提供基本的語法檢查和程式碼跟蹤功能。
“dash -n script.sh” - Shell 指令碼語法檢查
“dash -x script.sh” - 跟蹤一個 Shell 指令碼
“python -m py_compile script.py” - Python 指令碼語法檢查
“python -mtrace --trace script.py” - 跟蹤一個 Python 指令碼
“perl -I ../libpath -c script.pl” - Perl 指令碼語法檢查
“perl -d:Trace script.pl” - 跟蹤一個 Perl 指令碼
為測試 dash 程式碼,嘗試下 節 9.1.4, “Readline 封裝”,它提供了和 bash 類似的互動式環境。
為了測試 perl 程式碼,嘗試下 Perl 的 REPL 環境,它為 Perl 提供了 Python 類似的
REPL (=READ + EVAL + PRINT +
LOOP) 環境。 
shell 指令碼能夠被改進用來製作一個吸引人的 GUI(圖形使用者介面)程式。技巧是用一個所謂的對話程式來代替使用
echo 和 read 命令的乏味互動。
表格 12.9. 對話(dialog )程式列表
| 軟體包 | 流行度 | 大小 | 說明 | 
|---|---|---|---|
| x11-utils | V:192, I:566 | 651 | xmessage(1):在一個視窗中顯示一條訊息或疑問(X) | 
| whiptail | V:284, I:996 | 56 | 從 shell 指令碼中顯示使用者友好的對話方塊(newt) | 
| dialog | V:11, I:99 | 1227 | 從 shell 指令碼中顯示使用者友好的對話方塊(ncurses) | 
| zenity | V:76, I:363 | 183 | 從 shell 指令碼中顯示圖形對話方塊(GTK) | 
| ssft | V:0, I:0 | 75 | Shell 指令碼前端工具 (zenity, kdialog, and 帶有 gettext 的 dialog 封裝) | 
| gettext | V:56, I:259 | 5818 | “ /usr/bin/gettext.sh”:翻譯資訊 | 
這裡是一個用來演示的 GUI 程式的例子,僅使用一個 shell 指令碼是多麼容易。
這個指令碼使用 zenity 來選擇一個檔案 (預設 /etc/motd)
並顯示它。
這個指令碼的 GUI 啟動器能夠按 節 9.4.10, “從 GUI 啟動一個程式” 建立。
#!/bin/sh -e # Copyright (C) 2021 Osamu Aoki <[email protected]>, Public Domain # vim:set sw=2 sts=2 et: DATA_FILE=$(zenity --file-selection --filename="/etc/motd" --title="Select a file to check") || \ ( echo "E: File selection error" >&2 ; exit 1 ) # Check size of archive if ( file -ib "$DATA_FILE" | grep -qe '^text/' ) ; then zenity --info --title="Check file: $DATA_FILE" --width 640 --height 400 \ --text="$(head -n 20 "$DATA_FILE")" else zenity --info --title="Check file: $DATA_FILE" --width 640 --height 400 \ --text="The data is MIME=$(file -ib "$DATA_FILE")" fi
這種使用 shell 指令碼的 GUI 程式方案只對簡單選擇的場景有用。如果你寫一個其它任何複雜的程式,請考慮在功能更強的平臺上寫。
GUI(圖形使用者介面)檔案管理器在選定的檔案上,能夠用外加的擴充套件軟體包來擴充套件執行一些常見行為。透過增加特定的指令碼,它們也能夠用來定製執行非常特殊的行為。
對於 GNOME,參見 NautilusScriptsHowto。
對於 KDE,參見 Creating Dolphin Service Menus。
對於 Xfce,參見 Thunar - Custom Actions 和 https://help.ubuntu.com/community/ThunarCustomActions。
對於 LXDE,參見 Custom Actions。
為了處理資料,sh 需要生成子程序執行
cut、grep、 sed
等,是慢的。從另外一個方面,perl 有內部處理資料能力,是快的。所以 Debian 上的許多系統維護指令碼使用
perl。
讓我們考慮下面一行 AWK 指令碼片段和它在 Perl 中的等價物。
awk '($2=="1957") { print $3 }' |
          這等價於下列的任意一行。
perl -ne '@f=split; if ($f[1] eq "1957") { print "$f[2]\n"}' |
          perl -ne 'if ((@f=split)[1] eq "1957") { print "$f[2]\n"}' |
          perl -ne '@f=split; print $f[2] if ( $f[1]==1957 )' |
perl -lane 'print $F[2] if $F[1] eq "1957"' |
perl -lane 'print$F[2]if$F[1]eq+1957' |
最後一個簡直就是個迷。它用上了下面列出的這些 Perl 的特性。
空格為可選項。
存在從數字到字串的自動轉換。
 透過命令列選項: perlrun(1) 的 Perl 執行技巧 
 Perl 特異變數:perlvar(1) 
靈活性是 Perl 的強項。與此同時,這允許我們建立令人困惑和繁亂的程式碼。所以請小心。
表格 12.10. 編譯相關軟體包列表
| 軟體包 | 流行度 | 大小 | 說明 | 
|---|---|---|---|
| gcc | V:167, I:550 | 36 | GNU C 編譯器 | 
| libc6-dev | V:248, I:567 | 12053 | GNU C 庫:開發庫和標頭檔案 | 
| g++ | V:56, I:501 | 13 | GNU C++ 編譯器 | 
| libstdc++-10-dev | V:14, I:165 | 17537 | GNU 標準 C++ 庫 版本 3(開發檔案) | 
| cpp | V:334, I:727 | 18 | GNU C 預處理 | 
| gettext | V:56, I:259 | 5818 | GNU 國際化工具 | 
| glade | V:0, I:5 | 1204 | GTK 使用者介面構建器 | 
| valac | V:0, I:4 | 725 | 使用 GObject 系統類似 C# 的語言 | 
| flex | V:7, I:73 | 1243 | LEX 相容的 fast lexical analyzer generator | 
| bison | V:7, I:80 | 3116 | YACC 相容的 解析器生成器 | 
| susv2 | I:0 | 16 | 通過“單一UNIX規範(版本2)”獲得(英語文檔) | 
| susv3 | I:0 | 16 | 通過“單一UNIX規範(版本3)”獲得(英語文檔) | 
| susv4 | I:0 | 16 | 透過“單一UNIX規範(版本4)”獲取(英語文件) | 
| golang | I:20 | 11 | Go 程式語言編譯器 | 
| rustc | V:3, I:14 | 8860 | Rust 系統程式語言 | 
| haskell-platform | I:1 | 12 | 標準的 Haskell 庫和工具 | 
| gfortran | V:6, I:62 | 15 | GNU Fortran 95 編譯器 | 
| fpc | I:2 | 103 | 自由 Pascal | 
這裡,包括了 節 12.3.3, “Flex — 一個更好的 Lex” 和 節 12.3.4, “Bison — 一個更好的 Yacc”,用來說明 類似編譯器的程式怎樣用C 語言來編寫,是透過編譯高階描述到 C 語言。
你可以通過下列方法設定適當的環境來編譯使用 C 程式語言編寫的程式。
# apt-get install glibc-doc manpages-dev libc6-dev gcc build-essential
libc6-dev 軟體包,即 GNU C 庫,提供了 C 標準庫,它包含了 C 程式語言所使用的標頭檔案和庫例程。
參考資訊如下。
 “info libc”(C 庫函式參考) 
 gcc(1) 和 “info gcc” 
 each_C_library_function_name(3) 
Kernighan & Ritchie,“C 程式設計語言”,第二版(Prentice Hall)
一個簡單的例子 “example.c” 可以通過如下方式和 “libm”
庫一起編譯為可執行程式 “run_example”。
$ cat > example.c << EOF
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(int argc, char **argv, char **envp){
        double x;
        char y[11];
        x=sqrt(argc+7.5);
        strncpy(y, argv[0], 10); /* prevent buffer overflow */
        y[10] = '\0'; /* fill to make sure string ends with '\0' */
        printf("%5i, %5.3f, %10s, %10s\n", argc, x, y, argv[1]);
        return 0;
}
EOF
$ gcc -Wall -g -o run_example example.c -lm
$ ./run_example
        1, 2.915, ./run_exam,     (null)
$ ./run_example 1234567890qwerty
        2, 3.082, ./run_exam, 1234567890qwerty
          為了使用 sqrt(3),必須使用 “-lm” 連結來自
libc6 軟體包的庫
“/usr/lib/libm.so”。實際的庫檔案位於
“/lib/”,檔名為 “libm.so.6”,它是指向
“libm-2.7.so” 的一個連結。
請看一下輸出文字的最後一段。即使指定了 “%10s”,它依舊超出了 10 個字元。
使用沒有邊界檢查的指標記憶體操作函式,比如 sprintf(3) 和
strcpy(3), 是不建議使用,是為防止快取溢位洩露而導致上面的溢位問題。請使用
snprintf(3) 和 strncpy(3) 來替代.
可以使用 “info flex” 檢視 flex(1) 的教程。
很多簡單的例子能夠在
“/usr/share/doc/flex/examples/”下發現。[7] 
在 Debian 裡,有幾個軟體包提供 Yacc相容的前瞻性的 LR 解析 或 LALR 解析的生成器。
可以使用 “info bison” 檢視 bison(1) 的教學。
你需要提供你自己的的 "main()" 和
"yyerror()".通常,Flex 建立的 "main()" 呼叫
"yyparse()",它又呼叫了 "yylex()".
這裡是一個建立簡單終端計算程式的例子。
讓我們建立 example.y:
/* calculator source for bison */
%{
#include <stdio.h>
extern int yylex(void);
extern int yyerror(char *);
%}
/* declare tokens */
%token NUMBER
%token OP_ADD OP_SUB OP_MUL OP_RGT OP_LFT OP_EQU
%%
calc:
 | calc exp OP_EQU    { printf("Y: RESULT = %d\n", $2); }
 ;
exp: factor
 | exp OP_ADD factor  { $$ = $1 + $3; }
 | exp OP_SUB factor  { $$ = $1 - $3; }
 ;
factor: term
 | factor OP_MUL term { $$ = $1 * $3; }
 ;
term: NUMBER
 | OP_LFT exp OP_RGT  { $$ = $2; }
  ;
%%
int main(int argc, char **argv)
{
  yyparse();
}
int yyerror(char *s)
{
  fprintf(stderr, "error: '%s'\n", s);
}
          讓我們建立 example.l:
/* calculator source for flex */
%{
#include "example.tab.h"
%}
%%
[0-9]+ { printf("L: NUMBER = %s\n", yytext); yylval = atoi(yytext); return NUMBER; }
"+"    { printf("L: OP_ADD\n"); return OP_ADD; }
"-"    { printf("L: OP_SUB\n"); return OP_SUB; }
"*"    { printf("L: OP_MUL\n"); return OP_MUL; }
"("    { printf("L: OP_LFT\n"); return OP_LFT; }
")"    { printf("L: OP_RGT\n"); return OP_RGT; }
"="    { printf("L: OP_EQU\n"); return OP_EQU; }
"exit" { printf("L: exit\n");   return YYEOF; } /* YYEOF = 0 */
.      { /* ignore all other */ }
%%
          按下面的方法來從 shell 提示符執行來嘗試這個:
$ bison -d example.y $ flex example.l $ gcc -lfl example.tab.c lex.yy.c -o example $ ./example 1 + 2 * ( 3 + 1 ) = L: NUMBER = 1 L: OP_ADD L: NUMBER = 2 L: OP_MUL L: OP_LFT L: NUMBER = 3 L: OP_ADD L: NUMBER = 1 L: OP_RGT L: OP_EQU Y: RESULT = 9 exit L: exit
類似 Indent 的工具能夠幫助人進行程式碼檢查,透過一致性的重新格式化原始碼。
類似 Ctags 的工具能夠幫助人進行程式碼檢查,透過利用原始碼中發現的名字生成 索引(或標籤)檔案。
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 配置你喜歡的編輯器( | 
表格 12.12. 靜態程式碼分析工具的列表
| 軟體包 | 流行度 | 大小 | 說明 | 
|---|---|---|---|
| vim-ale | I:0 | 2591 | 用於 Vim 8 和 NeoVim 的非同步 Lint 引擎 | 
| vim-syntastic | I:3 | 1379 | vim 語法檢查利器 | 
| elpa-flycheck | V:0, I:1 | 808 | Emacs 現代即時語法檢查 | 
| elpa-relint | V:0, I:0 | 147 | Emacs Lisp 正則錯誤發現器 | 
| cppcheck-gui | V:0, I:1 | 7224 | 靜態 C/C++ 程式碼分析工具(GUI) | 
| shellcheck | V:2, I:13 | 18987 | shell 指令碼的 lint 工具 | 
| pyflakes3 | V:2, I:15 | 20 | Python 3 程式被動檢查器 | 
| pylint | V:4, I:20 | 2018 | Python 程式碼靜態檢查器 | 
| perl | V:707, I:989 | 673 | 帶有內部靜態程式碼檢測的直譯器: B::Lint(3perl) | 
| rubocop | V:0, I:0 | 3247 | Ruby 靜態程式碼分析器 | 
| clang-tidy | V:2, I:11 | 21 | 基於 clang 的 C++ 規則格式檢查工具 | 
| splint | V:0, I:2 | 2320 | 靜態檢查 C 程式 bug 的工具 | 
| flawfinder | V:0, I:0 | 205 | 檢查 C/C++ 原始碼和查詢安全漏洞的工具 | 
| black | V:3, I:13 | 660 | 強硬的 Python 程式碼格式化器 | 
| perltidy | V:0, I:4 | 2493 | Perl 指令碼縮排和重新格式化 | 
| indent | V:0, I:7 | 431 | C 語言原始碼格式化程式 | 
| astyle | V:0, I:2 | 785 | C、 C++、 Objective-C、 C# 和 Java 的原始碼縮排器 | 
| bcpp | V:0, I:0 | 111 | 美化 C(++) | 
| xmlindent | V:0, I:1 | 53 | XML 流 重新格式化 | 
| global | V:0, I:2 | 1908 | 原始碼檢索和瀏覽工具 | 
| exuberant-ctags | V:2, I:20 | 341 | 構建原始碼定義的標籤檔案索引 | 
| universal-ctags | V:1, I:11 | 3386 | 構建原始碼定義的標籤檔案索引 | 
除錯是程式中很重要的一部分。知道怎樣去除錯程式,能夠讓你成為一個好的 Debian 使用者, 能夠做出有意義的錯誤報告。
Debian 上原始的偵錯程式是 gdb(1),
它能讓你在程式執行的時候檢查程式。
讓我們通過如下所示的指令來安裝 gdb 及其相關程式。
# apt-get install gdb gdb-doc build-essential devscripts
好的 gdb 教程能夠被發現:
 “info gdb” 
 在 /usr/share/doc/gdb-doc/html/gdb/index.html 的 “Debugging
with GDB”  
這裡是一個簡單的列子,用 gdb(1) 在"程式"帶有
"-g" 選項編譯的時候來產生除錯資訊。
$ gdb program (gdb) b 1 # set break point at line 1 (gdb) run args # run program with args (gdb) next # next line ... (gdb) step # step forward ... (gdb) p parm # print parm ... (gdb) p parm=12 # set value to 12 ... (gdb) quit
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 許多  | 
Debian 系統在預設情況下,所有安裝的二進位制程式會被 stripped,因此大部分除錯符號(debugging
symbols)在通常的軟體包裡面會被移除。為了使用 gdb(1) 除錯 Debian 軟體包,
*-dbgsym 軟體包需要被安裝。(例如,安裝
coreutils-dbgsym,用於除錯coreutils)原始碼軟體包和普通的二進位制軟體包一起自動生成
*-dbgsym 軟體包。那些除錯軟體包將被獨立放在 debian-debug 檔案庫。更多資訊請參閱 Debian Wiki 文件 。
如果一個需要被除錯的軟體包沒有提供其 *-dbgsym 軟體包,你需要按如下所示的從原始碼中重構並且安裝它。
$ mkdir /path/new ; cd /path/new $ sudo apt-get update $ sudo apt-get dist-upgrade $ sudo apt-get install fakeroot devscripts build-essential $ apt-get source package_name $ cd package_name* $ sudo apt-get build-dep ./
按需修改 bug。
軟體包除錯版本跟它的官方 Debian 版本不衝突,例如當重新編譯已存在的軟體包版本產生的 "+debug1"
字尾,如下所示是編譯未發行的軟體包版本產生的 "~pre1" 字尾。
$ dch -i
如下所示編譯並安裝帶有除錯符號的軟體包。
$ export DEB_BUILD_OPTIONS="nostrip noopt" $ debuild $ cd .. $ sudo debi package_name*.changes
你需要檢查軟體包的構建指令碼並確保編譯二進位制的時候使用了 "CFLAGS=-g -Wall" 選項。
當你碰到程式崩潰的時候,報告 bug 時附上棧幀資訊是個不錯的注意。
使用如下方案之一,可以透過 gdb(1) 取得棧幀資訊:
在 GDB 中崩潰的方案:
從 GDB 執行程式。
崩潰程式。
在 GDB 提示符輸入 "bt"。
先奔潰的方案:
對於無限迴圈或者鍵盤凍結的情況,你可以透過按 Ctrl-\ 或 Ctrl-C
或者執行 “kill -ABRT PID” 強制奔潰程式。(參見
節 9.4.12, “殺死一個程序”)
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 通常,你會看到堆疊頂部有一行或者多行有 " $ MALLOC_CHECK_=2 gdb hello | 
表格 12.14. 高階 gdb 指令列表
| 指令 | 指令用途的描述 | 
|---|---|
| (gdb) thread apply all bt | 得到多執行緒程式的所有執行緒棧幀 | 
| (gdb) bt full | 檢視函式呼叫棧中的參數資訊 | 
| (gdb) thread apply all bt full | 和前面的選項一起得到堆疊和參數 | 
| (gdb) thread apply all bt full 10 | 得到前10個呼叫的棧幀和參數資訊,以此來去除不相關的輸出 | 
| (gdb) set logging on | 把 gdb的日誌輸出到檔案 (預設的是 "gdb.txt") | 
按如下所示使用 ldd(1) 來找出程式的庫依賴性。
$ ldd /usr/bin/ls
        librt.so.1 => /lib/librt.so.1 (0x4001e000)
        libc.so.6 => /lib/libc.so.6 (0x40030000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x40153000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
          因為 ls(1) 執行在 `chroot`ed 環境,以上的庫在 `chroot`ed 環境也必須是可用的。
在 Debian 中,有幾個動態呼叫跟蹤工具存在。參見 節 9.4, “監控、控制和啟動程式活動”。
如果一個 GNOME 程式 preview1 收到了一個 X 錯誤,您應當看見一條下面這樣的資訊。
The program 'preview1' received an X Window System error.
如果就是這種情況,你可以嘗試在執行程式的時候加上 "--sync" 選項,並且在
"gdk_x_error" 函式處設定中斷來獲得棧幀資訊。
Debian 上有一些可用的記憶體洩漏檢測工具。
表格 12.15. 記憶體洩漏檢測工具的列表
| 軟體包 | 流行度 | 大小 | 說明 | 
|---|---|---|---|
| libc6-dev | V:248, I:567 | 12053 | mtrace(1):除錯 glibc 中的 malloc | 
| valgrind | V:6, I:37 | 78191 | 記憶體偵錯程式和分析器 | 
| electric-fence | V:0, I:3 | 73 | malloc(e) 偵錯程式 | 
| libdmalloc5 | V:0, I:2 | 390 | 記憶體分配庫除錯 | 
| duma | V:0, I:0 | 296 | 在 C 和 C++ 程式中檢測快取溢位和快取欠載( buffer under-runs )的庫 | 
| leaktracer | V:0, I:1 | 56 | C++ 程式記憶體洩露跟蹤器 | 
表格 12.16. 編譯工具軟體包列表
| 軟體包 | 流行度 | 大小 | 包 | 
|---|---|---|---|
| make | V:151, I:555 | 1592 | 通過 make-doc包提供的“info make” | 
| autoconf | V:31, I:230 | 2025 | 由 autoconf-doc包提供的“info autoconf” | 
| automake | V:30, I:228 | 1837 | 由 automake1.10-doc包提供的“info automake” | 
| libtool | V:25, I:212 | 1213 | 由 libtool-doc包提供"info libtool" | 
| cmake | V:17, I:115 | 36607 | cmake(1) 跨平臺、開源的編譯系統 | 
| ninja-build | V:6, I:41 | 428 | ninja(1) 接近 Make 精髓的小編譯系統 | 
| meson | V:3, I:22 | 3759 | meson(1) 在ninja之上的高生產力的構建系統 | 
| xutils-dev | V:0, I:9 | 1484 | imake(1),xmkmf(1) 等。 | 
Make 是一個維護程式組的工具。一旦執行
make(1),make 會讀取規則檔案
Makefile,自從上次目標檔案被修改後,如果目標檔案依賴的相關檔案發生了改變,那麼就會更新目標檔案,或者目標檔案不存在,那麼這些檔案更新可能會同時發生。
規則檔案的語法如下所示。
target: [ prerequisites ... ] [TAB] command1 [TAB] -command2 # ignore errors [TAB] @command3 # suppress echoing
這裡面的 "[TAB]" 是一個 TAB 程式碼。每一行在進行變數替換以後會被 shell 解釋。在行末使用
"\" 來繼續此指令碼。使用 "$$" 輸入
"$" 來獲得 shell 指令碼中的環境變數值。
目標跟相關檔案也可以通過隱式規則給出,例如,如下所示。
%.o: %.c header.h
在這裡,目標包含了 "%" 字元 (只是它們中確切的某一個)。"%"
字元能夠匹配實際的目標檔案中任意一個非空的子串。相關檔案同樣使用 "%" 來表明它們是怎樣與目標檔案建立聯絡的。
執行 "make -p -f/dev/null" 指令來檢視內部自動化的規則。
Autotools 是一套程式設計工具,旨在協助使原始碼包可移植到許多 類 Unix 系統。
| ![[警告]](images/warning.png)  | 警告 | 
|---|---|
| 當你安裝編譯好的程式的時候,注意不要覆蓋系統檔案。 | 
Debian 不會在 "/usr/local" 或 "/opt"
目錄下建立檔案。如果你想要原始碼編譯程式,把它安裝到 "/usr/local/" 目錄下,因為這並不會影響到
Debian。
$ cd src $ ./configure --prefix=/usr/local $ make # this compiles program $ sudo make install # this installs the files in the system
如果你有原始碼並且它使用
autoconf(1)/automake(1),如果你能記得你是怎樣調配它的話,執行如下的指令來解除安裝程式。
$ ./configure all-of-the-options-you-gave-it
$ sudo make uninstall
            或者,如果你十分確信安裝程序把檔案都放在了 "/usr/local/"
下並且這裡沒什麼重要的東西,你可以通過如下的指令來清除它所有的內容。
# find /usr/local -type f -print0 | xargs -0 rm -f
如果你不確定檔案被安裝到了哪裡,你可以考慮使用 checkinstall 軟體包中的
checkinstall(8),它將會提供一個清晰的解除安裝路徑。現在,它支援建立帶有
“-D” 選項的 Debian 軟體包。
基本的動態互動網頁可由如下方法制作。
呈現給瀏覽器使用者的是 HTML 形式。
填充並點選表單條目將會從瀏覽器向 web 伺服器傳送帶有編碼參數的下列 URL 字串之一。
 "https://www.foo.dom/cgi-bin/program.pl?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3" 
 "https://www.foo.dom/cgi-bin/program.py?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3" 
 "https://www.foo.dom/program.php?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3" 
 在 URL 裡面 "%nn" 是使用一個 16 進位制字元的 nn 值代替。  
 環境變數設定為: "QUERY_STRING="VAR1=VAL1 VAR2=VAL2 VAR3=VAL3"".  
Web伺服器上的CGI程式 (任何一個
"program.*")在執行時,都會使用"$QUERY_STRING"環境變數.  
CGI 程式的 stdout傳送到瀏覽器,作為互動式的動態 web 頁面展示。  
出於安全考慮,最好不要自己從頭編寫解析CGI參數的手藝. 在Perl和Python中有現有的模組可以使用. PHP 中包含這些功能. 當需要客戶端資料儲存時, 可使用HTTP cookies . 當需要處理客戶端資料時, 通常使用Javascript.
更多資訊,參見 通用閘道器介面, Apache 軟體基金會, 和 JavaScript.
直接在瀏覽器地址中輸入 https://www.google.com/search?hl=en&ie=UTF-8&q=CGI+tutorial 就可以在 Google 上搜索 “CGI tutorial”。這是在 Google 伺服器上檢視 CGI 指令碼執行的好方法。
如果你想製作一個 Debian 套件,閱讀下面內容。
章 2, Debian 軟體包管理 理解基本的套件管理系統
節 2.7.13, “移植一個軟體包到 stable 系統” 理解基本的移植過程
節 9.11.4, “Chroot 系統” 理解基本的 chroot 技術
debuild(1) 和 sbuild(1) 
Debian 維護者指引
(debmake-doc 包) 
Debian 開發者參考手冊
(developers-reference 包) 
Debian 策略手冊
(debian-policy 包) 
debmake, dh-make,
dh-make-perl 等軟體包,對軟體包打包過程,也有幫助。