Example 12-42 按月偿还贷款
################################Start Script#######################################
1 #!/bin/bash
2 # monthlypmt.sh: 计算按月偿还贷款的数量.
3
4
5 # 这份代码是一份修改版本, 原始版本在 "mcalc" (贷款计算)包中,
6 #+ 这个包的作者是 Jeff Schmidt 和 Mendel Cooper (本书作者).
7 # http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz [15k]
8
9 echo
10 echo "Given the principal, interest rate, and term of a mortgage,"
11 echo "calculate the monthly payment."
12
13 bottom=1.0
14
15 echo
16 echo -n "Enter principal (no commas) "
17 read principal
18 echo -n "Enter interest rate (percent) " # 如果是 12%, 那就键入 "12", 别输入 ".12".
19 read interest_r
20 echo -n "Enter term (months) "
21 read term
22
23
24 interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数.
25 # "scale" 指定了有效数字的个数.
26
27
28 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
29
30
31 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
32
33 echo; echo "Please be patient. This may take a while."
34
35 let "months = $term - 1"
36 # ====================================================================
37 for ((x=$months; x > 0; x--))
38 do
39 bot=$(echo "scale=9; $interest_rate^$x" | bc)
40 bottom=$(echo "scale=9; $bottom+$bot" | bc)
41 # bottom = $(($bottom + $bot"))
42 done
43 # ====================================================================
44
45 # --------------------------------------------------------------------
46 # Rick Boivie 给出了一个对上边循环的修改,
47 #+ 这个修改更加有效率, 将会节省大概 2/3 的时间.
48
49 # for ((x=1; x <= $months; x++))
50 # do
51 # bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
52 # done
53
54
55 # 然后他又想出了一个更加有效率的版本,
56 #+ 将会节省 95% 的时间!
57
58 # bottom=`{
59 # echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
60 # for ((x=1; x <= $months; x++))
61 # do
62 # echo 'bottom = bottom * interest_rate + 1'
63 # done
64 # echo 'bottom'
65 # } | bc` # 在命令替换中嵌入一个 'for 循环'.
66 # --------------------------------------------------------------------------
67 # On the other hand, Frank Wang suggests:
68 # bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc)
69
70 # 因为 . . .
71 # 在循环后边的算法
72 #+ 事实上是一个等比数列的求和公式.
73 # 求和公式是 e0(1-q^n)/(1-q),
74 #+ e0 是第一个元素 并且 q=e(n+1)/e(n)
75 #+ 和 n 是元素的数量.
76 # --------------------------------------------------------------------------
77
78
79 # let "payment = $top/$bottom"
80 payment=$(echo "scale=2; $top/$bottom" | bc)
81 # 使用2位有效数字来表示美元和美分.
82
83 echo
84 echo "monthly payment = \$$payment" # 在总和的前边显示美元符号.
85 echo
86
87
88 exit 0
89
90
91 # 练习:
92 # 1) 处理输入允许本金总数中的逗号.
93 # 2) 处理输入允许按照百分号和小数点的形式输入利率.
94 # 3) 如果你真正想好好编写这个脚本,
95 # 那么就扩展这个脚本让它能够打印出完整的分期付款表.
################################End Script#########################################
Example 12-43 数制转换
################################Start Script#######################################
1 #!/bin/bash
2 ##########################################################################
3 # 脚本 : base.sh - 用不同的数值来打印数字 (Bourne Shell)
4 # 作者 : Heiner Steven (heiner.steven@odn.de)
5 # 日期 : 07-03-95
6 # 类型 : 桌面
7 # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
8 # ==> 上边这行是 RCS ID 信息.
9 ##########################################################################
10 # 描述
11 #
12 # Changes
13 # 21-03-95 stv fixed error occuring with 0xb as input (0.2)
14 ##########################################################################
15
16 # ==> 在本书中使用这个脚本通过了作者的授权.
17 # ==> 注释是本书作者添加的.
18
19 NOARGS=65
20 PN=`basename "$0"` # 程序名
21 VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2
22
23 Usage () {
24 echo "$PN - print number to different bases, $VER (stv '95)
25 usage: $PN [number ...]
26
27 If no number is given, the numbers are read from standard input.
28 A number may be
29 binary (base 2) starting with 0b (i.e. 0b1100)
30 octal (base 8) starting with 0 (i.e. 014)
31 hexadecimal (base 16) starting with 0x (i.e. 0xc)
32 decimal otherwise (i.e. 12)" >&2
33 exit $NOARGS
34 } # ==> 打印出用法信息的函数.
35
36 Msg () {
37 for i # ==> 省略 [list] .
38 do echo "$PN: $i" >&2
39 done
40 }
41
42 Fatal () { Msg "$@"; exit 66; }
43
44 PrintBases () {
45 # 决定数值的数制
46 for i # ==> 省略 [list]...
47 do # ==> 所以是对命令行参数进行操作.
48 case "$i" in
49 0b*) ibase=2;; # 2进制
50 0x*|[a-f]*|[A-F]*) ibase=16;; # 16进制
51 0*) ibase=8;; # 8进制
52 [1-9]*) ibase=10;; # 10进制
53 *)
54 Msg "illegal number $i - ignored"
55 continue;;
56 esac
57
58 # 去掉前缀, 将16进制数字转换为大写(bc需要大写)
59 number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
60 # ==>使用":" 作为sed分隔符, 而不使用"/".
61
62 # 将数字转换为10进制
63 dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc' 是个计算工具.
64 case "$dec" in
65 [0-9]*) ;; # 数字没问题
66 *) continue;; # 错误: 忽略
67 esac
68
69 # 在一行上打印所有的转换后的数字.
70 # ==> 'here document' 提供命令列表给'bc'.
71 echo `bc <<!
72 obase=16; "hex="; $dec
73 obase=10; "dec="; $dec
74 obase=8; "oct="; $dec
75 obase=2; "bin="; $dec
76 !
77 ` | sed -e 's: : :g'
78
79 done
80 }
81
82 while [ $# -gt 0 ]
83 # ==> 这里必须使用一个 "while 循环",
84 # ==>+ 因为所有的 case 都可能退出循环或者
85 # ==>+ 结束脚本.
86 # ==> (感谢, Paulo Marcel Coelho Aragao.)
87 do
88 case "$1" in
89 --) shift; break;;
90 -h) Usage;; # ==> 帮助信息.
91 -*) Usage;;
92 *) break;; # 第一个数字
93 esac # ==> 对于非法输入更严格检查是非常有用的.
94 shift
95 done
96
97 if [ $# -gt 0 ]
98 then
99 PrintBases "$@"
100 else # 从标准输入中读取
101 while read line
102 do
103 PrintBases $line
104 done
105 fi
106
107
108 exit 0
################################End Script#########################################
调用 bc 的另一种可选的方法就是使用 here document ,并把它嵌入到 命令替换 块中.
当一个脚本需要将一个选项列表和多个命令传递到 bc 中时, 这种方法就显得非常合适.
1 variable=`bc << LIMIT_STRING
2 options
3 statements
4 operations
5 LIMIT_STRING
6 `
7
8 ...or...
9
10
11 variable=$(bc << LIMIT_STRING
12 options
13 statements
14 operations
15 LIMIT_STRING
16 )
Example 12-44 使用 "here document" 来调用 bc
################################Start Script#######################################
1 #!/bin/bash
2 # 使用命令替换来调用 'bc'
3 # 并与 'here document' 相结合.
4
5
6 var1=`bc << EOF
7 18.33 * 19.78
8 EOF
9 `
10 echo $var1 # 362.56
11
12
13 # $( ... ) 这种标记法也可以.
14 v1=23.53
15 v2=17.881
16 v3=83.501
17 v4=171.63
18
19 var2=$(bc << EOF
20 scale = 4
21 a = ( $v1 + $v2 )
22 b = ( $v3 * $v4 )
23 a * b + 15.35
24 EOF
25 )
26 echo $var2 # 593487.8452
27
28
29 var3=$(bc -l << EOF
30 scale = 9
31 s ( 1.7 )
32 EOF
33 )
34 # 返回弧度为1.7的正弦.
35 # "-l" 选项将会调用 'bc' 算数库.
36 echo $var3 # .991664810
37
38
39 # 现在, 在函数中试一下...
40 hyp= # 声明全局变量.
41 hypotenuse () # 计算直角三角形的斜边.
42 {
43 hyp=$(bc -l << EOF
44 scale = 9
45 sqrt ( $1 * $1 + $2 * $2 )
46 EOF
47 )
48 # 不幸的是, 不能从bash 函数中返回浮点值.
49 }
50
51 hypotenuse 3.68 7.31
52 echo "hypotenuse = $hyp" # 8.184039344
53
54
55 exit 0
################################End Script#########################################
Example 12-45 计算圆周率
################################Start Script#######################################
1 #!/bin/bash
2 # cannon.sh: 通过开炮来取得近似的圆周率值.
3
4 # 这事实上是一个"Monte Carlo"蒙特卡洛模拟的非常简单的实例:
5 #+ 蒙特卡洛模拟是一种由现实事件抽象出来的数学模型,
6 #+ 由于要使用随机抽样统计来估算数学函数, 所以使用伪随机数来模拟真正的随机.
7
8 # 想象有一个完美的正方形土地, 边长为10000个单位.
9 # 在这块土地的中间有一个完美的圆形湖,
10 #+ 这个湖的直径是10000个单位.
11 # 这块土地的绝大多数面积都是水, 当然只有4个角上有一些土地.
12 # (可以把这个湖想象成为使这个正方形的内接圆.)
13 #
14 # 我们将使用老式的大炮和铁炮弹
15 #+ 向这块正方形的土地上开炮.
16 # 所有的炮弹都会击中这块正方形土地的某个地方.
17 #+ 或者是打到湖上, 或者是打到4个角的土地上.
18 # 因为这个湖占据了这个区域大部分地方,
19 #+ 所以大部分的炮弹都会"扑通"一声落到水里.
20 # 而只有很少的炮弹会"砰"的一声落到4个
21 #+ 角的土地上.
22 #
23 # 如果我们发出的炮弹足够随机的落到这块正方形区域中的话,
24 #+ 那么落到水里的炮弹与打出炮弹的总数的比率,
25 #+ 大概非常接近于 PI/4.
26 #
27 # 原因是所有的炮弹事实上都
28 #+ 打在了这个土地的右上角,
29 #+ 也就是, 笛卡尔坐标系的第一象限.
30 # (之前的解释只是一个简化.)
31 #
32 # 理论上来说, 如果打出的炮弹越多, 就越接近这个数字.
33 # 然而, 对于shell 脚本来说一定会作些让步的,
34 #+ 因为它肯定不能和那些内建就支持浮点运算的编译语言相比.
35 # 当然就会降低精度.
36
37
38 DIMENSION=10000 # 这块土地的边长.
39 # 这也是所产生的随机整数的上限.
40
41 MAXSHOTS=1000 # 开炮次数.
42 # 10000 或更多次的话, 效果应该更好, 但有点太浪费时间了.
43 PMULTIPLIER=4.0 # 接近于 PI 的比例因子.
44
45 get_random ()
46 {
47 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
48 RANDOM=$SEED # 来自于 "seeding-random.sh"
49 #+ 的例子脚本.
50 let "rnum = $RANDOM % $DIMENSION" # 范围小于 10000.
51 echo $rnum
52 }
53
54 distance= # 声明全局变量.
55 hypotenuse () # 从 "alt-bc.sh" 例子来的,
56 { # 计算直角三角形的斜边的函数.
57 distance=$(bc -l << EOF
58 scale = 0
59 sqrt ( $1 * $1 + $2 * $2 )
60 EOF
61 )
62 # 设置 "scale" 为 0 , 好让结果四舍五入为整数值,
63 #+ 这是这个脚本中必须折中的一个地方.
64 # 不幸的是, 这将降低模拟的精度.
65 }
66
67
68 # main() {
69
70 # 初始化变量.
71 shots=0
72 splashes=0
73 thuds=0
74 Pi=0
75
76 while [ "$shots" -lt "$MAXSHOTS" ] # 主循环.
77 do
78
79 xCoord=$(get_random) # 取得随机的 X 与 Y 坐标.
80 yCoord=$(get_random)
81 hypotenuse $xCoord $yCoord # 直角三角形斜边 =
82 #+ distance.
83 ((shots++))
84
85 printf "#%4d " $shots
86 printf "Xc = %4d " $xCoord
87 printf "Yc = %4d " $yCoord
88 printf "Distance = %5d " $distance # 到湖中心的
89 #+ 距离 --
90 # 起始坐标点 --
91 #+ (0,0).
92
93 if [ "$distance" -le "$DIMENSION" ]
94 then
95 echo -n "SPLASH! "
96 ((splashes++))
97 else
98 echo -n "THUD! "
99 ((thuds++))
100 fi
101
102 Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)
103 # 将比例乘以 4.0.
104 echo -n "PI ~ $Pi"
105 echo
106
107 done
108
109 echo
110 echo "After $shots shots, PI looks like approximately $Pi."
111 # 如果不太准的话, 那么就提高一下运行的次数. . .
112 # 可能是由于运行错误和随机数随机程度不高造成的.
113 echo
114
115 # }
116
117 exit 0
118
119 # 要想知道一个shell脚本到底适不适合作为
120 #+ 一种需要对复杂和精度都有要求的计算应用的模拟的话.
121 #
122 # 一般至少需要两个判断条件.
123 # 1) 作为一种概念的验证: 来显示它可以做到.
124 # 2) 在使用真正的编译语言来实现一个算法之前,
125 #+ 使用脚本来测试和验证这个算法.
################################End Script#########################################
dc
dc (桌面计算器desk calculator) 工具是面向栈的并且使用 RPN (逆波兰表达式
"Reverse Polish Notation" 又叫"后缀表达式"). 与 bc 命令很相像 , 但是这个工具
具备好多只有编程语言才具备的能力.
(译者注: 正常表达式 逆波兰表达式
a+b a,b,+
a+(b-c) a,b,c,-,+
a+(b-c)*d a,d,b,c,-,*,+
)
绝大多数人都避免使用这个工具, 因为它需要非直觉的 RPN 输入. 但是, 它却有特定的
用途.
Example 12-46 将10进制数字转换为16进制数字
################################Start Script#######################################
1 #!/bin/bash
2 # hexconvert.sh: 将10进制数字转换为16进制数字
3
4 E_NOARGS=65 # 缺命令行参数错误.
5 BASE=16 # 16进制.
6
7 if [ -z "$1" ]
8 then
9 echo "Usage: $0 number"
10 exit $E_NOARGS
11 # 需要一个命令行参数.
12 fi
13 # 练习: 添加命令行参数检查.
14
15
16 hexcvt ()
17 {
18 if [ -z "$1" ]
19 then
20 echo 0
21 return # 如果没有参数传递到这个函数中就 "return" 0.
22 fi
23
24 echo ""$1" "$BASE" o p" | dc
25 # "o" 设置输出的基数(数制).
26 # "p" 打印栈顶.
27 # 察看 dc 的 man 页来了解其他的选项.
28 return
29 }
30
31 hexcvt "$1"
32
33 exit 0
################################End Script#########################################
通过仔细学习 dc 命令的 info 页, 可以更深入的理解这个复杂的命令. 但是, 有一些
精通 dc巫术 的小组经常会炫耀他们使用这个强大而又晦涩难懂的工具时的一些技巧,
并以此为乐.
bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc"
Bash
Example 12-47 因子分解
################################Start Script#######################################
1 #!/bin/bash
2 # factr.sh: 分解约数
3
4 MIN=2 # 如果比这个数小就不行了.
5 E_NOARGS=65
6 E_TOOSMALL=66
7
8 if [ -z $1 ]
9 then
10 echo "Usage: $0 number"
11 exit $E_NOARGS
12 fi
13
14 if [ "$1" -lt "$MIN" ]
15 then
16 echo "Number to factor must be $MIN or greater."
17 exit $E_TOOSMALL
18 fi
19
20 # 练习: 添加类型检查 (防止非整型的参数).
21
22 echo "Factors of $1:"
23 # ---------------------------------------------------------------------------------
24 echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc
25 # ---------------------------------------------------------------------------------
26 # 上边这行代码是 Michel Charpentier 编写的<charpov@cs.unh.edu>.
27 # 在此使用经过授权 (thanks).
28
29 exit 0
################################End Script#########################################
awk
在脚本中使用浮点运算的另一种方法是使用 awk 内建的数学运算函数, 可以用在shell
wrapper中.
Example 12-48 计算直角三角形的斜边
################################Start Script#######################################
1 #!/bin/bash
2 # hypotenuse.sh: 返回直角三角形的斜边.
3 # ( 直角边长的平方和,然后对和取平方根)
4
5 ARGS=2 # 需要将2个直角边作为参数传递进来.
6 E_BADARGS=65 # 错误的参数值.
7
8 if [ $# -ne "$ARGS" ] # 测试传递到脚本中的参数值.
9 then
10 echo "Usage: `basename $0` side_1 side_2"
11 exit $E_BADARGS
12 fi
13
14
15 AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
16 # 命令 / 传递给awk的参数
17
18
19 # 现在, 将参数通过管道传递给awk.
20 echo -n "Hypotenuse of $1 and $2 = "
21 echo $1 $2 | awk "$AWKSCRIPT"
22
23 exit 0
################################End Script#########################################
12.9 混杂命令
-------------
一些不好归类的命令
jot, seq
这些工具通过用户指定的范围和增量来产生一系列的整数.
每个产生出来的整数一般都占一行, 但是可以使用 -s 选项来改变这种设置.
bash$ seq 5
1
2
3
4
5
bash$ seq -s : 5
1:2:3:4:5
jot 和 seq 命令都经常用在 for 循环中.
Example 12-49 使用 seq 来产生循环参数
################################Start Script#######################################
1 #!/bin/bash
2 # 使用 "seq"
3
4 echo
5
6 for a in `seq 80` # 或者 for a in $( seq 80 )
7 # 与 " for a in 1 2 3 4 5 ... 80 "相同 (少敲了好多字!).
8 # 也可以使用 'jot' (如果系统上有的话).
9 do
10 echo -n "$a "
11 done # 1 2 3 4 5 ... 80
12 # 这也是一个通过使用命令的输出
13 # 来产生 "for"循环中 [list] 列表的例子.
14
15 echo; echo
16
17
18 COUNT=80 # 当然, 'seq' 也可以使用一个可替换的参数.
19
20 for a in `seq $COUNT` # 或者 for a in $( seq $COUNT )
21 do
22 echo -n "$a "
23 done # 1 2 3 4 5 ... 80
24
25 echo; echo
26
27 BEGIN=75
28 END=80
29
30 for a in `seq $BEGIN $END`
31 # 传给 "seq" 两个参数, 从第一个参数开始增长,
32 #+ 一直增长到第二个参数为止.
33 do
34 echo -n "$a "
35 done # 75 76 77 78 79 80
36
37 echo; echo
38
39 BEGIN=45
40 INTERVAL=5
41 END=80
42
43 for a in `seq $BEGIN $INTERVAL $END`
44 # 传给 "seq" 三个参数从第一个参数开始增长,
45 #+ 并以第二个参数作为增量,
46 #+ 一直增长到第三个参数为止.
47 do
48 echo -n "$a "
49 done # 45 50 55 60 65 70 75 80
50
51 echo; echo
52
53 exit 0
################################End Script#########################################
一个简单些的例子:
1 # 产生10个连续扩展名的文件,
2 #+ 名字分别是 file.1, file.2 . . . file.10.
3 COUNT=10
4 PREFIX=file
5
6 for filename in `seq $COUNT`
7 do
8 touch $PREFIX.$filename
9 # 或者, 你可以做一些其他的操作,
10 #+ 比如 rm, grep, 等等.
11 done
Example 12-50 字母统计
################################Start Script#######################################
1 #!/bin/bash
2 # letter-count.sh: 统计一个文本文件中字母出现的次数.
3 # 由 Stefano Palmeri 编写.
4 # 经过授权使用在本书中.
5 # 本书作者做了少许修改.
6
7 MINARGS=2 # 本脚本至少需要2个参数.
8 E_BADARGS=65
9 FILE=$1
10
11 let LETTERS=$#-1 # 制定了多少个字母 (作为命令行参数).
12 # (从命令行参数的个数中减1.)
13
14
15 show_help(){
16 echo
17 echo Usage: `basename $0` file letters
18 echo Note: `basename $0` arguments are case sensitive.
19 echo Example: `basename $0` foobar.txt G n U L i N U x.
20 echo
21 }
22
23 # 检查参数个数.
24 if [ $# -lt $MINARGS ]; then
25 echo
26 echo "Not enough arguments."
27 echo
28 show_help
29 exit $E_BADARGS
30 fi
31
32
33 # 检查文件是否存在.
34 if [ ! -f $FILE ]; then
35 echo "File \"$FILE\" does not exist."
36 exit $E_BADARGS
37 fi
38
39
40
41 # 统计字母出现的次数.
42 for n in `seq $LETTERS`; do
43 shift
44 if [[ `echo -n "$1" | wc -c` -eq 1 ]]; then # 检查参数.
45 echo "$1" -\> `cat $FILE | tr -cd "$1" | wc -c` # 统计.
46 else
47 echo "$1 is not a single char."
48 fi
49 done
50
51 exit $?
52
53 # 这个脚本在功能上与 letter-count2.sh 完全相同,
54 #+ 但是运行得更快.
55 # 为什么?
################################End Script#########################################
getopt
getopt 命令将会分析以破折号开头的命令行选项. 这个外部命令与Bash的内建命令
getopts 作用相同. 通过使用 -l 标志, getopt 可以处理长(多字符)选项, 并且也允许参
数重置.
Example 12-51 使用getopt来分析命令行选项
################################Start Script#######################################
1 #!/bin/bash
2 # 使用 getopt.
3
4 # 尝试使用下边的不同的方法来调用这脚本:
5 # sh ex33a.sh -a
6 # sh ex33a.sh -abc
7 # sh ex33a.sh -a -b -c
8 # sh ex33a.sh -d
9 # sh ex33a.sh -dXYZ
10 # sh ex33a.sh -d XYZ
11 # sh ex33a.sh -abcd
12 # sh ex33a.sh -abcdZ
13 # sh ex33a.sh -z
14 # sh ex33a.sh a
15 # 解释上面每一次调用的结果.
16
17 E_OPTERR=65
18
19 if [ "$#" -eq 0 ]
20 then # 脚本需要至少一个命令行参数.
21 echo "Usage $0 -[options a,b,c]"
22 exit $E_OPTERR
23 fi
24
25 set -- `getopt "abcd:" "$@"`
26 # 为命令行参数设置位置参数.
27 # 如果使用 "$*" 来代替 "$@" 的话会发生什么?
28
29 while [ ! -z "$1" ]
30 do
31 case "$1" in
32 -a) echo "Option \"a\"";;
33 -b) echo "Option \"b\"";;
34 -c) echo "Option \"c\"";;
35 -d) echo "Option \"d\" $2";;
36 *) break;;
37 esac
38
39 shift
40 done
41
42 # 通常来说在脚本中使用内建的 'getopts' 命令,
43 #+ 会比使用 'getopt' 好一些.
44 # 参见 "ex33.sh".
45
46 exit 0
################################End Script#########################################
参见 Example 9-12 , 这是对 getopt 命令的一个简单模拟.
run-parts
run-parts 命令 [1] 将会执行目标目录中所有的脚本, 这些将本会以 ASCII 的循序进行
排列. 当然, 这些脚本都需要具有可执行权限.
cron 幽灵进程 会调用 run-parts 来运行 /etc/cron.* 下的所有脚本.
yes
yes 命令的默认行为是向 stdout 中连续不断的输出字符 y,每个y占一行.使用control-c
来结束运行. 如果想换一个输出字符的话, 可以使用 yes 其他的字符串, 这样就会连续
不同的输出你指定的字符串. 那么这样的命令究竟能做什么呢? 在命令行或者脚本中,
yes的输出可以通过重定向或管道来传递给一些需要用户输入进行交互的命令. 事实上,
这个命令可以说是 expect 命令(译者注: 这个命令本书未介绍, 一个自动实现交互的命
令)的一个简化版本.
yes | fsck /dev/hda1 将会以非交互的形式运行fsck(因为需要用户输入的 y 全由yes
命令搞定了)(小心使用!).
yes | rm -r dirname 与 rm -rf dirname 效果相同(小心使用!).
注意: 当用 yes 的管道形式来使用一些可能具有潜在危险的系统命令的时候一定要深思
熟虑, 比如 fsck 或 fdisk. 可能会产生一些意外的副作用.
banner
将会把字符串用一个 ASCII 字符(默认是 '#')来画出来(就是将多个'#'拼出一副字符的
图形).可以作为硬拷贝重定向到打印机上(译者注: 可以使用-w 选项设置宽度).
printenv
对于某个特定的用户, 显示出所有的 环境变量.
bash$ printenv | grep HOME
HOME=/home/bozo
lp
lp 和 lpr 命令将会把文件发送到打印队列中, 并且作为硬拷贝来打印. [2] 这些命令
会纪录它们名字的起始位置并传递到行打印机的另一个位置.<rojy bug>
bash$ lp file1.txt 或者 bash lp <file1.txt
通常情况下都是将pr的格式化的输出传递到 lp.
bash$ pr -options file1.txt | lp
格式化的包, 比如 groff 和 Ghostscript 就可以将它们的输出直接发送给 lp.
bash$ groff -Tascii file.tr | lp
bash$ gs -options | lp file.ps
还有一些相关的命令, 比如 lpq, 可以查看打印队列, lprm, 可以用来从打印队列中删
除作业.
tee
[UNIX 从管道行业借来的主意.]
这是一个重定向操作, 但是有些不同. 就像管道中的"三通"一样, 这个命令可以将命令或
者管道命令的输出抽出到一个文件中,而且并不影响结果. 当你想将一个正在运行的进程
的输出保存到文件中时, 或者为了debug而保存输出记录的时候, 这个命令就非常有用了.
(重定向)
|----> to file
|
==========================|====================
command ---> command ---> |tee ---> command ---> ---> output of pipe
===============================================
1 cat listfile* | sort | tee check.file | uniq > result.file
(在对排序的结果进行 uniq (去掉重复行) 之前,文件 check.file 中保存了排过序的
"listfiles".)
mkfifo
这个不大引人注意的命令可以创建一个命名管道, 并产生一个临时的先进先出的buffer
用来在两个进程间传输数据. [3] 典型的使用是一个进程向FIFO中写数据, 另一个进程读
出来. 参见 Example A-15.
pathchk
这个命令用来检查文件名的有效性. 如果文件名超过了最大允许长度(255 个字符), 或者
它所在的一个或多个路径搜索不到, 那么就会产生一个错误结果.
不幸的是,并不能够返回一个可识别的错误码, 因此它在脚本中几乎没有什么用. 一般都
使用文件测试操作.
dd
这也是一个不太出名的工具, 但却是一个令人恐惧的 "数据复制" 命令. 最开始, 这个命
令是被用来在UNIX 微机和IBM大型机之间通过磁带来交换数据, 这个命令现在仍然有它的
用途. dd 命令只不过是简单的拷贝一个文件 (或者 stdin/stdout), 但是它会做一些转
换. 下边是一些可能的转换, 比如 ASCII/EBCDIC, [4] 大写/小写, 在输入和输出之间
的字节对的交换, 还有对输入文件做一些截头去尾的工作. dd --help 列出了所有转换,
还有这个强力工具的一些其他选项.
1 # 将一个文件转换为大写:
2
3 dd if=$filename conv=ucase > $filename.uppercase
4 # lcase # 转换为小写
Example 12-52 一个拷贝自身的脚本
################################Start Script#######################################
1 #!/bin/bash
2 # self-copy.sh
3
4 # 这个脚本将会拷贝自身.
5
6 file_subscript=copy
7
8 dd if=$0 of=$0.$file_subscript 2>/dev/null
9 # 阻止dd产生的消息: ^^^^^^^^^^^
10
11 exit $?
################################End Script#########################################
Example 12-53 练习dd
################################Start Script#######################################
1 #!/bin/bash
2 # exercising-dd.sh
3
4 # 由Stephane Chazelas编写.
5 # 本文作者做了少量修改.
6
7 input_file=$0 # 脚本本身.
8 output_file=log.txt
9 n=3
10 p=5
11
12 dd if=$input_file of=$output_file bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null
13 # 从脚本中把位置n到p的字符提取出来.
14
15 # -------------------------------------------------------
16
17 echo -n "hello world" | dd cbs=1 conv=unblock 2> /dev/null
18 # 垂直的 echo "hello world" .
19
20 exit 0
################################End Script#########################################
为了展示dd的多种用途, 让我们使用它来记录按键.
Example 12-54 记录按键
################################Start Script#######################################
1 #!/bin/bash
2 # dd-keypress.sh: 记录按键, 不需要按回车.
3
4
5 keypresses=4 # 记录按键的个数.
6
7
8 old_tty_setting=$(stty -g) # 保存老的终端设置.
9
10 echo "Press $keypresses keys."
11 stty -icanon -echo # 禁用标准模式.
12 # 禁用本地 echo.
13 keys=$(dd bs=1 count=$keypresses 2> /dev/null)
14 # 如果不指定输入文件的话, 'dd' 使用标准输入.
15
16 stty "$old_tty_setting" # 恢复老的终端设置.
17
18 echo "You pressed the \"$keys\" keys."
19
20 # 感谢 Stephane Chazelas, 演示了这种方法.
21 exit 0
################################End Script#########################################
dd 命令可以在数据流上做随即存取.
1 echo -n . | dd bs=1 seek=4 of=file conv=notrunc
2 # "conv=notrunc" 选项意味着输出文件不能被截短.
3
4 # Thanks, S.C.
dd 命令可以将数据或磁盘镜像拷贝到设备中, 也可以从设备中拷贝数据或磁盘镜像, 比
如说磁盘或磁带设备都可以 (Example A-5). 通常用来创建启动盘.
dd if=kernel-image of=/dev/fd0H1440
同样的, dd 可以拷贝软盘的整个内容(甚至是其他操作系统的磁盘格式) 到硬盘驱动器上
(以镜像文件的形式).
dd if=/dev/fd0 of=/home/bozo/projects/floppy.img
dd 命令还有一些其他用途, 包括可以初始化临时交换文件 (Example 28-2) 和 ramdisks
(内存虚拟硬盘) (Example 28-3). 它甚至可以做一些对整个硬盘分区的底层拷贝, 虽然
不建议这么做.
一些(可能是比较无聊的)人总会想一些关于 dd 命令的有趣的应用.
Example 12-55 安全的删除一个文件
################################Start Script#######################################
1 #!/bin/bash
2 # blot-out.sh: 删除一个文件所有的记录.
3
4 # 这个脚本会使用随即字节交替的覆盖
5 #+ 目标文件, 并且在最终删除这个文件之前清零.
6 # 这么做之后, 即使你通过传统手段来检查磁盘扇区
7 #+ 也不能把文件原始数据重新恢复.
8
9 PASSES=7 # 破坏文件的次数.
10 # 提高这个数字会减慢脚本运行的速度,
11 #+ 尤其是对尺寸比较大的目标文件进行操作的时候.
12 BLOCKSIZE=1 # 带有 /dev/urandom 的 I/O 需要单位块尺寸,
13 #+ 否则你可能会获得奇怪的结果.
14 E_BADARGS=70 # 不同的错误退出码.
15 E_NOT_FOUND=71
16 E_CHANGED_MIND=72
17
18 if [ -z "$1" ] # 没指定文件名.
19 then
20 echo "Usage: `basename $0` filename"
21 exit $E_BADARGS
22 fi
23
24 file=$1
25
26 if [ ! -e "$file" ]
27 then
28 echo "File \"$file\" not found."
29 exit $E_NOT_FOUND
30 fi
31
32 echo; echo -n "Are you absolutely sure you want to blot out \"$file\" (y/n)? "
33 read answer
34 case "$answer" in
35 [nN]) echo "Changed your mind, huh?"
36 exit $E_CHANGED_MIND
37 ;;
38 *) echo "Blotting out file \"$file\".";;
39 esac
40
41
42 flength=$(ls -l "$file" | awk '{print $5}') # 5 是文件长度.
43 pass_count=1
44
45 chmod u+w "$file" # Allow overwriting/deleting the file.
46
47 echo
48
49 while [ "$pass_count" -le "$PASSES" ]
50 do
51 echo "Pass #$pass_count"
52 sync # 刷新buffer.
53 dd if=/dev/urandom of=$file bs=$BLOCKSIZE count=$flength
54 # 使用随机字节进行填充.
55 sync # 再刷新buffer.
56 dd if=/dev/zero of=$file bs=$BLOCKSIZE count=$flength
57 # 用0填充.
58 sync # 再刷新buffer.
59 let "pass_count += 1"
60 echo
61 done
62
63
64 rm -f $file # 最后, 删除这个已经被破坏得不成样子的文件.
65 sync # 最后一次刷新buffer.
66
67 echo "File \"$file\" blotted out and deleted."; echo
68
69
70 exit 0
71
72 # 这是一种真正安全的删除文件的办法,
73 #+ 但是效率比较低, 运行比较慢.
74 # GNU 的文件工具包中的 "shred" 命令,
75 #+ 也可以完成相同的工作, 不过更有效率.
76
77 # 使用普通的方法是不可能重新恢复这个文件了.
78 # 然而 . . .
79 #+ 这个简单的例子是不能够抵抗
80 #+ 那些经验丰富并且正规的分析.
81
82 # 这个脚本可能不会很好的运行在日志文件系统上.(译者注: JFS)
83 # 练习 (很难): 像它做的那样修正这个问题.
84
85
86
87 # Tom Vier的文件删除包可以更加彻底
88 #+ 的删除文件, 比这个简单的例子厉害得多.
89 # http://www.ibiblio.org/pub/Linux/utils/file/wipe-2.0.0.tar.bz2
90
91 # 如果想对安全删除文件这一论题进行深度的分析,
92 #+ 可以参见Peter Gutmann的页面,
93 #+ "Secure Deletion of Data From Magnetic and Solid-State Memory".
94 # http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html
################################End Script#########################################
od
od(octal dump)过滤器, 将会把输入(或文件)转换为8进制或者其他进制. 在你需要查看
或处理一些二进制数据文件或者一个不可读的系统设备文件的时候, 这个命令非常有用,
比如/dev/urandom,或者是一个二进制数据过滤器. 参见 Example 9-28 和
Example 12-13.
hexdump
对二进制文件进行 16进制, 8进制, 10进制, 或者 ASCII 码的查阅动作. 这个命令大体
上与上边的od命令作用相同, 但是远不及 od 命令有用.
objdump
显示编译后的2进制文件或2进制可执行文件的信息, 以16进制的形式显示, 或者显示反汇
编列表(使用-d选项).
bash$ objdump -d /bin/ls
/bin/ls: file format elf32-i386
Disassembly of section .init:
080490bc <.init>:
80490bc: 55 push %ebp
80490bd: 89 e5 mov %esp,%ebp
. . .
mcookie
这个命令会产生一个"magic cookie", 这是一个128-bit (32-字符) 的伪随机16进制数字,
这个数字一般都用来作为X server的鉴权"签名". 这个命令还可以用来在脚本中作为一
种生成随机数的手段, 当然这是一种"小吃店"(虽然不太正统, 但是很方便)的风格.
1 random000=$(mcookie)
当然, 完成同样的目的还可以使用 md5 命令.
1 # 产生关于脚本本身的 md5 checksum.
2 random001=`md5sum $0 | awk '{print $1}'`
3 # 使用 'awk' 来去掉文件名.
mcookie 还给出了产生"唯一"文件名的另一种方法.
Example 12-56 文件名产生器
################################Start Script#######################################
1 #!/bin/bash
2 # tempfile-name.sh: 临时文件名产生器
3
4 BASE_STR=`mcookie` # 32-字符的 magic cookie.
5 POS=11 # 字符串中随便的一个位置.
6 LEN=5 # 取得 $LEN 长度连续的字符串.
7
8 prefix=temp # 最终的一个临时文件.
9 # 如果想让这个文件更加唯一,
10 #+ 可以对这个前缀也使用下边的方法来生成.
11
12 suffix=${BASE_STR:POS:LEN}
13 # 提取从第11个字符之后的长度为5的字符串.
14
15 temp_filename=$prefix.$suffix
16 # 构造文件名.
17
18 echo "Temp filename = "$temp_filename""
19
20 # sh tempfile-name.sh
21 # Temp filename = temp.e19ea
22
23 # 与使用 'date' 命令(参考 ex51.sh)来创建唯一文件名
24 #+ 的方法相比较.
25
26 exit 0
################################End Script#########################################
units
这个工具用来在不同的计量单位之间互相转换. 当你在交互模式下正常调用时, 会发现在
脚本中 units 也很有用.
Example 12-57 将米转换为英里
################################Start Script#######################################
1 #!/bin/bash
2 # unit-conversion.sh
3
4
5 convert_units () # 通过参数取得需要转换的单位.
6 {
7 cf=$(units "$1" "$2" | sed --silent -e '1p' | awk '{print $2}')
8 # 除了真正需要转换的部分保留下来外,其他的部分都去掉.
9 echo "$cf"
10 }
11
12 Unit1=miles
13 Unit2=meters
14 cfactor=`convert_units $Unit1 $Unit2`
15 quantity=3.73
16
17 result=$(echo $quantity*$cfactor | bc)
18
19 echo "There are $result $Unit2 in $quantity $Unit1."
20
21 # 如果你传递了两个不匹配的单位会发生什么?
22 #+ 比如分别传入英亩和英里?
23
24 exit 0
################################End Script#########################################
m4
一个隐藏的财宝, m4 是一个强力的宏处理过滤器, [5] 差不多可以说是一种语言了. 虽
然最开始这个工具是用来作为 RatFor 的预处理器而编写的, 但是后来证明 m4 作为独
立的工具也是非常有用的. 事实上, m4 结合了许多工具的功能, 比如 eval, tr, 和 awk,
除此之外, 它还使得宏扩展变得容易.
在 2004年4月的 Linux Journal 的问题列表中有一篇关于 m4 命令用法得非常好的文章.
Example 12-58 使用 m4
################################Start Script#######################################
1 #!/bin/bash
2 # m4.sh: 使用 m4 宏处理器
3
4 # 字符操作
5 string=abcdA01
6 echo "len($string)" | m4 # 7
7 echo "substr($string,4)" | m4 # A01
8 echo "regexp($string,[0-1][0-1],\&Z)" | m4 # 01Z
9
10 # 算术操作
11 echo "incr(22)" | m4 # 23
12 echo "eval(99 / 3)" | m4 # 33
13
14 exit 0
################################End Script#########################################
doexec
doexec 命令允许将一个随便的参数列表传递到一个二进制可执行文件中. 特别的, 甚至
可以传递 arg[0] (相当于脚本中的 $0 ), 这样可以使用不同的名字来调用这个可执行
文件, 并且通过不同的调用的名字, 可以让这个可执行文件执行不同的动作. 这也可以
说是一种将参数传递到可执行文件中的比较绕圈子的做法.
比如, /usr/local/bin 目录可能包含一个 "aaa" 的二进制文件. 使用
doexec /usr/local/bin/aaa list 可以 列出 当前工作目录下所有以 "a" 开头的的文
件, 而使用 doexec /usr/local/bin/aaa delete 将会删除这些文件.
注意: 可执行文件的不同行为必须定义在可执行文件自身的代码中, 可以使用如下的
shell脚本作类比:
1 case `basename $0` in
2 "name1" ) do_something;;
3 "name2" ) do_something_else;;
4 "name3" ) do_yet_another_thing;;
5 * ) bail_out;;
6 esac
dialog
dialog 工具集提供了一种从脚本中调用交互对话框的方法. dialog 的更好的变种版本是
-- gdialog, Xdialog, 和 kdialog -- 事实上是调用的 X-Windows 的界面工具集. 参
见 Example 33-19.
sox
sox 命令, "sound exchange" (声音转换)命令, 可以进行声音文件的转换. 事实上,可执
行文件 /usr/bin/play (现在不建议使用) 只不过是 sox 的一个 shell 包装器而已.
举个例子, sox soundfile.wav soundfile.au 将会把一个 WAV 声音文件转换成一个
(Sun 音频格式) AU 声音文件.
Shell 脚本非常适合于使用 sox 的声音操作来批处理声音文件. 比如, 参见
Linux Radio Timeshift HOWTO 和 MP3do Project.
注意事项:
[1] 这个工具事实上是从 Debian Linux 发行版中的一个脚本借鉴过来的.
[2] 打印队列 就是"在线等待"打印的作业组.
[3] 对于本话题的一个完美的介绍, 请参见 Andy Vaught 的文章, 命名管道的介绍,
(http://www2.linuxjournal.com/lj-issues/issue41/2156.html), 这是
Linux Journal (http://www.linuxjournal.com/)1997年9月的一个问题.
[4] EBCDIC (发音是 "ebb-sid-ick") 是单词 (Extended Binary Coded Decimal
Interchange Code) 的首字母缩写. 这是 IBM 的数据格式, 现在已经不常见了.
dd 命令的 conv=ebcdic 选项的一个比较奇异的使用方法是对一个文件进行快速而
且容易但不太安全的编码.
1 cat $file | dd conv=swab,ebcdic > $file_encrypted
2 # 编码 (看起来好像没什么用).
3 # 应该交换字节(swab), 有点晦涩.
4
5 cat $file_encrypted | dd conv=swab,ascii > $file_plaintext
6 # 解码.
[5] 宏 是一个符号常量, 将会被扩展成一个命令字符串或者一系列的参数操作.
第13章 系统与管理命令
======================
在/etc/rc.d目录中的启动和关机脚本中包含了好多有用的(和没用的)这些系统管理命令. 这些
命令通常总是被root用户使用, 用与系统维护或者是紧急文件系统修复.一定要小心使用这些工
具, 因为如果滥用的话, 它们会损坏你的系统.
Users 和 Groups 类命令
users
显示所有的登录的用户. 这个命令与 who -q 基本一致.
groups
列出当前用户和他所属于的组. 这相当于 $GROUPS 内部变量, 但是这个命令将会给出组名
字, 而不是数字.
bash$ groups
bozita cdrom cdwriter audio xgrp
bash$ echo $GROUPS
501
chown, chgrp
chown 命令将会修改一个或多个文件的所有权. 对于root来说这是一种非常好的将文件的
所有权从一个用户换到另一个用户的方法. 一个普通用户不能修改文件的所有权, 即使他
是文件的宿主也不行. [1]
root# chown bozo *.txt
chgrp 将会修改一个或个文件党组所有权. 你必须是这些文件的宿主, 并且是目的组的成
员(或者root), 这样才能使用这个操作.
1 chgrp --recursive dunderheads *.data
2 # "dunderheads"(译者: 晕,蠢才...) 组现在拥有了所有的"*.data"文件.
3 #+ 包括所有$PWD目录下的子目录中的文件(--recursive的作用就是包含子目录).
useradd, userdel
useradd 管理命令将会在系统上添加一个用户帐号, 并且如果指定的话, 还会为特定的用
户创建home目录. 相应的userdel 命令将会从系统上删除一个用户帐号, [2] 并且删除相
应的文件.
注意: adduser命令与useradd是相同的, adduser通常都是一个符号链接.
usermod
修改用户帐号. 可以修改密码, 组身份, 截止日期, 或者给定用户帐号的其他的属性. 使
用这个命令, 用户的密码可能会被锁定, 因为密码会影响到帐号的有效性.
groupmod
修改指定组. 组名字或者ID号都可以使用这个命令来修改.
id
id 将会列出当前进程的真实和有效用户ID, 还有用户的组ID. 这与Bash的内部变量
$UID, $EUID, 和 $GROUPS 很相像.
bash$ id
uid=501(bozo) gid=501(bozo) groups=501(bozo),22(cdrom),80(cdwriter),81(audio)
bash$ echo $UID
501
注意: id 命令只有在有效ID与真实ID不符时才会显示有效id.
参见 Example 9-5.
who
显示系统上所有已经登录的用户.
bash$ who
bozo tty1 Apr 27 17:45
bozo pts/0 Apr 27 17:46
bozo pts/1 Apr 27 17:47
bozo pts/2 Apr 27 17:49
-m 选项将会只给出当前用户的详细信息. 将任意两个参数传递到who中 都等价于who -m,
就像 who am i 或者 who The Man.
bash$ who -m
localhost.localdomain!bozo pts/2 Apr 27 17:49
whoami 与who -m 很相似, 但是只列出用户名.
bash$ whoami
bozo
w
显示所有的登录的用户和属于它们的进程. 这是一个who的扩展版本. w的输出可以通过管
道传递到grep中, 这样就可以查找指定的用户或进程.
bash$ w | grep startx
bozo tty1 - 4:22pm 6:41 4.47s 0.45s startx
logname
显示当前用户的登录名(可以在/var/run/utmp中找到). 这与上边的whoami很相近.
bash$ logname
bozo
bash$ whoami
bozo
然而...
bash$ su
Password: ......
bash# whoami
root
bash# logname
bozo
注意: logname只会打印出登录的用户名, 而whoami 将会给出附着到当前进程的用户名.
就像我们上边看到的那样, 这两个名字有时会不同.
su
使用一个代替的用户来运行一个程序或脚本. su rjones 将会以 rjones 来启动一个
shell. 一个不加参数的su默认就是root. 参见 Example A-15.
sudo
以root(或其他用户)的身份来运行一个命令. 这个命令可以运行在脚本中, 这样就允许以
正规的用户身份来运行脚本.
1 #!/bin/bash
2
3 # 一些命令.
4 sudo cp /root/secretfile /home/bozo/secret
5 # 一些命令.
文件 /etc/sudoers 持有允许调用sudo的用户名.
passwd
设置, 修改, 或者管理用户的密码.
passwd 命令可以用在脚本中, 但可能你不想这么用.
Example 13-1 设置一个新密码
################################Start Script#######################################
1 #!/bin/bash
2 # setnew-password.sh: 只用于说明目的.
3 # 如果真正运行这个脚本并不是一个好主意.
4 # 这个脚本必须以root身份运行.
5
6 ROOT_UID=0 # Root 的 $UID 0.
7 E_WRONG_USER=65 # 不是 root?
8
9 E_NOSUCHUSER=70
10 SUCCESS=0
11
12
13 if [ "$UID" -ne "$ROOT_UID" ]
14 then
15 echo; echo "Only root can run this script."; echo
16 exit $E_WRONG_USER
17 else
18 echo
19 echo "You should know better than to run this script, root."
20 echo "Even root users get the blues... "
21 echo
22 fi
23
24
25 username=bozo
26 NEWPASSWORD=security_violation
27
28 # 检查bozo是否在这里.
29 grep -q "$username" /etc/passwd
30 if [ $? -ne $SUCCESS ]
31 then
32 echo "User $username does not exist."
33 echo "No password changed."
34 exit $E_NOSUCHUSER
35 fi
36
37 echo "$NEWPASSWORD" | passwd --stdin "$username"
38 # 'passwd'命令 '--stdin' 选项允许
39 #+ 从stdin(或者管道)中获得一个新的密码.
40
41 echo; echo "User $username's password changed!"
42
43 # 在脚本中使用'passwd'命令是很危险的.
44
45 exit 0
################################End Script#########################################
passwd 命令的 -l, -u, 和 -d 选项允许锁定, 解锁,和删除一个用户的密码. 只有root
用户可以使用这些选项.
ac
显示用户登录的连接时间, 就像从 /var/log/wtmp 中读取一样. 这是GNU的一个统计工具.
bash$ ac
total 68.08
last
用户最后登录的信息, 就像从/var/log/wtmp中读出来一样. 这个命令也可以用来显示远
端登录.
比如, 显示最后几次系统的重启信息:
bash$ last reboot
reboot system boot 2.6.9-1.667 Fri Feb 4 18:18 (00:02)
reboot system boot 2.6.9-1.667 Fri Feb 4 15:20 (01:27)
reboot system boot 2.6.9-1.667 Fri Feb 4 12:56 (00:49)
reboot system boot 2.6.9-1.667 Thu Feb 3 21:08 (02:17)
. . .
wtmp begins Tue Feb 1 12:50:09 2005
newgrp
不用登出就可以修改用户的组ID. 并且允许存取新组的文件. 因为用户可能同时属于多个
组, 这个命令很少被使用.
终端类命令
tty
显示当前用户终端的名字. 注意每一个单独的xterm窗口都被算作一个不同的终端.
bash$ tty
/dev/pts/1
stty
显示并(或)修改终端设置. 这个复杂命令可以用在脚本中, 并可以用来控制终端的行为和
其显示输出的方法. 参见这个命令的info页, 并仔细学习它.
Example 13-2 设置一个擦除字符
################################Start Script#######################################
1 #!/bin/bash
2 # erase.sh: 在读取输入时使用"stty"来设置一个擦除字符.
3
4 echo -n "What is your name? "
5 read name # 试试退格键
6 #+ 来删除输入的字符.
7 # 有什么问题?
8 echo "Your name is $name."
9
10 stty erase '#' # 将 "hashmark" (#) 设置为退格字符.
11 echo -n "What is your name? "
12 read name # 使用#来删除最后键入的字符.
13 echo "Your name is $name."
14
15 # 警告: 即使在脚本退出后, 新的键值还是保持设置.(译者: 使用stty erase '^?' 恢复)
16
17 exit 0
################################End Script#########################################
Example 13-3 关掉终端对于密码的echo
################################Start Script#######################################
1 #!/bin/bash
2 # secret-pw.sh: 保护密码不被显示
3
4 echo
5 echo -n "Enter password "
6 read passwd
7 echo "password is $passwd"
8 echo -n "If someone had been looking over your shoulder, "
9 echo "your password would have been compromised."
10
11 echo && echo # 在一个"与列表"中产生2个换行.
12
13
14 stty -echo # 关闭屏幕的echo.
15
16 echo -n "Enter password again "
17 read passwd
18 echo
19 echo "password is $passwd"
20 echo
21
22 stty echo # 恢复屏幕的echo.
23
24 exit 0
25
26 # 详细的阅读stty命令的info页, 以便于更好的掌握这个有用并且狡猾的工具.
################################End Script#########################################
一个具有创造性的stty命令的用法, 检测用户所按的键(不用敲回车).
Example 13-4 按键检测
################################Start Script#######################################
1 #!/bin/bash
2 # keypress.sh: 检测用户按键 ("hot keys").
3
4 echo
5
6 old_tty_settings=$(stty -g) # 保存老的设置(为什么?).
7 stty -icanon
8 Keypress=$(head -c1) # 或者 $(dd bs=1 count=1 2> /dev/null)
9 # 在非GNU的系统上
10
11 echo
12 echo "Key pressed was \""$Keypress"\"."
13 echo
14
15 stty "$old_tty_settings" # 恢复老的设置.
16
17 # 感谢, Stephane Chazelas.
18
19 exit 0
################################End Script#########################################
参见 Example 9-3.
注意: 终端与模式terminals and modes
一般情况下, 一个终端都是工作在canonical(标准)模式下. 当用户按键后, 事实上所
产生的字符并没有马上传递到运行在当前终端上的程序. 终端上的一个本地的缓存保
存了这些按键. 当用按下ENTER键的时候, 才会将所有保存的按键信息传递到运行的程
序中. 这就意味着在终端内部存在一个基本的行编辑器.
bash$ stty -a
speed 9600 baud; rows 36; columns 96; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O;
...
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
在使用canonical模式的时候, 可以对本地终端行编辑器所定义的特殊按键进行重新定
义.
bash$ cat > filexxx
wha<ctl-W>I<ctl-H>foo bar<ctl-U>hello world<ENTER>
<ctl-D>
bash$ cat filexxx
hello world
bash$ wc -c < filexxx
12
控制终端的进程只保存了12个字符(11个字母加上一个换行), 虽然用户敲了26个按键.
在 non-canonical ("raw") 模式, 每次按键(包括特殊定义的按键, 比如 ctl-H)将会
立即发送一个字符到控制进程.
Bash提示符禁用了icanon和echo, 因为它用自己的更好的行编辑器代替了终端的基本
行编辑器. 比如, 当你在Bash提示符下敲ctl-A的时候, 终端将不会显示 ^A, 但是
Bash将会获得\1字符, 然后解释这个字符, 这样光标就移动到行首了.
Stéphane Chazelas
setterm
设置特定的终端属性. 这个命令将向它的终端的stdout写一个字符串, 这个字符串将修改
终端的行为.
bash$ setterm -cursor off
bash$
setterm 命令可以被用在脚本中来修改写到stdout的文本的外观, 虽然如果你仅仅只想完
成这个目的, 还有特定的更好的工具可以用.
1 setterm -bold on
2 echo bold hello
3
4 setterm -bold off
5 echo normal hello
tset
显示或初始化终端设置. 可以说这是stty的功能比较弱的版本.
bash$ tset -r
Terminal type is xterm-xfree86.
Kill is control-U (^U).
Interrupt is control-C (^C).
setserial
设置或者显示串口参数. 这个脚本只能被root用户来运行, 并且通常都在系统安装脚本
中使用.
1 # 来自于 /etc/pcmcia/serial 脚本:
2
3 IRQ=`setserial /dev/$DEVICE | sed -e 's/.*IRQ: //'`
4 setserial /dev/$DEVICE irq 0 ; setserial /dev/$DEVICE irq $IRQ
getty, agetty
一个终端的初始化过程通常都是使用getty或agetty来建立, 这样才能让用户登录. 这些
命令并不用在用户的shell脚本中. 它们的行为与stty很相似.
mesg
使能或禁用当前用户终端的存取权限. 禁用存取权限将会阻止网络上的另一用户向这个终
端写消息.
注意: 当你正在编写文本文件的时候, 在文本中间突然来了一个莫名其妙的消息, 这对你
来说是非常烦人的. 在多用户的网络环境下, 当你不想被打断的时候, 你可能因此希
望禁用对你终端的写权限.
wall
这是一个缩写单词 "write all", 也就是, 向登录到网络上的任何终端的所有用户都发送
一个消息. 最早这是一个管理员的工具, 很有用, 比如, 当系统有问题的时候, 管理可以
警告系统上的所有人暂时离开 (参见 Example 17-1).
bash$ wall System going down for maintenance in 5 minutes!
Broadcast message from bozo (pts/1) Sun Jul 8 13:53:27 2001...
System going down for maintenance in 5 minutes!
注意: 如果某个特定终端使用mesg来禁止了写权限, 那么wall将不会给它发消息.
信息与统计类
uname
输出系统的说明(OS, 内核版本, 等等.)到stdout. 使用 -a 选项, 将会给出详细的信息
(参见 Example 12-5). 使用-s选项只会输出OS类型.
bash$ uname -a
Linux localhost.localdomain 2.2.15-2.5.0 #1 Sat Feb 5 00:13:43 EST 2000 i686 unknown
bash$ uname -s
Linux
arch
显示系统的硬件体系结构. 等价于 uname -m. 参见 Example 10-26.
bash$ arch
i686
bash$ uname -m
i686
lastcomm
给出前一个命令的信息, 存储在/var/account/pacct文件中. 命令名字与用户名字都可以
使用选项来指定. 这是GNU的一个统计工具.
lastlog
列出系统上所有用户最后登录的时间. 存在/var/log/lastlog文件中.
bash$ lastlog
root tty1 Fri Dec 7 18:43:21 -0700 2001
bin **Never logged in**
daemon **Never logged in**
...
bozo tty1 Sat Dec 8 21:14:29 -0700 2001
bash$ lastlog | grep root
root tty1 Fri Dec 7 18:43:21 -0700 2001
注意: 如果用户对于/var/log/lastlog文件没有读权限的话, 那么调用这个命令就会失败.
lsof
列出打开的文件. 这个命令将会把所有当前打开的文件列出一份详细的表格, 包括文件的
所有者信息, 尺寸, 与它们相关的信息等等. 当然, lsof也可以管道输出到 grep 和(或)
awk来分析它的结果.
bash$ lsof
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
init 1 root mem REG 3,5 30748 30303 /sbin/init
init 1 root mem REG 3,5 73120 8069 /lib/ld-2.1.3.so
init 1 root mem REG 3,5 931668 8075 /lib/libc-2.1.3.so
cardmgr 213 root mem REG 3,5 36956 30357 /sbin/cardmgr
...
strace
为了跟踪系统和信号的诊断和调试工具. 调用它最简单的方法就是strace COMMAND.
bash$ strace df
execve("/bin/df", ["df"], [/* 45 vars */]) = 0
uname({sys="Linux", node="bozo.localdomain", ...}) = 0
brk(0) = 0x804f5e4
...
这是 Solaris truss命令的Linux的等价工具.
nmap
网络端口扫描器. 这个命令将会扫描一个服务器来定位打开的端口, 并且定位这些端口相
关的服务. 这是一个防止网络被黑客入侵的一个重要的安全工具.
1 #!/bin/bash
2
3 SERVER=$HOST # localhost.localdomain (127.0.0.1).
4 PORT_NUMBER=25 # SMTP 端口.
5
6 nmap $SERVER | grep -w "$PORT_NUMBER" # 这个指定端口打开了么?
7 # grep -w 匹配整个单词,
8 #+ 这样就不会匹配类似于1025这种含有25的端口了.
9
10 exit 0
11
12 # 25/tcp open smtp
nc
nc(netcat)工具是一个完整的工具包, 可以使用它来连接和监听TCP和UDP端口. 它可以用
来作为诊断和测试工具, 也可以用来作为基于脚本的HTTP客户端和服务器的组件.
bash$ nc localhost.localdomain 25
220 localhost.localdomain ESMTP Sendmail 8.13.1/8.13.1; Thu, 31 Mar 2005 15:41:35 -0700
Example 13-5 Checking a remote server for identd<rojy bug>
################################Start Script#######################################
1 #! /bin/sh
2 ## Duplicate DaveG's ident-scan thingie using netcat. Oooh, he'll be p*ssed.
3 ## Args: target port [port port port ...]
4 ## Hose stdout _and_ stderr together.
5 ##
6 ## 优点: runs slower than ident-scan, giving remote inetd less cause
7 ##+ for alarm, and only hits the few known daemon ports you specify.
8 ## 缺点: requires numeric-only port args, the output sleazitude,
9 ##+ and won't work for r-services when coming from high source ports.
10 # 脚本作者: Hobbit <hobbit@avian.org>
11 # 授权使用在本书中.
12
13 # ---------------------------------------------------
14 E_BADARGS=65 # 至少需要两个参数.
15 TWO_WINKS=2 # 需要睡多长时间.
16 THREE_WINKS=3
17 IDPORT=113 # Authentication "tap ident" port.
18 RAND1=999
19 RAND2=31337
20 TIMEOUT0=9
21 TIMEOUT1=8
22 TIMEOUT2=4
23 # ---------------------------------------------------
24
25 case "${2}" in
26 "" ) echo "Need HOST and at least one PORT." ; exit $E_BADARGS ;;
27 esac
28
29 # Ping 'em once and see if they *are* running identd.
30 nc -z -w $TIMEOUT0 "$1" $IDPORT || { echo "Oops, $1 isn't running identd." ; exit 0 ; }
31 # -z scans for listening daemons.
32 # -w $TIMEOUT = How long to try to connect.
33
34 # Generate a randomish base port.
35 RP=`expr $$ % $RAND1 + $RAND2`
36
37 TRG="$1"
38 shift
39
40 while test "$1" ; do
41 nc -v -w $TIMEOUT1 -p ${RP} "$TRG" ${1} < /dev/null > /dev/null &
42 PROC=$!
43 sleep $THREE_WINKS
44 echo "${1},${RP}" | nc -w $TIMEOUT2 -r "$TRG" $IDPORT 2>&1
45 sleep $TWO_WINKS
46
47 # 这个脚本看起来是不是一个瘸腿脚本, 或者其它更差的什么东西?
48 # ABS Guide 作者注释: "并不是真的那么差,
49 #+ 事实上相当清楚."
50
51 kill -HUP $PROC
52 RP=`expr ${RP} + 1`
53 shift
54 done
55
56 exit $?
57
58 # 注意事项:
59 # ---------
60
61 # 尝试注释一下第30行的程序, 并且使用"localhost.localdomain 25"
62 #+ 作为参数来运行这个脚本.
63
64 # For more of Hobbit's 'nc' example scripts,
65 #+ look in the documentation:
66 #+ the /usr/share/doc/nc-X.XX/scripts directory.
################################End Script#########################################
并且, 当然, 这里还有Dr. Andrew Tridgell在BistKeeper事件中臭名卓著的一行脚本:
1 echo clone | nc thunk.org 5000 > e2fsprogs.dat
free
使用表格形式来显示内存和缓存的使用情况. 这个命令的输出非常适合于使用 grep, awk
或者Perl来分析. procinfo命令将会显示free命令所能显示的所有信息, 而且更多.
bash$ free
total used free shared buffers cached
Mem: 30504 28624 1880 15820 1608 16376
-/+ buffers/cache: 10640 19864
Swap: 68540 3128 65412
显示未使用的RAM内存:
bash$ free | grep Mem | awk '{ print $4 }'
1880
procinfo
从/proc pseudo-filesystem中提取和显示所有信息和统计资料. 这个命令将给出更详细
的信息.
bash$ procinfo | grep Bootup
Bootup: Wed Mar 21 15:15:50 2001 Load average: 0.04 0.21 0.34 3/47 6829
lsdev
显示设备, 也就是显示安装的硬件.
bash$ lsdev
Device DMA IRQ I/O Ports
------------------------------------------------
cascade 4 2
dma 0080-008f
dma1 0000-001f
dma2 00c0-00df
fpu 00f0-00ff
ide0 14 01f0-01f7 03f6-03f6
...
du
递归的显示(磁盘)文件的使用状况. 除非指定, 默认是当前工作目录.
bash$ du -ach
1.0k ./wi.sh
1.0k ./tst.sh
1.0k ./random.file
6.0k .
6.0k total
df
使用列表的形式显示文件系统的使用状况.
bash$ df
Filesystem 1k-blocks Used Available Use% Mounted on
/dev/hda5 273262 92607 166547 36% /
/dev/hda8 222525 123951 87085 59% /home
/dev/hda7 1408796 1075744 261488 80% /usr
dmesg
将所有的系统启动消息输出到stdout上. 方便出错,并且可以查出安装了哪些设备驱动和
察看使用了哪些系统中断. dmesg命令的输出当然也可以在脚本中使用 grep, sed, 或
awk 来进行分析.
bash$ dmesg | grep hda
Kernel command line: ro root=/dev/hda2
hda: IBM-DLGA-23080, ATA DISK drive
hda: 6015744 sectors (3080 MB) w/96KiB Cache, CHS=746/128/63
hda: hda1 hda2 hda3 < hda5 hda6 hda7 > hda4
stat
显示一个或多个给定文件(也可以是目录文件或设备文件)的详细的统计信息.
bash$ stat test.cru
File: "test.cru"
Size: 49970 Allocated Blocks: 100 Filetype: Regular File
Mode: (0664/-rw-rw-r--) Uid: ( 501/ bozo) Gid: ( 501/ bozo)
Device: 3,8 Inode: 18185 Links: 1
Access: Sat Jun 2 16:40:24 2001
Modify: Sat Jun 2 16:40:24 2001
Change: Sat Jun 2 16:40:24 2001
如果目标文件不存在, stat 将会返回一个错误信息.
bash$ stat nonexistent-file
nonexistent-file: No such file or directory
vmstat
显示虚拟内存的统计信息.
bash$ vmstat
procs memory swap io system cpu
r b w swpd free buff cache si so bi bo in cs us sy id
0 0 0 0 11040 2636 38952 0 0 33 7 271 88 8 3 89
netstat
显示当前网络的统计和信息, 比如路由表和激活的连接. 这个工具存取/proc/net(第27章)
中的信息. 参见 Example 27-3.
netstat -r 等价于 route 命令.
bash$ netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
unix 11 [ ] DGRAM 906 /dev/log
unix 3 [ ] STREAM CONNECTED 4514 /tmp/.X11-unix/X0
unix 3 [ ] STREAM CONNECTED 4513
. . .
uptime
显示系统运行的时间, 还有其他一些统计信息.
bash$ uptime
10:28pm up 1:57, 3 users, load average: 0.17, 0.34, 0.27
注意: load average 如果小于或等于1, 那么就意味着系统会马上处理. 如果
load average大于1, 那么就意味着进程需要排队. 如果load average大于3,
那么就意味着, 系统性能已经显著下降了.
hostname
显示系统的主机名字. 这个命令在 /etc/rc.d 安装脚本(/etc/rc.d/rc.sysinit
或类似的)中设置主机名. 等价于uname -n, 并且与$HOSTNAME内部变量很相像.
bash$ hostname
localhost.localdomain
bash$ echo $HOSTNAME
localhost.localdomain
与 hostname 命令很相像的命令还有 domainname, dnsdomainname, nisdomainname, 和
ypdomainname 命令. 使用这些来显示或设置系统DNS 或者 NIS/YP 域名. 对于hostname
命令来说使用不同的选项一样可以达到上边这些命令的目的.
hostid
显示主机的32位的16进制ID.
bash$ hostid
7f0100
注意: 这个命令据说对于特定系统可以获得一个"唯一"的序号. 某些产品的注册过程可能
会需要这个序号来作为用户的许可证. 不幸的是, hostid 只会使用字节转换的方法
来用16进制显示机器的网络地址.
一个没有网络的Linux机器的典型的网络地址设置在/ect/hosts中.
bash$ cat /etc/hosts
127.0.0.1 localhost.localdomain localhost
碰巧, 通过对127.0.0.1进行字节转换, 我们获得了 0.127.1.0, 用16进制表示就是
007f0100, 这就是上边hostid返回的结果. 这样几乎所有的无网络的Linux机器都会
得到这个hostid.
sar
sar (System Activity Reporter系统活动报告) 命令将会给出系统统计的一个非常详细的
概要. Santa Cruz Operation("老" SCO)公司在1999年4月份以开源软件的形式发布了sar.
这个命令并不是基本Linux发行版的一部分, 但是你可以从Sebastien Godard 写的
sysstat utilities 包中获得这个工具.
bash$ sar
Linux 2.4.9 (brooks.seringas.fr) 09/26/03
10:30:00 CPU %user %nice %system %iowait %idle
10:40:00 all 2.21 10.90 65.48 0.00 21.41
10:50:00 all 3.36 0.00 72.36 0.00 24.28
11:00:00 all 1.12 0.00 80.77 0.00 18.11
Average: all 2.23 3.63 72.87 0.00 21.27
14:32:30 LINUX RESTART
15:00:00 CPU %user %nice %system %iowait %idle
15:10:00 all 8.59 2.40 17.47 0.00 71.54
15:20:00 all 4.07 1.00 11.95 0.00 82.98
15:30:00 all 0.79 2.94 7.56 0.00 88.71
Average: all 6.33 1.70 14.71 0.00 77.26
readelf
显示指定的 elf 格式的2进制文件的统计信息. 这个工具是binutils工具包的一部分.
bash$ readelf -h /bin/bash
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
. . .
size
size [/path/to/binary] 命令可以显示2进制可执行文件或归档文件每部分的尺寸. 这个
工具主要是程序员使用.
bash$ size /bin/bash
text data bss dec hex filename
495971 22496 17392 535859 82d33 /bin/bash
系统日志类
logger
附加一个用户产生的消息到系统日之中 (/var/log/messages). 不是root用户也可以调用
logger.
1 logger Experiencing instability in network connection at 23:10, 05/21.
2 # 现在, 运行 'tail /var/log/messages'.
通过在脚本中调用一个logger命令, 就可以将调试信息写到/var/log/messages中.
1 logger -t $0 -i Logging at line "$LINENO".
2 # "-t" 选项可以为长的入口指定标签.
3 # "-i" 选项记录进程ID.
4
5 # tail /var/log/message
6 # ...
7 # Jul 7 20:48:58 localhost ./test.sh[1712]: Logging at line 3.
logrotate
这个工具用来管理系统的log文件, 可以在合适的时候轮换, 压缩, 删除, 和(或)e-mail
它们. 这个工具将从老的log文件中取得一些杂乱的记录保存在/var/log中. 通常使用
cron 来每天运行logrotate.
在/etc/logrotate.conf中添加合适的入口就可以管理自己的log文件了, 就像管理系统
log文件一样.
注意: Stefano Falsetto 创造了rottlog, 他认为这是logrotate的改进版本.
返回顶部