第 12 章 編程

目录

12.1. Shell 腳本
12.1.1. POSIX shell 兼容性
12.1.2. Shell 引數
12.1.3. Shell 條件語句
12.1.4. shell 迴圈
12.1.5. shell 命令列的處理順序
12.1.6. 用於 shell 指令碼的應用程式
12.1.7. shell 指令碼對話方塊
12.1.8. zenity 的 shell 指令碼案例
12.2. make
12.3. C
12.3.1. 簡單的 C 程式(gcc)
12.4. 除錯
12.4.1. 基本的 gdb 使用命令
12.4.2. 除錯 Debian 軟體包
12.4.3. 獲得棧幀
12.4.4. 高階 gdb 命令
12.4.5. 除錯與 X 相關的錯誤
12.4.6. 檢查庫依賴性
12.4.7. 記憶體洩漏檢測工具
12.4.8. 靜態程式碼分析工具
12.4.9. 反彙編二進位制程式
12.5. Flex — 一個更好的Lex
12.6. Bison —— 一個更好的 Yacc
12.7. Autoconf
12.7.1. 編譯並安裝程式
12.7.2. 解除安裝程式
12.8. Perl 短指令碼的瘋狂
12.9. Web
12.10. 原始碼轉換
12.11. 製作 Debian 包

這裡我給出一些 Debian 系統中的資訊,幫助學習程式設計的人找出打包的原始碼。下面是值得關注的軟體包和與之對應的文件。

表 12.1. 幫助程式設計的軟體包清單

軟件包 流行度 大小
autoconf V:31, I:242 1868 autoconf-doc 包提供的“info autoconf
automake V:30, I:237 1710 automake1.10-doc 包提供的“info automake
bash V:846, I:999 5798 bash-doc 包提供的“info bash
bison V:10, I:110 2061 bison-doc 包提供的“info bison
cpp V:393, I:802 42 cpp-doc 包提供的“info cpp
ddd V:0, I:13 3965 ddd-doc 包提供的“info ddd
exuberant-ctags V:7, I:41 333 exuberant-ctags(1)
flex V:9, I:98 1174 flex-doc 包提供的“info flex
gawk V:384, I:494 2199 gawk-doc 包提供的“info gawk
gcc V:154, I:599 45 gcc-doc 包提供的“info gcc
gdb V:19, I:133 7983 gdb-doc 包提供的“info gdb
gettext V:67, I:360 6496 gettext-doc 包提供的“info gettext
gfortran V:7, I:64 16 gfortran-doc 包提供的“info gfortran”(Fortran 95)
fpc I:4 115 fpc(1) 和由 fp-docs 包提供的 html 文檔(Pascal)
glade V:0, I:10 2214 通過 UI Builder 菜單提供的文檔
libc6 V:933, I:999 10677 通過 glibc-docglibc-doc-reference 提供的“info libc
make V:152, I:607 1211 通過 make-doc 包提供的“info make
xutils-dev V:1, I:16 1466 imake(1)xmkmf(1) 等。
mawk V:365, I:997 183 mawk(1)
perl V:556, I:995 568 perl(1) 以及通過 perl-docperl-doc-html 提供的 html 文檔
python V:627, I:988 648 python(1) 以及通過 python-doc 包提供的 html 文檔
tcl V:32, I:434 21 tcl(3) 以及通過 tcl-doc 包提供的更詳細的手冊頁文件
tk V:33, I:422 21 tk(3) 以及通過 tk-doc 包提供的更詳細的手冊頁文件
ruby V:125, I:333 38 ruby(1) 以及通過 ri 包提供的交互式參考手冊
vim V:122, I:392 2470 通過 vim-doc 包提供的幫助(F1)菜單
susv2 I:0 15 通過“單一UNIX規範(版本2)”獲取(英語文檔)
susv3 I:0 15 通過“單一UNIX規範(版本3)”獲取(英語文檔)

安裝 manpagesmanpages-dev 包之後,可以通過運行“man 名稱”查看手冊頁中的參考信息。安裝了 GNU 工具的相關文檔包之後,可以通過運行“info 程序名稱”查看參考文檔。某些 GFDL 協議的文檔與 DFSG 並不兼容,所以你可能需要在 main 倉庫中包含 contribnon-free 才能下載並安裝它們。

[警告] 警告

不要用“test”作爲可執行的測試文件的名字,因爲 shell 中內建有“test”命令。

[小心] 小心

你可以把從源代碼編譯得到的程序直接放到“/usr/local”或“/opt”目錄,這樣可以避免與系統程序撞車。

[提示] 提示

“歌曲:99瓶啤酒”的代碼示例可以給你提供實踐各種語言的好範本。

Shell 腳本 是指包含有下面格式的可執行的文本文件。

#!/bin/sh
…… 命令

第一行指明瞭讀取並執行這個文件的 shell 解釋器。

讀懂 shell 腳本的最好 辦法是先理解類 UNIX 系統是如何工作的。這裏有一些 shell 編程的提示。看看“Shell 錯誤”(http://www.greenend.org.uk/rjk/2001/04/shell.html),可以從錯誤中學習。

不像 shell 交互模式(參見第 1.5 节 “簡單 shell 命令”第 1.6 节 “類 Unix 的文本處理”),shell 腳本會頻繁使用參數、條件和循環等。

每個命令都會返回 退出狀態,這可以被條件語句使用。

  • 成功:0 ("True")

  • 失敗:非0 ("False")

[注意] 注意

"0" 在 shell 條件語句中的意思是 "True",然而 "0" 在 C 條件語句中的含義為 "False"。

[注意] 注意

"[" 跟 test 命令是等價的,它評估到 "]" 之間的引數來作為一個條件表示式.

如下所示是需要記憶的基礎 條件語法

  • "<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”選項而被呼叫時不會在該行意外地退出。



算術整數的比較在條件表示式中為 "-eq","-ne","-lt","-le","-gt" 和 "-ge"。

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 顯示出所有執行的命令。這對除錯來說是非常方便的。

下面是一個簡單的指令碼,它通過 dvdisaster(1) 建立了帶有 RS02 補充資料的 ISO 映像。

#!/bin/sh -e
# gmkrs02 : Copyright (C) 2007 Osamu Aoki <osamu@debian.org>, Public Domain
#set -x
error_exit()
{
  echo "$1" >&2
  exit 1
}
# Initialize variables
DATA_ISO="$HOME/Desktop/iso-$$.img"
LABEL=$(date +%Y%m%d-%H%M%S-%Z)
if [ $# != 0 ] && [ -d "$1" ]; then
  DATA_SRC="$1"
else
  # Select directory for creating ISO image from folder on desktop
  DATA_SRC=$(zenity --file-selection --directory  \
    --title="Select the directory tree root to create ISO image") \
    || error_exit "Exit on directory selection"
fi
# Check size of archive
xterm -T "Check size $DATA_SRC" -e du -s $DATA_SRC/*
SIZE=$(($(du -s $DATA_SRC | awk '{print $1}')/1024))
if [ $SIZE -le 520 ] ; then
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is good for CD backup:\\n $SIZE MB"
elif [ $SIZE -le 3500 ]; then
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is good for DVD backup :\\n $SIZE MB"
else
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is too big to backup : $SIZE MB"
  error_exit "The data size is too big to backup :\\n $SIZE MB"
fi
# only xterm is sure to have working -e option
# Create raw ISO image
rm -f "$DATA_ISO" || true
xterm -T "genisoimage $DATA_ISO" \
  -e genisoimage -r -J -V "$LABEL" -o "$DATA_ISO" "$DATA_SRC"
# Create RS02 supplemental redundancy
xterm -T "dvdisaster $DATA_ISO" -e  dvdisaster -i "$DATA_ISO" -mRS02 -c
zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
  --text="ISO/RS02 data ($SIZE MB) \\n created at: $DATA_ISO"
# EOF

你可能想要在桌面建立一個啟動器,其中的命令設定為類似 “/usr/local/bin/gmkrs02 %d” 的形式。

Make 是一個維護程式組的工具。一旦執行 make(1)make 會讀取規則檔案 Makefile,自從上次目標檔案被修改後,如果目標檔案依賴的相關檔案發生了改變,那麼就會更新目標檔案,或者目標檔案不存在,那麼這些檔案更新可能會同時發生。

規則檔案的語法如下所示。

目標:[相關檔案 ...]
[TAB] 命令1
[TAB] -命令2 # 忽略錯誤
[TAB] @命令3 # 禁止回顯

這裡面的 "[TAB]" 是一個 TAB 程式碼。每一行在進行變數替換以後會被 shell 解釋。在行末使用 "\" 來繼續此指令碼。使用 "$$" 輸入 "$" 來獲得 shell 指令碼中的環境變數值。

目標跟相關檔案也可以通過隱式規則給出,例如,如下所示。

%.o: %.c header.h

在這裡,目標包含了 "%" 字元 (只是它們中確切的某一個)。"%" 字元能夠匹配實際的目標檔案中任意一個非空的子串。相關檔案同樣使用 "%" 來表明它們是怎樣與目標檔案建立聯絡的。



執行 "make -p -f/dev/null" 命令來檢視內部自動化的規則。

你可以通過下列方法設定適當的環境來編譯使用 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)

除錯是程式中很重要的一部分。知道怎樣去除錯程式使得作為 Debian 使用者的你, 能夠做出有意義的錯誤報告。

Debian 上原始的偵錯程式gdb(1), 它能讓你在程式執行的時候檢查程式。

讓我們通過如下所示的命令來安裝 gdb 及其相關程式。

# apt-get install gdb gdb-doc build-essential devscripts

gdb 的好的教程由 "info gdb" 提供或者可以在網上的其他地方找到。如下是用 gdb(1) 在"程式"帶有 "-g" 選項編譯的時候來產生除錯資訊。

$ gdb program
(gdb) b 1                # 在第一行設定斷點
(gdb) run args           # 帶引數執行程式
(gdb) next               # 執行下一步
...
(gdb) step               # 單步進入
...
(gdb) p parm             # 列印 parm 的值
...
(gdb) p parm=12          # 把值設為 12
...
(gdb) quit
[提示] 提示

許多 gdb(1) 命令都能被縮寫。Tab 擴充套件跟在 shell 一樣都能工作。

因為在 Debian 系統上預設所有已安裝的二進位制程式都是精簡的,絕大多數的除錯符號已經從常規的軟體包中移除了。為了能用 gdb(1) 除錯 Debian 軟體包,相對應的 *-dbg 軟體包或 *-dbgsym 軟體包需要被安裝 (例如 libc6 需要安裝 libc6-dbgcoreutils 需要安裝 coreutils-dbgsym)。

老式的軟體包將提供相應的 *-dbg 軟體包。它將和原始軟體包一起,直接放在 Debian main 檔案庫。對於新的軟體包,當它們編譯時,將會自動產生 *-dbgsym 軟體包,那些除錯軟體包將被獨立放在 debian-debug 檔案庫. 更多資訊請參閱 Debian Wiki 文件 .

如果一個需要被除錯的軟體包沒有提供其 *-dbg 軟體包或 *-dbgsym 軟體包,你需要按如下所示的從原始碼中重構並且安裝它。

$ mkdir /path/new ; cd /path/new
$ sudo apt-get update
$ sudo apt-get dist-upgrade
$ sudo apt-get install fakeroot devscripts build-essential
$ sudo apt-get build-dep source_package_name
$ apt-get source package_name
$ cd package_name*

按需修改 bug。

軟體包除錯版本跟它的官方 Debian 版本不衝突,例如當重新編譯已存在的軟體包版本產生的 "+debug1" 字尾,如下所示是編譯未發行的軟體包版本產生的 "~pre1" 字尾。

$ dch -i

如下所示編譯並安裝帶有除錯符號的軟體包。

$ export DEB_BUILD_OPTIONS=nostrip,noopt
$ debuild
$ cd ..
$ sudo debi package_name*.changes

你需要檢查軟體包的構建指令碼並確保編譯二進位制的時候使用了 "CFLAGS=-g -Wall" 選項。

Flex 是相容 Lex 的快速語法分析程式生成器。

可以使用 “info flex” 檢視 flex(1) 的教程。

你需要提供你自己的 "main()" 和 "yywrap()".否則,你的 flex 程式,看起來像這樣的,編譯的時候將不會帶庫。這是因為 "yywrap" 是一個巨集, "%option main" 隱性打開了 "%option noyywrap".

%option main
%%
.|\n    ECHO ;
%%

另外一種方法,在你的 cc(1) 命令列結尾,你可以使用編譯連結器選項,"-lfl"。(像使用 "-ll" 的 AT&T-Lex ). 在這種情況下,不需要 "%option".

在 Debian 裡,有幾個軟體包提供 Yacc相容的前瞻性的 LR 解析LALR 解析的生成器。


可以使用 “info bison” 檢視 bison(1) 的教程。

你需要提供你自己的的 "main()" 和 "yyerror()".通常,Flex 建立的 "main()" 呼叫 "yyparse()",它又呼叫了 "yylex()".

%%

%%

autoconf 是一種用於自動生成軟體原始碼包配置 shell 指令碼的工具,以適應使用完整 GNU 構建系統的各種類 Unix 系統。

autoconf(1) 生成配置指令碼 “configure”。“configure” 使用 “Makefile.in” 模板自動生成一個自定義的 “Makefile”。

雖然任何 AWK 指令碼都可以通過 a2p(1) 轉換成 Perl,但單行的 AWK 指令碼最好還是手動轉換為單行的 Perl 指令碼。

讓我們來看看下面這個 AWK 指令碼片段。

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 Golf

基本的動態互動網頁可由如下方法制作。

  • 呈現給瀏覽器使用者的是 HTML 形式。

  • 填充並點選表單條目將會從瀏覽器向 web 伺服器傳送帶有編碼引數的下列 URL 字串之一。

    • "http://www.foo.dom/cgi-bin/program.pl?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3"

    • "http://www.foo.dom/cgi-bin/program.py?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3"

    • "http://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.

直接在瀏覽器地址中輸入 http://www.google.com/search?hl=en&ie=UTF-8&q=CGI+tutorial 就可以在 Google 上搜索 “CGI tutorial”。這是在 Google 伺服器上檢視 CGI 指令碼執行的好方法。

原始碼轉換程式。


如果你想製作一個 Debian 包,閱讀下面內容。

debmake, dh-make, dh-make-perl 等軟體包,對軟體包打包過程,也有幫助。