Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1076764
  • 博文数量: 242
  • 博客积分: 10209
  • 博客等级: 上将
  • 技术积分: 3028
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 09:27
文章分类

全部博文(242)

文章存档

2014年(1)

2013年(1)

2010年(51)

2009年(65)

2008年(124)

我的朋友

分类:

2008-04-10 16:48:15

12.3 时间/日期 命令
-------------------
时间/日期 和计时
date
 直接调用, date 就会打印日期和时间到 stdout 上. 这个命令有趣的地方在于它的格式化
 和分析选项上.
Example 12-10 使用 date 命令
################################Start Script#######################################
 1 #!/bin/bash
 2 # 练习 'date' 命令
 3
 4 echo "The number of days since the year's beginning is `date +%j`."
 5 # 需要在调用格式的前边加上一个 '+' 号.
 6 # %j 给出今天是本年度的第几天.
 7
 8 echo "The number of seconds elapsed since 01/01/1970 is `date +%s`."
 9 #  %s 将产生从 "UNIX 元年" 到现在为止的秒数,yields number of seconds since "UNIX epoch" began,
10 #+ 但是这东西有用么?
11
12 prefix=temp
13 suffix=$(date +%s)  # 'date'命令的 "+%s" 选项是 GNU-特性.
14 filename=$prefix.$suffix
15 echo $filename
16 #  这是一种非常好的产生 "唯一" 的临时文件的办法,
17 #+ 甚至比使用 $$ 都强.
18
19 # 如果想了解 'date' 命令的更多选项, 请查阅这个命令的 man 页.
20
21 exit 0
################################End Script#########################################
 -u 选项将给出 UTC (译者: UTC 是协调世界时英文缩写) 时间(Universal Coordinated T
 ime).
 bash$ date
 Fri Mar 29 21:07:39 MST 2002
 
 bash$ date -u
 Sat Mar 30 04:07:42 UTC 2002
 date 命令有许多的输出选项. 比如 %N 将以10亿分之一为单位表示当前时间. 这个选项的
 一个有趣的用法就是用来产生一个6位的随机数.
 1 date +%N | sed -e 's/000$//' -e 's/^0//'
 2            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 3 # 去掉开头和结尾的0.
 当然,还有许多其它的选项 (请查看 man date).
  1 date +%j
  2 # 显示今天是本年度的第几天(从1月1日开始计算).
  3
  4 date +%k%M
  5 # 显示当前小时数和分钟数.
  6
  7
  8
  9 #  'TZ' 参数允许改变当前的默认时区.
 10 date                 # Mon Mar 28 21:42:16 MST 2005
 11 TZ=EST date          # Mon Mar 28 23:42:16 EST 2005
 12 # Thanks, Frank Kannemann and Pete Sjoberg, for the tip.
 13
 14
 15 SixDaysAgo=$(date --date='6 days ago')
 16 OneMonthAgo=$(date --date='1 month ago')  # 4周前(不是一个月).
 17 OneYearAgo=$(date --date='1 year ago')
 参见 Example 3-4.
zdump
 查看特定时区的当前时间.
 bash$ zdump EST
 EST  Tue Sep 18 22:09:22 2001 EST
time 
 输出统计出来的命令执行的时间.
 time ls -l / 给出的输出大概是如下格式:
  
 0.00user 0.01system 0:00.05elapsed 16%CPU (0avgtext+0avgdata 0maxresident)k
 0inputs+0outputs (149major+27minor)pagefaults 0swaps
 参见前边章节所讲的一个类似的命令 times . 
 注意: 在Bash的  2.0版本 中, time 成为了shell的一个保留字, 并且在一个带有管道的
  命令行中,这个命令的行为有些小的变化.
touch
 这是一个用来更新文件被存取或修改的时间的工具,这个时间可以是当前系统的时间,也可
 以是指定的时间, 这个命令也用来产生一个新文件.命令 touch zzz 将产生一个以zzz为名
 字的0字节长度文件, 当然前提是zzz文件不存在. 为了存储时间信息, 就需要一个时间戳
 为空的文件, 比如当你想跟踪一个工程的修改时间的时候,这就非常有用了. 
 注意: touch 命令等价于 : >> newfile  或 >> newfile (对于一个普通文件).
at
 at 命令是一个作业控制命令, 用来在指定时间执行给定的命令集合.它有点像 cron 命令,
 然而, at 命令主要还是用来执行那种一次性执行的命令集合.
 at 2pm January 15 将会提示让你输入需要在这个时间你要执行的命令序列. 这些命令应
 该是可以和shell脚本兼容的,因为, 实际上, 在一个可执行的脚本中, 用户每次只能敲一
 行. 输入以 Ctl-D 结束.
 你可以使用-f选项或者使用 (<)重定向操作符, 来让 at  命令从一个文件中读取命令
 集合. 这个文件其实就一个可执行的的脚本, 虽然它是一个不可交互的脚本. 在文件中包
 含一个 run-parts 命令, 对于执行一套不同的脚本来说是非常聪明的做法.
 bash$ at 2:30 am Friday < at-jobs.list
 job 2 at 2000-10-27 02:30
batch
 batch 作业控制命令与 at 命令的行为很相像, 但 batch 命令被用来在系统平均载量降到
 0.8 以下时执行一次性的任务. 与 at 命令相似的是, 它也可以使用 -f 选项来从文件中
 读取命令.
cal
 从stdout中输出一个格式比较整齐的日历. 也可以指定年和月来显示那个月的日历.
sleep
 这个命令与一个等待循环的效果一样. 你可以指定需要暂停的秒数, 这段时间将什么都不
 干.当一个后台运行的进程需要偶尔检测一个事件时,这个功能很有用. 也可用于计时. 参
 见 Example 29-6.
 1 sleep 3     # Pauses 3 seconds.
 注意: sleep 命令默认为秒, 但是你也可以指定天数, 小时数或分钟数.
 1 sleep 3 h   # Pauses 3 hours!
 注意: 如果你想每隔一段时间来运行一个命令的话, 那么 watch 命令将比 sleep 命令好
  得多.
usleep
 Microsleep 睡眠微秒( "u"  会被希腊人读成 "mu", 或者是 micro- 前缀). 与上边的 sl
 eep 命令作用相同, 但这个命令是以百万分之一秒为单位的. 当需要精确计时, 或者需要
 非常频繁的监控一个正在运行的进程的时候, 这个命令非常有用.
 1 usleep 30     # 暂停 30 microseconds.
 这个命令是 Red Hat initscripts / rc-scripts 包的一部分.
 注意: 事实上 usleep 命令并不能提供非常精确的计时, 所以如果你需要一个实时的任务
  的话, 这个命令并不适合.
hwclock, clock
 hwclock 命令可以存取或调整硬件时钟. 这个命令的一些选项需要 root 权限. 在系统启
 动的时候, /etc/rc.d/rc.sysinit 这个启动文件,会使用 hwclock 来从硬件时钟中读取
 并设置系统时间.clock at bootup.
 clock 命令与 hwclock命令完全相同.

12.4 文本处理命令
-----------------
处理文本和文本文件的命令
sort
 文件排序, 通常用在管道中当过滤器来使用. 这个命令可以依据指定的关键字或指定的字
 符位置, 对文件行进行排序. 使用 -m 选项, 它将会合并预排序的输入文件. 想了解这个
 命令的全部参数请参考这个命令的 info 页. 见 Example 10-9, Example 10-10, 和
 Example A-8.
tsort
 拓扑排序 ,读取以空格分隔的有序对, 并且依靠输入模式进行排序.
uniq
 这个过滤器将会删除一个已排序文件中的重复行.这个命令经常出现在 sort命令的管道后
 边.
 1 cat list-1 list-2 list-3 | sort | uniq > final.list
 2 # 将3个文件连接起来,
 3 # 将它们排序,
 4 # 删除其中重复的行,
 5 # 最后将结果重定向到一个文件中.
 -c选项的意思是在输出行前面加上每行在输入文件中出现的次数.
 bash$ cat testfile
 This line occurs only once.
 This line occurs twice.
 This line occurs twice.
 This line occurs three times.
 This line occurs three times.
 This line occurs three times.

 bash$ uniq -c testfile
    1 This line occurs only once.
    2 This line occurs twice.
    3 This line occurs three times.

 bash$ sort testfile | uniq -c | sort -nr
    3 This line occurs three times.
    2 This line occurs twice.
    1 This line occurs only once.
 sort INPUTFILE | uniq -c | sort -nr  命令 先对 INPUTFILE 排序, 然后统计 每行出
 现的次数, 最后的(-nr 选项将会产生一个数字的反转排序). 这种命令模版一般都用来分
 析 log 文件或者用来分析字典列表, 或者用在那些需要检查文本词汇结构的地方.
Example 12-11 分析单词出现的频率
################################Start Script#######################################
 1 #!/bin/bash
 2 # wf.sh: 分析文本文件中自然词汇出现的频率.
 3 # "wf2.sh" 是一个效率更高的版本.
 4
 5
 6 # 从命令行中检查输入的文件.
 7 ARGS=1
 8 E_BADARGS=65
 9 E_NOFILE=66
10
11 if [ $# -ne "$ARGS" ]  # 检验传递到脚本中参数的个数.
12 then
13   echo "Usage: `basename $0` filename"
14   exit $E_BADARGS
15 fi
16
17 if [ ! -f "$1" ]       # 检查传入的文件参数是否存在.
18 then
19   echo "File \"$1\" does not exist."
20   exit $E_NOFILE
21 fi
22
23
24
25 ########################################################
26 # main ()
27 sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
28 /g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
29 #                           =========================
30 #                               检查单词出现的频率
31
32 #  过滤掉句号和逗号,
33 #+ 并且把单词间的空格转化为换行,
34 #+ 然后转化为小写,
35 #+ 最后统计出现的频率并按频率排序.
36
37 #  Arun Giridhar 建议将上边的代码修改为:
38 #  . . . | sort | uniq -c | sort +1 [-f] | sort +0 -nr
39 #  这句添加了第2个排序主键, 所以
40 #+ 这个与上边等价的例子将按照字母顺序进行排序.
41 #  就像他所解释的:
42 #  "这是一个有效的根排序, 首先对频率最少的
43 #+ 列进行排序
44 #+ (单词或者字符串, 忽略大小写)
45 #+ 然后对频率最高的列进行排序."
46 #
47 #  像 Frank Wang 所解释的那样, 上边的代码等价于:
48 #+       . . . | sort | uniq -c | sort +0 -nr
49 #+ 用下边这行也行:
50 #+       . . . | sort | uniq -c | sort -k1nr -k
51 ########################################################
52
53 exit 0
54
55 # 练习:
56 # -----
57 # 1) 使用 'sed' 命令来过滤其他的标点符号,
58 #+   比如分号.
59 # 2) 修改这个脚本, 添加能够过滤多个空格或者
60 #    空白的能力.
################################End Script#########################################
 bash$ cat testfile
 This line occurs only once.
 This line occurs twice.
 This line occurs twice.
 This line occurs three times.
 This line occurs three times.
 This line occurs three times.

 bash$ ./wf.sh testfile
    6 this
    6 occurs
    6 line
    3 times
    3 three
    2 twice
    1 only
    1 once
expand, unexpand
 expand 将会把每个tab转化为一个空格.这个命令经常用在管道中.
 unexpand 将会把每个空格转化为一个tab.效果与 expand 相反.
cut
 一个从文件中提取特定域的工具. 这个命令与 awk 中使用的 print $N命令很相似, 但是
 更受限. 在脚本中使用cut命令会比使用 awk 命令来得容易一些. 最重要的选项就是 -d
 (字段定界符) 和 -f (域分隔符) 选项.
 使用 cut 来获得所有mount上的文件系统的列表:
 1 cut -d ' ' -f1,2 /etc/mtab
 使用 cut 命令列出 OS 和 kernel的版本:
 1 uname -a | cut -d" " -f1,3,11,12
 使用 cut 命令从 e-mail 中提取消息头:
 bash$ grep '^Subject:' read-messages | cut -c10-80
 Re: Linux suitable for mission-critical apps?
 MAKE MILLIONS WORKING AT HOME!!!
 Spam complaint
 Re: Spam complaint
 使用 cut 命令来分析一个文件:
  1 # 列出所有在/etc/passwd中的用户.
  2
  3 FILENAME=/etc/passwd
  4
  5 for user in $(cut -d: -f1 $FILENAME)
  6 do
  7   echo $user
  8 done
  9
 10 # Thanks, Oleg Philon for suggesting this.
 cut -d ' ' -f2,3 filename 等价于 awk -F'[ ]' '{ print $2, $3 }' filename
 注意: 
  你甚至可以指定换行符作为字段定界符. 这个小伎俩实际上就是在命令行上插入一个
  换行(RETURN).(译者: linux使用lf作为换行符的).
  bash$ cut -d'
  ' -f3,7,19 testfile
  This is line 3 of testfile.
  This is line 7 of testfile.
  This is line 19 of testfile.
  Thank you, Jaka Kranjc, for pointing this out.
 参见 Example 12-43.
paste
 将多个文件,以每个文件一列的形式合并到一个文件中, 合并后的文件没列就是原来的一个
 文件.对于创建系统log文件来说, 使用 cut 命令与 paste 命令相结合是非常有用的.
join
 这个命令与 paste 命令属于同类命令, 但是它能够完成某些特殊的目地. 这个强力工具能
 够以一种特殊的形式来合并2个文件, 这种特殊的形式本质上就是一个关联数据库的简单版
 本.
 join 命令只能够操作2个文件, 它可以将那些具有特定标记域(通常是一个数字标签)的行
 合并起来, 并且将结果输出到stdout. 被加入的文件应该事先根据标记域进行排序以便于
 能够正确的匹配.
 1 File: 1.data
 2
 3 100 Shoes
 4 200 Laces
 5 300 Socks
 1 File: 2.data
 2
 3 100 $40.00
 4 200 $1.00
 5 300 $2.00
 bash$ join 1.data 2.data
 File: 1.data 2.data
 100 Shoes $40.00
 200 Laces $1.00
 300 Socks $2.00
 注意: 在输出中标记域将只会出现一次.
head
 将一个文件的头打印到stdout上 ( 默认为10行, 可以自己修改 ). 这个命令也有一些有趣
 的选项.
Example 12-12 那个文件是脚本?
################################Start Script#######################################
 1 #!/bin/bash
 2 # script-detector.sh: 在一个目录中检查所有的脚本文件.
 3
 4 TESTCHARS=2    # 测试前两个字节.
 5 SHABANG='#!'   # 脚本都是以 "sha-bang." 开头的.
 6
 7 for file in *  # 遍历当前目录下的所有文件.
 8 do
 9   if [[ `head -c$TESTCHARS "$file"` = "$SHABANG" ]]
10   #      head -c2                      #!
11   #  '-c' 选项将从文件头输出指定个数的字符,
12   #+ 而不是默认的行数.
13   then
14     echo "File \"$file\" is a script."
15   else
16     echo "File \"$file\" is *not* a script."
17   fi
18 done
19  
20 exit 0
21
22 #  练习:
23 #  -----
24 #  1) 将这个脚本修改为可以指定目录
25 #+    来扫描目录下的脚本.
26 #+    (而不是只搜索当前目录).
27 #
28 #  2) 就目前看来, 这个脚本将不能正确识别出
29 #+    Perl, awk, 和其他一些脚本语言的脚本文件.
30 #     修正这个问题.
################################End Script#########################################
Example 12-13 产生10进制随机数
################################Start Script#######################################
 1 #!/bin/bash
 2 # rnd.sh: 输出一个10进制随机数
 3
 4 # Script by Stephane Chazelas.
 5
 6 head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
 7
 8
 9 # =================================================================== #
10
11 # 分析
12 # ----
13
14 # head:
15 # -c4 选项将取得前4个字节.
16
17 # od:
18 # -N4 选项将限制输出为4个字节.
19 # -tu4 选项将使用无符号10进制格式来输出.
20
21 # sed:
22 # -n 选项, 使用 "s" 命令与 "p" 标志组合的方式,
23 # 将会只输出匹配的行.
24
25
26
27 # 本脚本作者解释 'sed' 命令的行为如下.
28
29 # head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
30 # ----------------------------------> |
31
32 # 假设一直处理到 "sed"命令时的输出--> |
33 # 为 0000000 1198195154\n
34
35 #  sed 命令开始读取字串: 0000000 1198195154\n.
36 #  这里它发现一个换行符,
37 #+ 所以 sed 准备处理第一行 (0000000 1198195154).
38 #  sed命令开始匹配它的 . 第一个匹配的并且只有这一个匹配的:
39
40 #   range     action
41 #   1         s/.* //p
42
43 #  因为行号在range中, 所以 sed 开始执行 action:
44 #+ 替换掉以空格结束的最长的字符串, 在这行中这个字符串是
45 #  ("0000000 ") ,用空字符串(//)将这个匹配到的字串替换掉, 如果成功, 那就打印出结果
46 #  ("p" 在这里是 "s" 命令的标志, 这与单独的 "p" 命令是不同的).
47
48 #  sed 命令现在开始继续读取输入. (注意在继续之前,
49 #+ continuing, 如果没使用 -n 选项的话, sed 命令将再次
50 #+ 将这行打印一遍).
51
52 # 现在, sed 命令读取剩余的字符串, 并且找到文件的结尾.
53 # sed 命令开始处理第2行(这行也被标记为 '$'
54 # 因为这已经是最后一行).
55 # 所以这行没被匹配到 中, 这样sed命令就结束了.
56
57 #  这个 sed 命令的简短的解释是:
58 #  "在第一行中删除第一个空格左边全部的字符,
59 #+ 然后打印出来."
60
61 # 一个更好的来达到这个目的的方法是:
62 #           sed -e 's/.* //;q'
63
64 # 这里, 分别是 (也可以写成
65 #           sed -e 's/.* //' -e q):
66
67 #   range                    action
68 #   nothing (matches line)   s/.* //
69 #   nothing (matches line)   q (quit)
70
71 #  这里, sed 命令只会读取第一行的输入.
72 #  将会执行2个命令, 并且会在退出之前打印出(已经替换过的)这行(因为 "q" action),
73 #+ 因为没使用 "-n" 选项.
74
75 # =================================================================== #
76
77 # 也可以使用如下一个更简单的语句来代替:
78 #           head -c4 /dev/urandom| od -An -tu4
79
80 exit 0
################################End Script#########################################
 参见 Example 12-35.
tail
 将一个文件的结尾输出到 stdout  中(默认为 10 行). 通常用来跟踪一个系统 logfile
 的修改状况, 使用 -f 选项的话, tail 命令将会继续显示添加到文件中的行.
Example 12-14 使用 tail 命令来监控系统log
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 filename=sys.log
 4
 5 cat /dev/null > $filename; echo "Creating / cleaning out file."
 6 #  如果文件不存在的话就创建文件,
 7 #+ 然后将这个文件清空.
 8 #  : > filename   和   > filename 也可以完成这个工作.
 9
10 tail /var/log/messages > $filename 
11 # /var/log/messages 必须具有全局可读权限才行.
12
13 echo "$filename contains tail end of system log."
14
15 exit 0
################################End Script#########################################
 注意:
  为了列出一个文本文件中的指定行数, 可以将 head 命令的输出通过 管道 传递到
  tail -1 中 . 比如 head -8 database.txt | tail -1 将会列出 database.txt 文
  件的第8行.
  下边是将一个文本文件中指定范围的所有行都保存到一个变量中:
  1 var=$(head -$m $filename | tail -$n)
  2
  3 # filename = 文件名
  4 # m = 从文件开头到想取得的指定范围的行数的最后一行
  5 # n = 取得指定范围的行数 (从块结尾开始截断)
 参见 Example 12-5, Example 12-35 和 Example 29-6.
grep
 使用 正则表达式 的一个多用途文本搜索工具. 这个命令本来是 ed 行编辑器中的一个命
 令/过滤器: g/re/p -- global - regular expression - print.
 grep pattern [file...]
 在文件中搜索所有 pattern 出现的位置, pattern 既可以是要搜索的字符串,也可以是一
 个正则表达式.
 bash$ grep '[rst]ystem.$' osinfo.txt
 The GPL governs the distribution of the Linux operating system.
 如果没有指定文件参数, grep  通常用在管道中对 stdout 进行过滤.
 bash$ ps ax | grep clock
 765 tty1     S      0:00 xclock
 901 pts/1    S      0:00 grep clock
 -i 选项在搜索时忽略大小写.
 -w 选项用来匹配整词.
 -l 选项仅列出符合匹配的文件, 而不列出匹配行.
 -r (递归) 选项不仅在当前工作目录下搜索匹配, 而且搜索子目录.
 -n 选项列出所有匹配行, 并显示行号.
 bash$ grep -n Linux osinfo.txt
 2:This is a file containing information about Linux.
 6:The GPL governs the distribution of the Linux operating system.
 -v (或者--invert-match) 选项将会显示所有不匹配的行.
 1 grep pattern1 *.txt | grep -v pattern2
 2
 3 # 匹配在"*.txt"中所有包含 "pattern1"的行,
 4 # 而不显示匹配包含 "pattern2"的行. 

 -c (--count) 选项将只会显示匹配到的行数的总数,而不会列出具体的匹配.
  1 grep -c txt *.sgml   # (在 "*.sgml" 文件中, 匹配"txt"的行数的总数.)
  2
  3
  4 #   grep -cz .
  5 #            ^ 点
  6 # 意思是计数 (-c) 所有以空字符分割(-z) 的匹配 "."的项
  7 # "."是正则表达式的一个符号, 表达匹配任意一个非空字符(至少要包含一个字符).
  8 #
  9 printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz .     # 3
 10 printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$'   # 5
 11 printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^'   # 5
 12 #
 13 printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$'    # 9
 14 # 默认情况下, 是使用换行符(\n)来分隔匹配项.
 15
 16 # 注意  -z 选项是 GNU "grep" 特定的选项.
 17
 18
 19 # Thanks, S.C.
 当有多个文件参数的时候, grep 将会指出哪个文件中包含具体的匹配.
 bash$ grep Linux osinfo.txt misc.txt
 osinfo.txt:This is a file containing information about Linux.
 osinfo.txt:The GPL governs the distribution of the Linux operating system.
 misc.txt:The Linux operating system is steadily gaining in popularity.
 注意: 如果在 grep 命令只搜索一个文件的时候, 那么可以简单的把 /dev/null 作为第2
  个文件参数传给 grep .
  bash$ grep Linux osinfo.txt /dev/null
  osinfo.txt:This is a file containing information about Linux.
  osinfo.txt:The GPL governs the distribution of the Linux operating system.
 如果存在一个成功的匹配, 那么 grep  命令将会返回 0 作为 退出状态  ,这样就可以将
 grep 命令的结果放在脚本的条件测试中来使用, 尤其和 -q (禁止输出)选项组合时特别有
 用.
  1 SUCCESS=0                      # 如果 grep 匹配成功
  2 word=Linux
  3 filename=data.file
  4
  5 grep -q "$word" "$filename"    # "-q" 选项将使得什么都不输出到 stdout 上.
  6
  7 if [ $? -eq $SUCCESS ]
  8 # if grep -q "$word" "$filename"   这句话可以代替行 5 - 7.
  9 then
 10   echo "$word found in $filename"
 11 else
 12   echo "$word not found in $filename"
 13 fi
 Example 29-6 展示了如何使用 grep 命令来在一个系统 logfile 中进行一个单词的模式
 匹配.
Example 12-15 在一个脚本中模仿 "grep" 的行为
################################Start Script#######################################
 1 #!/bin/bash
 2 # grp.sh: 一个非常粗糙的 'grep' 的实现.
 3
 4 E_BADARGS=65
 5
 6 if [ -z "$1" ]    # 检查传递给脚本的参数.
 7 then
 8   echo "Usage: `basename $0` pattern"
 9   exit $E_BADARGS
10 fi 
11
12 echo
13
14 for file in *     # 遍历 $PWD 下的所有文件.
15 do
16   output=$(sed -n /"$1"/p $file)  # 命令替换.
17
18   if [ ! -z "$output" ]           # 如果"$output" 不加双引号将会发生什么?
19   then
20     echo -n "$file: "
21     echo $output
22   fi              #  sed -ne "/$1/s|^|${file}: |p"  这句与上边这段等价.
23
24   echo
25 done 
26
27 echo
28
29 exit 0
30
31 # 练习:
32 # -----
33 # 1) 在任何给定的文件中,如果有超过一个匹配的话, 在输出中添加新行.
34 # 2) 添加一些特征.
################################End Script#########################################
 如何使用 grep 命令来搜索两个(或两个以上)独立的模式? 如果你想显示在一个或多个文
 件中既匹配"pattern1" 又匹配 "pattern2"的所有匹配行又该如何做呢?(译者: 这是取交
 集的情况, 如果取并集该怎么办呢?)
 一个方法是通过 管道 来将 grep pattern1 的结果传递到 grep pattern2 中 .
 例如, 给定如下文件:
 1 # Filename: tstfile
 2
 3 This is a sample file.
 4 This is an ordinary text file.
 5 This file does not contain any unusual text.
 6 This file is not unusual.
 7 Here is some text.
 现在, 让我们在这个文件中搜索既包含 "file" 又包含 "text" 的所有行
 bash$ grep file tstfile
 # Filename: tstfile
 This is a sample file.
 This is an ordinary text file.
 This file does not contain any unusual text.
 This file is not unusual.
 bash$ grep file tstfile | grep text
 This is an ordinary text file.
 This file does not contain any unusual text.
 --
 egrep  - 扩展的 grep - 这个命令与 grep -E 等价. 这个命令用起来有些不同, 由于正
 则表达式扩展, 将会使得搜索更具灵活性.
 fgrep - 快速的 grep  - 这个命令与 grep -F 等价. 这是一种按照字符串字面意思进行
 的搜索(即不允许使用正则表达式), 这样有时候会使搜索变得容易一些.
 注意: 在某些linux发行版中, egrep 和 fgrep 都是 grep 命令的符号连接或者是别名,
  只不过调用的时候分别使用 -E 和 -F 选项罢了.
Example 12-16 在1913年的韦氏词典中查找定义
################################Start Script#######################################
 1 #!/bin/bash
 2 # dict-lookup.sh
 3
 4 #  这个脚本在1913年的韦氏词典中查找定义.
 5 #  这本公共词典可以通过不同的
 6 #+ 站点来下载,包括
 7 #+ Project Gutenberg ().
 8 #
 9 #  在通过本脚本使用之前,
10 #+ 先要将这本字典由 DOS 格式转换为 UNIX格式(只以 LF 作为行结束符).
11 #  将这个文件存储为纯文本形式, 并且保证是未压缩的 ASCII 格式.
12 #  将DEFAULT_DICTFILE 变量以 path/filename 形式设置好.
13
14
15 E_BADARGS=65
16 MAXCONTEXTLINES=50                        # 显示的最大行数.
17 DEFAULT_DICTFILE="/usr/share/dict/webster1913-dict.txt"
18                                           # 默认的路径和文件名.
19                                           # 在必要的时候可以进行修改.
20 #  注意:
21 #  -----
22 #  这个特定的1913年版的韦氏词典
23 #+ 在每个入口都是以大写字母开头的
24 #+ (剩余的字符都是小写).
25 #  只有每部分的第一行是以这种形式开始的,
26 #+ 这也就是为什么搜索算法是下边的这个样子.
27
28
29
30 if [[ -z $(echo "$1" | sed -n '/^[A-Z]/p') ]]
31 #  必须指定一个要查找的单词,
32 #+ 并且这个单词必须以大写字母开头.
33 then
34   echo "Usage: `basename $0` Word-to-define [dictionary-file]"
35   echo
36   echo "Note: Word to look up must start with capital letter,"
37   echo "with the rest of the word in lowercase."
38   echo "--------------------------------------------"
39   echo "Examples: Abandon, Dictionary, Marking, etc."
40   exit $E_BADARGS
41 fi
42
43
44 if [ -z "$2" ]                            #  也可以指定不同的词典
45                                           #+ 作为这个脚本的第2个参数传递进来.
46 then
47   dictfile=$DEFAULT_DICTFILE
48 else
49   dictfile="$2"
50 fi
51
52 # ---------------------------------------------------------
53 Definition=$(fgrep -A $MAXCONTEXTLINES "$1 \\" "$dictfile")
54 #                                   以 "Word \..." 这种形式定义
55 #
56 #  当然, 即使搜索一个特别大的文本文件的时候
57 #+ "fgrep" 也是足够快的.
58
59
60 # 现在, 剪掉定义块.
61
62 echo "$Definition" |
63 sed -n '1,/^[A-Z]/p' |
64 #  从输出的第一行
65 #+ 打印到下一部分的第一行.
66 sed '$d' | sed '$d'
67 #  删除输出的最后两行Delete last two lines of output
68 #+ (空行和下一部分的第一行).
69 # ---------------------------------------------------------
70
71 exit 0
72
73 # 练习:
74 # -----
75 # 1)  修改这个脚本, 让它具备能够处理任何字符形式的输入
76 #   + (大写, 小写, 或大小写混合), 然后将其转换为
77 #   + 能够处理的统一形式.
78 #
79 # 2)  将这个脚本转化为一个 GUI 应用,
80 #   + 使用一些比如像 "gdialog"的东西 .  .  .
81 #     这样的话, 脚本将不再从命令行中
82 #   + 取得这些参数.
83 #
84 # 3)  修改这个脚本让它具备能够分析另外一个
85 #   + 公共词典的能力,比如 U.S. Census Bureau Gazetteer.
################################End Script#########################################
 agrep (近似 grep) 扩展了 grep 近似匹配的能力. 搜索的字符串可能会与最终匹配结果
 所找到字符串有些不同.这个工具并不是核心 Linux 发行版的一部分.
 注意: 为了搜索压缩文件, 应使用 zgrep, zegrep, 或 zfgrep. 这些命令也可以对未压缩
  的文件进行搜索, 只不过会比一般的 grep, egrep, 和 fgrep 慢上一些. 当然, 在你
  要搜索的文件中如果混合了压缩和未压缩的文件的话, 那么使用这些命令是非常方便
  的.
  如果要搜索 bzipped  类型的文件, 使用 bzgrep.
look
 命令 look 与命令 grep 很相似, 但是这个命令只能做字典查询, 也就是它所搜索的文件
 必须已经排过序的单词列表. 默认情况下, 如果没有指定搜索那个文件, 那就默认搜索
 /usr/dict/words文件(译者: 感觉好像应该是/usr/share/dict/words), 当然也可以指定
 其他目录下的文件进行搜索.
Example 12-17 检查列表中单词的正确性
################################Start Script#######################################
 1 #!/bin/bash
 2 # lookup: 对指定数据文件中的每个单词都做一遍字典查询..
 3
 4 file=words.data  # 指定的要搜索的数据文件.
 5
 6 echo
 7
 8 while [ "$word" != end ]  # 数据文件中最后一个单词.
 9 do
10   read word      # 从数据文件中读, 因为在循环的后边重定向了.
11   look $word > /dev/null  # 不想将字典文件中的行显示出来.
12   lookup=$?      #  'look' 命令的退出状态.
13
14   if [ "$lookup" -eq 0 ]
15   then
16     echo "\"$word\" is valid."
17   else
18     echo "\"$word\" is invalid."
19   fi 
20
21 done <"$file"    # 将 stdin 重定向到 $file, 所以 "reads" 来自于 $file.
22
23 echo
24
25 exit 0
26
27 # ----------------------------------------------------
28 # 下边的代码行将不会执行, 因为上边已经有 "exit"命令了.
29
30
31 # Stephane Chazelas 建议使用下边更简洁的方法:
32
33 while read word && [[ $word != end ]]
34 do if look "$word" > /dev/null
35    then echo "\"$word\" is valid."
36    else echo "\"$word\" is invalid."
37    fi
38 done <"$file"
39
40 exit 0
################################End Script#########################################
sed, awk
 这个两个命令都是独立的脚本语言, 尤其适合分析文本文件和命令输出. 既可以单独使用,
 也可以结合管道和在shell脚本中使用.
sed
 非交互式的 "流编辑器", 在批量模式下, 允许使用许多 ex 命令.你会发现它在shell脚本
 中非常有用.
awk
 可编程的文件提取器和文件格式化工具, 在结构化的文本文件中,处理或提取特定域(特定
 列)具有非常好的表现.它的语法与 C 语言很类似.
wc
 wc 可以统计文件或 I/O 流中的单词数量.
 bash $ wc /usr/share/doc/sed-4.1.2/README
 13  70  447 README
 [13 lines  70 words  447 characters]
 wc -w 统计单词数量.
 wc -l 统计行数量.
 wc -c 统计字节数量.
 wc -m 统计字符数量.
 wc -L 给出文件中最长行的长度.
 使用 wc 命令来统计当前工作目录下有多少个 .txt 文件.
 1 $ ls *.txt | wc -l
 2 # 因为列出的文件名都是以换行符区分的,所以使用 -l 来统计.
 3
 4 # 另一种达到这个目的的方法:
 5 #      find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
 6 #      (shopt -s nullglob; set -- *.txt; echo $#)
 7
 8 # Thanks, S.C.
 使用 wc 命令来统计所有以 d - h 开头的文件的大小.
 bash$ wc [d-h]* | grep total | awk '{print $3}'
 71832
 使用 wc 命令来查看指定文件中包含 "Linux" 的行一共有多少.
 bash$ grep Linux abs-book.sgml | wc -l
 50
 参见 Example 12-35 和 Example 16-8.
 某些命令的某些选项其实已经包含了 wc 命令的部分功能.
 1 ... | grep foo | wc -l
 2 # 这个命令使用得非常频繁, 但事实上它有更简便的写法.
 3
 4 ... | grep -c foo
 5 # 只要使用 grep 命令的 "-c" (或 "--count")选项就能达到同样的目的.
 6
 7 # Thanks, S.C.
tr
 字符转换过滤器.
 注意: 必须使用引用或中括号, 这样做才是合理的. 引用可以阻止 shell 重新解释出现在
   tr 命令序列中的特殊字符.中括号应该被引用起来防止被shell扩展.
 无论 tr "A-Z" "*"  写字符修改为星号(写到 stdout).但是在某些系统上可能就不能正常工作了, 而 tr A-Z '
 [**]' 在任何系统上都可以正常工作.
 -d 选项删除指定范围的字符.
 1 echo "abcdef"                 # abcdef
 2 echo "abcdef" | tr -d b-d     # aef
 3
 4
 5 tr -d 0-9  6 # 删除 "filename" 中所有的数字.
 --squeeze-repeats (或 -s) 选项用来在重复字符序列中除去除第一个字符以外的所有字
 符. 这个选项在删除多余的whitespace 的时候非常有用.
 bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
 X
 -c "complement"  选项将会 反转 匹配的字符集. 通过这个选项, tr 将只会对那些 不
 匹配的字符起作用.
 bash$ echo "acfdeb123" | tr -c b-d +
 +c+d+b++++
 注意 tr 命令支持 POSIX 字符类. [1]
 bash$ echo "abcd2ef1" | tr '[:alpha:]' -
 ----2--1
Example 12-18 转换大写: 把一个文件的内容全部转换为大写.
################################Start Script#######################################
 1 #!/bin/bash
 2 # 把一个文件的内容全部转换为大写.
 3
 4 E_BADARGS=65
 5
 6 if [ -z "$1" ]  # 检查命令行参数.
 7 then
 8   echo "Usage: `basename $0` filename"
 9   exit $E_BADARGS
10 fi 
11
12 tr a-z A-Z <"$1"
13
14 # 与上边的作用相同, 但是使用了 POSIX 字符集标记方法:
15 #        tr '[:lower:]' '[:upper:]' <"$1"
16 # Thanks, S.C.
17
18 exit 0
19
20 #  练习:
21 #  重写这个脚本, 通过选项可以控制脚本或者
22 #+ 转换为大写或者转换为小写.
################################End Script#########################################
Example 12-19 转换小写: 将当前目录下的所有文全部转换为小写.
################################Start Script#######################################
 1 #!/bin/bash
 2 #
 3 #  将当前目录下的所有文全部转换为小写.
 4 #
 5 #  灵感来自于 John Dubois 的脚本,
 6 #+ 转换为 Bash 脚本,
 7 #+ 然后被本书作者精简了一下.
 8
 9
10 for filename in *                # 遍历当前目录下的所有文件.
11 do
12    fname=`basename $filename`
13    n=`echo $fname | tr A-Z a-z`  # 将名字修改为小写.
14    if [ "$fname" != "$n" ]       # 只对那些文件名不是小写的文件进行重命名.
15    then
16      mv $fname $n
17    fi 
18 done  
19
20 exit $?
21
22
23 # 下边的代码将不会被执行, 因为上边的 "exit".
24 #-------------------------------------------#
25 # 删除上边的内容,来运行下边的内容.
26
27 # 对于那些文件名中包含空白和新行的文件, 上边的脚本就不能工作了.
28 # Stephane Chazelas 因此建议使用下边的方法:
29
30
31 for filename in *    # 不必非得使用 basename 命令,
32                      # 因为 "*" 不会返回任何包含 "/" 的文件.
33 do n=`echo "$filename/" | tr '[:upper:]' '[:lower:]'`
34 #                             POSIX 字符集标记法.
35 #                    添加的斜线是为了在文件名结尾换行不会被
36 #                    命令替换删掉.
37    # 变量替换:
38    n=${n%/}          # 从文件名中将上边添加在结尾的斜线删除掉.
39    [[ $filename == $n ]] || mv "$filename" "$n"
40                      # 检查文件名是否已经是小写.
41 done
42
43 exit $?
################################End Script#########################################
Example 12-20 Du: DOS 到 UNIX 文本文件的转换.
################################Start Script#######################################
 1 #!/bin/bash
 2 # Du.sh: DOS 到 UNIX 文本文件的转换.
 3
 4 E_WRONGARGS=65
 5
 6 if [ -z "$1" ]
 7 then
 8   echo "Usage: `basename $0` filename-to-convert"
 9   exit $E_WRONGARGS
10 fi
11
12 NEWFILENAME=$1.unx
13
14 CR='\015'  # 回车Carriage return.
15            # 015 是 8 进制的 ASCII 码的回车.
16            # DOS 中文本文件的行结束符是 CR-LF.
17            # UNIX 中文本文件的行结束符只是 LF.
18
19 tr -d $CR < $1 > $NEWFILENAME
20 # 删除回车并且写到新文件中.
21
22 echo "Original DOS text file is \"$1\"."
23 echo "Converted UNIX text file is \"$NEWFILENAME\"."
24
25 exit 0
26
27 # 练习:
28 # -----
29 # 修改上边的脚本完成从UNIX 到 DOS 的转换.
################################End Script#########################################
Example 12-21 rot13: rot13, 弱智加密.
################################Start Script#######################################
 1 #!/bin/bash
 2 # rot13.sh: 典型的 rot13 算法,
 3 #           使用这种方法加密可能可以愚弄一下3岁小孩.
 4
 5 # 用法: ./rot13.sh filename
 6 # 或    ./rot13.sh  7 # 或    ./rot13.sh and supply keyboard input (stdin)
 8
 9 cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M'   # "a" 变为 "n", "b" 变为 "o", 等等.
10 #  'cat " 结构
11 #+ 允许从stdin或者从文件中获得输入.
12
13 exit 0
################################End Script#########################################
Example 12-22 Generating "Crypto-Quote" Puzzles
################################Start Script#######################################
 1 #!/bin/bash
 2 # crypto-quote.sh: 加密
 3
 4 #  使用单码替换(单一字母替换法)来进行加密.
 5 #  The result is similar to the "Crypto Quote" puzzles
 6 #+ seen in the Op Ed pages of the Sunday paper. (不太了解这句的内容, 应该是有特定的含义)
 7
 8
 9 key=ETAOINSHRDLUBCFGJMQPVWZYXK
10 # "key" 不过是一个乱序的字母表.
11 # 修改 "key" 就会修改加密的结果.
12
13 # The 'cat " construction gets input either from stdin or from files.
14 # 如果使用stdin, 那么要想结束输入就使用 Control-D.
15 # 否则就要在命令行上指定文件名.
16
17 cat "$@" | tr "a-z" "A-Z" | tr "A-Z" "$key"
18 #        |   转化为大写   |     加密
19 # 小写, 大写, 或混合大小写, 都可以正常工作.
20 # 但是传递进来的非字母字符将不会起任何变化.
21
22
23 # 用下边的语句试试这个脚本:
24 # "Nothing so needs reforming as other people's habits."
25 # --Mark Twain
26 #
27 # 输出为:
28 # "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."
29 # --BEML PZERC
30
31 # 解密:
32 # cat "$@" | tr "$key" "A-Z"
33
34
35 #  这个简单的密码可以轻易的被一个12岁的小孩
36 #+ 用铅笔和纸破解.
37
38 exit 0
39
40 #  练习:
41 #  -----
42 #  修改这个脚本, 让它可以用命令行参数
43 #+ 来决定加密或解密.
################################End Script#########################################
 注意: tr 的不同版本
  tr 工具在历史上有2个重要版本. BSD 版本不需要使用中括号 (tr a-z A-Z), 但是
  SysV 版本则需要中括号 (tr '[a-z]' '[A-Z]'). GNU 版本的 tr 命令与 BSD 版本比
  较相像, 所以使用中括号来引用字符范围是强制性的(译者: 感觉这句说反了, 读者可
  自行参照原文).
fold
 将输入按照指定宽度进行折行. 这里有一个非常有用的选项 -s ,这个选项可以使用空格进
 行断行.(译者: 事实上只有外文才需要使用空格断行, 中文是不需要的) (参见 Example
  12-23 和 Example A-1).
fmt
 一个简单的文件格式器, 通常用在管道中, 将一个比较长的文本行输出进行折行.
Example 12-23 格式化文件列表.
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 WIDTH=40                    # 设为 40 列宽.
 4
 5 b=`ls /usr/local/bin`       # 取得文件列表...
 6
 7 echo $b | fmt -w $WIDTH
 8
 9 # 也可以使用如下方法,作用相同
10 #    echo $b | fold - -s -w $WIDTH
11 
12 exit 0
################################End Script#########################################
 参见 Example 12-5.
 注意: 如果想找到一个更强力的 fmt 工具可以选择 Kamil Toman 的 par  工具, 这个工
  具可以从后边的这个网址取得.
col
    这个命令用来滤除标准输入的反向换行符号. 这个工具还可以将空白用等价的 tab 来替
 换. col 工具最主要的应用还是从特定的文本处理工具中过滤输出, 比如 groff 和 tbl.
 (译者: 主要用来将man页转化为文本)
column
 列格式化工具. 这个过滤工具将会将列类型的文本转化为"易于打印"的表格式进行输出,
 通过在合适的位置插入tab.
Example 12-24 使用 column 来格式化目录列表
################################Start Script#######################################
 1 #!/bin/bash
 2 # 这是"column" man页中的一个例子, 作者对这个例子做了很小的修改.
 3
 4
 5 (printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \
 6 ; ls -l | sed 1d) | column -t
 7
 8 #  管道中的 "sed 1d" 删除输出的第一行,
 9 #+ 第一行将是 "total        N",
10 #+ 其中 "N" 是 "ls -l" 找到的文件总数.
11
12 # "column" 中的 -t 选项用来转化为易于打印的表形式.
13
14 exit 0
################################End Script#########################################
colrm
 列删除过滤器. 这个工具将会从文件中删除指定的列(列中的字符串)并且写到文件中, 如
 果指定的列不存在,那么就回到 stdout. colrm 2 4  每行删除第2到第4列之间的所有字符.
 注意: 如果这个文件包含tab和不可打印字符, 那将会引起不可预期的行为. 在这种情况下
  , 应该通过管道的手段使用 expand 和 unexpand 命令来预处理 colrm.
nl
 计算行号过滤器. nl filename  将会在 stdout 中列出文件的所有内容, 但是会在每个非
 空行的前面加上连续的行号. 如果没有 filename 参数, 那么就操作 stdin.
 nl 命令的输出与 cat -n 非常相似, 然而, 默认情况下 nl 不会列出空行.
Example 12-25 nl: 一个自己计算行号的脚本.
################################Start Script#######################################
 1 #!/bin/bash
 2 # line-number.sh
 3
 4 # 这个脚本将会 echo 自身两次, 并显示行号.
 5
 6 # 'nl' 命令显示的时候你将会看到, 本行是第4行, 因为它不计空行.
 7 # 'cat -n' 命令显示的时候你将会看到, 本行是第6行.
 8
 9 nl `basename $0`
10
11 echo; echo  # 下边, 让我们试试 'cat -n'
12
13 cat -n `basename $0`
14 # 区别就是 'cat -n' 对空行也进行计数.
15 # 注意 'nl -ba' 也会这么做.
16
17 exit 0
18 # -----------------------------------------------------------------
################################End Script#########################################
pr
 格式化打印过滤器. 这个命令会将文件(或stdout)分页, 将它们分成合适的小块以便于硬
 拷贝打印或者在屏幕上浏览.使用这个命令的不同的参数可以完成好多任务, 比如对行和列
 的操作,加入行, 设置页边, 计算行号, 添加页眉, 合并文件等等. pr  命令集合了许多命
 令的功能, 比如 nl, paste, fold, column, 和 expand.
 pr -o 5 --width=65 fileZZZ | more  这个命令对fileZZZ进行了比较好的分页,并且打印
 到屏幕上.文件的缩进被设置为5, 总宽度设置为65.
 一个特定的使用选项 -d, 强制隔行打印 (与 sed -G 效果相同).
gettext
 GNU gettext 包是专门用来将程序的输出翻译或者本地化为不同国家语言的工具集.在最开
 始的时候仅仅支持C 语言, 现在已经支持了相当数量的其它程序语言和脚本语言.
 要想查看 gettext  程序 如何在shell脚本中工作. 参见 info 页.
msgfmt
    一个产生2进制消息目录的程序. 这个命令主要用来 本地化.
iconv
 一个可以将文件转化为不同编码格式(字符集)的工具. 这个命令主要用来 本地化.
  1 # 将字符符串由 UTF-8 格式转换为 UTF-16 并且打印到 BookList 中
  2 function write_utf8_string {
  3     STRING=$1
  4     BOOKLIST=$2
  5     echo -n "$STRING" | iconv -f UTF8 -t UTF16 | cut -b 3- | tr -d >> "$BOOKLIST"
  6 }
  7
  8 #  来自于 Peter Knowles' "booklistgen.sh" 脚本
  9 #+ 目的是把文件转换为 Sony Librie 格式.
 10 #  ()
recode
 可以认为这个命令时上边 iconv 命令的一个空想家版本. 这个非常灵活的并可以把整个文
 件都转换为不同编码格式的工具并不是Linux 标准安装的一部分.
TeX, gs
 TeX 和 Postscript  都是文本标记语言, 用来对打印和格式化的视频显示进行预拷贝.
 TeX 是 Donald Knuth 精心制作的排版系统. 通常情况下, 通过编写脚本的手段来把所有
 的选项和参数封装起来一起传到标记语言中是一件很方便的事情.
 Ghostscript  (gs) 是一个 遵循 GPL 的Postscript 解释器.
enscript
 将纯文本文件转换为 PostScript 的工具
 比如, enscript filename.txt -p filename.ps  产生一个 PostScript 输出文件
 filename.ps.
groff, tbl, eqn
 另一种文本标记和显示格式化语言是 groff. 这是一个对传统 UNIX roff/troff 显示和排
 版包的 GNU 增强版本.Man页  使用的就是 groff.
 tbl 表处理工具可以认为是 groff 的一部分, 它的功能就是将表标记转化到 groff
 命令中.
 eqn 等式处理工具也是 groff 的一部分, 它的功能是将等式标记转化到 groff 命令中.
Example 12-26 manview: 查看格式化的man页
################################Start Script#######################################
 1 #!/bin/bash
 2 # manview.sh: 将man页源文件格式化以方便查看.
 3
 4 #  当你想阅读man页的时候, 这个脚本就有用了.
 5 #  它允许你在运行的时候查看
 6 #+ 中间结果.
 7
 8 E_WRONGARGS=65
 9
10 if [ -z "$1" ]
11 then
12   echo "Usage: `basename $0` filename"
13   exit $E_WRONGARGS
14 fi
15
16 # ---------------------------
17 groff -Tascii -man $1 | less
18 # 来自于 groff man页.
19 # ---------------------------
20
21 #  如果man业中包括表或者等式,
22 #+ 那么上边的代码就够呛了.
23 #  下边的这行代码可以解决上边的这个问题.
24 #
25 #   gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man
26 #
27 #   Thanks, S.C.
28
29 exit 0
################################End Script#########################################
lex, yacc
 lex 是用于模式匹配的词汇分析产生程序. 在Linux系统上这个命令已经被 flex 取代了.
 yacc 工具基于一系列的语法规范生成语法分析程序. 在Linux系统上这个命令已经被
 bison 取代了.
注意事项:
[1]  对于 GNU 版本的 tr 命令来说这是唯一一处比那些商业 UNIX 系统上的一般版本合适
  的地方.

12.5 文件与归档命令
-------------------
归档命令
tar
    标准的 UNIX 归档工具. [1] 起初这只是一个 磁带 归档 程序, 而现在这个工具已经被开
 发为通用打包程序, 它能够处理所有设备的所有类型的归档文件, 包括磁带设备, 正常文
 件, 甚至是 stdout (参见Example 3-4). GNU 的tar工具现在可以接受不同种类的压缩过
 滤器, 比如tar czvf archive_name.tar.gz *, 并且可以递归的处理归档文件, 还可以用
 gzip 压缩目录下的所有文件, 除了当前目录下($PWD)的 点文件 . [2]
 一些有用的 tar 命令选项:
  1.  -c 创建 (一个新的归档文件)
  2.  -x 解压文件 (从存在的归档文件中)
  3.  --delete 删除文件 (从存在的归档文件中)
  注意: 这个选项不能用于磁带类型设备.
  4.  -r 将文件添加到现存的归档文件的尾部
  5.  -A 将 tar 文件添加到现存的归档文件的尾部
  6.  -t 列出现存的归档文件中包含的内容
  7.  -u 更新归档文件
  8.  -d 使用指定的文件系统 比较归档文件
  9.  -z 用 gzip 压缩归档文件
  (压缩还是解压, 依赖于是否组合了 -c 或 -x)选项
 10.  -j 用 bzip2 压缩归档文件
 注意: 如果想从损坏的用 gzip 压缩过的 tar 文件中取得数据, 那将是很困难的. 所有当
  我们归档重要的文件的时候, 一定要保留多个备份.
shar
 Shell 归档工具. 存在于 shell 归档文件中的所有文件都是未经压缩的, 并且本质上是一
 个shell 脚本,以 #!/bin/sh 开头, 并且包含所有必要的解档命令. Shar 归档文件  至今
 还在 Internet 新闻组中使用, 否则的话 shar早就被 tar/gzip 所取代了. unshar 命令
 用来解档 shar 归档文件.
ar
 创建和操作归档文件的工具, 主要在对2进制目标文件打包成库时才会用到.
rpm
 Red Hat 包管理器, 或者说 rpm 工具提供了一种对源文件或2进制文件进行打包的方法.
 除此之外, 它还包括安装命令, 并且还检查包的完整性.
 一个简单的 rpm -i package_name.rpm  命令对于安装一个包来说就足够了, 虽然这个命
 令还有好多其它的选项.
 注意: rpm -qf 列出一个文件属于那个包.
   bash$ rpm -qf /bin/ls
   coreutils-5.2.1-3
 注意: rpm -qa 将会列出给定系统上所有安装了的 rpm 包. rpm -qa package_name 命令
  将会列出于给定名字匹配的包.
   bash$ rpm -qa
   redhat-logos-1.1.3-1
   glibc-2.2.4-13
   cracklib-2.7-12
   dosfstools-2.7-1
   gdbm-1.8.0-10
   ksymoops-2.4.1-1
   mktemp-1.5-11
   perl-5.6.0-17
   reiserfs-utils-3.x.0j-2
   ...
  
  
   bash$ rpm -qa docbook-utils
   docbook-utils-0.6.9-2
  
  
   bash$ rpm -qa docbook | grep docbook
   docbook-dtd31-sgml-1.0-10
   docbook-style-dsssl-1.64-3
   docbook-dtd30-sgml-1.0-10
   docbook-dtd40-sgml-1.0-11
   docbook-utils-pdf-0.6.9-2
   docbook-dtd41-sgml-1.0-10
   docbook-utils-0.6.9-2
cpio
 这个特殊的归档拷贝命令(拷贝输入和输出)现在已经很少能见到了, 因为它已经被 tar/gz
 ip 所替代了.现在这个命令只在一些比较特殊的地方还在使用,比如拷贝一个目录树.
Example 12-27 使用 cpio 来拷贝一个目录树
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 # 使用 'cpio' 拷贝目录树.
 4
 5 # 使用 'cpio' 的优点:
 6 #   加速拷贝. 比通过管道使用 'tar' 命令快一些.
 7 #   很适合拷贝一些 'cp' 命令
 8 #+  搞不定的的特殊文件(比如名字叫 pipes 的文件, 等等)
 9
10 ARGS=2
11 E_BADARGS=65
12
13 if [ $# -ne "$ARGS" ]
14 then
15   echo "Usage: `basename $0` source destination"
16   exit $E_BADARGS
17 fi 
18
19 source=$1
20 destination=$2
21
22 find "$source" -depth | cpio -admvp "$destination"
23 #               ^^^^^         ^^^^^
24 # 阅读 'find' 和 'cpio' 的man 页来了解这些选项的意义.
25
26
27 # 练习:
28 # -----
29
30 #  添加一些代码来检查 'find | cpio' 管道命令的退出码($?)
31 #+ 并且如果出现错误的时候输出合适的错误码.
32
33 exit 0
################################End Script#########################################
rpm2cpio
 这个命令可以从 rpm 归档文件中解出一个 cpio 归档文件.
Example 12-28 解包一个 rpm 归档文件
################################Start Script#######################################
 1 #!/bin/bash
 2 # de-rpm.sh: 解包一个 'rpm' 归档文件
 3
 4 : ${1?"Usage: `basename $0` target-file"}
 5 # 必须指定 'rpm' 归档文件名作为参数.
 6
 7
 8 TEMPFILE=$$.cpio                         # Tempfile 必须是一个"唯一"的名字.
 9                                          # $$ 是这个脚本的进程 ID.
10
11 rpm2cpio < $1 > $TEMPFILE                # 将 rpm 归档文件转换为 cpio 归档文件.
12 cpio --make-directories -F $TEMPFILE -i  # 解包 cpio 归档文件.
13 rm -f $TEMPFILE                          # 删除 cpio 归档文件.
14
15 exit 0
16
17 #  练习:
18 #  添加一些代码来检查    1) "target-file" 是否存在
19 #+                       2) 这个文件是否是一个 rpm 归档文件.
20 #  暗示:                    分析 'file' 命令的输出.
################################End Script#########################################
压缩命令
gzip
 标准的 GNU/UNIX 压缩工具, 取代了比较差的 compress 命令. 相应的解压命令是gunzip,
  gzip -d 是等价的.
 zcat 过滤器可以将一个 gzip 文件解压到 stdout, 所以尽可能的使用管道和重定向. 这
 个命令事实上就是一个可以工作于压缩文件(包括一些的使用老的 compress 工具压缩的文
 件)的 cat 命令. zcat 命令等价于 gzip -dc.
 注意: 在某些商业的 UNIX 系统上, zcat  与 uncompress -c 等价, 并且不能工作于
  gzip  文件.
 参见 Example 7-7.
bzip2
 用来压缩的一个可选的工具, 通常比 gzip 命令压缩率更高(所以更慢), 适用于比较大的
 文件. 相应的解压命令是 bunzip2.
 注意: 新版本的 tar 命令已经直接支持 bzip2 了.
compress, uncompress
 这是一个老的, 私有的压缩工具, 一般的商业 UNIX 发行版都会有这个工具. 更有效率的
 gzip 工具早就把这个工具替换掉了. Linux 发行版一般也会包含一个兼容的 compress 命
 令, 虽然 gunzip 也可以加压用 compress 工具压缩的文件.
 注意: znew 命令可以将 compress 压缩的文件转换为 gzip 压缩的文件.
sq
 另一种压缩工具, 一个只能工作于排过序的 ASCII 单词列表的过滤器.这个命令使用过滤
 器标准的调用语法, sq < input-file >  output-file. 速度很快, 但是效率远不及
 gzip. 相应的解压命令为 unsq, 调用方法与 sq 相同.
 注意: sq 的输出可以通过管道传递给 gzip 以便于进一步的压缩.
zip, unzip
 跨平台的文件归档和压缩工具, 与 DOS 下的 pkzip.exe 兼容. zip 归档文件看起来在互
 联网上比 tar 包更流行.
unarc, unarj, unrar
 这些 Linux 工具可以用来解档那些用 DOS 下的 arc.exe, arj.exe, 和 rar.exe 程序进
 行归档的文件.
文件信息
file
 确定文件类型的工具. 命令 file file-name 将会用 ascii 文本或数据的形式返回
 file-name 文件的详细描述. 这个命令会使用 /usr/share/magic, /etc/magic, 或
 /usr/lib/magic 中定义的 魔法数字  来标识包含某种魔法数字的文件, 上边所举出的这
 3个文件需要依赖于具体的 Linux/UNIX 发行版.
 -f 选项将会让 file 命令运行于批处理模式, 也就是说它会分析 -f 后边所指定的文件,
 从中读取需要处理的文件列表, 然后依次执行 file 命令. -z 选项, 当对压缩过的目标文
 件使用时, 将会强制分析压缩的文件类型.
  bash$ file test.tar.gz
  test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix
 
  bash file -z test.tar.gz
  test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix)

  1 # 在给定的目录中找出sh和Bash脚本文件:
  2
  3 DIRECTORY=/usr/local/bin
  4 KEYWORD=Bourne
  5 # Bourne 和 Bourne-Again shell 脚本
  6
  7 file $DIRECTORY/* | fgrep $KEYWORD
  8
  9 # 输出:
 10
 11 # /usr/local/bin/burn-cd:          Bourne-Again shell script text executable
 12 # /usr/local/bin/burnit:           Bourne-Again shell script text executable
 13 # /usr/local/bin/cassette.sh:      Bourne shell script text executable
 14 # /usr/local/bin/copy-cd:          Bourne-Again shell script text executable
 15 # . . .
Example 12-29 从 C 文件中去掉注释
################################Start Script#######################################
 1 #!/bin/bash
 2 # strip-comment.sh: 去掉C 程序中的注释 (/* 注释 */)
 3
 4 E_NOARGS=0
 5 E_ARGERROR=66
 6 E_WRONG_FILE_TYPE=67
 7
 8 if [ $# -eq "$E_NOARGS" ]
 9 then
10   echo "Usage: `basename $0` C-program-file" >&2 # 将错误消息发到 stderr.
11   exit $E_ARGERROR
12 fi 
13
14 # 检查文件类型是否正确.
15 type=`file $1 | awk '{ print $2, $3, $4, $5 }'`
16 # "file $1" echoe 出文件类型 . . .
17 # 然后 awk 会删掉第一个域,  就是文件名 . . .
18 # 然后结果将会传递到变量 "type" 中.
19 correct_type="ASCII C program text"
20
21 if [ "$type" != "$correct_type" ]
22 then
23   echo
24   echo "This script works on C program files only."
25   echo
26   exit $E_WRONG_FILE_TYPE
27 fi 
28
29
30 # 相当隐秘的 sed 脚本:
31 #--------
32 sed '
33 /^\/\*/d
34 /.*\*\//d
35 ' $1
36 #--------
37 # 如果你花上几个小时来学习 sed 语法的话, 上边这个命令还是很好理解的.
38
39
40 #  如果注释和代码在同一行上, 上边的脚本就不行了.
41 #+ 所以需要添加一些代码来处理这种情况.
42 #  这是一个很重要的练习.
43
44 #  当然, 上边的代码也会删除带有 "*/" 的非注释行 --
45 #+ 这也不是一个令人满意的结果.
46
47 exit 0
48
49
50 # ----------------------------------------------------------------
51 # 下边的代码不会执行, 因为上边已经 'exit 0' 了.
52
53 # Stephane Chazelas 建议使用下边的方法:
54
55 usage() {
56   echo "Usage: `basename $0` C-program-file" >&2
57   exit 1
58 }
59
60 WEIRD=`echo -n -e '\377'`   # or WEIRD=$'\377'
61 [[ $# -eq 1 ]] || usage
62 case `file "$1"` in
63   *"C program text"*) sed -e "s%/\*%${WEIRD}%g;s%\*/%${WEIRD}%g" "$1" \
64      | tr '\377\n' '\n\377' \
65      | sed -ne 'p;n' \
66      | tr -d '\n' | tr '\377' '\n';;
67   *) usage;;
68 esac
69
70 #  如果是下列的这些情况, 还是很糟糕:
71 #  printf("/*");
72 #  or
73 #  /*  /* buggy embedded comment */
74 #
75 #  为了处理上边所有这些特殊情况(字符串中的注释, 含有 \", \\" ...
76 #+ 的字符串中的注释) 唯一的方法还是写一个 C 分析器
77 #+ (或许可以使用lex 或者 yacc ?).
78
79 exit 0
################################End Script#########################################
which
 which command-xxx 将会给出 "command-xxx" 的完整路径. 当你想在系统中准确定位一个
 特定的命令或工具的时候, 这个命令就非常有用了.
 $bash which rm
  /usr/bin/rm
whereis
 与上边的 which 很相似, whereis command-xxx 不只会给出 "command-xxx" 的完整路径,
  而且还会给出这个命令的 man页 的完整路径.
 $bash whereis rm
  rm: /bin/rm /usr/share/man/man1/rm.1.bz2
whatis
 whatis filexxx 将会在 whatis 数据库中查询 "filexxx". 当你想确认系统命令和重要的
 配置文件的时候, 这个命令就非常重要了. 可以把这个命令认为是一个简单的 man 命令.
 $bash whatis whatis
  whatis               (1)  - search the whatis database for complete words
Example 12-30 Exploring /usr/X11R6/bin
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 # 在 /usr/X11R6/bin 中的所有神秘的2进制文件都是什么东西?
 4
 5 DIRECTORY="/usr/X11R6/bin"
 6 # 也试试 "/bin", "/usr/bin", "/usr/local/bin", 等等.
 7
 8 for file in $DIRECTORY/*
 9 do
10   whatis `basename $file`   # 将会 echo 出这个2进制文件的信息.
11 done
12
13 exit 0
14
15 # 你可能希望将这个脚本的输出重定向, 像这样:
16 # ./what.sh >>whatis.db
17 # 或者一页一页的在 stdout 上查看,
18 # ./what.sh | less
################################End Script#########################################
 参见 Example 10-3.
vdir
 显示详细的目录列表. 与 ls -l 的效果类似.
 这是一个 GNU fileutils.
  bash$ vdir
  total 10
  -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
  -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
  -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo
 
  bash ls -l
  total 10
  -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
  -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
  -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo
locate, slocate
 locate 命令将会在预先建立好的档案数据库中查询文件. slocate 命令是 locate 的安全
 版本( locate 命令可能已经被关联到 slocate 命令上了).
 $bash locate hickson
  /usr/lib/xephem/catalogs/hickson.edb
readlink
 显示符号连接所指向的文件.
  bash$ readlink /usr/bin/awk
  ../../bin/gawk
strings
 使用 strings 命令在二进制或数据文件中找出可打印字符. 它将在目标文件中列出所有找
 到的可打印字符的序列. 这个命令对于想进行快速查找一个 n 个字符的打印检查来说是很
 方便的,也可以用来检查一个未知格式的图片文件 (strings image-file | more 可能会搜
 索出像 JFIF 这样的字符串, 那么这就意味着这个文件是一个 jpeg  格式的图片文件).
 在脚本中, 你可能会使用 grep 或 sed 命令来分析 strings 命令的输出. 参见
 Example 10-7  和 Example 10-9.
Example 12-31 一个"改进过"的 strings  命令
################################Start Script#######################################
 1 #!/bin/bash
 2 # wstrings.sh: "word-strings" (增强的 "strings" 命令)
 3 #
 4 #  这个脚本将会过滤 "strings" 命令的输出.
 5 #+ 通过排除标准单词列表的形式检查来过滤输出.
 6 #  这将有效的过滤掉无意义的字符,
 7 #+ 并且指挥输出可以识别的字符.
 8
 9 # ===========================================================
10 #                 脚本参数的标准检查
11 ARGS=1
12 E_BADARGS=65
13 E_NOFILE=66
14
15 if [ $# -ne $ARGS ]
16 then
17   echo "Usage: `basename $0` filename"
18   exit $E_BADARGS
19 fi
20
21 if [ ! -f "$1" ]                      # 检查文件是否存在.
22 then
23     echo "File \"$1\" does not exist."
24     exit $E_NOFILE
25 fi
26 # ===========================================================
27
28
29 MINSTRLEN=3                           #  最小的字符串长度.
30 WORDFILE=/usr/share/dict/linux.words  #  字典文件.
31                                       #  也可以指定一个不同的
32                                       #+ 单词列表文件,
33                                       #+ 但这种文件必须是以每个单词一行的方式进行保存.
34
35
36 wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
37 tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`
38
39 # 将'strings' 命令的输出通过管道传递到多个 'tr' 命令中.
40 #  "tr A-Z a-z"  全部转换为小写字符.
41 #  "tr '[:space:]'"  转换空白字符为多个 Z.
42 #  "tr -cs '[:alpha:]' Z"  将非字母表字符转换为多个 Z,
43 #+ 然后去除多个连续的 Z.
44 #  "tr -s '\173-\377' Z"  把所有z后边的字符都转换为 Z.
45 #+ 并且去除多余重复的Z.(注意173(123 ascii "{")和377(255 ascii 最后一个字符)都是8进制)
46 #+ 这样处理之后, 我们所有之前需要处理的令我们头痛的字符
47 #+ 就全都转换为字符 Z 了.
48 #  最后"tr Z ' '" 将把所有的 Z 都转换为空格,
49 #+ 这样我们在下边循环中用到的变量 wlist 中的内容就全部以空格分隔了.
50
51 #  ****************************************************************
52 #  注意, 我们使用管道来将多个 'tr' 的输出传递到下一个 'tr' 时
53 #+ 每次都使用了不同的参数.
54 #  ****************************************************************
55
56
57 for word in $wlist                    # 重要:
58                                       # $wlist 这里不能使用双引号.
59                                       # "$wlist" 不能正常工作.
60                                       # 为什么不行?
61 do
62
63   strlen=${#word}                     # 字符串长度.
64   if [ "$strlen" -lt "$MINSTRLEN" ]   # 跳过短的字符串.
65   then
66     continue
67   fi
68
69   grep -Fw $word "$WORDFILE"          #  只匹配整个单词.
70 #      ^^^                            #  "固定字符串" 和
71                                       #+ "整个单词" 选项.
72
73 done 
74
75
76 exit $?
################################End Script#########################################
比较命令
diff, patch
 diff: 一个非常灵活的文件比较工具. 这个工具将会以一行接一行的形式来比较目标文件.
  在某些应用中, 比如说比较单词词典, 在通过管道将结果传递给 diff 命令之前, 使用诸
 如 sort 和 uniq 命令来对文件进行过滤将是非常有用的.diff file-1 file-2 将会输出
 2个文件不同的行,并会通过符号标识出每个不同行所属的文件.
 diff 命令的 --side-by-side 选项将会把2个比较中的文件全部输出, 按照左右分隔的形
 式, 并会把不同的行标记出来. -c 和 -u 选项也会使得 diff 命令的输出变得容易解释
 一些.
 还有一些 diff 命令的变种, 比如 sdiff, wdiff, xdiff, 和 mgdiff.
 注意: 如果比较的两个文件是完全一样的话, 那么 diff 命令会返回 0 作为退出码, 如果
  不同的话就返回 1 作为退出码. 这样 diff 命令就可以用在 shell 脚本的测试结构
  中了. (见下边)
 diff 命令的一个重要用法就是产生区别文件, 这个文件将用作 patch 命令的 -e 选项的
 参数, -e 选项接受 ed 或 ex 脚本.
 patch: 灵活的版本工具.给出一个用 diff 命令产生的区别文件, patch 命令可以将一个
 老版本的包更新为一个新版本的包. 因为你发布一个小的区别文件远比重新发布一个大的
 软件包来的容易得多.对于频繁更新的 Linux 内核来说, 使用补丁包的形式来发布将是一
 种很好的方法.
    1 patch -p1     2 # 在'patch-file'中取得所有的修改列表
    3 # 然后把它们应用于其中索引到的文件上.
    4 # 那么这个包就被更新为新版本了.
 更新 kernel:
    1 cd /usr/src
    2 gzip -cd patchXX.gz | patch -p0
    3 #  使用'patch'来更新内核源文件.
    4 # 来自于匿名作者(Alan Cox?)的
    5 # Linux 内核文档 "README".
 注意: diff 命令也可以递归的比较目录下的所有文件(包含子目录).
   bash$ diff -r ~/notes1 ~/notes2
   Only in /home/bozo/notes1: file02
   Only in /home/bozo/notes1: file03
   Only in /home/bozo/notes2: file04
 注意: 使用 zdiff 来比较 gzip 文件.
diff3
 一个 diff 命令的扩展版本, 可以同时比较3个文件. 如果成功执行那么这个命令就返回0,
  但是不幸的是这个命令不给出比较结果的信息.
  bash$ diff3 file-1 file-2 file-3
  ====
  1:1c
    This is line 1 of "file-1".
  2:1c
    This is line 1 of "file-2".
  3:1c
    This is line 1 of "file-3"
sdiff
 比较 和/或 编辑2个文件, 将它们合并到一个输出文件中. 因为这个命令的交互特性, 所
 以在脚本中很少使用这个命令.
cmp
 cmp 命令是上边 diff 命令的一个简单版本. diff  命令会报告两个文件的不同之处, 而
 cmp 命令仅仅指出那些位置有不同, 而不会显示不同的具体细节.
 注意: 与 diff 一样,如果两个文件相同 cmp  返回0作为退出码, 如果不同返回1. 这样就
  可以用在 shell 脚本的测试结构中了.
Example 12-32 在一个脚本中使用 cmp 来比较2个文件.
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 ARGS=2  # 脚本需要2个参数.
 4 E_BADARGS=65
 5 E_UNREADABLE=66
 6
 7 if [ $# -ne "$ARGS" ]
 8 then
 9   echo "Usage: `basename $0` file1 file2"
10   exit $E_BADARGS
11 fi
12
13 if [[ ! -r "$1" || ! -r "$2" ]]
14 then
15   echo "Both files to be compared must exist and be readable."
16   exit $E_UNREADABLE
17 fi
18
19 cmp $1 $2 &> /dev/null  # /dev/null 将会禁止 "cmp" 命令的输出.
20 #   cmp -s $1 $2  与上边这句结果相同 ("-s" 选项是安静标志)
21 #   Thank you  Anders Gustavsson for pointing this out.
22 #
23 # 用 'diff' 命令也可以, 比如,   diff $1 $2 &> /dev/null
24
25 if [ $? -eq 0 ]         # 测试 "cmp" 命令的退出码.
26 then
27   echo "File \"$1\" is identical to file \"$2\"."
28 else 
29   echo "File \"$1\" differs from file \"$2\"."
30 fi
31
32 exit 0
################################End Script#########################################
 注意: 用 zcmp 处理 gzip 文件.
comm
 多功能的文件比较工具. 使用这个命令之前必须先排序.
 comm -options  first-file  second-file
 comm file-1 file-2 将会输出3列:
  * 第 1 列 = 只在 file-1 中存在的行
  * 第 2 列 = 只在 file-2 中存在的行
  * 第 2 列 = 两边相同的行.
 下列选项可以禁止1列或多列的输出.
  * -1 禁止显示第一栏 (译者: 在 File1 中的行)
  * -2 禁止显示第二栏 (译者: 在 File2 中的行)
  * -3 禁止显示第三栏 (译者: File1 和 File2 公共的行)
  * -12 禁止第一列和第二列, (就是说选项可以组合).
一般工具
basename
 从文件名中去掉路径信息, 只打印出文件名. 结构 basename $0 可以让脚本知道它自己的
 名字, 也就是, 它被调用的名字. 可以用来显示用法信息, 比如如果你调用脚本的时候缺
 少参数, 可以使用如下语句:
    1 echo "Usage: `basename $0` arg1 arg2 ... argn"
dirname
 从带路径的文件名中去掉文件名, 只打印出路径信息.
 注意: basename 和 dirname  可以操作任意字符串. 参数可以不是一个真正存在的文件,
  甚至可以不是一个文件名.(参见 Example A-7).
Example 12-33 basename 和 dirname
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 a=/home/bozo/daily-journal.txt
 4
 5 echo "Basename of /home/bozo/daily-journal.txt = `basename $a`"
 6 echo "Dirname of /home/bozo/daily-journal.txt = `dirname $a`"
 7 echo
 8 echo "My own home is `basename ~/`."         # `basename ~` also works.
 9 echo "The home of my home is `dirname ~/`."  # `dirname ~`  also works.
10
11 exit 0
################################End Script#########################################
split, csplit
 将一个文件分割为几个小段的工具. 这些命令通常用来将大的文件分割, 并备份到软盘上,
  或者是为了切成合适的尺寸用 email 上传.
 csplit 根据 上下文 来切割文件, 切割的位置将会发生在模式匹配的地方.
sum, cksum, md5sum, sha1sum
 这些都是用来产生 checksum 的工具. checksum 的目的是用来检验文件的完整性, 是对文
 件的内容进行数学计算而得到的. 出于安全目的一个脚本可能会有一个 checksum 列表,
 这样可以确保关键系统文件的内容不会被修改或损坏. 对于需要安全性的应用来说, 应该
 使用 md5sum (message digest 5  checksum) 命令, 或者更好的更新的 sha1sum
 (安全 Hash 算法).
  bash$ cksum /boot/vmlinuz
  1670054224 804083 /boot/vmlinuz
 
  bash$ echo -n "Top Secret" | cksum
  3391003827 10
 
 
 
  bash$ md5sum /boot/vmlinuz
  0f43eccea8f09e0a0b2b5cf1dcf333ba  /boot/vmlinuz
 
  bash$ echo -n "Top Secret" | md5sum
  8babc97a6f62a4649716f4df8d61728f  -
 注意: cksum 命令将会显示目标的尺寸(字节), 目标可以使文件或 stdout.
  md5sum 和 sha1sum 命令在它们收到 stdout 的输入时候, 显示一个 dash .
Example 12-34 检查文件完整性
################################Start Script#######################################
  1 #!/bin/bash
  2 # file-integrity.sh: 检查一个给定目录下的文件
  3 #                    是否被改动了.
  4
  5 E_DIR_NOMATCH=70
  6 E_BAD_DBFILE=71
  7
  8 dbfile=File_record.md5
  9 # 存储记录的文件名 (数据库文件).
 10
 11
 12 set_up_database ()
 13 {
 14   echo ""$directory"" > "$dbfile"
 15   # 把目录名写到文件的第一行.
 16   md5sum "$directory"/* >> "$dbfile"
 17   # 在文件中附上  md5 checksums 和 filenames.
 18 }
 19
 20 check_database ()
 21 {
 22   local n=0
 23   local filename
 24   local checksum
 25
 26   # ------------------------------------------- #
 27   #  这个文件检查其实是不必要的,
 28   #+ 但是能安全一些.
 29
 30   if [ ! -r "$dbfile" ]
 31   then
 32     echo "Unable to read checksum database file!"
 33     exit $E_BAD_DBFILE
 34   fi
 35   # ------------------------------------------- #
 36
 37   while read record[n]
 38   do
 39
 40     directory_checked="${record[0]}"
 41     if [ "$directory_checked" != "$directory" ]
 42     then
 43       echo "Directories do not match up!"
 44       # 换个目录试一下.
 45       exit $E_DIR_NOMATCH
 46     fi
 47
 48     if [ "$n" -gt 0 ]   # 不是目录名.
 49     then
 50       filename[n]=$( echo ${record[$n]} | awk '{ print $2 }' )
 51       #  md5sum 向后写记录,
 52       #+ 先写 checksum, 然后写 filename.
 53       checksum[n]=$( md5sum "${filename[n]}" )
 54
 55
 56       if [ "${record[n]}" = "${checksum[n]}" ]
 57       then
 58         echo "${filename[n]} unchanged."
 59
 60       elif [ "`basename ${filename[n]}`" != "$dbfile" ]
 61              #  跳过checksum 数据库文件,
 62              #+ 因为在每次调用脚本它都会被修改.
 63       #  ---
 64       #  这不幸的意味着当我们在 $PWD中运行这个脚本
 65       #+ 时, 修改这个 checksum 数
 66       #+ 据库文件将不会被检测出来.
 67       #  练习: 修复这个问题.
 68  then
 69           echo "${filename[n]} : CHECKSUM ERROR!"
 70         # 因为最后的检查, 文件已经被修改.
 71       fi
 72
 73       fi
 74
 75
 76
 77     let "n+=1"
 78   done <"$dbfile"       # 从 checksum 数据库文件中读.
 79
 80 } 
 81
 82 # =================================================== #
 83 # main ()
 84
 85 if [ -z  "$1" ]
 86 then
 87   directory="$PWD"      #  如果没制定参数,
 88 else                    #+ 那么就使用当前的工作目录.
 89   directory="$1"
 90 fi 
 91
 92 clear                   # 清屏.
 93 echo " Running file integrity check on $directory"
 94 echo
 95
 96 # ------------------------------------------------------------------ #
 97   if [ ! -r "$dbfile" ] # 是否需要建立数据库文件?
 98   then
 99     echo "Setting up database file, \""$directory"/"$dbfile"\"."; echo
100     set_up_database
101   fi 
102 # ------------------------------------------------------------------ #
103
104 check_database          # 调用主要处理函数.
105
106 echo
107
108 #  你可能想把这个脚本的输出重定向到文件中,
109 #+ 尤其在这个目录中有很多文件的时候.
110
111 exit 0
112
113 #  如果要对数量非常多的文件做完整性检查,
114 #+ 可以考虑一下 "Tripwire" 包,
115 #+ .
116
################################End Script#########################################
 参见 Example A-19 和 Example 33-14 , 这两个例子展示了 md5sum 命令的用法.
 注意: 已经有 128-bit md5sum 被破解的报告了,所以现在更安全的 160-bit sha1sum 是
  非常受欢迎的, 并且已经被加入到 checksum 工具包中.
  一些安全顾问认为即使是 sha1sum 也是会被泄漏的. 所以, 下一个工具是什么呢?
  -- 512-bit 的 checksum 工具?
   bash$ md5sum testfile
   e181e2c8720c60522c4c4c981108e367  testfile
  
  
   bash$ sha1sum testfile
   5d7425a9c08a66c3177f1e31286fa40986ffc996  testfile
shred
 用随机字符填充文件, 使得文件无法恢复, 这样就可以保证文件安全的被删除. 这个命令
 的效果与 Example 12-55 一样, 但是使用这个命令是一种更优雅更彻底的方法.
 这是一个 GNU fileutils.
 注意: 即使使用了 shred 命令, 高级的(forensic)辩论技术还是能够恢复文件的内容.
编码和解码
uuencode
 这个工具用来把二进制文件编码成 ASCII 字符串,这个工具适用于编码e-mail消息体,或者
 新闻组消息.
uudecode
 这个工具用来把 uuencode 后的 ASCII 字符串恢复为二进制文件.
Example 12-35 Uudecod 编码后的文件
################################Start Script#######################################
 1 #!/bin/bash
 2 # 在当前目录下 uudecode 所有用 uuencode 编码的文件.
 3
 4 lines=35        # 允许读头部的 35 行(范围很宽).
 5
 6 for File in *   # Test 所有 $PWD 下的文件.
 7 do
 8   search1=`head -$lines $File | grep begin | wc -w`
 9   search2=`tail -$lines $File | grep end | wc -w`
10   #  Uuencode 过的文件在文件开始的地方有个 "begin",
11   #+ 在文件结尾的地方有个 "end".
12   if [ "$search1" -gt 0 ]
13   then
14     if [ "$search2" -gt 0 ]
15     then
16       echo "uudecoding - $File -"
17       uudecode $File
18     fi 
19   fi
20 done 
21
22 #  小心不要让这个脚本运行自己,
23 #+ 因为它也会把自身也认为是一个 uuencoded 文件,
24 #+ 这都是因为这个脚本自身也包含 "begin" 和 "end".
25
26 #  练习:
27 #  -----
28 #  修改这个脚本, 让它可以检查一个新闻组的每个文件,
29 #+ 并且如果下一个没找的话就跳过.
30
31 exit 0
################################End Script#########################################
 注意: fold -s 命令在处理从 Usenet 新闻组下载下来的长的uudecode 文本消息的时候可
  能会有用(可能在管道中).
mimencode, mmencode
 mimencode 和 mmencode 命令处理多媒体编码的 email 附件. 虽然 mail 用户代理
 (比如 pine 或 kmail) 通常情况下都会自动处理, 但是这些特定的工具允许从命令行或
 shell脚本中来手动操作这些附件.
crypt
 这个工具曾经是标准的 UNIX 文件加密工具. [3]  政府由于政策上的动机规定禁止加密软
 件的输出, 这样导致了 crypt 命令从 UNIX 世界消失, 并且在大多数的 Linux 发行版中
 也没有这个命令. 幸运的是, 程序员们想出了一些替代它的方法, 在这些方法中有作者自
 己的 cruft (参见 Example A-4).
一些杂项工具
mktemp
 使用一个"唯一"的文件名来创建一个 临时文件  [4]  . 如果不带参数的在命令行下调用
 这个命令时, 将会在 /tmp 目录下产生一个零长度的文件.
  bash$ mktemp
  /tmp/tmp.zzsvql3154
    1 PREFIX=filename
    2 tempfile=`mktemp $PREFIX.XXXXXX`
    3 #                        ^^^^^^ 在这个临时的文件名中
    4 #+                              至少需要6个占位符.
    5 #  如果没有指定临时文件的文件名,
    6 #+ 那么默认就是 "tmp.XXXXXXXXXX".
    7
    8 echo "tempfile name = $tempfile"
    9 # tempfile name = filename.QA2ZpY
   10 #                 或者一些其他的相似的名字...
   11
   12 #  使用 600 为文件权限
   13 #+ 来在当前工作目录下创建一个这样的文件.
   14 #  这样就不需要 "umask 177" 了.
   15 #  但不管怎么说, 这也是一个好的编程风格.
make
 build 和 compile 二进制包的工具. 当源文件被增加或修改时就会触发一些操作, 这个工
 具用来控制这些操作.
 make 命令将会检查 Makefile, makefile 是文件的依赖和操作列表.
install
 特殊目的的文件拷贝命令, 与 cp 命令相似, 但是具有设置拷贝文件的权限和属性的能力.
  这个命令看起来是为了安装软件包所定制的, 而且就其本身而言, 这个命令经常出现在
 Makefile 中(在 make install : 区中). 在安装脚本中也会看到这个命令的使用.
dos2unix
 这个工具是由 Benjamin Lin 和其同事编写的, 目的是将 DOS 格式的文本文件
 (以 CR-LF 为行结束符) 转换为 UNIX 格式 (以 LF 为行结束符), 反过来也一样.
ptx
 ptx [targetfile] 命令将会输出目标文件的序列改变的索引(交叉引用列表). 如果必要的
 话, 这个命令可以在管道中进行更深层次的过滤和格式化.
more, less
 分页显示文本文件或 stdout, 一次一屏.可以用来过滤 stdout 的输出 . . . 或一个脚本
 的输出.
 more 命令的一个有趣的应用就是测试一个命令序列的执行, 来避免可能发生的糟糕的
 结果.
    1 ls /home/bozo | awk '{print "rm -rf " $1}' | more
    2 #                                            ^^^^
    3   
    4 # 检测下边(灾难性的)命令行的效果:
    5 #      ls /home/bozo | awk '{print "rm -rf " $1}' | sh
    6 #      推入 shell 中执行 . . .
注意事项:
[1]  在这里所讨论的一个归档文件, 只不过是存储在一个单一位置上的一些相关文件的
  集合.
[2]  tar czvf archive_name.tar.gz *  可以 包含当前工作目录下的点文件. 这是一个
  未文档化的 GNU tar 的"特征".
[3]  这是一个对称的块密码, 过去曾在单系统或本地网络中用来加密文件, 用来对抗
  "public key" 密码类, pgp 就是一个众所周知的例子.
[4]  使用 -d 选项可以创建一个临时的目录.
 
12.6 通讯命令
-------------
 下边命令中的某几个命令你会在 "追踪垃圾邮件" 练习中找到其用法, 用来进行网络数
据的转换和分析.
信息与统计
host
 通过名字或 IP 地址来搜索一个互联网主机的信息, 使用 DNS.
  bash$ host surfacemail.com
  surfacemail.com. has address 202.92.42.236
ipcalc
 显示一个主机 IP 信息. 使用 -h 选项, ipcalc 将会做一个 DNS 的反向查询, 通过 IP
 地址找到主机(服务器)名.
  bash$ ipcalc -h 202.92.42.236
  HOSTNAME=surfacemail.com
nslookup
 通过 IP 地址在一个主机上做一个互联网的 "名字服务查询". 事实上这与 ipcalc -h 或
 dig -x 等价. 这个命令既可以交互运行也可以非交互运行, 换句话说, 就是在脚本中运
 行.
 nslookup 命令据说已经慢慢被"忽视"了, 但是它还是有它的用处.
  bash$ nslookup -sil 66.97.104.180
  nslookup kuhleersparnis.ch
  Server:         135.116.137.2
  Address:        135.116.137.2#53
  Non-authoritative answer:
  Name:   kuhleersparnis.ch
dig
 域信息查询. 与 nslookup 很相似, dig 在一个主机上做一个互联网的 "名字服务查询".
 这个命令既可以交互运行也可以非交互运行, 换句话说, 就是在脚本中运行.
 下边是一些 dig 命令有趣的选项, +time=N 选项用来设置查询超时为 N 秒, +nofail
 选项用来持续查询服务器直到收到一个响应, -x 选项会做反向地址查询.
 比较下边这3个命令的输出, dig -x , ipcalc -h 和 nslookup.
  bash$ dig -x 81.9.6.2
  ;; Got answer:
  ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11649
  ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
  ;; QUESTION SECTION:
  ;2.6.9.81.in-addr.arpa.         IN      PTR
  ;; AUTHORITY SECTION:
  6.9.81.in-addr.arpa.    3600    IN      SOA     ns.eltel.net. noc.eltel.net.
  2002031705 900 600 86400 3600
  ;; Query time: 537 msec
  ;; SERVER: 135.116.137.2#53(135.116.137.2)
  ;; WHEN: Wed Jun 26 08:35:24 2002
  ;; MSG SIZE  rcvd: 91
Example 12-36 查找滥用的连接来报告垃圾邮件发送者
################################Start Script#######################################
 1 #!/bin/bash
 2 # spam-lookup.sh: 查找滥用的连接来报告垃圾邮件发送者.
 3 # 感谢 Michael Zick.
 4
 5 # 检查命令行参数.
 6 ARGCOUNT=1
 7 E_WRONGARGS=65
 8 if [ $# -ne "$ARGCOUNT" ]
 9 then
10   echo "Usage: `basename $0` domain-name"
11   exit $E_WRONGARGS
12 fi
13
14
15 dig +short $1.contacts.abuse.net -c in -t txt
16 # 也试试:
17 #     dig +nssearch $1
18 #     尽量找到 "可信赖的名字服务器" 并且显示 SOA 记录.
19
20 # 下边这句也可以:
21 #     whois -h whois.abuse.net $1
22 #           ^^ ^^^^^^^^^^^^^^^  指定主机.
23 #     使用这个命令也可以查找多个垃圾邮件发送者, 比如:"
24 #     whois -h whois.abuse.net $spamdomain1 $spamdomain2 . . .
25
26
27 #  练习:
28 #  -----
29 #  扩展这个脚本的功能,
30 #+ 让它可以自动发送 e-mail 来通知
31 #+ 需要对此负责的 ISP 的联系地址.
32 #  暗示: 使用 "mail" 命令.
33
34 exit $?
35
36 # spam-lookup.sh chinatietong.com
37 #                一个已知的垃圾邮件域.(译者: 中国铁通. . .)
38
39 # ""
40 # ""
41 # ""
42
43
44 #  如果想找到这个脚本的一个更详尽的版本,
45 #+ 请访问 SpamViz 的主页, .
################################End Script#########################################
Example 12-37 分析一个垃圾邮件域
################################Start Script#######################################
  1 #! /bin/bash
  2 # is-spammer.sh: 鉴别一个垃圾邮件域
  3
  4 # $Id: is-spammer, v 1.4 2004/09/01 19:37:52 mszick Exp $
  5 # 上边这行是 RCS ID 信息.
  6 #
  7 #  这是附件中捐献脚本 is_spammer.bash
  8 #+ 的一个简单版本.
  9
 10 # is-spammer
 11
 12 # 使用外部程序: 'dig'
 13 # 测试版本: 9.2.4rc5
 14
 15 # 使用函数.
 16 # 使用 IFS 来分析分配在数组中的字符串.
 17 # 检查 e-mail 黑名单.
 18
 19 # 使用来自文本体中的 domain.name:
 20 #
 21 #                       ^^^^^^^^^^^
 22 # 或者使用来自任意 e-mail 地址的 domain.name:
 23 #
 24 #
 25 # 并将其作为这个脚本的唯一参数.
 26 #(另: 你的 Inet 连接应该保证连接)
 27 #
 28 # 这样, 在上边两个实例中调用这个脚本:
 29 #       is-spammer.sh spammer.biz
 30
 31
 32 # Whitespace == :Space:Tab:Line Feed:Carriage Return:
 33 WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D'
 34
 35 # No Whitespace == Line Feed:Carriage Return
 36 No_WSP=$'\x0A'$'\x0D'
 37
 38 # 域分隔符为点分10进制 ip 地址
 39 ADR_IFS=${No_WSP}'.'
 40
 41 # 取得 dns 文本资源记录.
 42 # get_txt
 43 get_txt() {
 44
 45     # 分析在"."中分配的 $1.
 46     local -a dns
 47     IFS=$ADR_IFS
 48     dns=( $1 )
 49     IFS=$WSP_IFS
 50     if [ "${dns[0]}" == '127' ]
 51     then
 52         # 查看此处是否有原因.
 53         echo $(dig +short $2 -t txt)
 54     fi
 55 }
 56
 57 # 取得 dns 地址资源记录.
 58 # chk_adr
 59 chk_adr() {
 60     local reply
 61     local server
 62     local reason
 63
 64     server=${1}${2}
 65     reply=$( dig +short ${server} )
 66
 67     # 假设应答可能是一个错误码 . . .
 68     if [ ${#reply} -gt 6 ]
 69     then
 70         reason=$(get_txt ${reply} ${server} )
 71         reason=${reason:-${reply}}
 72     fi
 73     echo ${reason:-' not blacklisted.'}
 74 }
 75
 76 # 需要从名字中取得 IP 地址.
 77 echo 'Get address of: '$1
 78 ip_adr=$(dig +short $1)
 79 dns_reply=${ip_adr:-' no answer '}
 80 echo ' Found address: '${dns_reply}
 81
 82 # 一个可用的应答至少是4个数字加上3个点.
 83 if [ ${#ip_adr} -gt 6 ]
 84 then
 85     echo
 86     declare query
 87
 88     # 分析点中的分配.
 89     declare -a dns
 90     IFS=$ADR_IFS
 91     dns=( ${ip_adr} )
 92     IFS=$WSP_IFS
 93
 94     # Reorder octets into dns query order.
 95     rev_dns="${dns[3]}"'.'"${dns[2]}"'.'"${dns[1]}"'.'"${dns[0]}"'.'
 96
 97 # 参见: (Conservative, well maintained)
 98     echo -n 'spamhaus.org says: '
 99     echo $(chk_adr ${rev_dns} 'sbl-xbl.spamhaus.org')
100
101 # 参见: (Open mail relays)
102     echo -n '   ordb.org  says: '
103     echo $(chk_adr ${rev_dns} 'relays.ordb.org')
104
105 # 参见: (你可以在这里报告 spammer)
106     echo -n ' spamcop.net says: '
107     echo $(chk_adr ${rev_dns} 'bl.spamcop.net')
108
109 # # # 其他的黑名单操作 # # #
110
111 # 参见: .
112     echo -n ' abuseat.org says: '
113     echo $(chk_adr ${rev_dns} 'cbl.abuseat.org')
114
115 # 参见: (Various mail relays)
116     echo
117     echo 'Distributed Server Listings'
118     echo -n '       list.dsbl.org says: '
119     echo $(chk_adr ${rev_dns} 'list.dsbl.org')
120
121     echo -n '   multihop.dsbl.org says: '
122     echo $(chk_adr ${rev_dns} 'multihop.dsbl.org')
123
124     echo -n 'unconfirmed.dsbl.org says: '
125     echo $(chk_adr ${rev_dns} 'unconfirmed.dsbl.org')
126
127 else
128     echo
129     echo 'Could not use that address.'
130 fi
131
132 exit 0
133
134 # 练习:
135 # -----
136
137 # 1) 检查脚本的参数,
138 #    并且如果必要的话使用合适的错误消息退出.
139
140 # 2) 检查调用这个脚本的时候是否在线,
141 #    并且如果必要的话使用合适的错误消息退出.
142
143 # 3) Substitute generic variables for "hard-coded" BHL domains.
144
145 # 4) 通过对 'dig' 命令使用 "+time=" 选项
146      来给这个脚本设置一个暂停.
################################End Script#########################################
 想获得比上边这个脚本更详细的版本, 参见 Example A-27.
traceroute
 跟踪包发送到远端主机过程中的路由信息. 这个命令在 LAN, WAN, 或者在 Internet 上都
 可以正常工作. 远端主机可以通过 IP 地址来指定. 这个命令的输出也可以通过管道中的
 grep 或 sed 命令来过滤.
  bash$ traceroute 81.9.6.2
  traceroute to 81.9.6.2 (81.9.6.2), 30 hops max, 38 byte packets
  1  tc43.xjbnnbrb.com (136.30.178.8)  191.303 ms  179.400 ms  179.767 ms
  2  or0.xjbnnbrb.com (136.30.178.1)  179.536 ms  179.534 ms  169.685 ms
  3  192.168.11.101 (192.168.11.101)  189.471 ms  189.556 ms *
  ...
ping
 广播一个 "ICMP ECHO_REQUEST" 包到其他主机上, 既可以是本地网络也可以使远端网络.
 这是一个测试网络连接的诊断工具, 应该小心使用.
 一个成功的 ping 返回的 退出码 为 0. 可以用在脚本的测试语句中.
  bash$ ping localhost
  PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.
  64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=0 ttl=255 time=709 usec
  64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=255 time=286 usec
  --- localhost.localdomain ping statistics ---
  2 packets transmitted, 2 packets received, 0% packet loss
  round-trip min/avg/max/mdev = 0.286/0.497/0.709/0.212 ms
whois
 执行DNS (域名系统) 查询lookup. -h 选项允许指定需要查询的特定的 whois 服务器.
 参见 Example 4-6 和 Example 12-36.
finger
 取得网络上的用户信息. 另外这个命令可以显示一个用户的~/.plan, ~/.project, 和
 ~/.forward 文件, 如果存在的话.
  bash$ finger
  Login  Name           Tty      Idle  Login Time   Office     Office Phone
  bozo   Bozo Bozeman   tty1        8  Jun 25 16:59
  bozo   Bozo Bozeman   ttyp0          Jun 25 16:59
  bozo   Bozo Bozeman   ttyp1          Jun 25 17:07
 
 
 
  bash$ finger bozo
  Login: bozo                             Name: Bozo Bozeman
  Directory: /home/bozo                   Shell: /bin/bash
  Office: 2355 Clown St., 543-1234
  On since Fri Aug 31 20:13 (MST) on tty1    1 hour 38 minutes idle
  On since Fri Aug 31 20:13 (MST) on pts/0   12 seconds idle
  On since Fri Aug 31 20:13 (MST) on pts/1
  On since Fri Aug 31 20:31 (MST) on pts/2   1 hour 16 minutes idle
  No mail.
  No Plan.
 处于安全上的考虑, 许多网络都禁用了 finger 以及和它相关的幽灵进程. [1]
chfn
 修改 finger 命令所显示出来的用户信息.
vrfy
 验证一个互联网的 e-mail 地址.
远端主机接入
sx, rx
 sx 和 rx 命令使用 xmodem 协议, 设置服务来向远端主机传输文件和接收文件. 这些都
 是通讯安装包的一般部分, 比如 minicom.
sz, rz
 sz 和 rz 命令使用 zmodem 协议, 设置服务来向远端主机传输文件和接收文件. zmodem
 协议在某些方面比 xmodem强, 比如使用更快的的传输波特率, 并且可以对中断的文件进
 行续传.与 sx 一样 rx, 这些都是通讯安装包的一般部分.
ftp
 向远端服务器上传或下载的工具和协议. 一个ftp会话可以写到脚本中自动运行. (见
 Example 17-6, Example A-4, 和 Example A-13).
uucp, uux, cu
 uucp: UNIX 到 UNIX 拷贝. 这是一个通讯安装包, 目的是为了在 UNIX 服务器之间传输
 文件. 使用 shell 脚本来处理 uucp 命令序列是一种有效的方法.
 因为互联网和电子邮件的出现, uucp 现在看起来已经很落伍了, 但是这个命令在互联网
 连接不可用或者不适合使用的地方, 这个命令还是可以完美的运行. uucp 的优点就是它
 的容错性, 即使有一个服务将拷贝操作中断了, 那么当连接恢复的时候, 这个命令还是
 可以在中断的地方续传.
 ---
 uux: UNIX 到 UNIX 执行. 在远端系统上执行一个命令.这个命令是 uucp 包的一部分.
 ---
 cu: Call Up 一个远端系统并且作为一个简单终端进行连接. 这是一个 telnet 的缩减
 版本. 这个命令是 uucp 包的一部分.
telnet
 连接远端主机的工具和协议.
 注意:telnet 协议本身包含安全漏洞, 因此我们应该适当的避免使用.
wget
 wget 工具使用非交互的形式从 web 或 ftp 站点上取得或下载文件. 在脚本中使用正好.
    1 wget -p
    2 #  The -p or --page-requisite 选项将会使得 wget 取得显示指定页时
    3 #+ 所需要的所有文件.(译者: 比如内嵌图片和样式表等).
    4
    5 wget -r -O $SAVEFILE
    6 #  -r 选项将会递归的从指定站点
    7 #+ 上下载所有连接.
Example 12-38 获得一份股票报价
################################Start Script#######################################
 1 #!/bin/bash
 2 # quote-fetch.sh: 下载一份股票报价.
 3
 4
 5 E_NOPARAMS=66
 6
 7 if [ -z "$1" ]  # 必须指定需要获取的股票(代号).
 8   then echo "Usage: `basename $0` stock-symbol"
 9   exit $E_NOPARAMS
10 fi
11
12 stock_symbol=$1
13
14 file_suffix=.html
15 # 获得一个 HTML 文件, 所以要正确命名它.
16 URL=''
17 # Yahoo 金融板块, 后缀是股票查询.
18
19 # -----------------------------------------------------------
20 wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
21 # -----------------------------------------------------------
22
23
24 # 在 上查询相关材料:
25 # -----------------------------------------------------------
26 # URL="}"
27 # wget -O "$savefilename" "${URL}"
28 # -----------------------------------------------------------
29 # 保存相关 URL 的列表.
30
31 exit $?
32
33 # 练习:
34 # -----
35 #
36 # 1) 添加一个测试来验证用户正在线.
37 #    (暗示: 对 "ppp" 或 "connect" 来分析 'ps -ax' 的输出.
38 #
39 # 2) 修改这个脚本, 让这个脚本具有获得本地天气预报的能力,
40 #+   将用户的 zip code 作为参数.
################################End Script#########################################
 参见 Example A-29 和 Example A-30.
lynx
 lynx 是一个网页浏览器, 也是一个文件浏览器. 它可以(通过使用 -dump 选项)在脚本中
 使用. 它的作用是可以从 Web 或 ftp 站点上非交互的获得文件.
    1 lynx -dump >$SAVEFILE
 使用 -traversal 选项, lynx 将从参数中指定的 HTTP URL 开始, 遍历指定服务器上的
 所有链接. 如果与 -crawl 选项一起用的话, 将会把每个输出的页面文本都放到一个 log
 文件中.
rlogin
 远端登陆, 在远端的主机上开启一个会话. 这个命令存在安全隐患, 所以要使用 ssh 来
 代替.
rsh
 远端 shell, 在远端的主机上执行命令. 这个命令存在安全隐患, 所以要使用 ssh 来代
 替.
rcp
 远端拷贝, 在网络上的不同主机间拷贝文件.
rsync
 远端同步, 在网络上的不同主机间(同步)更新文件.
  bash$ rsync -a ~/sourcedir/*txt /node1/subdirectory/
Example 12-39 更新 Fedora 4  
################################Start Script#######################################
  1 #!/bin/bash
  2 # fc4upd.sh
  3
  4 # 脚本作者: Frank Wang.
  5 # 本书作者作了少量修改.
  6 # 授权在本书中使用.
  7
  8
  9 #  使用 rsync 命令从镜像站点上下载 Fedora 4 的更新.
 10 #  为了节省空间, 如果有多个版本存在的话,
 11 #+ 只下载最新的包.
 12
 13 URL=rsync://distro.ibiblio.org/fedora-linux-core/updates/
 14 # URL=rsync://ftp.kddilabs.jp/fedora/core/updates/
 15 # URL=rsync://rsync.planetmirror.com/fedora-linux-core/updates/
 16
 17 DEST=${1:-/var/www/html/fedora/updates/}
 18 LOG=/tmp/repo-update-$(/bin/date +%Y-%m-%d).txt
 19 PID_FILE=/var/run/${0##*/}.pid
 20
 21 E_RETURN=65        # 某些意想不到的错误.
 22
 23
 24 # 一搬 rsync 选项
 25 # -r: 递归下载
 26 # -t: 保存时间
 27 # -v: verbose
 28
 29 OPTS="-rtv --delete-excluded --delete-after --partial"
 30
 31 # rsync include 模式
 32 # Leading slash causes absolute path name match.
 33 INCLUDE=(
 34     "/4/i386/kde-i18n-Chinese*"
 35 #   ^                         ^
 36 # 双引号是必须的, 用来防止file globbing.
 37 )
 38
 39
 40 # rsync exclude 模式
 41 # 使用 "#" 临时注释掉一些不需要的包.
 42 EXCLUDE=(
 43     /1
 44     /2
 45     /3
 46     /testing
 47     /4/SRPMS
 48     /4/ppc
 49     /4/x86_64
 50     /4/i386/debug
 51    "/4/i386/kde-i18n-*"
 52    "/4/i386/openoffice.org-langpack-*"
 53    "/4/i386/*i586.rpm"
 54    "/4/i386/GFS-*"
 55    "/4/i386/cman-*"
 56    "/4/i386/dlm-*"
 57    "/4/i386/gnbd-*"
 58    "/4/i386/kernel-smp*"
 59 #  "/4/i386/kernel-xen*"
 60 #  "/4/i386/xen-*"
 61 )
 62
 63
 64 init () {
 65     # 让管道命令返回可能的 rsync 错误, 比如, 网络延时(stalled network).
 66     set -o pipefail
 67
 68     TMP=${TMPDIR:-/tmp}/${0##*/}.$$     # 保存精炼的下载列表.
 69     trap "{
 70         rm -f $TMP 2>/dev/null
 71     }" EXIT                             # 删除存在的临时文件.
 72 }
 73
 74
 75 check_pid () {
 76 # 检查进程是否存在.
 77     if [ -s "$PID_FILE" ]; then
 78         echo "PID file exists. Checking ..."
 79         PID=$(/bin/egrep -o "^[[:digit:]]+" $PID_FILE)
 80         if /bin/ps --pid $PID &>/dev/null; then
 81             echo "Process $PID found. ${0##*/} seems to be running!"
 82            /usr/bin/logger -t ${0##*/} \
 83                  "Process $PID found. ${0##*/} seems to be running!"
 84             exit $E_RETURN
 85         fi
 86         echo "Process $PID not found. Start new process . . ."
 87     fi
 88 }
 89
 90
 91 #  根据上边的模式,
 92 #+ 设置整个文件的更新范围, 从 root 或 $URL 开始.
 93 set_range () {
 94     include=
 95     exclude=
 96     for p in "${INCLUDE[@]}"; do
 97         include="$include --include \"$p\""
 98     done
 99
100     for p in "${EXCLUDE[@]}"; do
101         exclude="$exclude --exclude \"$p\""
102     done
103 }
104
105
106 # 获得并提炼 rsync 更新列表.
107 get_list () {
108     echo $$ > $PID_FILE || {
109         echo "Can't write to pid file $PID_FILE"
110         exit $E_RETURN
111     }
112
113     echo -n "Retrieving and refining update list . . ."
114
115     # 获得列表 -- 为了作为单个命令来运行 rsync 需要 'eval'.
116     # $3 和 $4 是文件创建的日期和时间.
117     # $5 是完整的包名字.
118     previous=
119     pre_file=
120     pre_date=0
121     eval /bin/nice /usr/bin/rsync \
122         -r $include $exclude $URL | \
123         egrep '^dr.x|^-r' | \
124         awk '{print $3, $4, $5}' | \
125         sort -k3 | \
126         { while read line; do
127             # 获得这段运行的秒数, 过滤掉不用的包.
128             cur_date=$(date -d "$(echo $line | awk '{print $1, $2}')" +%s)
129             #  echo $cur_date
130
131             # 取得文件名.
132             cur_file=$(echo $line | awk '{print $3}')
133             #  echo $cur_file
134
135             # 如果可能的话, 从文件名中取得 rpm 的包名字.
136             if [[ $cur_file == *rpm ]]; then
137                 pkg_name=$(echo $cur_file | sed -r -e \
138                     's/(^([^_-]+[_-])+)[[:digit:]]+\..*[_-].*$/\1/')
139             else
140                 pkg_name=
141             fi
142             # echo $pkg_name
143
144             if [ -z "$pkg_name" ]; then   #  如果不是一个 rpm 文件,
145                 echo $cur_file >> $TMP    #+ 然后添加到下载列表里.
146             elif [ "$pkg_name" != "$previous" ]; then   # 发现一个新包.
147                 echo $pre_file >> $TMP                  # 输出最新的文件.
148                 previous=$pkg_name                      # 保存当前状态.
149                 pre_date=$cur_date
150                 pre_file=$cur_file
151             elif [ "$cur_date" -gt "$pre_date" ]; then  #  如果是相同的包, 但是更新一些,
152                 pre_date=$cur_date                      #+ 那么就更新最新的.
153                 pre_file=$cur_file
154             fi
155             done
156             echo $pre_file >> $TMP                      #  TMP 现在包含所有
157                                                         #+ 提炼过的列表.
158             # echo "subshell=$BASH_SUBSHELL"
159
160     }       # 这里的打括号是为了让最后这句"echo $pre_file >> $TMP"
161             # 也能与整个循环一起放到同一个子 shell ( 1 )中.
162
163     RET=$?  # 取得管道命令的返回码.
164
165     [ "$RET" -ne 0 ] && {
166         echo "List retrieving failed with code $RET"
167         exit $E_RETURN
168     }
169
170     echo "done"; echo
171 }
172
173 # 真正的 rsync 的下载部分.
174 get_file () {
175
176     echo "Downloading..."
177     /bin/nice /usr/bin/rsync \
178         $OPTS \
179         --filter "merge,+/ $TMP" \
180         --exclude '*'  \
181         $URL $DEST     \
182         | /usr/bin/tee $LOG
183
184     RET=$?
185
186         #  --filter merge,+/ is crucial for the intention.
187         #  + modifier means include and / means absolute path.
188         #  Then sorted list in $TMP will contain ascending dir name and
189         #+ prevent the following --exclude '*' from "shortcutting the circuit."
190
191     echo "Done"
192
193     rm -f $PID_FILE 2>/dev/null
194
195     return $RET
196 }
197
198 # -------
199 # Main
200 init
201 check_pid
202 set_range
203 get_list
204 get_file
205 RET=$?
206 # -------
207
208 if [ "$RET" -eq 0 ]; then
209     /usr/bin/logger -t ${0##*/} "Fedora update mirrored successfully."
210 else
211     /usr/bin/logger -t ${0##*/} "Fedora update mirrored with failure code: $RET"
212 fi
213
214 exit $RET
################################End Script#########################################
 使用 rcp, rsync, 和其他一些有安全问题的类似工具, 并将这些工具用在 shell 脚本中
 是不明智的. 应该考虑使用 ssh, scp, 或者一个 expect 脚本来代替这些不安全的工具.
ssh
 安全 shell, 登陆远端主机并在其上运行命令. 这个工具具有身份认证和加密的功能, 可
 以安全的替换 telnet, rlogin, rcp, 和 rsh 等工具. 参见 man页 来获取详细信息.
Example 12-40 使用 ssh
################################Start Script#######################################
 1 #!/bin/bash
 2 # remote.bash: 使用 ssh.
 3
 4 # 这个例子是 Michael Zick 编写的.
 5 # 授权使用.
 6
 7
 8 #   假设:
 9 #   -----
10 #   fd-2(文件描述符2) 并没有被抛弃 ( '2>/dev/null' ).
11 #   ssh/sshd 假设 stderr ('2') 将会被显示给用户.
12 #
13 #   sshd 正运行在你的机器上.
14 #   对于大多数 '标准' 的发行版, 是应该有的,
15 #+  并且没有一些稀奇古怪的 ssh-keygen.
16
17 # 在你的机器上从命令行中试一下 ssh:
18 #
19 # $ ssh $HOSTNAME
20 # 不同特殊的准备, 你将被要求输入你的密码.
21 #   输入密码
22 #   完成后,  $ exit
23 #
24 # 好使了么? 如果好使了, 你可以做好准备来获取更多的乐趣了.
25
26 # 在你的机器上用 'root'身份来试试 ssh:
27 #
28 #   $  ssh -l root $HOSTNAME
29 #   当询问密码时, 输入 root 的密码, 别输入你的密码.
30 #          Last login: Tue Aug 10 20:25:49 2004 from localhost.localdomain
31 #   完成后键入 'exit'.
32
33 #  上边的动作将会给你一个交互的shell.
34 #  在 'single command' 模式下建立 sshd 是可能的,
35 #+ 不过这已经超出本例的范围了.
36 #  唯一需要注意的事情是下面都可以工作在
37 #+ 'single command' 模式.
38
39
40 # 一个基本的写输出(本地)命令.
41
42 ls -l
43
44 # 现在在远端机器上使用同样的基本命令.
45 # 使用一套不同的 'USERNAME' 和 'HOSTNAME' :
46 USER=${USERNAME:-$(whoami)}
47 HOST=${HOSTNAME:-$(hostname)}
48
49 #  现在在远端主机上运行上边的命令行命令,
50 #+ 当然, 所有的传输都被加密了.
51
52 ssh -l ${USER} ${HOST} " ls -l "
53
54 #  期望的结果就是在远端主机上列出你的
55 #+ username 主目录的所有文件.
56 #  如果想看点不一样的, 那就
57 #+ 在别的地方运行这个脚本, 别再你的主目录上运行这个脚本.
58
59 #  换句话说, Bash 命令已经作为一个引用行
60 #+ 被传递到远端的shell 中了,这样就可以在远端的机器上运行它了.
61 #  在这种情况下, sshd 代表你运行了 ' bash -c "ls -l" '.
62
63 #  对于每个命令行如果想不输入密码的话,
64 #+ 对于这种类似的议题, 可以参阅
65 #+    man ssh
66 #+    man ssh-keygen
67 #+    man sshd_config.
68
69 exit 0
################################End Script#########################################
 注意: 在循环中, ssh 可能会引起意想不到的异常行为. 根据comp.unix 上的shell文档
   Usenet post , ssh 继承了循环的标准输入.为了解决这个问题, 使用 ssh 的 -n
   或者 -f 选项.
   感谢 Jason Bechtel, 指出这点.
scp
 安全拷贝, 在功能上与 rcp 很相似, 就是在2个不同的网络主机之间拷贝文件, 但是要通
 过鉴权的方式, 并且使用与 ssh 类似的安全层.
Local Network
write
 这是一个端到端通讯的工具. 这个工具可以从你的终端上(console 或者 xterm)发送整行
 到另一个用户的终端上. mesg 命令当然也可以用来对于一个终端的写权限
 因为 write 是需要交互的, 所以这个命令通常不使用在脚本中.
netconfig
 用来配置网络适配器(使用 DHCP)的命令行工具. 这个命令对于红帽发行版来说是内置的.
 
Mail
mail
 发送或读取 e-mail 消息.
 如果把这个命令行的 mail 客户端当成一个脚本中的命令来使用的话, 效果非常好.
Example 12-41 一个可以mail自己的脚本
################################Start Script#######################################
 1 #!/bin/sh
 2 # self-mailer.sh: mail自己的脚本
 3
 4 adr=${1:-`whoami`}     # 如果不指定的话, 默认是当前用户.
 5 #  键入 'self-mailer.sh
 6 #+ 发送这个脚本到这个地址.
 7 #  如果只键入 'self-mailer.sh' (不给参数) 的话, 那么这脚本就会被发送给
 8 #+ 调用者, 比如 .
 9 #
10 #  如果想了解 ${parameter:-default} 结构的更多细节,
11 #+ 请参见第9章 变量重游中的
12 #+ 第3节 参数替换.
13
14 # ============================================================================
15   cat $0 | mail -s "Script \"`basename $0`\" has mailed itself to you." "$adr"
16 # ============================================================================
17
18 # --------------------------------------------
19 #  来自 self-mailing 脚本的一份祝福.
20 #  一个喜欢恶搞的家伙运行了这个脚本,
21 #+ 这导致了他自己收到了这份mail.
22 #  显然的, 有些人确实没什么事好做,
23 #+ 就只能浪费他们自己的时间玩了.
24 # --------------------------------------------
25
26 echo "At `date`, script \"`basename $0`\" mailed to "$adr"."
27
28 exit 0
################################End Script#########################################
mailto
 与 mail 命令很相似, mailto 命令可以使用命令行或在脚本中发送 e-mail 消息. 然而,
 mailto 命令也允许发送 MIME (多媒体) 消息.
vacation
 这个工具可以自动回复 e-mail 给发送者, 表示邮件的接受者正在度假暂时无法收到邮件.
 这个工具与 sendmail 一起运行于网络上, 并且这个工具不支持拨号的 POPmail 帐号.
注意事项:
[1]  一个幽灵进程指的是并未附加在终端会话中的后台进程. 幽灵进程 在指定的时间执
  行指定的服务, 或者由特定的事件出发来执行指定的服务.

12.7 终端控制命令
-----------------
影响控制台或终端的命令
tput
 初始化终端或者从 terminfo data 中取得终端信息. 不同的选项允许特定的终端操作.
 tput clear 与下边的 clear 等价. tput reset 与下边的 reset 等价. tput sgr0 也可
 以重置终端, 但是并不清除屏幕.
  bash$ tput longname
  xterm terminal emulator (XFree86 4.0 Window System)
 使用 tput cup X Y 将会把光标移动到当前终端的(X,Y)坐标上. 使用这个命令之前一边
 都要先使用一下 clear 命令, 把屏幕清除一下.
 注意: stty 提供了一个更强力的命令专门用来设置如何控制终端.
infocmp
 这个命令会打印出大量的当前终端的信息. 事实上它是引用了 terminfo 数据库.
  bash$ infocmp
  #       通过来自于文件的 infocmp 显示出来:
  /usr/share/terminfo/r/rxvt
  rxvt|rxvt terminal emulator (X Window System),
    am, bce, eo, km, mir, msgr, xenl, xon,
    colors#8, cols#80, it#8, lines#24, pairs#64,
    acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
    bel=^G, blink=\E[5m, bold=\E[1m,
    civis=\E[?25l,
    clear=\E[H\E[2J, cnorm=\E[?25h, cr=^M,
    ...
reset
 重置终端参数并且清除屏幕. 与 clear 命令一样, 光标和提示符将会重新出现在终端的
 左上角.
clear
 clear 命令只不过是简单的清除控制台或者 xterm 的屏幕. 光标和提示符将会重新出现
 在屏幕或者 xterm window 的左上角. 这个命令既可以用在命令行中也可以用在脚本中.
 参见 Example 10-25.
script
 这个工具将会记录(保存到一个文件中)所有的用户在控制台下的或在 xterm window下的
 按键信息. 这其实就是创建了一个会话记录.

12.8 数学计算命令
-----------------
"Doing the numbers"
factor
 将一个正数分解为多个素数.
  bash$ factor 27417
  27417: 3 13 19 37
bc
 Bash 不能处理浮点运算, 并且缺乏特定的一些操作,这些操作都是一些重要的计算功能.
 幸运的是, bc 可以解决这个问题.
 bc 不仅仅是个多功能灵活的精确的工具, 而且它还提供许多编程语言才具备的一些方便
 的功能.
 bc 比较类似于 C 语言的语法.
 因为它是一个完整的 UNIX 工具, 所以它可以用在管道中, bc 在脚本中也是很常用的.
 这里有一个简单的使用 bc 命令的模版可以用来在计算脚本中的变量. 用在命令替换中.
     variable=$(echo "OPTIONS; OPERATIONS" | bc)
Example 12-42 按月偿还贷款
################################Start Script#######################################
 1 #!/bin/bash
 2 # monthlypmt.sh: 计算按月偿还贷款的数量.
 3
 4
 5 #  这份代码是一份修改版本, 原始版本在 "mcalc" (贷款计算)包中,
 6 #+ 这个包的作者是 Jeff Schmidt 和 Mendel Cooper (本书作者).
 7 #     [15k]
 8
 9 echo
10 echo "Given the principal, interest rate, and term of a mortgage,"
11 echo "calculate the monthly payment."
12
13 bottom=1.0
14
15 echo
16 echo -n "Enter principal (no commas) "
17 read principal
18 echo -n "Enter interest rate (percent) "  # 如果是 12%, 那就键入 "12", 别输入 ".12".
19 read interest_r
20 echo -n "Enter term (months) "
21 read term
22
23
24  interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数.
25                  # "scale" 指定了有效数字的个数.
26  
27
28  interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
29 
30
31  top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
32
33  echo; echo "Please be patient. This may take a while."
34
35  let "months = $term - 1"
36 # ====================================================================
37  for ((x=$months; x > 0; x--))
38  do
39    bot=$(echo "scale=9; $interest_rate^$x" | bc)
40    bottom=$(echo "scale=9; $bottom+$bot" | bc)
41 #  bottom = $(($bottom + $bot"))
42  done
43 # ====================================================================
44
45 # --------------------------------------------------------------------
46 #  Rick Boivie 给出了一个对上边循环的修改,
47 #+ 这个修改更加有效率, 将会节省大概 2/3 的时间.
48
49 # for ((x=1; x <= $months; x++))
50 # do
51 #   bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
52 # done
53
54
55 #  然后他又想出了一个更加有效率的版本,
56 #+ 将会节省 95% 的时间!
57
58 # bottom=`{
59 #     echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
60 #     for ((x=1; x <= $months; x++))
61 #     do
62 #          echo 'bottom = bottom * interest_rate + 1'
63 #     done
64 #     echo 'bottom'
65 #     } | bc`       # 在命令替换中嵌入一个 'for 循环'.
66 # --------------------------------------------------------------------------
67 #  On the other hand, Frank Wang suggests:
68 #  bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc)
69
70 #  因为 . . .
71 #  在循环后边的算法
72 #+ 事实上是一个等比数列的求和公式.
73 #  求和公式是 e0(1-q^n)/(1-q),
74 #+ e0 是第一个元素 并且 q=e(n+1)/e(n)
75 #+ 和 n 是元素的数量.
76 # --------------------------------------------------------------------------
77
78
79  # let "payment = $top/$bottom"
80  payment=$(echo "scale=2; $top/$bottom" | bc)
81  # 使用2位有效数字来表示美元和美分.
82 
83  echo
84  echo "monthly payment = \$$payment"  # 在总和的前边显示美元符号.
85  echo
86
87
88  exit 0
89
90
91  # 练习:
92  #   1) 处理输入允许本金总数中的逗号.
93  #   2) 处理输入允许按照百分号和小数点的形式输入利率.
94  #   3) 如果你真正想好好编写这个脚本,
95  #      那么就扩展这个脚本让它能够打印出完整的分期付款表.
################################End Script#########################################
Example 12-43 数制转换
################################Start Script#######################################
  1 #!/bin/bash
  2 ##########################################################################
  3 # 脚本       : base.sh - 用不同的数值来打印数字 (Bourne Shell)
  4 # 作者       : Heiner Steven ()
  5 # 日期       : 07-03-95
  6 # 类型       : 桌面
  7 # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
  8 # ==> 上边这行是 RCS ID 信息.
  9 ##########################################################################
 10 # 描述
 11 #
 12 # Changes
 13 # 21-03-95 stv fixed error occuring with 0xb as input (0.2)
 14 ##########################################################################
 15
 16 # ==> 在本书中使用这个脚本通过了作者的授权.
 17 # ==> 注释是本书作者添加的.
 18
 19 NOARGS=65
 20 PN=`basename "$0"`          # 程序名
 21 VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2`  # ==> VER=1.2
 22
 23 Usage () {
 24     echo "$PN - print number to different bases, $VER (stv '95)
 25 usage: $PN [number ...]
 26
 27 If no number is given, the numbers are read from standard input.
 28 A number may be
 29     binary (base 2)  starting with 0b (i.e. 0b1100)
 30     octal (base 8)  starting with 0  (i.e. 014)
 31     hexadecimal (base 16) starting with 0x (i.e. 0xc)
 32     decimal   otherwise (i.e. 12)" >&2
 33     exit $NOARGS
 34 }   # ==> 打印出用法信息的函数.
 35
 36 Msg () {
 37     for i   # ==> 省略 [list] .
 38     do echo "$PN: $i" >&2
 39     done
 40 }
 41
 42 Fatal () { Msg "$@"; exit 66; }
 43
 44 PrintBases () {
 45     # 决定数值的数制
 46     for i      # ==> 省略 [list]...
 47     do         # ==> 所以是对命令行参数进行操作.
 48  case "$i" in
 49      0b*)  ibase=2;; # 2进制
 50      0x*|[a-f]*|[A-F]*) ibase=16;; # 16进制
 51      0*)   ibase=8;; # 8进制
 52      [1-9]*)  ibase=10;; # 10进制
 53      *)
 54   Msg "illegal number $i - ignored"
 55   continue;;
 56  esac
 57
 58  # 去掉前缀, 将16进制数字转换为大写(bc需要大写)
 59  number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
 60  # ==>使用":" 作为sed分隔符, 而不使用"/".
 61
 62  # 将数字转换为10进制
 63  dec=`echo "ibase=$ibase; $number" | bc`  # ==> 'bc' 是个计算工具.
 64  case "$dec" in
 65      [0-9]*) ;;    # 数字没问题
 66      *)  continue;;   # 错误: 忽略
 67  esac
 68
 69  # 在一行上打印所有的转换后的数字.
 70  # ==> 'here document' 提供命令列表给'bc'.
 71  echo `bc < 72      obase=16; "hex="; $dec
 73      obase=10; "dec="; $dec
 74      obase=8;  "oct="; $dec
 75      obase=2;  "bin="; $dec
 76 !
 77     ` | sed -e 's: : :g'
 78
 79     done
 80 }
 81
 82 while [ $# -gt 0 ]
 83 # ==>  这里必须使用一个 "while 循环",
 84 # ==>+ 因为所有的 case 都可能退出循环或者
 85 # ==>+ 结束脚本.
 86 # ==> (感谢, Paulo Marcel Coelho Aragao.)
 87 do
 88     case "$1" in
 89  --)     shift; break;;
 90  -h)     Usage;;                 # ==> 帮助信息.
 91  -*)     Usage;;
 92          *)     break;;   # 第一个数字
 93     esac   # ==> 对于非法输入更严格检查是非常有用的.
 94     shift
 95 done
 96
 97 if [ $# -gt 0 ]
 98 then
 99     PrintBases "$@"
100 else     # 从标准输入中读取
101     while read line
102     do
103  PrintBases $line
104     done
105 fi
106
107
108 exit 0
################################End Script#########################################
 调用 bc 的另一种可选的方法就是使用 here document ,并把它嵌入到 命令替换 块中.
 当一个脚本需要将一个选项列表和多个命令传递到 bc 中时, 这种方法就显得非常合适.
    1 variable=`bc << LIMIT_STRING
    2 options
    3 statements
    4 operations
    5 LIMIT_STRING
    6 `
    7
    8 ...or...
    9
   10
   11 variable=$(bc << LIMIT_STRING
   12 options
   13 statements
   14 operations
   15 LIMIT_STRING
   16 )
Example 12-44 使用 "here document" 来调用 bc
################################Start Script#######################################
 1 #!/bin/bash
 2 # 使用命令替换来调用 'bc'
 3 # 并与 'here document' 相结合.
 4
 5
 6 var1=`bc << EOF
 7 18.33 * 19.78
 8 EOF
 9 `
10 echo $var1       # 362.56
11
12
13 #  $( ... ) 这种标记法也可以.
14 v1=23.53
15 v2=17.881
16 v3=83.501
17 v4=171.63
18
19 var2=$(bc << EOF
20 scale = 4
21 a = ( $v1 + $v2 )
22 b = ( $v3 * $v4 )
23 a * b + 15.35
24 EOF
25 )
26 echo $var2       # 593487.8452
27
28
29 var3=$(bc -l << EOF
30 scale = 9
31 s ( 1.7 )
32 EOF
33 )
34 # 返回弧度为1.7的正弦.
35 # "-l" 选项将会调用 'bc' 算数库.
36 echo $var3       # .991664810
37
38
39 # 现在, 在函数中试一下...
40 hyp=             # 声明全局变量.
41 hypotenuse ()    # 计算直角三角形的斜边.
42 {
43 hyp=$(bc -l << EOF
44 scale = 9
45 sqrt ( $1 * $1 + $2 * $2 )
46 EOF
47 )
48 # 不幸的是, 不能从bash 函数中返回浮点值.
49 }
50
51 hypotenuse 3.68 7.31
52 echo "hypotenuse = $hyp"    # 8.184039344
53
54
55 exit 0
################################End Script#########################################
Example 12-45 计算圆周率
################################Start Script#######################################
  1 #!/bin/bash
  2 # cannon.sh: 通过开炮来取得近似的圆周率值.
  3
  4 # 这事实上是一个"Monte Carlo"蒙特卡洛模拟的非常简单的实例:
  5 #+ 蒙特卡洛模拟是一种由现实事件抽象出来的数学模型,
  6 #+ 由于要使用随机抽样统计来估算数学函数, 所以使用伪随机数来模拟真正的随机.
  7
  8 #  想象有一个完美的正方形土地, 边长为10000个单位.
  9 #  在这块土地的中间有一个完美的圆形湖,
 10 #+ 这个湖的直径是10000个单位.
 11 #  这块土地的绝大多数面积都是水, 当然只有4个角上有一些土地.
 12 #  (可以把这个湖想象成为使这个正方形的内接圆.)
 13 #
 14 #  我们将使用老式的大炮和铁炮弹
 15 #+ 向这块正方形的土地上开炮.
 16 #  所有的炮弹都会击中这块正方形土地的某个地方.
 17 #+ 或者是打到湖上, 或者是打到4个角的土地上.
 18 #  因为这个湖占据了这个区域大部分地方,
 19 #+ 所以大部分的炮弹都会"扑通"一声落到水里.
 20 #  而只有很少的炮弹会"砰"的一声落到4个
 21 #+ 角的土地上.
 22 #
 23 #  如果我们发出的炮弹足够随机的落到这块正方形区域中的话,
 24 #+ 那么落到水里的炮弹与打出炮弹的总数的比率,
 25 #+ 大概非常接近于 PI/4.
 26 #
 27 #  原因是所有的炮弹事实上都
 28 #+ 打在了这个土地的右上角,
 29 #+ 也就是, 笛卡尔坐标系的第一象限.
 30 #  (之前的解释只是一个简化.)
 31 #
 32 #  理论上来说, 如果打出的炮弹越多, 就越接近这个数字.
 33 #  然而, 对于shell 脚本来说一定会作些让步的,
 34 #+ 因为它肯定不能和那些内建就支持浮点运算的编译语言相比.
 35 #  当然就会降低精度.
 36
 37
 38 DIMENSION=10000  # 这块土地的边长.
 39                  # 这也是所产生的随机整数的上限.
 40
 41 MAXSHOTS=1000    # 开炮次数.
 42                  # 10000 或更多次的话, 效果应该更好, 但有点太浪费时间了.
 43 PMULTIPLIER=4.0  # 接近于 PI 的比例因子.
 44
 45 get_random ()
 46 {
 47 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
 48 RANDOM=$SEED                                  #  来自于 "seeding-random.sh"
 49                                               #+ 的例子脚本.
 50 let "rnum = $RANDOM % $DIMENSION"             #  范围小于 10000.
 51 echo $rnum
 52 }
 53
 54 distance=        # 声明全局变量.
 55 hypotenuse ()    # 从 "alt-bc.sh" 例子来的,
 56 {                # 计算直角三角形的斜边的函数.
 57 distance=$(bc -l << EOF
 58 scale = 0
 59 sqrt ( $1 * $1 + $2 * $2 )
 60 EOF
 61 )
 62 #  设置 "scale" 为 0 , 好让结果四舍五入为整数值,
 63 #+ 这是这个脚本中必须折中的一个地方.
 64 #  不幸的是, 这将降低模拟的精度.
 65 }
 66
 67
 68 # main() {
 69
 70 # 初始化变量.
 71 shots=0
 72 splashes=0
 73 thuds=0
 74 Pi=0
 75
 76 while [ "$shots" -lt  "$MAXSHOTS" ]           # 主循环.
 77 do
 78
 79   xCoord=$(get_random)                        # 取得随机的 X 与 Y 坐标.
 80   yCoord=$(get_random)
 81   hypotenuse $xCoord $yCoord                  #  直角三角形斜边 =
 82                                               #+ distance.
 83   ((shots++))
 84
 85   printf "#%4d   " $shots
 86   printf "Xc = %4d  " $xCoord
 87   printf "Yc = %4d  " $yCoord
 88   printf "Distance = %5d  " $distance         #  到湖中心的
 89                                               #+ 距离 --
 90                                               #  起始坐标点 --
 91                                               #+  (0,0).
 92
 93   if [ "$distance" -le "$DIMENSION" ]
 94   then
 95     echo -n "SPLASH!  "
 96     ((splashes++))
 97   else
 98     echo -n "THUD!    "
 99     ((thuds++))
100   fi
101
102   Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)
103   # 将比例乘以 4.0.
104   echo -n "PI ~ $Pi"
105   echo
106
107 done
108
109 echo
110 echo "After $shots shots, PI looks like approximately $Pi."
111 # 如果不太准的话, 那么就提高一下运行的次数. . .
112 # 可能是由于运行错误和随机数随机程度不高造成的.
113 echo
114
115 # }
116
117 exit 0
118
119 #  要想知道一个shell脚本到底适不适合作为
120 #+ 一种需要对复杂和精度都有要求的计算应用的模拟的话.
121 #
122 #  一般至少需要两个判断条件.
123 #  1) 作为一种概念的验证: 来显示它可以做到.
124 #  2) 在使用真正的编译语言来实现一个算法之前,
125 #+    使用脚本来测试和验证这个算法.
################################End Script#########################################
dc
 dc (桌面计算器desk calculator) 工具是面向栈的并且使用 RPN (逆波兰表达式
 "Reverse Polish Notation" 又叫"后缀表达式"). 与 bc 命令很相像 , 但是这个工具
 具备好多只有编程语言才具备的能力.
 (译者注: 正常表达式  逆波兰表达式
    a+b    a,b,+
    a+(b-c)   a,b,c,-,+
    a+(b-c)*d  a,d,b,c,-,*,+
 )
    绝大多数人都避免使用这个工具, 因为它需要非直觉的 RPN 输入. 但是, 它却有特定的
 用途.
Example 12-46 将10进制数字转换为16进制数字
################################Start Script#######################################
 1 #!/bin/bash
 2 # hexconvert.sh: 将10进制数字转换为16进制数字
 3
 4 E_NOARGS=65 # 缺命令行参数错误.
 5 BASE=16     # 16进制.
 6
 7 if [ -z "$1" ]
 8 then
 9   echo "Usage: $0 number"
10   exit $E_NOARGS
11   # 需要一个命令行参数.
12 fi
13 # 练习: 添加命令行参数检查.
14
15
16 hexcvt ()
17 {
18 if [ -z "$1" ]
19 then
20   echo 0
21   return    # 如果没有参数传递到这个函数中就 "return" 0.
22 fi
23
24 echo ""$1" "$BASE" o p" | dc
25 #                 "o" 设置输出的基数(数制).
26 #                   "p" 打印栈顶.
27 # 察看 dc 的 man 页来了解其他的选项.
28 return
29 }
30
31 hexcvt "$1"
32
33 exit 0
################################End Script#########################################
 通过仔细学习 dc 命令的 info 页, 可以更深入的理解这个复杂的命令. 但是, 有一些
 精通 dc巫术 的小组经常会炫耀他们使用这个强大而又晦涩难懂的工具时的一些技巧,
 并以此为乐.
  bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc"
  Bash
Example 12-47 因子分解
################################Start Script#######################################
 1 #!/bin/bash
 2 # factr.sh: 分解约数
 3
 4 MIN=2       # 如果比这个数小就不行了.
 5 E_NOARGS=65
 6 E_TOOSMALL=66
 7
 8 if [ -z $1 ]
 9 then
10   echo "Usage: $0 number"
11   exit $E_NOARGS
12 fi
13
14 if [ "$1" -lt "$MIN" ]
15 then
16   echo "Number to factor must be $MIN or greater."
17   exit $E_TOOSMALL
18 fi 
19
20 # 练习: 添加类型检查 (防止非整型的参数).
21
22 echo "Factors of $1:"
23 # ---------------------------------------------------------------------------------
24 echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc
25 # ---------------------------------------------------------------------------------
26 # 上边这行代码是 Michel Charpentier 编写的<>.
27 # 在此使用经过授权 (thanks).
28
29  exit 0
################################End Script#########################################
awk
 在脚本中使用浮点运算的另一种方法是使用 awk  内建的数学运算函数, 可以用在shell
 wrapper中.
Example 12-48 计算直角三角形的斜边
################################Start Script#######################################
 1 #!/bin/bash
 2 # hypotenuse.sh: 返回直角三角形的斜边.
 3 #               ( 直角边长的平方和,然后对和取平方根)
 4
 5 ARGS=2                # 需要将2个直角边作为参数传递进来.
 6 E_BADARGS=65          # 错误的参数值.
 7
 8 if [ $# -ne "$ARGS" ] # 测试传递到脚本中的参数值.
 9 then
10   echo "Usage: `basename $0` side_1 side_2"
11   exit $E_BADARGS
12 fi
13
14
15 AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
16 #              命令 / 传递给awk的参数
17
18
19 # 现在, 将参数通过管道传递给awk.
20 echo -n "Hypotenuse of $1 and $2 = "
21 echo $1 $2 | awk "$AWKSCRIPT"
22
23 exit 0
################################End Script#########################################

12.9 混杂命令
-------------
一些不好归类的命令
jot, seq
 这些工具通过用户指定的范围和增量来产生一系列的整数.
 每个产生出来的整数一般都占一行, 但是可以使用 -s 选项来改变这种设置.
  bash$ seq 5
  1
  2
  3
  4
  5
 
 
 
  bash$ seq -s : 5
  1:2:3:4:5
 jot 和 seq  命令都经常用在 for 循环中.
Example 12-49 使用 seq 来产生循环参数
################################Start Script#######################################
 1 #!/bin/bash
 2 # 使用 "seq"
 3
 4 echo
 5
 6 for a in `seq 80`  # 或者   for a in $( seq 80 )
 7 # 与  " for a in 1 2 3 4 5 ... 80 "相同  (少敲了好多字!).
 8 # 也可以使用 'jot' (如果系统上有的话).
 9 do
10   echo -n "$a "
11 done      # 1 2 3 4 5 ... 80
12 # 这也是一个通过使用命令的输出
13 # 来产生 "for"循环中 [list] 列表的例子.
14
15 echo; echo
16
17
18 COUNT=80  # 当然, 'seq' 也可以使用一个可替换的参数.
19
20 for a in `seq $COUNT`  # 或者   for a in $( seq $COUNT )
21 do
22   echo -n "$a "
23 done      # 1 2 3 4 5 ... 80
24
25 echo; echo
26
27 BEGIN=75
28 END=80
29
30 for a in `seq $BEGIN $END`
31 #  传给 "seq" 两个参数, 从第一个参数开始增长,
32 #+ 一直增长到第二个参数为止.
33 do
34   echo -n "$a "
35 done      # 75 76 77 78 79 80
36
37 echo; echo
38
39 BEGIN=45
40 INTERVAL=5
41 END=80
42
43 for a in `seq $BEGIN $INTERVAL $END`
44 #  传给 "seq" 三个参数从第一个参数开始增长,
45 #+ 并以第二个参数作为增量,
46 #+ 一直增长到第三个参数为止.
47 do
48   echo -n "$a "
49 done      # 45 50 55 60 65 70 75 80
50
51 echo; echo
52
53 exit 0
################################End Script#########################################
 一个简单些的例子:
  1 #  产生10个连续扩展名的文件,
  2 #+ 名字分别是 file.1, file.2 . . . file.10.
  3 COUNT=10
  4 PREFIX=file
  5
  6 for filename in `seq $COUNT`
  7 do
  8   touch $PREFIX.$filename
  9   #  或者, 你可以做一些其他的操作,
 10   #+ 比如 rm, grep, 等等.
 11 done

Example 12-50 字母统计
################################Start Script#######################################
 1 #!/bin/bash
 2 # letter-count.sh: 统计一个文本文件中字母出现的次数.
 3 # 由 Stefano Palmeri 编写.
 4 # 经过授权使用在本书中.
 5 # 本书作者做了少许修改.
 6
 7 MINARGS=2          # 本脚本至少需要2个参数.
 8 E_BADARGS=65
 9 FILE=$1
10
11 let LETTERS=$#-1   # 制定了多少个字母 (作为命令行参数).
12                    # (从命令行参数的个数中减1.)
13
14
15 show_help(){
16     echo
17            echo Usage: `basename $0` file letters 
18            echo Note: `basename $0` arguments are case sensitive.
19            echo Example: `basename $0` foobar.txt G n U L i N U x.
20     echo
21 }
22
23 # 检查参数个数.
24 if [ $# -lt $MINARGS ]; then
25    echo
26    echo "Not enough arguments."
27    echo
28    show_help
29    exit $E_BADARGS
30 fi 
31
32
33 # 检查文件是否存在.
34 if [ ! -f $FILE ]; then
35     echo "File \"$FILE\" does not exist."
36     exit $E_BADARGS
37 fi
38
39
40
41 # 统计字母出现的次数.
42 for n in `seq $LETTERS`; do
43       shift
44       if [[ `echo -n "$1" | wc -c` -eq 1 ]]; then             #  检查参数.
45              echo "$1" -\> `cat $FILE | tr -cd  "$1" | wc -c` #  统计.
46       else
47              echo "$1 is not a  single char."
48       fi 
49 done
50
51 exit $?
52
53 #  这个脚本在功能上与 letter-count2.sh 完全相同,
54 #+ 但是运行得更快.
55 #  为什么?
################################End Script#########################################
getopt
 getopt 命令将会分析以破折号开头的命令行选项. 这个外部命令与Bash的内建命令
 getopts 作用相同. 通过使用 -l 标志, getopt 可以处理长(多字符)选项, 并且也允许参
 数重置.
Example 12-51 使用getopt来分析命令行选项
################################Start Script#######################################
 1 #!/bin/bash
 2 # 使用 getopt.
 3
 4 # 尝试使用下边的不同的方法来调用这脚本:
 5 #   sh ex33a.sh -a
 6 #   sh ex33a.sh -abc
 7 #   sh ex33a.sh -a -b -c
 8 #   sh ex33a.sh -d
 9 #   sh ex33a.sh -dXYZ
10 #   sh ex33a.sh -d XYZ
11 #   sh ex33a.sh -abcd
12 #   sh ex33a.sh -abcdZ
13 #   sh ex33a.sh -z
14 #   sh ex33a.sh a
15 # 解释上面每一次调用的结果.
16
17 E_OPTERR=65
18
19 if [ "$#" -eq 0 ]
20 then   # 脚本需要至少一个命令行参数.
21   echo "Usage $0 -[options a,b,c]"
22   exit $E_OPTERR
23 fi 
24
25 set -- `getopt "abcd:" "$@"`
26 # 为命令行参数设置位置参数.
27 # 如果使用 "$*" 来代替 "$@" 的话会发生什么?
28
29 while [ ! -z "$1" ]
30 do
31   case "$1" in
32     -a) echo "Option \"a\"";;
33     -b) echo "Option \"b\"";;
34     -c) echo "Option \"c\"";;
35     -d) echo "Option \"d\" $2";;
36      *) break;;
37   esac
38
39   shift
40 done
41
42 #  通常来说在脚本中使用内建的 'getopts' 命令,
43 #+ 会比使用 'getopt' 好一些.
44 #  参见 "ex33.sh".
45
46 exit 0
################################End Script#########################################
 参见 Example 9-12 , 这是对 getopt 命令的一个简单模拟.
run-parts
 run-parts 命令 [1] 将会执行目标目录中所有的脚本, 这些将本会以 ASCII 的循序进行
 排列. 当然, 这些脚本都需要具有可执行权限.
 cron 幽灵进程 会调用 run-parts 来运行 /etc/cron.* 下的所有脚本.
yes
 yes 命令的默认行为是向 stdout 中连续不断的输出字符 y,每个y占一行.使用control-c
 来结束运行. 如果想换一个输出字符的话, 可以使用 yes 其他的字符串, 这样就会连续
 不同的输出你指定的字符串. 那么这样的命令究竟能做什么呢? 在命令行或者脚本中,
 yes的输出可以通过重定向或管道来传递给一些需要用户输入进行交互的命令. 事实上,
 这个命令可以说是 expect 命令(译者注: 这个命令本书未介绍, 一个自动实现交互的命
 令)的一个简化版本.
 yes | fsck /dev/hda1 将会以非交互的形式运行fsck(因为需要用户输入的 y 全由yes
 命令搞定了)(小心使用!).
 yes | rm -r dirname 与 rm -rf dirname 效果相同(小心使用!).
 注意: 当用 yes 的管道形式来使用一些可能具有潜在危险的系统命令的时候一定要深思
  熟虑, 比如 fsck 或 fdisk. 可能会产生一些意外的副作用.
banner
 将会把字符串用一个 ASCII 字符(默认是 '#')来画出来(就是将多个'#'拼出一副字符的
 图形).可以作为硬拷贝重定向到打印机上(译者注: 可以使用-w 选项设置宽度).
printenv
 对于某个特定的用户, 显示出所有的 环境变量.
  bash$ printenv | grep HOME
  HOME=/home/bozo
lp
 lp 和 lpr 命令将会把文件发送到打印队列中, 并且作为硬拷贝来打印. [2] 这些命令
 会纪录它们名字的起始位置并传递到行打印机的另一个位置.
 bash$ lp file1.txt 或者 bash lp
 通常情况下都是将pr的格式化的输出传递到 lp.
 bash$ pr -options file1.txt | lp
 格式化的包, 比如 groff 和 Ghostscript 就可以将它们的输出直接发送给 lp.
 bash$ groff -Tascii file.tr | lp
 bash$ gs -options | lp file.ps
 还有一些相关的命令, 比如 lpq, 可以查看打印队列, lprm, 可以用来从打印队列中删
 除作业.
tee
 [UNIX 从管道行业借来的主意.]
 这是一个重定向操作, 但是有些不同. 就像管道中的"三通"一样, 这个命令可以将命令或
 者管道命令的输出抽出到一个文件中,而且并不影响结果. 当你想将一个正在运行的进程
 的输出保存到文件中时, 或者为了debug而保存输出记录的时候, 这个命令就非常有用了.
                              (重定向)
                             |----> to file
                             |
   ==========================|====================
   command ---> command ---> |tee ---> command ---> ---> output of pipe
   ===============================================
   1 cat listfile* | sort | tee check.file | uniq > result.file
 (在对排序的结果进行 uniq (去掉重复行) 之前,文件 check.file 中保存了排过序的
 "listfiles".)
mkfifo
 这个不大引人注意的命令可以创建一个命名管道, 并产生一个临时的先进先出的buffer
 用来在两个进程间传输数据. [3] 典型的使用是一个进程向FIFO中写数据, 另一个进程读
 出来. 参见 Example A-15.
pathchk
 这个命令用来检查文件名的有效性. 如果文件名超过了最大允许长度(255 个字符), 或者
 它所在的一个或多个路径搜索不到, 那么就会产生一个错误结果.
 不幸的是,并不能够返回一个可识别的错误码, 因此它在脚本中几乎没有什么用. 一般都
 使用文件测试操作.
dd
 这也是一个不太出名的工具, 但却是一个令人恐惧的 "数据复制" 命令. 最开始, 这个命
 令是被用来在UNIX 微机和IBM大型机之间通过磁带来交换数据, 这个命令现在仍然有它的
 用途. dd 命令只不过是简单的拷贝一个文件 (或者 stdin/stdout), 但是它会做一些转
 换. 下边是一些可能的转换, 比如 ASCII/EBCDIC, [4]  大写/小写, 在输入和输出之间
 的字节对的交换, 还有对输入文件做一些截头去尾的工作. dd --help  列出了所有转换,
 还有这个强力工具的一些其他选项.
    1 # 将一个文件转换为大写:
    2
    3 dd if=$filename conv=ucase > $filename.uppercase
    4 #                    lcase   # 转换为小写
Example 12-52 一个拷贝自身的脚本
################################Start Script#######################################
 1 #!/bin/bash
 2 # self-copy.sh
 3
 4 # 这个脚本将会拷贝自身.
 5
 6 file_subscript=copy
 7
 8 dd if=$0 of=$0.$file_subscript 2>/dev/null
 9 # 阻止dd产生的消息:            ^^^^^^^^^^^
10
11 exit $?
################################End Script#########################################
Example 12-53 练习dd
################################Start Script#######################################
 1 #!/bin/bash
 2 # exercising-dd.sh
 3
 4 # 由Stephane Chazelas编写.
 5 # 本文作者做了少量修改.
 6
 7 input_file=$0   # 脚本本身.
 8 output_file=log.txt
 9 n=3
10 p=5
11
12 dd if=$input_file of=$output_file bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null
13 # 从脚本中把位置n到p的字符提取出来.
14
15 # -------------------------------------------------------
16
17 echo -n "hello world" | dd cbs=1 conv=unblock 2> /dev/null
18 # 垂直的 echo "hello world" .
19
20 exit 0
################################End Script#########################################
 为了展示dd的多种用途, 让我们使用它来记录按键.
Example 12-54 记录按键
################################Start Script#######################################
 1 #!/bin/bash
 2 # dd-keypress.sh: 记录按键, 不需要按回车.
 3
 4
 5 keypresses=4                      # 记录按键的个数.
 6
 7
 8 old_tty_setting=$(stty -g)        # 保存老的终端设置.
 9
10 echo "Press $keypresses keys."
11 stty -icanon -echo                # 禁用标准模式.
12                                   # 禁用本地 echo.
13 keys=$(dd bs=1 count=$keypresses 2> /dev/null)
14 # 如果不指定输入文件的话, 'dd' 使用标准输入.
15
16 stty "$old_tty_setting"           # 恢复老的终端设置.
17
18 echo "You pressed the \"$keys\" keys."
19
20 # 感谢 Stephane Chazelas, 演示了这种方法.
21 exit 0
################################End Script#########################################
 dd 命令可以在数据流上做随即存取.
    1 echo -n . | dd bs=1 seek=4 of=file conv=notrunc
    2 # "conv=notrunc" 选项意味着输出文件不能被截短.
    3
    4 # Thanks, S.C.
 dd 命令可以将数据或磁盘镜像拷贝到设备中, 也可以从设备中拷贝数据或磁盘镜像, 比
 如说磁盘或磁带设备都可以 (Example A-5). 通常用来创建启动盘.
 dd if=kernel-image of=/dev/fd0H1440
 同样的, dd 可以拷贝软盘的整个内容(甚至是其他操作系统的磁盘格式) 到硬盘驱动器上
 (以镜像文件的形式).
 dd if=/dev/fd0 of=/home/bozo/projects/floppy.img
 dd 命令还有一些其他用途, 包括可以初始化临时交换文件 (Example 28-2) 和 ramdisks
 (内存虚拟硬盘) (Example 28-3). 它甚至可以做一些对整个硬盘分区的底层拷贝, 虽然
 不建议这么做.
 一些(可能是比较无聊的)人总会想一些关于 dd 命令的有趣的应用.

Example 12-55 安全的删除一个文件
################################Start Script#######################################
 1 #!/bin/bash
 2 # blot-out.sh: 删除一个文件所有的记录.
 3
 4 #  这个脚本会使用随即字节交替的覆盖
 5 #+ 目标文件, 并且在最终删除这个文件之前清零.
 6 #  这么做之后, 即使你通过传统手段来检查磁盘扇区
 7 #+ 也不能把文件原始数据重新恢复.
 8
 9 PASSES=7         #  破坏文件的次数.
10                  #  提高这个数字会减慢脚本运行的速度,
11                  #+ 尤其是对尺寸比较大的目标文件进行操作的时候.
12 BLOCKSIZE=1      #  带有 /dev/urandom 的 I/O 需要单位块尺寸,
13                  #+ 否则你可能会获得奇怪的结果.
14 E_BADARGS=70     #  不同的错误退出码.
15 E_NOT_FOUND=71
16 E_CHANGED_MIND=72
17
18 if [ -z "$1" ]   # 没指定文件名.
19 then
20   echo "Usage: `basename $0` filename"
21   exit $E_BADARGS
22 fi
23
24 file=$1
25
26 if [ ! -e "$file" ]
27 then
28   echo "File \"$file\" not found."
29   exit $E_NOT_FOUND
30 fi 
31
32 echo; echo -n "Are you absolutely sure you want to blot out \"$file\" (y/n)? "
33 read answer
34 case "$answer" in
35 [nN]) echo "Changed your mind, huh?"
36       exit $E_CHANGED_MIND
37       ;;
38 *)    echo "Blotting out file \"$file\".";;
39 esac
40
41
42 flength=$(ls -l "$file" | awk '{print $5}')  # 5 是文件长度.
43 pass_count=1
44
45 chmod u+w "$file"   # Allow overwriting/deleting the file.
46
47 echo
48
49 while [ "$pass_count" -le "$PASSES" ]
50 do
51   echo "Pass #$pass_count"
52   sync         # 刷新buffer.
53   dd if=/dev/urandom of=$file bs=$BLOCKSIZE count=$flength
54                # 使用随机字节进行填充.
55   sync         # 再刷新buffer.
56   dd if=/dev/zero of=$file bs=$BLOCKSIZE count=$flength
57                # 用0填充.
58   sync         # 再刷新buffer.
59   let "pass_count += 1"
60   echo
61 done 
62
63
64 rm -f $file    # 最后, 删除这个已经被破坏得不成样子的文件.
65 sync           # 最后一次刷新buffer.
66
67 echo "File \"$file\" blotted out and deleted."; echo
68
69
70 exit 0
71
72 #  这是一种真正安全的删除文件的办法,
73 #+ 但是效率比较低, 运行比较慢.
74 #  GNU 的文件工具包中的 "shred" 命令,
75 #+ 也可以完成相同的工作, 不过更有效率.
76
77 #  使用普通的方法是不可能重新恢复这个文件了.
78 #  然而 . . .
79 #+ 这个简单的例子是不能够抵抗
80 #+ 那些经验丰富并且正规的分析.
81
82 #  这个脚本可能不会很好的运行在日志文件系统上.(译者注: JFS)
83 #  练习 (很难): 像它做的那样修正这个问题.
84
85
86
87 #  Tom Vier的文件删除包可以更加彻底
88 #+ 的删除文件, 比这个简单的例子厉害得多.
89 #    
90
91 #  如果想对安全删除文件这一论题进行深度的分析,
92 #+ 可以参见Peter Gutmann的页面,
93 #+     "Secure Deletion of Data From Magnetic and Solid-State Memory".
94 #       ~pgut001/pubs/secure_del.html
################################End Script#########################################
od
 od(octal dump)过滤器, 将会把输入(或文件)转换为8进制或者其他进制. 在你需要查看
 或处理一些二进制数据文件或者一个不可读的系统设备文件的时候, 这个命令非常有用,
 比如/dev/urandom,或者是一个二进制数据过滤器. 参见 Example 9-28 和
 Example 12-13.
hexdump
 对二进制文件进行 16进制, 8进制, 10进制, 或者 ASCII 码的查阅动作. 这个命令大体
 上与上边的od命令作用相同, 但是远不及 od 命令有用.
objdump
 显示编译后的2进制文件或2进制可执行文件的信息, 以16进制的形式显示, 或者显示反汇
 编列表(使用-d选项).
  bash$ objdump -d /bin/ls
  /bin/ls:     file format elf32-i386
  Disassembly of section .init:
  080490bc <.init>:
   80490bc:       55                      push   %ebp
   80490bd:       89 e5                   mov    %esp,%ebp
   . . .
mcookie
 这个命令会产生一个"magic cookie", 这是一个128-bit (32-字符) 的伪随机16进制数字,
 这个数字一般都用来作为X server的鉴权"签名". 这个命令还可以用来在脚本中作为一
 种生成随机数的手段, 当然这是一种"小吃店"(虽然不太正统, 但是很方便)的风格.
    1 random000=$(mcookie)
 当然, 完成同样的目的还可以使用 md5 命令.
    1 # 产生关于脚本本身的 md5 checksum.
    2 random001=`md5sum $0 | awk '{print $1}'`
    3 # 使用 'awk' 来去掉文件名.
 mcookie 还给出了产生"唯一"文件名的另一种方法.
Example 12-56 文件名产生器
################################Start Script#######################################
 1 #!/bin/bash
 2 # tempfile-name.sh:  临时文件名产生器
 3
 4 BASE_STR=`mcookie`   # 32-字符的 magic cookie.
 5 POS=11               # 字符串中随便的一个位置.
 6 LEN=5                # 取得 $LEN 长度连续的字符串.
 7
 8 prefix=temp          #  最终的一个临时文件.
 9                      #  如果想让这个文件更加唯一,
10                      #+ 可以对这个前缀也使用下边的方法来生成.
11
12 suffix=${BASE_STR:POS:LEN}
13                      # 提取从第11个字符之后的长度为5的字符串.
14
15 temp_filename=$prefix.$suffix
16                      # 构造文件名.
17
18 echo "Temp filename = "$temp_filename""
19
20 # sh tempfile-name.sh
21 # Temp filename = temp.e19ea
22
23 #  与使用 'date' 命令(参考 ex51.sh)来创建唯一文件名
24 #+ 的方法相比较.
25
26 exit 0
################################End Script#########################################
units
 这个工具用来在不同的计量单位之间互相转换. 当你在交互模式下正常调用时, 会发现在
 脚本中 units 也很有用.
Example 12-57 将米转换为英里
################################Start Script#######################################
 1 #!/bin/bash
 2 # unit-conversion.sh
 3
 4
 5 convert_units ()  # 通过参数取得需要转换的单位.
 6 {
 7   cf=$(units "$1" "$2" | sed --silent -e '1p' | awk '{print $2}')
 8   # 除了真正需要转换的部分保留下来外,其他的部分都去掉.
 9   echo "$cf"
10 } 
11
12 Unit1=miles
13 Unit2=meters
14 cfactor=`convert_units $Unit1 $Unit2`
15 quantity=3.73
16
17 result=$(echo $quantity*$cfactor | bc)
18
19 echo "There are $result $Unit2 in $quantity $Unit1."
20
21 #  如果你传递了两个不匹配的单位会发生什么?
22 #+ 比如分别传入英亩和英里?
23
24 exit 0
################################End Script#########################################
m4
 一个隐藏的财宝, m4 是一个强力的宏处理过滤器, [5]  差不多可以说是一种语言了. 虽
 然最开始这个工具是用来作为 RatFor 的预处理器而编写的, 但是后来证明 m4  作为独
 立的工具也是非常有用的. 事实上, m4 结合了许多工具的功能, 比如 eval, tr, 和 awk,
 除此之外, 它还使得宏扩展变得容易.
 在 2004年4月的 Linux Journal  的问题列表中有一篇关于 m4 命令用法得非常好的文章.
Example 12-58 使用 m4
################################Start Script#######################################
 1 #!/bin/bash
 2 # m4.sh: 使用 m4 宏处理器
 3
 4 # 字符操作
 5 string=abcdA01
 6 echo "len($string)" | m4                           # 7
 7 echo "substr($string,4)" | m4                      # A01
 8 echo "regexp($string,[0-1][0-1],\&Z)" | m4         # 01Z
 9
10 # 算术操作
11 echo "incr(22)" | m4                               # 23
12 echo "eval(99 / 3)" | m4                           # 33
13
14 exit 0
################################End Script#########################################
doexec
 doexec 命令允许将一个随便的参数列表传递到一个二进制可执行文件中. 特别的, 甚至
 可以传递 arg[0] (相当于脚本中的 $0 ), 这样可以使用不同的名字来调用这个可执行
 文件, 并且通过不同的调用的名字, 可以让这个可执行文件执行不同的动作. 这也可以
 说是一种将参数传递到可执行文件中的比较绕圈子的做法.
 比如, /usr/local/bin 目录可能包含一个 "aaa" 的二进制文件. 使用
 doexec /usr/local/bin/aaa list  可以 列出 当前工作目录下所有以 "a" 开头的的文
 件, 而使用 doexec /usr/local/bin/aaa delete  将会删除这些文件.
 注意: 可执行文件的不同行为必须定义在可执行文件自身的代码中, 可以使用如下的
  shell脚本作类比:
    1 case `basename $0` in
    2 "name1" ) do_something;;
    3 "name2" ) do_something_else;;
    4 "name3" ) do_yet_another_thing;;
    5 *       ) bail_out;;
    6 esac
dialog
 dialog 工具集提供了一种从脚本中调用交互对话框的方法. dialog 的更好的变种版本是
  -- gdialog, Xdialog, 和 kdialog -- 事实上是调用的 X-Windows 的界面工具集. 参
 见 Example 33-19.
sox
 sox 命令, "sound exchange" (声音转换)命令, 可以进行声音文件的转换. 事实上,可执
 行文件 /usr/bin/play (现在不建议使用) 只不过是 sox 的一个 shell 包装器而已.
 举个例子, sox soundfile.wav soundfile.au 将会把一个 WAV 声音文件转换成一个
 (Sun 音频格式) AU 声音文件.
 Shell 脚本非常适合于使用 sox 的声音操作来批处理声音文件. 比如, 参见
 Linux Radio Timeshift HOWTO 和 MP3do Project.
注意事项:
[1]  这个工具事实上是从 Debian Linux 发行版中的一个脚本借鉴过来的.
[2]  打印队列 就是"在线等待"打印的作业组.
[3]  对于本话题的一个完美的介绍, 请参见 Andy Vaught 的文章, 命名管道的介绍,
  (), 这是
  Linux Journal (年9月的一个问题.
[4]  EBCDIC (发音是 "ebb-sid-ick") 是单词 (Extended Binary Coded Decimal
  Interchange Code) 的首字母缩写. 这是 IBM 的数据格式, 现在已经不常见了.
  dd 命令的 conv=ebcdic 选项的一个比较奇异的使用方法是对一个文件进行快速而
  且容易但不太安全的编码.
     1 cat $file | dd conv=swab,ebcdic > $file_encrypted
     2 # 编码 (看起来好像没什么用).     
     3 # 应该交换字节(swab), 有点晦涩.
     4
     5 cat $file_encrypted | dd conv=swab,ascii > $file_plaintext
     6 # 解码.
[5]  宏 是一个符号常量, 将会被扩展成一个命令字符串或者一系列的参数操作. 
阅读(10093) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~