魅力博客

魅力Linux|魅力空间|魅力博客|学习Linux|ubuntu日记|电脑教程|手机软件

深入学习shell脚本艺术-高级Bash脚本编程指南8



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 <patch-file
       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 #+ http://sourceforge.net/projects/tripwire/.
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 # "crnet_mgr@chinatietong.com"
40 # "crnet_tec@chinatietong.com"
41 # "postmaster@chinatietong.com"
42
43
44 #  如果想找到这个脚本的一个更详尽的版本,
45 #+ 请访问 SpamViz 的主页, http://www.spamviz.net/index.html.
################################End Script#########################################

Example 12-37 分析一个垃圾邮件域<rojy bug>
################################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 <domain.name>
 11
 12 # 使用外部程序: 'dig'
 13 # 测试版本: 9.2.4rc5
 14
 15 # 使用函数.
 16 # 使用 IFS 来分析分配在数组中的字符串.
 17 # 检查 e-mail 黑名单.
 18
 19 # 使用来自文本体中的 domain.name:
 20 # http://www.good_stuff.spammer.biz/just_ignore_everything_else
 21 #                       ^^^^^^^^^^^
 22 # 或者使用来自任意 e-mail 地址的 domain.name:
 23 # Really_Good_Offer@spammer.biz
 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 <error_code> <list_query>
 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 <rev_dns> <list_server>
 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 # 参见: http://www.spamhaus.org (Conservative, well maintained)
 98     echo -n 'spamhaus.org says: '
 99     echo $(chk_adr ${rev_dns} 'sbl-xbl.spamhaus.org')
100
101 # 参见: http://ordb.org (Open mail relays)
102     echo -n '   ordb.org  says: '
103     echo $(chk_adr ${rev_dns} 'relays.ordb.org')
104
105 # 参见: http://www.spamcop.net/ (你可以在这里报告 spammer)
106     echo -n ' spamcop.net says: '
107     echo $(chk_adr ${rev_dns} 'bl.spamcop.net')
108
109 # # # 其他的黑名单操作 # # #
110
111 # 参见: http://cbl.abuseat.org.
112     echo -n ' abuseat.org says: '
113     echo $(chk_adr ${rev_dns} 'cbl.abuseat.org')
114
115 # 参见: http://dsbl.org/usage (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 http://www.xyz23.com/file01.html
       2 #  The -p or --page-requisite 选项将会使得 wget 取得显示指定页时
       3 #+ 所需要的所有文件.(译者: 比如内嵌图片和样式表等).
       4
       5 wget -r ftp://ftp.xyz24.net/~bozo/project_files/ -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='http://finance.yahoo.com/q?s='
17 # Yahoo 金融板块, 后缀是股票查询.
18
19 # -----------------------------------------------------------
20 wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
21 # -----------------------------------------------------------
22
23
24 # 在 http://search.yahoo.com 上查询相关材料:
25 # -----------------------------------------------------------
26 # URL="http://search.yahoo.com/search?fr=ush-news&p=${query}"
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 http://www.xyz23.com/file01.html >$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 <rojy bug>  
################################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 是可能的, <rojy bug>
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 wiseguy@superdupergenius.com'
 6 #+ 发送这个脚本到这个地址.
 7 #  如果只键入 'self-mailer.sh' (不给参数) 的话, 那么这脚本就会被发送给
 8 #+ 调用者, 比如 bozo@localhost.localdomain.
 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)




返回顶部

发表评论:

Powered By Z-BlogPHP 1.7.3


知识共享许可协议
本作品采用知识共享署名 3.0 中国大陆许可协议进行许可。
网站备案号粤ICP备15104741号-1