📄 shell编程.txt
字号:
第2章 带着一个Sha-Bang出发(Sha-Bang指的是#!)
==============================================
在一个最简单的例子中,一个shell脚本其实就是将一堆系统命令列在一个文件中.它的最基本的
用处就是,在你每次输入这些特定顺序的命令时可以少敲一些字.
Example 2-1 清除:清除/var/log下的log文件
################################Start Script#######################################
1 # Cleanup
2 # 当然要使用root身份来运行这个脚本
3
4 cd /var/log
5 cat /dev/null > messages
6 cat /dev/null > wtmp
7 echo "Logs cleaned up."
################################End Script#########################################
这根本就没什么稀奇的, 只不过是命令的堆积, 来让从console或者xterm中一个一个的输入命
令更方便一些.好处就是把所有命令都放在一个脚本中,不用每次都敲它们.这样的话,对于特定
的应用来说,这个脚本就很容易被修改或定制.
Example 2-2 清除:一个改良的清除脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 一个Bash脚本的正确的开头部分.
3
4 # Cleanup, 版本 2
5
6 # 当然要使用root身份来运行.
7 # 在此处插入代码,来打印错误消息,并且在不是root身份的时候退出.
8
9 LOG_DIR=/var/log
10 # 如果使用变量,当然比把代码写死的好.
11 cd $LOG_DIR
12
13 cat /dev/null > messages
14 cat /dev/null > wtmp
15
16
17 echo "Logs cleaned up."
18
19 exit # 这个命令是一种正确并且合适的退出脚本的方法.
################################End Script#########################################
现在,让我们看一下一个真正意义的脚本.而且我们可以走得更远...
Example 2-3. cleanup:一个增强的和广义的删除logfile的脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 清除, 版本 3
3
4 # Warning:
5 # -------
6 # 这个脚本有好多特征,这些特征是在后边章节进行解释的,大概是进行到本书的一半的
7 # 时候,
8 # 你就会觉得它没有什么神秘的了.
9 #
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0 # $UID为0的时候,用户才具有根用户的权限
15 LINES=50 # 默认的保存行数
16 E_XCD=66 # 不能修改目录?
17 E_NOTROOT=67 # 非根用户将以error退出
18
19
20 # 当然要使用根用户来运行
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23 echo "Must be root to run this script."
24 exit $E_NOTROOT
25 fi
26
27 if [ -n "$1" ]
28 # 测试是否有命令行参数(非空).
29 then
30 lines=$1
31 else
32 lines=$LINES # 默认,如果不在命令行中指定
33 fi
34
35
36 # Stephane Chazelas 建议使用下边
37 #+ 的更好方法来检测命令行参数.
38 #+ 但对于这章来说还是有点超前.
39 #
40 # E_WRONGARGS=65 # 非数值参数(错误的参数格式)
41 #
42 # case "$1" in
43 # "" ) lines=50;;
44 # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 # * ) lines=$1;;
46 # esac
47 #
48 #* 直到"Loops"的章节才会对上边的内容进行详细的描述.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ] # 或者 if[ "$PWD" != "$LOG_DIR" ]
54 # 不在 /var/log中?
55 then
56 echo "Can't change to $LOG_DIR."
57 exit $E_XCD
58 fi # 在处理log file之前,再确认一遍当前目录是否正确.
59
60 # 更有效率的做法是
61 #
62 # cd /var/log || {
63 # echo "Cannot change to necessary directory." >&2
64 # exit $E_XCD;
65 # }
66
67
68
69
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages # 变为新的log目录.
72
73
74 # cat /dev/null > messages
75 #* 不再需要了,使用上边的方法更安全.
76
77 cat /dev/null > wtmp # ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79
80 exit 0
81 # 退出之前返回0,返回0表示成功.
82 #
################################End Script#########################################
因为你可能希望将系统log全部消灭,这个版本留下了log消息最后的部分.你将不断地找到新
的方法来完善这个脚本,并提高效率.
要注意,在每个脚本的开头都使用"#!",这意味着告诉你的系统这个文件的执行需要指定一个解
释器.#!实际上是一个2字节[1]的魔法数字,这是指定一个文件类型的特殊标记, 换句话说, 在
这种情况下,指的就是一个可执行的脚本(键入man magic来获得关于这个迷人话题的更多详细
信息).在#!之后接着是一个路径名.这个路径名指定了一个解释脚本中命令的程序,这个程序可
以是shell,程序语言或者是任意一个通用程序.这个指定的程序从头开始解释并且执行脚本中
的命令(从#!行下边的一行开始),忽略注释.[2]
如:
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/perl
4 #!/usr/bin/tcl
5 #!/bin/sed -f
6 #!/usr/awk -f
上边每一个脚本头的行都指定了一个不同的命令解释器,如果是/bin/sh,那么就是默认shell
(在Linux系统中默认是Bash).[3]使用#!/bin/sh,在大多数商业发行的UNIX上,默认是Bourne
shell,这将让你的脚本可以正常的运行在非Linux机器上,虽然这将会牺牲Bash一些独特的特征.
脚本将与POSIX[4] 的sh标准相一致.
注意: #! 后边给出的路径名必须是正确的,否则将会出现一个错误消息,通常是
"Command not found",这将是你运行这个脚本时所得到的唯一结果.
当然"#!"也可以被忽略,不过这样你的脚本文件就只能是一些命令的集合,不能够使用shell内建
的指令了,如果不能使用变量的话,当然这也就失去了脚本编程的意义了.
注意:这个例子鼓励你使用模块化的方式来编写脚本,平时也要注意收集一些零碎的代码,
这些零碎的代码可能用在你将来编写的脚本中.这样你就可以通过这些代码片段来构
造一个较大的工程用例. 以下边脚本作为序,来测试脚本被调用的参数是否正确.
################################Start Script#######################################
1 E_WRONG_ARGS=65
2 script_parameters="-a -h -m -z"
3 # -a = all, -h = help, 等等.
4
5 if [ $# -ne $Number_of_expected_args ]
6 then
7 echo "Usage: `basename $0` $script_parameters"
8 # `basename $0`是这个脚本的文件名
9 exit $E_WRONG_ARGS
10 fi
################################End Script#########################################
大多数情况下,你需要编写一个脚本来执行一个特定的任务,在本章中第一个脚本就是一个这样
的例子, 然后你会修改它来完成一个不同的,但比较相似的任务.用变量来代替写死的常量,就是
一个好方法,将重复的代码放到一个函数中,也是一种好习惯.
2.1 调用一个脚本
----------------
编写完脚本之后,你可以使用sh scriptname,[5]或者bash scriptname来调用它.
(不推荐使用sh <scriptname,因为这禁用了脚本从stdin中读数据的功能.)
更方便的方法是让脚本本身就具有可执行权限,通过chmod命令可以修改.
比如:
chmod 555 scriptname (允许任何人都具有 可读和执行权限) [6]
或:
chmod +rx scriptname (允许任何人都具有 可读和执行权限)
chmod u+rx scriptname (只给脚本的所有者 可读和执行权限)
既然脚本已经具有了可执行权限,现在你可以使用./scriptname.[7]来测试它了.如果这个脚本
以一个"#!"行开头,那么脚本将会调用合适的命令解释器来运行.
最后一步,在脚本被测试和debug之后,你可能想把它移动到/usr/local/bin(当然是以root身份)
,来让你的脚本对所有用户都有用.这样用户就可以直接敲脚本名字来运行了.
注意事项:
[1] 那些具有UNIX味道的脚本(基于4.2BSD)需要一个4字节的魔法数字,在#!后边需要一个
空格#! /bin/sh.
[2] 脚本中的#!行的最重要的任务就是命令解释器(sh或者bash).因为这行是以#开始的,
当命令解释器执行这个脚本的时候,会把它作为一个注释行.当然,在这之前,这行语句
已经完成了它的任务,就是调用命令解释器.
如果在脚本的里边还有一个#!行,那么bash将把它认为是一个一般的注释行.
1 #!/bin/bash
2
3 echo "Part 1 of script."
4 a=1
5
6 #!/bin/bash
7 # 这将不会开始一个新脚本.
8
9 echo "Part 2 of script."
10 echo $a # Value of $a stays at 1.
[3] 这里可以玩一些小技巧.
1 #!/bin/rm
2 # 自删除脚本.
3
4 # 当你运行这个脚本时,基本上什么都不会发生...除非这个文件消失不见.
5
6 WHATEVER=65
7
8 echo "This line will never print (betcha!)."
9
10 exit $WHATEVER # 没关系,脚本是不会在这退出的.
当然,你还可以试试在一个README文件的开头加上#!/bin/more,并让它具有执行权限.
结果将是文档自动列出自己的内容.(一个使用cat命令的here document可能是一个
更好的选则,--见Example 17-3).
[4] 可移植的操作系统接口,标准化类UNIX操作系统的一种尝试.POSIX规范可以在
http://www.opengroup.org/onlinepubs/007904975/toc.htm中查阅.
[5] 小心:使用sh scriptname来调用脚本的时候将会关闭一些Bash特定的扩展,脚本可能
因此而调用失败.
[6] 脚本需要读和执行权限,因为shell需要读这个脚本.
[7] 为什么不直接使用scriptname来调用脚本?如果你当前的目录下($PWD)正好有你想要
执行的脚本,为什么它运行不了呢?失败的原因是,出于安全考虑,当前目录并没有被
加在用户的$PATH变量中.因此,在当前目录下调用脚本必须使用./scriptname这种
形式.
2.2 初步的练习
--------------
1. 系统管理员经常会为了自动化一些常用的任务而编写脚本.举出几个这种有用的脚本的实例.
2. 编写一个脚本,显示时间和日期,列出所有的登录用户,显示系统的更新时间.然后这个脚本
将会把这些内容保存到一个log file中.
第二部分 基本
++++++++++++++++
第3章 特殊字符
================
# 注释,行首以#开头为注释(#!是个例外).
1 # This line is a comment.
注释也可以存在于本行命令的后边.
1 echo "A comment will follow." # 注释在这里
2 # ^ 注意#前边的空白
注释也可以在本行空白的后边.
1 # A tab precedes this comment.
注意:命令是不能跟在同一行上注释的后边的,没有办法,在同一行上,注释的后边想
要再使用命令,只能另起一行.
当然,在echo命令中被转义的#是不能作为注释的.
同样的,#也可以出现在特定的参数替换结构中或者是数字常量表达式中.
1 echo "The # here does not begin a comment."
2 echo 'The # here does not begin a comment.'
3 echo The \# here does not begin a comment.
4 echo The # 这里开始一个注释
5
6 echo ${PATH#*:} # 参数替换,不是一个注释
7 echo $(( 2#101011 )) # 数制转换,不是一个注释
8
9 # Thanks, S.C.
标准的引用和转义字符("'\)可以用来转义#
; 命令分隔符,可以用来在一行中来写多个命令.
1 echo hello; echo there
2
3
4 if [ -x "$filename" ]; then # 注意:"if"和"then"需要分隔
5 # 为啥?
6 echo "File $filename exists."; cp $filename $filename.bak
7 else
8 echo "File $filename not found."; touch $filename
9 fi; echo "File test complete."
有时候需要转义
;; 终止"case"选项.
1 case "$variable" in
2 abc) echo "\$variable = abc" ;;
3 xyz) echo "\$variable = xyz" ;;
4 esac
. .命令等价于source命令(见Example 11-20).这是一个bash的内建命令.
. .作为文件名的一部分.如果作为文件名的前缀的话,那么这个文件将成为隐藏文件.
将不被ls命令列出.
bash$ touch .hidden-file
bash$ ls -l
total 10
-rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook
bash$ ls -al
total 14
drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook
-rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file
.命令如果作为目录名的一部分的话,那么.表达的是当前目录.".."表示上一级目录.
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
.命令经常作为一个文件移动命令的目的地.
bash$ cp /home/bozo/current_work/junk/* .
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -