第 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 等软件包,对软件包打包过程,也有帮助。