不要用“test”命名可执行的测试文件。test是一个shell的内建命令。
参考资源:
/usr/share/doc/package下的文档和样例
Unix / Programming
Information
更详细的文档可以从GNU获得打印版本。
接下来的四个小节中包含了用不同的编程语言编写的脚本样例,该脚本创建一个包含用户帐户信息的文本文件,调用一组进程如newusers程序,将这些信息加入到/etc/passwd。每个脚本均需要一个输入文件,该文件应包含格式如first_name
last_name password的行。(这些脚本并不创建真正的用户目录。)
理解类Unix系统如何工作的最好方法就是阅读shell脚本。在此,我们就shell编程做个简单的介绍。
Bash参考资源:
bash(1)
BASH Programming -
Introduction HOWTO as starter information.
(安装bash-doc软件包阅读样例文件。)
一个简短的程序样例(从标准输入端创建帐户信息供newusers使用):
#!/bin/bash
# (C) Osamu Aoki Sun Aug 26 16:53:55 UTC 2001 Public Domain
pid=1000;
while read n1 n2 n3 ; do
if [ ${n1:0:1} != "#" ]; then
let pid=$pid+1
echo ${n1}_${n2}:password:${pid}:${pid}:,,,/home/${n1}_${n2}:/bin/bash
fi
done
Debian中有几个软件包提供了POSIX shell:
dash (Sarge)
ash (Woody)
bash
pdksh
如果你想编写具有通用性的shell脚本,最好写POSIX
shell脚本。可将/bin/sh链接到ash或(dash)来测试脚本的POSIX兼容性。避免用bash化或zsh化(它看起来与csh语法很相似)的思维去编写脚本。例如,应避免:
Several special parameters to remember:
$0 = shell名称或shel脚本名称
$1 = 第一个(1)shell参数
...
$9 = 第九个(9)shell参数
$# = 位置参数的个数
"$*" = "$1 $2 $3 $4 ... $n"
"$@" = "$1" "$2" "$3" "$4" ... "$n"
$? = 最近执行的命令的退出状态
$$ = 当前shell脚本的PID
$! = 最近启动的后台作业的PID
需要记住的基本扩展参数:
Form If var is set If var is not set
${var:-string} $var string
${var:+string} string null
${var:=string} $var string
(并且执行var=string)
${var:?string} $var (返回string然后退出)
在此,冒号“:”在所有运算表达式中事实上均是可选的。
需要记住的替换参数:
Form Result
${var%suffix} 删除位于var结尾的suffix最小匹配模式
${var%%suffix} 删除位于var结尾的suffix最大匹配模式
${var#prefix} 删除位于var开头的prefix最小匹配模式
${var##prefix} 删除位于var开头的prefix最大匹配模式
需要记住的基本重定向(redirection)运算符(在此[n]表示可选参数):
[n]> file 重定向标准输出(或 n)到file。
[n]>> file 重定向标准输出(或 n)到file。
[n]< file 将file重定向到标准输入(或 n)。
[n1]>&n2 重定向标准输出(或 n1)到n2。
> file >&2 重定向标准输出和错误输出到file。
| command 将标准输出通过管道传递给command。
>&2 | command 将标准输出或错误输出通过管道传递给command。
每条命令均可返回一个退出状态,这个状态值可用于条件表达式:
注意该用法,返回值0用来表示“true”与计算机其它领域中常见的转换是不同的。另外`['等阶于使用test命令进行参数赋值`]'相当于一个条件表达式。
需要记住的常用基本条件表达式:
command && if_success_run_this_command_too
command || if_not_success_run_this_command_too
if [ conditional_expression ]; then
if_success_run_this_command
else
if_not_success_run_this_command
fi
在条件表达式中使用的文件比较运算符有:
-e file file存在则返回True。
-d file file存在且是一个目录则返回True。
-f file 如果file存在且是一个普通文件则返回True。
-w file 如果file存在且可写则返回True。
-x file 如果file存在且可执行则返回True。
file1 -nt file2 如果file1比file2新则返回True。(指修改日期)
file1 -ot file2 如果file1比file2旧则返回True。(指修改日期)
file1 -ef file2 如果两者是相同的设备和具有相同的结点(inode)数则返回True。
条件表达式中使用的字符串比较运算符有:
-z str 如果str长度为零则返回True。
-n str 如果str长度为非零则返回True。
str1 == str2 如果字符串相等则返回True。
str1 = str2 如果字符串相等则返回True。
(使用"=="代替"="符合严格意义上的POSIX兼容)
str1 != str2 如果字符串不相等则返回True。
str1 < str2 如果str1排在str2之前则返回True(与当前位置有关)。
str1 > str2 如果str1排在str2之后则返回True(与当前位置有关)。
条件表达式中的算术整数比较运算符有-eq、-ne、-lt、-le、-gt和-ge。
shell按如下的方式处理脚本:
在双单号内单引号将失效。
Awk的参考资源:
mawk(1) and gawk(1)
简短的程序样例(创建newusers命令输入):
#!/usr/bin/awk -f
# Script to create a file suitable for use in the 'newusers' command,
# from a file consisting of user IDs and passwords in the form:
# first_name last_name password
# Copyright (c) KMSelf Sat Aug 25 20:47:38 PDT 2001
# Distributed under GNU GPL v 2, or at your option, any later version.
# This program is distributed WITHOUT ANY WARRANTY.
BEGIN {
# Assign starting UID, GID
if ( ARGC > 2 ) {
startuid = ARGV[1]
delete ARGV[1]
}
else {
printf( "Usage: newusers startUID file\n" \
" where:\n" \
" startUID is the starting userid to add, and\n" \
" file is an input file in form:\n" \
" first_name last_name password\n" \
)
exit
}
infile = ARGV[1]
printf( "Starting UID: %s\n\n", startuid )
}
/^#/ { next }
{
++record
first = $1
last = $2
passwd = $3
user= substr( tolower( first ), 1, 1 ) tolower( last )
uid = startuid + record - 1
gid = uid
printf( "%s:%s:%d:%d:%s %s,,/home/%s:/bin/bash\n", \
user, passwd, uid, gid, first, last, user \
)
}
Debian中有两个软件包提供了POSIX awk:
mawk
gawk
运行于类Unix系统上的解释器。
Perl参考资源:
perl(1)
简短的程序样例(创建newusers命令输入):
#!/usr/bin/perl
# (C) Osamu Aoki Sun Aug 26 16:53:55 UTC 2001 Public Domain
$pid=1000;
while (<STDIN>) {
if (/^#/) { next;}
chop;
$pid++;
($n1, $n2, $n3) = split / /;
print $n1,"_",$n2,":", $n3, ":",$pid,
":",$pid,",,,/home/",$n1,"_",$n2,":/bin/bash\n"
}
安装Perl模块module_name:
# perl -MCPAN -e 'install module_name'
一个不错的面向对象的解释器。
Python参考资源:
python(1)
简短的程序样例(创建newusers命令输入):
#! /usr/bin/env python
import sys, string
# (C) Osamu Aoki Sun Aug 26 16:53:55 UTC 2001 Public Domain
# Ported from awk script by KMSelf Sat Aug 25 20:47:38 PDT 2001
# This program is distributed WITHOUT ANY WARRANTY.
def usages():
print \
"Usage: ", sys.argv[0], " start_UID [filename]\n" \
"\tstartUID is the starting userid to add.\n" \
"\tfilename is input file name. If not specified, standard input.\n\n" \
"Input file format:\n"\
"\tfirst_name last_name password\n"
return 1
def parsefile(startuid):
#
# main filtering
#
uid = startuid
while 1:
line = infile.readline()
if not line:
break
if line[0] == '#':
continue
(first, last, passwd) = string.split(string.lower(line))
# above crashes with wrong # of parameters :-)
user = first[0] + last
gid = uid
lineout = "%s:%s:%d:%d:%s %s,,/home/%s:/bin/bash\n" % \
(user, passwd, uid, gid, first, last, user)
sys.stdout.write(lineout)
+uid
if __name__ == '__main__':
if len(sys.argv) == 1:
usages()
else:
uid = int(sys.argv[1])
#print "# UID start from: %d\n" % uid
if len(sys.argv) > 1:
infilename = string.join(sys.argv[2:])
infile = open(infilename, 'r')
#print "# Read file from: %s\n\n" % infilename
else:
infile = sys.stdin
parsefile(uid)
Make参考资源:
make(1)
简单自动变量:
语法规则:
target: [ prerequisites ... ]
[TAB] command1
[TAB] -command2 # ignore errors
[TAB] @command3 # suppress echoing
在此[TAB]代表一个TAB符。完成变量代换后shell将逐行进行解释。在行尾使用\可以续行。使用$$可将$加入到shell脚本的环境变量中。
适用于target和prerequisites的隐含的等价规则:
%: %.c header.h
or,
%.o: %.c header.h
在此,target包含了%字符(确切地说是其中之一),%可匹配实际的target文件名中任何非空子串。prerequisites同样也使用%来显示它们的名字是如何关联到实际的target文件名的。
用Suffix rules方法来定义make的隐含规则(implicit
rules)已经过时。GNU
make因为兼容性的考虑仍支持它,但只要有可能就应该使用与之等价的模版规则(pattern
rules):
old suffix rule --> new pattern rule
.c: --> % : %.c
.c.o: --> %.o: %.c
上述规则所使用的自动变量:
foo.o: new1.c new2.c old1.c new3.c
$@ == foo.o (target)
$< == new1.c (first one)
$? == new1.c new2.c new3.c (newer ones)
$^ == new1.c new2.c old1.c new3.c (all)
$* == `%' matched stem in the target pattern.
变量参考:
foo1 := bar # One-time expansion
foo2 = bar # Recursive expansion
foo3 += bar # Append
SRCS := $(wildcard *.c)
OBJS := $(foo:c=o)
OBJS := $(foo:%.c=%.o)
OBJS := $(patsubst %.c,%.o,$(foo))
DIRS = $(dir directory/filename.ext) # Extracts "directory"
$(notdir NAMES...), $(basename NAMES...), $(suffix NAMES...) ...
执行make -p -f/dev/null可查看内部自动规则。
准备工作:
# apt-get install glibc-doc manpages-dev libc6-dev gcc
C参考资源:
gcc(1)
each_C_library_function_name(3)
gcc)
一个简单的例子,将example.c和库函数libm编译成可执行文件run_example:
A simple example to compile example.c with a library
libm into an executable 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()链接库函数libm需要-lm选项。真正的库函数是位于/lib下的libm.so.6,它是libm-2.1.3.so的一个符号链接。
看看输出文本中最后的参数,尽管指定了%10s,它还是多于10个字符。
使用不带边界检查的指针内存操作函数如sprintf和strcpy会妨碍缓冲区溢出侦测,故使用snprintf和strncpy。
gdb进行调试准备工作:
# apt-get install gdb
gdb参考资源:
gdb(1)
http://www.unknownroad.com/rtfm/gdbtut/gdbtoc.html
使用-g选项编译程序就可使用gdb进行调试。许多命令都可以缩写。Tab扩展功能和在shell中的一样。
$ gdb program
(gdb) b 1 # 在line 1设置断点
(gdb) run arg1 arg2 arg3 # 运行程序
(gdb) next # 下一行
...
(gdb) step # 前进一步
...
(gdb) p parm # 打印parm
...
(gdb) p parm=12 # 设置其值为12
在Emacs环境下调试程序,参阅编辑器命令总汇(Emacs,Vim), 第 11.3.4 节。
使用ldd可查看程序与库函数的关联关系:
$ ldd /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)
可在chrooted环境下使用ls检查上述库函数在你的chroot环境中是否可见。
下面的命令也很有用:
strace: 跟踪系统调用和消息
ltrace: 跟踪库函数调用
Debian中有几个内存查漏工具。
njamd
valgrind
dmalloc
electric-fence
memprof
memwatch (not packaged, get this from memwatch.)
mpatrol
leaktracer
libgc6
Parasoft. (non-free, commercial for
fee)
亦可查阅Debugging
Tools for Dynamic Storage Allocation and Memory Management.
flex是一个快速的词法分析机生成器。
flex参考资源:
flex(1)
需要提供你自己的main()或yywrap(),或者你的program.l象这样不带library编译(yywrap是一个宏;%option
main隐含地打开了%option noyywrap):
%option main
%%
.|\n ECHO ;
%%
另外,还可以在cc命令行末尾加上-lfl链接选项进行编译(象AT&T-Lex使用-ll一样),此时就不需要%option了。
Debian中有几个软件包提供了与Yacc兼容的LALR词法生成器:
bison: GNU LALR parser generator
byacc: The Berkeley LALR parser generator
byyacc: Backtracking parser generator based on byacc
bison参考资源:
bison(1)
需要提供自己的main()和yyerror()。main()调用yyparse(),而yyparse()调用yylex(),通常由FleX创建。
%%
%%
autoconf一个shell脚本生成工具,由它生成的脚本能自动配置软件源码包,以适用于各种使用全套GNU
build系统的类UNIX系统。
autoconf会生成配置脚本configure。configure使用Makefile.in模版自动创建自定义Makefile。
Debian不会改动/usr/local下的文件(参阅多样性支持, 第 2.5
节)。所以如果是从源码编译程序,并将其安装到/usr/local下,是不会影响到Debian的。
$ cd src
$ ./configure --prefix=/usr/local
$ make
$ make install # this puts the files in the system
如果仍保存有源码,对其使用了autoconf/automake,并且记得是如何进行配置的:
$ ./configure all-of-the-options-you-gave-it
# make uninstall
另一种方法是,如果可以确定安装过程将文件都放在了/usr/local,并且该目录下没有什么别的重要文件,可用下面的命令将其全部删除:
# find /usr/local -type f -print0 | xargs -0 rm -f
如果不能确定文件安装到什么位置,最好使用checkinstall,该命令可提供明确的卸载路径。
传统上,roff是主要的Unix文字处理系统。
参阅roff(7)、groff(7)、groff(1)、grotty(1)、troff(1)、groff_mdoc(7)、groff_man(7)、groff_ms(7)、groff_me(7)、groff_mm(7)以及info
groff。
-me宏提供了一个不错了说明文档。如果使用的是groff(1.18或更新的版本),找到/usr/share/doc/groff/meintro.me.gz并执行下面的命令:
$ zcat /usr/share/doc/groff/meintro.me.gz | \
groff -Tascii -me - | less -R
下面的命令将生成一个完整的纯文本文件:
$ zcat /usr/share/doc/groff/meintro.me.gz | \
GROFF_NO_SGR=1 groff -Tascii -me - | col -b -x > meintro.txt
如果想打印出来,可使用PostScript输出:
$ groff -Tps meintro.txt | lpr
$ groff -Tps meintro.txt | mpage -2 | lpr
准备工作:
# apt-get install debiandoc-sgml debiandoc-sgml-doc
debiandoc-sgml参考资源:
/usr/share/doc/debiandoc-sgml-doc
debiandoc-sgml(1)
SGML能管理多重格式的文档。更简单的SGML系统是Debiandoc,本文档就使用到它完成的。只需对原始的文本文件的下列字符进行少许转换:
设置章节为非打印注释,输入:
<!-- State issue here ... -->
设置章节为可控注释,输入:
<![ %FIXME; [ State issue here ... ]]>
在SGML中,仅条目的首次声明(first definition)有效。例如:
<!entity % qref "INCLUDE">
<![ %qref; [ <!entity param "Data 1"> ]]>
<!entity param "Data 2">
¶m;
最终结果是“Data 1”。如果第一行使用“IGNORE”而非“INCLUDE”,则最终结果为“Data 2”(第二行是一个候选声明)。同样,重复出现的短语可分别提前在文档中定义。
<!entity whoisthis "my">
Hello &whoisthis; friend.
This is &whoisthis; book.
该定义的结果如下:
Hello my friend.
This is my book.
可参阅examples目录中简短的SGML样例文件sample.sgml。
当SGML文档不断增大后,TeX偶尔会出错。可通过修改/etc/texmf/texmf.cnf,增加缓冲池的容量来解决这个问题(更好的方法是编辑/etc/texmf/texmf.d/95NonPath然后运行update-texmf)。
准备工作:
# apt-get install debian-policy developers-reference \
maint-guide dh-make debhelper
# apt-get install packaging-manual # if Potato
有关打包的参考资源:
dh-make(1)
Joey Hess的快速粗糙的打包法:生成一个单独的二进制包
# mkdir -p mypkg/usr/bin mypkg/DEBIAN
# cp binary mypkg/usr/bin
# cat > mypkg/DEBIAN/control
Package: mypackage
Version: 1
Architecture: i386
Maintainer: Joey Hess <[email protected]>
Description: my little package
Don't expect much.
^D
# dpkg-deb -b mypkg
使用dh_make软件包中的dh-make工具创建一个基线包,接着按照dh-make(1)中描述的方法打包。会用到debian/rules中的debhelper。
一个较老的方法是使用debmake软件包中的deb-make。不需要debhelper脚本,仅需要shell环境。
有关多重源码包的例子,参阅“mc”(dpkg-source -x
mc_4.5.54.dsc),其中用到Adam Heath([email protected])的“sys-build.mk”以及“glibc”(dpkg-source
-x glibc_2.2.4-1.dsc)它由已故的Joel Klecker([email protected])所写的另一个系统打包。
Debian 参考手册
CVS, 星期二 八月 17 00:35:13 UTC 2004[email protected][email protected]