目录
这里我给出一些 Debian 系统中的信息,帮助学习编程的人找出打包的源代码。下面是值得关注的软件包和与之对应的文档。
安装 manpages
和 manpages-dev
包之后,可以通过运行“man 名称
”查看手册页中的参考信息。安装了 GNU
工具的相关文档包之后,可以通过运行“info 程序名称
”查看参考文档。某些 GFDL 协议的文档与 DFSG
并不兼容,所以你可能需要在 main
仓库中包含 contrib
和
non-free
才能下载并安装它们。
请考虑使用版本控制系统工具。参见 第 10.5 节 “Git”。
警告 | |
---|---|
不要用“ |
小心 | |
---|---|
你可以把从源代码编译得到的程序直接放到“ |
提示 | |
---|---|
“歌曲: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 不同,计算后的结果也不一样。
注意 | |
---|---|
尽管“ |
提示 | |
---|---|
如果你想要在输出字符串中嵌入转义序列,用 " |
特殊的 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")
注意 | |
---|---|
"0" 在 shell 条件语句中的意思是 "True",然而 "0" 在 C 条件语句中的含义为 "False"。 |
注意 | |
---|---|
" |
如下所示是需要记忆的基础 条件语法。
"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
" 可以用来重新开始下一次循环。
提示 | |
---|---|
C 语言中的数值迭代可以用 |
提示 | |
---|---|
普通的 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 工具 |
提示 | |
---|---|
尽管 |
参见 第 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 的工具能够帮助人进行代码检查,通过利用源代码中发现的名字生成 索引(或标签)文件。
提示 | |
---|---|
配置你喜欢的编辑器( |
表 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
提示 | |
---|---|
许多 |
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 节 “杀死一个进程”)
提示 | |
---|---|
通常,你会看到堆栈顶部有一行或者多行有 " $ 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 系统。
警告 | |
---|---|
当你安装编译好的程序的时候,注意不要覆盖系统文件。 |
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
等软件包,对软件包打包过程,也有帮助。