目录
这里我给出一些 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:883, I:997 | 191 | 小和快的 POSIX 兼容 shell,用于 sh | 
| coreutils | V:879, I:999 | 18307 | GNU 核心工具 | 
| grep | V:781, I:999 | 1266 | GNU grep、egrep和fgrep | 
| sed | V:787, I:999 | 987 | GNU sed | 
| mawk | V:437, I:997 | 285 | 小和快的 awk | 
| debianutils | V:907, I:999 | 223 | 用于 Debian 的各种工具 | 
| bsdutils | V:519, I:999 | 356 | 来自 4.4BSD-Lite 的基础工具 | 
| bsdextrautils | V:582, I:698 | 339 | 来自 4.4BSD-Lite 的额外的工具 | 
| moreutils | V:15, I:38 | 244 | 额外的 Unix 工具 | 
| ![[提示]](images/tip.png)  | 提示 | 
|---|---|
| 尽管  | 
参见 第 1.6 节 “类 Unix 的文本处理” 的例子。
表 12.8. 解释器相关软件包列表
| 软件包 | 流行度 | 大小 | 文档 | 
|---|---|---|---|
| dash | V:883, I:997 | 191 | sh: 小和快的 POSIX 兼容的 shell,用于 sh | 
| bash | V:837, I:999 | 7175 | sh: 由 bash-doc包提供的“info bash” | 
| mawk | V:437, I:997 | 285 | AWK: 小和快的 awk | 
| gawk | V:286, I:351 | 2906 | AWK: 由 gawk-doc包提供的“info gawk” | 
| perl | V:702, 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 | I:0 | 171 | Perl 的 REPL : reply(1) | 
| libdevel-repl-perl | V:0, I:0 | 237 | Perl 的 REPL : re.pl(1) | 
| python3 | V:714, I:951 | 81 | Python: python3(1) 以及通过python3-doc包提供的 html 文档 | 
| tcl | V:25, I:223 | 20 | Tcl: tcl(3) 以及通过tcl-doc包提供的更详细的手册页文档 | 
| tk | V:20, I:217 | 20 | Tk: tk(3) 以及通过tk-doc包提供的更详细的手册页文档 | 
| ruby | V:85, I:211 | 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:197, I:564 | 651 | xmessage(1):在一个窗口中显示一条消息或疑问(X) | 
| whiptail | V:274, I:996 | 56 | 从 shell 脚本中显示用户友好的对话框(newt) | 
| dialog | V:11, I:101 | 1227 | 从 shell 脚本中显示用户友好的对话框(ncurses) | 
| zenity | V:76, I:362 | 183 | 从 shell 脚本中显示图形对话框(GTK) | 
| ssft | V:0, I:0 | 75 | Shell 脚本前端工具 (zenity, kdialog, and 带有 gettext 的 dialog 封装) | 
| gettext | V:57, I:259 | 5817 | “ /usr/bin/gettext.sh”:翻译信息 | 
这里是一个用来演示的 GUI 程序的例子,仅使用一个 shell 脚本是多么容易。
这个脚本使用 zenity 来选择一个文件 (默认 /etc/motd)
并显示它。
这个脚本的 GUI 启动器能够按 第 9.4.10 节 “从 GUI 启动一个程序” 创建。
#!/bin/sh -e
# Copyright (C) 2021 Osamu Aoki <osamu@debian.org>, 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:166, I:551 | 47 | GNU C 编译器 | 
| libc6-dev | V:259, I:569 | 12051 | GNU C 库:开发库和头文件 | 
| g++ | V:53, I:501 | 14 | GNU C++ 编译器 | 
| libstdc++-10-dev | V:15, I:172 | 17537 | GNU 标准 C++ 库 版本 3(开发文件) | 
| cpp | V:331, I:727 | 30 | GNU C 预处理 | 
| gettext | V:57, I:259 | 5817 | GNU 国际化工具 | 
| glade | V:0, I:5 | 1209 | GTK 用户界面构建器 | 
| valac | V:0, I:4 | 724 | 使用 GObject 系统类似 C# 的语言 | 
| flex | V:7, I:74 | 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:63 | 16 | 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/”下发现。[8] 
在 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 $ ./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:2 | 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:12 | 18987 | shell 脚本的 lint 工具 | 
| pyflakes3 | V:2, I:15 | 20 | Python 3 程序被动检查器 | 
| pylint | V:4, I:19 | 2018 | Python 代码静态检查器 | 
| perl | V:702, 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 | 639 | 强硬的 Python 代码格式化器 | 
| perltidy | V:0, I:4 | 2493 | Perl 脚本缩进和重新格式化 | 
| indent | V:0, I:8 | 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 | 1895 | 源代码检索和浏览工具 | 
| 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:259, I:569 | 12051 | mtrace(1):调试 glibc 中的 malloc | 
| valgrind | V:5, I:36 | 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:152, I:556 | 1592 | 通过 make-doc包提供“info make” | 
| autoconf | V:31, I:232 | 2025 | 由 autoconf-doc包提供“info autoconf” | 
| automake | V:31, I:231 | 1837 | 由 automake1.10-doc包提供“info automake” | 
| libtool | V:26, I:215 | 1213 | 由 libtool-doc包提供"info libtool" | 
| cmake | V:16, I:115 | 36695 | cmake(1) 跨平台、开源的编译系统 | 
| ninja-build | V:6, I:40 | 428 | ninja(1) 接近 Make 精髓的小编译系统 | 
| meson | V:3, I:22 | 3741 | 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 参数的手艺. 在 Per l和 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 等软件包,对软件包打包过程,也有帮助。