第12章 外部过滤器,程序和命令
=============================
标准的 UNIX 命令使得 shell 脚本更加灵活.通过简单的编程结构把shell指令和系统命令结
合起来,这才是脚本能力的所在.
12.1 基本命令
-------------
新手必须要掌握的初级命令
ls
基本的列出所有文件的命令.但是往往就是因为这个命令太简单,所以我们总是低估它.比如
,用 -R 选项,这是递归选项,ls 将会以目录树的形式列出所有文件, 另一个很有用的选项
是 -S ,将会按照文件尺寸列出所有文件, -t, 将会按照修改时间来列出文件,-i 选项会显
示文件的inode(见 Example 12-4).
Example 12-1 使用ls命令来创建一个烧录CDR的内容列表
################################Start Script#######################################
1 #!/bin/bash
2 # ex40.sh (burn-cd.sh)
3 # 自动刻录CDR的脚本.
4
5
6 SPEED=2 # 如果你的硬件支持的话,你可以选用更高的速度.
7 IMAGEFILE=cdimage.iso
8 CONTENTSFILE=contents
9 DEVICE=cdrom
10 # DEVICE="0,0" 为了使用老版本的CDR
11 DEFAULTDIR=/opt # 这是包含需要被刻录内容的目录.
12 # 必须保证目录存在.
13 # 小练习: 测试一下目录是否存在.
14
15 # Uses Joerg Schilling's "cdrecord" package:
15 # 使用 Joerg Schilling 的 "cdrecord"包:
16 # http://www.fokus.fhg.de/usr/schilling/cdrecord.html
17
18 # 如果一般用户调用这个脚本的话,可能需要root身份
19 #+ chmod u+s /usr/bin/cdrecord
20 # 当然, 这会产生安全漏洞, 虽然这是一个比较小的安全漏洞.
21
22 if [ -z "$1" ]
23 then
24 IMAGE_DIRECTORY=$DEFAULTDIR
25 # 如果命令行没指定的话, 那么这个就是默认目录.
26 else
27 IMAGE_DIRECTORY=$1
28 fi
29
30 # 创建一个内容列表文件.
31 ls -lRF $IMAGE_DIRECTORY > $IMAGE_DIRECTORY/$CONTENTSFILE
32 # "l" 选项将给出一个"长"文件列表.
33 # "R" 选项将使这个列表递归.
34 # "F" 选项将标记出文件类型 (比如: 目录是以 /结尾, 而可执行文件以 *结尾).
35 echo "Creating table of contents."
36
37 # 在烧录到CDR之前创建一个镜像文件.
38 mkisofs -r -o $IMAGEFILE $IMAGE_DIRECTORY
39 echo "Creating ISO9660 file system image ($IMAGEFILE)."
40
41 # 烧录CDR.
42 echo "Burning the disk."
43 echo "Please be patient, this will take a while."
44 cdrecord -v -isosize speed=$SPEED dev=$DEVICE $IMAGEFILE
45
46 exit $?
################################End Script#########################################
cat, tac
cat, 是单词 concatenate的缩写, 把文件的内容输出到stdout. 当与重定向操作符 (> 或
>>)结合使用时, 一般都是用来将多个文件连接起来.
1 # Uses of 'cat'
2 cat filename # 打印出文件内容.
3
4 cat file.1 file.2 file.3 > file.123 # 把3个文件连接到一个文件中.
cat 命令的 -n 选项是为了在目标文件中的所有行前边插入行号. -b 选项 与 -n 选项一
样, 区别是不对空行进行编号. -v 选项可以使用 ^ 标记法 来echo 出不可打印字符.-s选
项可以把多个空行压缩成一个空行.
见 Example 12-25 和 Example 12-21.
注意: 在一个 管道 中, 可能有一种把stdin 重定向 到一个文件中的更有效的办法, 这
种方法比 cat文件的方法更有效率.
1 cat filename | tr a-z A-Z
2
3 tr a-z A-Z < filename # 效果相同,但是处理更少,
4 #+ 并且连管道都省掉了.
tac 命令, 就是 cat的反转, 将从文件的结尾列出文件.
rev
把每一行中的内容反转, 并且输出到 stdout上. 这个命令与 tac命令的效果是不同的, 因
为它并不反转行序, 而是把每行的内容反转.
bash$ cat file1.txt
This is line 1.
This is line 2.
bash$ tac file1.txt
This is line 2.
This is line 1.
bash$ rev file1.txt
.1 enil si sihT
.2 enil si sihT
cp
这是文件拷贝命令. cp file1 file2 把 file1 拷贝到 file2, 如果存在 file2 的话,那
file2 将被覆盖 (见 Example 12-6).
注意: 特别有用的选项就是 -a 归档 选项 (为了copy一个完整的目录树), -u 是更新选
项, 和 -r 与 -R 递归选项.
1 cp -u source_dir/* dest_dir
2 # "Synchronize" dest_dir to source_dir把源目录"同步"到目标目录上,
3 #+ 也就是拷贝所有更新的文件和之前不存在的文件.
mv
这是文件移动命令. 它等价于 cp 与 rm 命令的组合. 它可以把多个文件移动到目录中,甚
至将目录重命名. 想查看 mv 在脚本中使用的例子, 见 Example 9-18 和 Example A-2.
注意: 当使用非交互脚本时,可以使用 mv 的-f (强制) 选项来避免用户的输入.
当一个目录被移动到一个已存在的目录时,那么它将成为目标目录的子目录.
bash$ mv source_directory target_directory
bash$ ls -lF target_directory
total 1
drwxrwxr-x 2 bozo bozo 1024 May 28 19:20 source_directory/
rm
删除(清除)一个或多个文件. -f 选项将强制删除文件,即使这个文件是只读的.并且可以
用来避免用户输入(在非交互脚本中使用).
注意: rm 将无法删除以破折号开头的文件.
bash$ rm -badname
rm: invalid option -- b
Try `rm --help' for more information.
解决这个问题的一个方法就是在要删除的文件的前边加上"./".
bash$ rm ./-badname
另一种解决的方法是 在文件名前边加上 " -- ".
bash$ rm -- -badname
注意: 当使用递归参数 -r时, rm 命令将会删除整个目录树. 如果不慎使用 rm -rf *那整
个目录树就真的完了.
rmdir
删除目录. 但是只有这个目录中没有文件 -- 当然会包含不可见的 点文件 [1] -- 的
时候这个命令才会成功.
mkdir
生成目录, 创建一个空目录. 比如, mkdir -p project/programs/December 将会创建出
这个指定的目录, 即使project目录和programs目录都不存在. -p 选项将会自动产生必要
的父目录, 这样也就同时创建了多个目录.
chmod
修改一个现存文件的属性 (见 Example 11-12).
1 chmod +x filename
2 # 使得文件filename对所有用户都可执行.
3
4 chmod u+s filename
5 # 设置"filename"文件的"suid"位.
6 # 这样一般用户就可以执行"filename", 他将拥有和文件宿主相同的权限.
7 # (这并不适用于shell 脚本)
1 chmod 644 filename
2 # Makes "filename" readable/writable to owner, readable to
3 # 设置文件宿主的 r/w 权限,并对一般用户
3 # 设置读权限.
4 # (8进制模式).
1 chmod 1777 directory-name
2 # 对这个目录设置r/w 和可执行权限, 并开放给所有人.
3 # 同时设置 "粘贴位".
4 # 这意味着, 只有目录宿主,
5 # 文件宿主, 当然, 还有root
6 # 可以删除这个目录中的任何特定的文件.
chattr
修改文件属性. 这个命令与上边的 chmod 命令相类似, 但是有不同的选项和不同的调用语
法, 并且这个命令只能工作在ext2文件系统中.
chattr 命令的一个特别有趣的选项是i. chattr +i filename 将使得这个文件被标记为
永远不变. 这个文件将不能被修改, 连接, 或删除, 即使是root也不行. 这个文件属性只
能被root设置和删除. 类似的, a 选项将会把文件标记为只能追加数据.
root# chattr +i file1.txt
root# rm file1.txt
rm: remove write-protected regular file `file1.txt'? y
rm: cannot remove `file1.txt': Operation not permitted
如果文件设置了s(安全)属性, 那么当这个文件被删除时,这个文件所在磁盘的块将全部被0
填充.
如果文件设置了u(不可删除)属性, 那么当这个文件被删除后, 这个文件的内容还可以被恢
复(不可删除).
如果文件设置了c(压缩)属性, 那么当这个文件在进行写操作时,它将自动被压缩,并且在
读的时候, 自动解压.
注意: 使用命令chattr do设置的属性, 将不会显示在文件列表中(ls -l).
ln
创建文件链接, 前提是这个文件是存在的. "链接" 就是一个文件的引用, 也就是这个文
件的另一个名字. ln 命令允许对同一个文件引用多个链接,并且是避免混淆的一个很好的
方法 (见 Example 4-6).
ln 对于文件来说只不过是创建了一个引用, 一个指针而已, 因为创建出来的连接文件只有
几个字节.
绝大多数使用ln 命令时使用是 -s 选项, 可以称为符号链接, 或软链接.使用 -s 选项的
一个优点是它可以穿越文件系统来链接目录.
关于使用这个命令的语法还是有点小技巧的. 比如: ln -s oldfile newfile 将对老文件
产生一个新的文件链接.
注意: 如果之前就存在newfile的话, 那么将会产生一个错误消息.
使用链接中的哪种类型?
就像 John Macdonald 解释的那样:
不论是那种类型的链接, 都提供了一种双向引用的手段 -- 也就是说, 不管你用文件
的那个名字对文件内容进行修改, 你修改的效果都即会反映到原始名字的文件, 也会
反映到链接名字的文件.当你工作在更高层次的时候, 才会发生软硬链接的不同. 硬链
接的优点是, 原始文件与链接文件之间是相互独立的 -- 如果你删除或者重命名老文
件, 那么这种操作将不会影响硬链接的文件, 硬链接的文件讲还是原来文件的内容.
然而如果你使用软链接的, 当你把老文件删除或重命名后, 软链接将再也找不到原来
文件的内容了. 而软链接的优点是它可以跨越文件系统(因为它只不过是文件名的一个
引用, 而并不是真正的数据). 与硬链接的另一个不同是, 一个符号链接可以指向一个
目录.
链接给出了一种可以用多个名字来调用脚本的能力(当然这也适用于任何可执行的类型),
并且脚本的行为将依赖于脚本是如何被调用的.
Example 12-2 Hello or Good-bye
################################Start Script#######################################
1 #!/bin/bash
2 # hello.sh: 显示"hello" 还是 "goodbye"
3 #+ 依赖于脚本是如何被调用的.
4
5 # 在当前目录下($PWD)为这个脚本创建一个链接:
6 # ln -s hello.sh goodbye
7 # 现在, 通过如下两种方法来调用这个脚本:
8 # ./hello.sh
9 # ./goodbye
10
11
12 HELLO_CALL=65
13 GOODBYE_CALL=66
14
15 if [ $0 = "./goodbye" ]
16 then
17 echo "Good-bye!"
18 # 当然, 在这里你也可以添加一些其他的 goodbye类型的命令.Some other goodbye-type commands, as appropriate.
19 exit $GOODBYE_CALL
20 fi
21
22 echo "Hello!"
23 # 当然, 在这里你也可以添加一些其他的 hello类型的命令.
24 exit $HELLO_CALL
################################End Script#########################################
man, info
These 这两个命令用来查看系统命令或安装工具的手册和信息.当两者都可用时, info 页
一般比 man也会包含更多的细节描述.
注意事项:
[1] Dotfiles 就是文件名以"."开头的文件, 比如 ~/.Xdefaults. 这样的文件在一般的 l
s 命令使用中将不会被显示出来 (当然 ls -a 将会显示它们), 并且它们也不会被一
个意外的 rm -rf *删除. 在用户的home目录中,Dotfiles 一般被用来当作安装和配置
文件.
12.2 复杂命令
-------------
更高级的用户命令
find
-exec COMMAND \;
在每一个find 匹配到的文件执行 COMMAND 命令. 命令序列以 ; 结束( ";" 是 转义符 以
保证 shell 传递到find命令中的字符不会被解释为其他的特殊字符).
bash$ find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt
如果 COMMAND 中包含 {}, 那么 find 命令将会用所有匹配文件的路径名来替换 "{}" .
1 find ~/ -name 'core*' -exec rm {} \;
2 # 从用户的 home 目录中删除所有的 core dump文件.
1 find /home/bozo/projects -mtime 1
2 # 列出最后一天被修改的
3 #+ 在/home/bozo/projects目录树下的所有文件.
4 #
5 # mtime = last modification time of the target file
6 # ctime = last status change time (via 'chmod' or otherwise)
7 # atime = last access time
8
9 DIR=/home/bozo/junk_files
10 find "$DIR" -type f -atime +5 -exec rm {} \;
11 # ^^
12 # 大括号就是"find"命令用来替换目录的地方.
13 #
14 # 删除至少5天内没被存取过的
15 #+ "/home/bozo/junk_files" 中的所有文件.
16 #
17 # "-type filetype", where
18 # f = regular file
19 # d = directory, etc.
20 # ('find' 命令的 man页有完整的选项列表.)
1 find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;
2
3 # 在/etc 目录中的文件找到所所有包含 IP 地址(xxx.xxx.xxx.xxx) 的文件.
4 # 可能会查找到一些多余的匹配. 我们如何去掉它们呢?
5
6 # 或许可以使用如下方法:
7
8 find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
9 | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
10 #
11 # [:digit:] 是一种字符类.is one of the character classes
12 #+ 关于字符类的介绍见 POSIX 1003.2 标准化文档.
13
14 # Thanks, Stéphane Chazelas.
注意: find 命令的 -exec 选项不应该与shell中的内建命令 exec 相混淆.
Example 12-3 删除当前目录下文件名中包含一些特殊字符(包括空白)的文件..
################################Start Script#######################################
1 #!/bin/bash
2 # badname.sh
3 # 删除当前目录下文件名中包含一些特殊字符的文件.
4
5 for filename in *
6 do
7 badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
8 # badname=`echo "$filename" | sed -n '/[+{;"\=?~()<>&*|$]/p'` 这句也行.
9 # 删除文件名包含这些字符的文件: + { ; " \ = ? ~ ( ) < > & * | $
10 #
11 rm $badname 2>/dev/null
12 # ^^^^^^^^^^^ 错误消息将被抛弃.
13 done
14
15 # 现在, 处理文件名中以任何方式包含空白的文件.
16 find . -name "* *" -exec rm -f {} \;
17 # "find"命令匹配到的目录名将替换到{}的位置.
18 # '\' 是为了保证 ';'被正确的转义, 并且放到命令的结尾.
19
20 exit 0
21
22 #---------------------------------------------------------------------
23 # 这行下边的命令将不会运行, 因为 "exit" 命令.
24
25 # 这句是上边脚本的一个可选方法:
26 find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
27 # (Thanks, S.C.)
################################End Script#########################################
Example 12-4 通过文件的 inode 号来删除文件
################################Start Script#######################################
1 #!/bin/bash
2 # idelete.sh: 通过文件的inode号来删除文件.
3
4 # 当文件名以一个非法字符开头的时候, 这就非常有用了,
5 #+ 比如 ? 或 -.
6
7 ARGCOUNT=1 # 文件名参数必须被传递到脚本中.
8 E_WRONGARGS=70
9 E_FILE_NOT_EXIST=71
10 E_CHANGED_MIND=72
11
12 if [ $# -ne "$ARGCOUNT" ]
13 then
14 echo "Usage: `basename $0` filename"
15 exit $E_WRONGARGS
16 fi
17
18 if [ ! -e "$1" ]
19 then
20 echo "File \""$1"\" does not exist."
21 exit $E_FILE_NOT_EXIST
22 fi
23
24 inum=`ls -i | grep "$1" | awk '{print $1}'`
25 # inum = inode (索引节点) 号.
26 # --------------------------------------------------------
27 # 每个文件都有一个inode号, 这个号用来记录文件物理地址信息.
28 # --------------------------------------------------------
29
30 echo; echo -n "Are you absolutely sure you want to delete \"$1\" (y/n)? "
31 # 'rm' 命令的 '-v' 选项也会问这句话.
32 read answer
33 case "$answer" in
34 [nN]) echo "Changed your mind, huh?"
35 exit $E_CHANGED_MIND
36 ;;
37 *) echo "Deleting file \"$1\".";;
38 esac
39
40 find . -inum $inum -exec rm {} \;
41 # ^^
42 # 大括号就是"find"命令
43 #+ 用来替换文本输出的地方.
44 echo "File "\"$1"\" deleted!"
45
46 exit 0
################################End Script#########################################
见 Example 12-27, Example 3-4, 和 Example 10-9 这些例子展示了使用 find 命令. 对
于这个复杂而有强大的命令来说, 查看man页可以获得更多的细节.
xargs
这是给命令传递参数的一个过滤器, 也是组合多个命令的一个工具.它把一个数据流分割为
一些足够小的块, 以方便过滤器和命令进行处理. 由此这个命令也是后置引用的一个强有
力的替换. 在一般使用过多参数的命令替换失败的时候,用xargs 来替换它一般都能成功.
[1] 通常情况下, xargs 从管道或者stdin中读取数据, 但是它也能够从文件的输出中读取
数据.
xargs的默认命令是 echo. 这意味着通过管道传递给xargs的输入将会包含换行和空白, 不
过通过xargs的处理, 换行和空白将被空格取代.
bash$ ls -l
total 0
-rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1
-rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2
bash$ ls -l | xargs
total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2
bash$ find ~/mail -type f | xargs grep "Linux"
./misc:User-Agent: slrn/0.9.8.1 (Linux)
./sent-mail-jul-2005: hosted by the Linux Documentation Project.
./sent-mail-jul-2005: (Linux Documentation Project Site, rtf version)
./sent-mail-jul-2005: Subject: Criticism of Bozo's Windows/Linux article
./sent-mail-jul-2005: while mentioning that the Linux ext2/ext3 filesystem
. . .
ls | xargs -p -l gzip 使用gzips 压缩当前目录下的每个文件, 一次压缩一个, 并且在
每次压缩前都提示用户.
注意: 一个有趣的 xargs 选项是 -n NN, NN 是限制每次传递进来参数的个数.
ls | xargs -n 8 echo 以每行8列的形式列出当前目录下的所有文件.
注意: 另一个有用的选项是 -0, 使用 find -print0 或 grep -lZ 这两种组合方式. 这允
许处理包含空白或引号的参数.
find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f
grep -rliwZ GUI / | xargs -0 rm -f
上边两行都可用来删除任何包含 "GUI" 的文件. (Thanks, S.C.)
Example 12-5 Logfile: 使用 xargs 来监控系统 log
################################Start Script#######################################
1 #!/bin/bash
2
3 # 从 /var/log/messagesGenerates 的尾部开始
4 # 产生当前目录下的一个lof 文件.
5
6 # 注意: 如果这个脚本被一个一般用户调用的话,
7 # /var/log/messages 必须是全部可读的.
8 # #root chmod 644 /var/log/messages
9
10 LINES=5
11
12 ( date; uname -a ) >>logfile
13 # 时间和机器名
14 echo --------------------------------------------------------------------- >>logfile
15 tail -$LINES /var/log/messages | xargs | fmt -s >>logfile
16 echo >>logfile
17 echo >>logfile
18
19 exit 0
20
21 # 注意:
22 # -----
23 # 像 Frank Wang 所指出,
24 #+ 在原文件中的任何不匹配的引号(包括单引号和双引号)
25 #+ 都会给xargs造成麻烦.
26 #
27 # 他建议使用下边的这行来替换上边的第15行:
28 # tail -$LINES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile
29
30
31
32 # 练习:
33 # -----
34 # 修改这个脚本, 使得这个脚本每个20分钟
35 #+ 就跟踪一下 /var/log/messages 的修改记录.
36 # 提示: 使用 "watch" 命令.
################################End Script#########################################
在find命令中, 一对大括号就一个文本替换的位置.
Example 12-6 把当前目录下的文件拷贝到另一个文件中
################################Start Script#######################################
1 #!/bin/bash
2 # copydir.sh
3
4 # 拷贝 (verbose) 当前目录($PWD)下的所有文件到
5 #+ 命令行中指定的另一个目录下.
6
7 E_NOARGS=65
8
9 if [ -z "$1" ] # 如果没有参数传递进来那就退出.
10 then
11 echo "Usage: `basename $0` directory-to-copy-to"
12 exit $E_NOARGS
13 fi
14
15 ls . | xargs -i -t cp ./{} $1
16 # ^^ ^^ ^^
17 # -t 是 "verbose" (输出命令行到stderr) 选项.
18 # -i 是"替换字符串"选项.
19 # {} 是输出文本的替换点.
20 # 这与在"find"命令中使用{}的情况很相像.
21 #
22 # 列出当前目录下的所有文件(ls .),
23 #+ 将 "ls" 的输出作为参数传递到 "xargs"(-i -t 选项) 中,
24 #+ 然后拷贝(cp)这些参数({})到一个新目录中($1).
25 #
26 # 最终的结果和下边的命令等价,
27 #+ cp * $1
28 #+ 除非有文件名中嵌入了"空白"字符.
29
30 exit 0
################################End Script#########################################
Example 12-7 通过名字Kill进程
################################Start Script#######################################
1 #!/bin/bash
2 # kill-byname.sh: 通过名字kill进程.
3 # 与脚本kill-process.sh相比较.
4
5 # 例如,
6 #+ 试一下 "./kill-byname.sh xterm" --
7 #+ 并且查看你系统上的所有xterm都将消失.
8
9 # 警告:
10 # -----
11 # 这是一个非常危险的脚本.
12 # 运行它的时候一定要小心. (尤其是以root身份运行时)
13 #+ 因为运行这个脚本可能会引起数据丢失或产生其他一些不好的效果.
14
15 E_BADARGS=66
16
17 if test -z "$1" # 没有参数传递进来?
18 then
19 echo "Usage: `basename $0` Process(es)_to_kill"
20 exit $E_BADARGS
21 fi
22
23
24 PROCESS_NAME="$1"
25 ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
26 # ^^ ^^
27
28 # -----------------------------------------------------------
29 # 注意:
30 # -i 参数是xargs命令的"替换字符串"选项.
31 # 大括号对的地方就是替换点.
32 # 2&>/dev/null 将会丢弃不需要的错误消息.
33 # -----------------------------------------------------------
34
35 exit $?
################################End Script#########################################
Example 12-8 使用xargs分析单词出现的频率
################################Start Script#######################################
1 #!/bin/bash
2 # wf2.sh: Crude word frequency analysis on a text file.
3
4 # 使用 'xargs' 将文本行分解为单词.
5 # 于后边的 "wf.sh" 脚本相比较.
6
7
8 # 检查命令行上输入的文件.
9 ARGS=1
10 E_BADARGS=65
11 E_NOFILE=66
12
13 if [ $# -ne "$ARGS" ]
14 # 纠正传递到脚本中的参数个数?
15 then
16 echo "Usage: `basename $0` filename"
17 exit $E_BADARGS
18 fi
19
20 if [ ! -f "$1" ] # 检查文件是否存在.
21 then
22 echo "File \"$1\" does not exist."
23 exit $E_NOFILE
24 fi
25
26
27
28 #################################################################
29 cat "$1" | xargs -n1 | \
30 # 列出文件, 每行一个单词.
31 tr A-Z a-z | \
32 # 将字符转换为小写.
33 sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\
34 /g' | \
35 # 过滤掉句号和逗号,
36 #+ 并且将单词间的空格修改为换行,
37 sort | uniq -c | sort -nr
38 # 最后统计出现次数,把数字显示在第一列,然后显示单词,并按数字排序.
39 #################################################################
40
41 # 这个例子的作用与"wf.sh"的作用是一样的,
42 #+ 但是这个例子比较臃肿, 并且运行起来更慢一些(为什么?).
43
44 exit 0
################################End Script#########################################
expr
通用求值表达式: 通过给定的操作(参数必须以空格分开)连接参数,并对参数求值.可以使
算术操作, 比较操作, 字符串操作或者是逻辑操作.
expr 3 + 5
返回 8
expr 5 % 3
返回 2
expr 1 / 0
返回错误消息, expr: division by zero
不允许非法的算术操作.
expr 5 \* 3
返回 15
在算术表达式expr中使用乘法操作时, 乘法符号必须被转义.
y=`expr $y + 1`
增加变量的值, 与 let y=y+1 和 y=$(($y+1)) 的效果相同. 这是使用算术表达式的
一个例子.
z=`expr substr $string $position $length`
在位置$position上提取$length长度的子串.
Example 12-9 使用 expr
################################Start Script#######################################
1 #!/bin/bash
2
3 # 展示一些 'expr'的使用
4 # =====================
5
6 echo
7
8 # 算术 操作
9 # ---- ----
10
11 echo "Arithmetic Operators"
12 echo
13 a=`expr 5 + 3`
14 echo "5 + 3 = $a"
15
16 a=`expr $a + 1`
17 echo
18 echo "a + 1 = $a"
19 echo "(incrementing a variable)"
20
21 a=`expr 5 % 3`
22 # 取模操作
23 echo
24 echo "5 mod 3 = $a"
25
26 echo
27 echo
28
29 # 逻辑 操作
30 # ---- ----
31
32 # true返回 1 ,false 返回 0 ,
33 #+ 而Bash的使用惯例则相反.
34
35 echo "Logical Operators"
36 echo
37
38 x=24
39 y=25
40 b=`expr $x = $y` # 测试相等.
41 echo "b = $b" # 0 ( $x -ne $y )
42 echo
43
44 a=3
45 b=`expr $a \> 10`
46 echo 'b=`expr $a \> 10`, therefore...'
47 echo "If a > 10, b = 0 (false)"
48 echo "b = $b" # 0 ( 3 ! -gt 10 )
49 echo
50
51 b=`expr $a \< 10`
52 echo "If a < 10, b = 1 (true)"
53 echo "b = $b" # 1 ( 3 -lt 10 )
54 echo
55 # Note escaping of operators.
56
57 b=`expr $a \<= 3`
58 echo "If a <= 3, b = 1 (true)"
59 echo "b = $b" # 1 ( 3 -le 3 )
60 # 也有 "\>=" 操作 (大于等于).
61
62
63 echo
64 echo
65
66
67
68 # 字符串 操作
69 # ------ ----
70
71 echo "String Operators"
72 echo
73
74 a=1234zipper43231
75 echo "The string being operated upon is \"$a\"."
76
77 # 长度: 字符串长度
78 b=`expr length $a`
79 echo "Length of \"$a\" is $b."
80
81 # 索引: 从字符串的开头查找匹配的子串,
82 # 并取得第一个匹配子串的位置.
83 b=`expr index $a 23`
84 echo "Numerical position of first \"2\" in \"$a\" is \"$b\"."
85
86 # substr: 从指定位置提取指定长度的字串.
87 b=`expr substr $a 2 6`
88 echo "Substring of \"$a\", starting at position 2,\
89 and 6 chars long is \"$b\"."
90
91
92 # 'match' 操作的默认行为就是
93 #+ 从字符串的开始进行搜索,并匹配第一个匹配的字符串.
94 #
95 # 使用正则表达式
96 b=`expr match "$a" '[0-9]*'` # 数字的个数.
97 echo Number of digits at the beginning of \"$a\" is $b.
98 b=`expr match "$a" '\([0-9]*\)'` # 注意需要转义括号
99 # == == + 这样才能触发子串的匹配.
100 echo "The digits at the beginning of \"$a\" are \"$b\"."
101
102 echo
103
104 exit 0
################################End Script#########################################
注意: ":" 操作可以替换 match. 比如, b=`expr $a : [0-9]*`与上边所使用的 b=`expr
match $a [0-9]*` 完全等价.
################################Start Script#######################################
1 #!/bin/bash
2
3 echo
4 echo "String operations using \"expr \$string : \" construct"
5 echo "==================================================="
6 echo
7
8 a=1234zipper5FLIPPER43231
9
10 echo "The string being operated upon is \"`expr "$a" : '\(.*\)'`\"."
11 # 转义括号对操作. == ==
12
13 # ***************************
14 #+ 转移括号对
15 #+ 用来匹配一个子串
16 # ***************************
17
18
19 # 如果不转义括号的话...
20 #+ 那么 'expr' 将把string操作转换为一个整数.
21
22 echo "Length of \"$a\" is `expr "$a" : '.*'`." # 字符串长度
23
24 echo "Number of digits at the beginning of \"$a\" is `expr "$a" : '[0-9]*'`."
25
26 # ------------------------------------------------------------------------- #
27
28 echo
29
30 echo "The digits at the beginning of \"$a\" are `expr "$a" : '\([0-9]*\)'`."
31 # == ==
32 echo "The first 7 characters of \"$a\" are `expr "$a" : '\(.......\)'`."
33 # ===== == ==
34 # 再来一个, 转义括号对强制一个子串匹配.
35 #
36 echo "The last 7 characters of \"$a\" are `expr "$a" : '.*\(.......\)'`."
37 # ==== end of string operator ^^
38 # (最后这个模式的意思是忽略前边的任何字符,直到最后7个字符,
39 #+ 最后7个点就是需要匹配的任意7个字符的字串)
40
41 echo
42
43 exit 0
################################End Script#########################################
上边的脚本展示了expr是如何使用转义的括号对 -- \( ... \) -- 和 正则表达式 一起来分
析和匹配子串. 下边是另外一个例子, 这次的例子是真正的应用用例.
1 # 去掉字符串开头和结尾的空白.
2 LRFDATE=`expr "$LRFDATE" : '[[:space:]]*\(.*\)[[:space:]]*$'`
3
4 # 来自于 Peter Knowle的 "booklistgen.sh" 脚本
5 #+ 用来将文件转换为Sony Librie格式.
6 # (http://booklistgensh.peterknowles.com)
Perl, sed, 和 awk 是更强大的字符串分析工具. 在脚本中嵌入一段比较短的 sed 或 awk
操作 (见 Section 33.2) 比使用 expr 更加有吸引力.
见 Section 9.2 将会有更多使用 expr 进行字符串操作的例子.
注意事项:
[1] 即使在不必非得强制使用 xargs 的时候, 使用 xargs 也可以明显地提高多文件批处
理执行命令的速度.
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命令开始匹配它的 <range> 和 <action>. 第一个匹配的并且只有这一个匹配的:
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 # 所以这行没被匹配到 <range> 中, 这样sed命令就结束了.
56
57 # 这个 sed 命令的简短的解释是:
58 # "在第一行中删除第一个空格左边全部的字符,
59 #+ 然后打印出来."
60
61 # 一个更好的来达到这个目的的方法是:
62 # sed -e 's/.* //;q'
63
64 # 这里, <range> 和 <action> 分别是 (也可以写成
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 (http://www.gutenberg.org/etext/247).
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" "*" <filename 还是 tr A-Z \* <filename 都可以将 filename 中的大
写字符修改为星号(写到 stdout).但是在某些系统上可能就不能正常工作了, 而 tr A-Z '
[**]' 在任何系统上都可以正常工作.
-d 选项删除指定范围的字符.
1 echo "abcdef" # abcdef
2 echo "abcdef" | tr -d b-d # aef
3
4
5 tr -d 0-9 <filename
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 <filename
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. <rojy bug>(不太了解这句的内容, 应该是有特定的含义)
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 工具, 这个工
具可以从后边的这个网址取得http://www.cs.berkeley.edu/~amc/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 <filename 将会在filename文件中对
每行删除第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 \\n >> "$BOOKLIST"
6 }
7
8 # 来自于 Peter Knowles' "booklistgen.sh" 脚本
9 #+ 目的是把文件转换为 Sony Librie 格式.
10 # (http://booklistgensh.peterknowles.com)
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 系统上的一般版本合适
的地方.
返回顶部