计算机脚本常用套路详解1.docx
- 文档编号:9614575
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:51
- 大小:39.67KB
计算机脚本常用套路详解1.docx
《计算机脚本常用套路详解1.docx》由会员分享,可在线阅读,更多相关《计算机脚本常用套路详解1.docx(51页珍藏版)》请在冰豆网上搜索。
计算机脚本常用套路详解1
脚本常用套路详解(以下总结仅供参考)
关于bash编程,
每个人都有自己的一套,
但目的都是一样的:
正确、安全、高效、低耗、工整的完成任务,
同时,有更好的扩展性、调式性
所以,总结一些乱七八糟的,
仅供真正喜欢bash的人参考之用,
以下总结如有雷同,无尚荣兴
2007-02-12bywwy
Index
1、脚本编写的结构
2、定义变量(包含eval命令用法)
3、循环遍历
4、逻辑结构
5、信息的传递
6、配置文件的处理
7、为了安全
8、编程风格
9、浅谈IO
10、vim杂项
11、bash部分内建命令总结
12、其他杂项
1、脚本编写的结构
Bash是一个纯面向过程的编程语言,而又区别与某些其他语言,
区别就是,是从上到下顺序执行的,是一种脚本语言
所以,基本的结构就是:
变量定义---函数定义---循环、逻辑判断
结构看起来比较简单,但是严格遵守却比较难,
会因为脚本比较简单、改来改去等,忽略了这个简单的基本规则
不是用这样结构的隐患在于:
1、外部变量与函数变量冲突
2、脚本修改容易出错
3、变量的遗漏等等
然而,这不是绝对的,打破这个规则的理由:
1、有明显的逻辑判断,而剩去大量的,没意义的函数、变量定义
2、可以避免变量冲突
3、脚本太简单
4、有自己编程风格等等
2、定义变量
eval的应用,可以实现变量的自动化定义,
先看这个例子:
[root@fxstest1~]#echopwd
pwd#“pwd”是随便的一个字符串,打到stdout上
[root@fxstest1~]#eval`echopwd`#注意echo命令两边是反引号
/home/wwy#此时,pwd不再是字符串,而是一个命令
所以,同理可以自动定义变量,
例如:
定义变量$a,$b,$c,$d,$e,$f的值分别为1,2,3,4,5,6
做法:
[root@fxstest1~]#seq05|tr'[0-9]''[a-z]'|awk'{i++;print$1"="i}'
#以下是这个命令的输出结果
a=1
b=2
c=3
d=4
e=5
f=6
[root@fxstest1~]#eval`seq05|tr'[0-9]''[a-z]'|awk'{i++;print$1"="i}'`
#eval`(反引号)命令`(反引号),这样之后,将以上命令的输出自动执行
[root@fxstest1~]#echo$a$b$c$d$e$f
123456#此时发现,变量已经被自动附值了
此种做法当然不是为偷懒,或故意让人迷惑,
(耍酷的成份有一点,如果你认为这也算“酷”的话)
真正的力量在于,自动的、快速的,生成数据结构,
生成数组的例子:
[root@fxstest1~]#eval`awk-F:
'{print$1"=("$1,$(NF-1),$NF")"}'/etc/passwd`
#上面这句命令,将passwd文件做成了一个数组,
#数组名是各个用户名,各个数组的内容分别是用户名、数组目录、loginshell
[root@fxstest1~]#echo${wwy[@]}#输出wwy这个数组的内容
wwy/home/wwy/bin/bash
[root@fxstest1~]#echo${wwy[1]}#随意的输出wwy这个数组的某个值
/home/wwy
(关于具体的数据结构应用,可以参考“汇率检查脚本”那个文档)
引入外部文件,也是定义变量的一个方法,也常用于引入函数
(关于具体的应用,可以参考“批处理检查脚本”那个文档)
3、循环遍历
循环的操作,直接决定了脚本的效率性能,也存在很多的技巧
(以下都是处理一些效率相关的问题,而并非逻辑问题)
1、for循环
在语法上,for循环有两种,即forin循环与fordo循环,
两者相比,forin循环偏向于in后面的读取,fordo循环则偏向于数据的遍历
所以,一般来说,forin用于读文件等操作,在效率上表现的比较好,
用法:
forlinein`catfile.txt`;do#与cat命令连用这种方式
echo$line#将每一行的结果存入$line变量内,由echo命令处理
done
而fordo循环的遍历能力是很强的,
用法1:
for((i=0;i<10;i++));do#根据不同的算法,随意改变$i的初始值、累加值等
echo$i
done#输出0到9,10个数
用法2:
a=(ABCDEF)
for((i=0;i<${#a[@]};i++));do
echo${a[$i]}
done#输出a数组的所有内容
(关于具体的数据结构应用,可以参考“汇率检查脚本”那个文档)
另外,有些人喜欢用forin结构遍历数组,即:
a=(ABCDE)
foriin${!
a[@]}#in后面将是a数组的从头到尾的所有下标
echo${a[$i]}
done#这样同样输出了a数组的所有内容
但是,这种方法,和fordo相比,速度比较慢,而且不灵活($i的值完全依赖于数组的下标)
所以,忘了这个方法吧
2、whileread循环
关于whileread,用法就很多很多了,
因为read命令本身功能很多,可以读入两个以上的变量、从文件描述符读内容等,
所以可以实现很多复杂功能,
尤其在读文件这个操作上,比forin结构灵活很多(但是效率不如forin)
常用的相关技巧有在于读取配置文件、多线程等方面
(请参考以前那个PPT)
4、逻辑结构
脚本编程实现的功能一般不是很复杂,逻辑判断占了其中很大或最大的部分
1、除了if之外,要用的:
(现在发现写的这些脚本,ifthen几乎没用过….)
用短路与、或,即$$、||、$?
来解决问题(其中$?
一般是隐含的)
例如:
[!
–e“./txt”]&&{
echo“./txtisnotexist”
exit1
}||{
echook
exit0
}
这种技巧的关键在于“顺序流的控制”
(请参考以前那个PPT)
2、更清晰的逻辑
例如,比较以下两种写法:
[-e“./txt”]&&[-e“./txt2”]#不好
[-e“./txt”-a-e“./txt2”]#好一些
第二种的写法比较好,因为扩展的话,
可以这样:
[-e“./txt”-a-e“./txt2”-a“./txt3”]
或者这样:
[-e“./txt”-a-e“./txt2”]&&{
#…..Dosomething…..
}
尽量的将逻辑封锁在一个整体里面,即“[]”当中
逻辑封装有很多形式,比如:
[-e“./txt”]||exit1#意义:
如果“txt文件存”在这个事是假的,那么退出
[!
-e“./txt”]&&exit1#意义:
如果“txt文件不存在”的话,那么退出
感觉上,第二种更加的清晰,因为它把“txt”不存在这个事情“封装”了,
当逻辑更复杂时,不会绕的人头昏眼花
但,这种方式有一个不容易发现的隐患,即:
如果文件存在的话,那么$?
的返回值是1,不是0
(在下面的“为了安全”那一节会有更详细的介绍)
5、信息的传递
这里的信息传递,指的是:
脚本内部之间、或脚本与脚本之间、或本地脚本与远程脚本之间等等传递信息的方法
而这些被传递的信息指的是:
类似于“标记”、“信号”等少量的,比如传递0、1这样的逻辑信息或有逻辑意义的字符串
1、最基本的逻辑传递:
$?
返回值
$?
返回值,是bash编程逻辑实现的基本元素,大部分的传递方式是隐含而自然的
比如:
[!
-e“./txt”]&&{#到了此处,$?
已经是0
exit2#退出之后将$?
执为2
}#如果到了此处,那么此处的$?
是1
像这种最基本的内部信息的传递很是自然,但了解这种“自然”很必要,免得伤了自己
所以,人为的控制$?
返回值的方法多用于函数当中,即return命令
可是在随着bash语言的发展,
这种类似于C语言的传递返回值的手法显得没什么意思了
2、更多样的传递、更多样的信息
在脚本内部传递信息,一般是与函数之间的信息交互,
以下是如何“传入”、“传出”的一些例子
补充:
“传入”、“传出”是一种通俗的说法了,
模仿其他的语言来说,大概就是传参和返回值的意思,
但这样说失去了bash的特点
同样的,
单单实现某些功能的函数,即没有传入、传出,
可以理解成无类型函数(void),
如果你真的热衷于这种c或java的说法的话…
一般的,给自定义函数“传入”信息的方法,大概有三种:
1、参数:
$1、$2…
2、环境变量
3、标准输入
一般的,“传出”信息的方法也是三种:
1、返回值
2、环境变量
3、标准输出(函数只执行一次的话,不能和第二种方法同时使用)
补充:
除了这3种方法,还有其他的方法吗?
其实这就看你自己怎么认为了,
比如,
函数根据自己的逻辑,生成一些不同的标志文件,
这是一种信息的表达,一种“传出”;
函数根据自己的“心情”,格式化一些分区、发邮件对你进行辱骂等等…
这也是一种信息的表达,一种“传出”,
只要你认为这个传出的信息有意义,
那么,它就是你的第4种方法…
可能这时你发现了bash编程是多么的不严谨,多么的令人烦恼,
而事实上,
是bash的简单造成了这种不严谨,
即,没有那么多的规定、定义,
去掉了很多束缚,完全看你自己的想法和风格
(在“编程风格”一节有更详细的介绍)
下面,用例子来说明这所有的方法:
传入的例子:
#!
/bin/bash
functionmy_function{
localinfo1info2#info1、info2这两个变量的作用范围在函数之内
#info1是第一个传入的信息,来自$1
#info2是第二个传入的信息,来自外部变量
#info3是第三个传入的信息,来自标准输入
info1="$1"
cat<<-End
info1:
$info1
info2:
$info2
info3:
`cat-`
End
}
#这是这三种信息的传入方法,信息的内容分别是xxx1xxx、xxx2xxx、xxx3xxx
info2="xxx2xxx"my_function"xxx1xxx"<<-End
xxx3xxx
End
传出的例子:
#!
/bin/bash
functionmy_function2{
info1=X1#设置一个变量
echoX2#输出一个字符
return33#设置$?
为33
}
info2=`my_function2`#标准输出到$info2,这是第一次执行函数,info1并没有定义到全局
my_function2>/dev/null#又一次执行函数,为了定义$info1
echo$?
echo$info1
echo$info2
在脚本外部传递信息的话,与内部传递类似,
问题复杂的地方在于,变量的作用范围、返回值的获得等
所以,解决这些问题的最好办法,就是临时文件
(单单内部的信息传递,一般就没有必要用临时文件了)
一个临时文件,到底能传递出多少逻辑信息呢?
最起码可以自定的有:
文件名、文件内容
当然还有更多的信息(我们一起想):
文件创建时间、文件大小、文件权限相关、文件路径相关、文件innode节点号…
这样,一个临时文件,再加上一些系统命令,我们就至少可以表达出6种逻辑了,
而且,这些逻辑当中,有几种是可以跨主机传递的(哪几种?
)
(利用临时文件这种方法是低效的,所以当脚本比较多、逻辑很杂时才会用到,请见“批处理检查脚本”)
关于远程主机之间的消息传递,传输的介质这里简单的提一下,
我们用到的是ssh、snmp、http(以后可能还有类似于nagios这种第三方工具),
然后借助一些系统命令来实现
关于ssh有一些要注意的地方是:
公钥密匙的设置、以及返回值控制、stdin重置等问题;
而snmp的应用大都是获得信息,是单向的;
(把snmp发扬光大的话,再研究一下snmptrapd,来实现双向)
利用http这种协议的话,还需要一方有http服务器,另一方写cgi脚本等等…
具体的不多说了,
关于ssh的应用,在“为了安全”那一节有一些注意事项
6、配置文件的处理
配置文件的意义在于从脚本之外引入一些信息,
而我们系统当中最为常用的就是一个中心服务器上的那个配置文件了
文件格式,类似如下:
NOUSERIPNAME
1mqm10.0.154.22UBS-GW
2mfx10.0.154.23TOMCAT1
3mqm10.0.154.112HMWR-GW
……
配置文件的格式是不能随便更改了,但一般可以增加一些规则,比如:
1、第一行为每一列的说明文字
2、“#”开头的行为注释行
3、可以有空行
4、列之间用tab或者空格分隔
这样,程序读取这样的配置信息的时候就做一些判断,来适应这些规则,如:
awk'$1!
~/^#|^$/&&NR>1'配置文件.txt
配置文件的读取,有很多的方法来实现:
1、whileread结构
whileread结构最为常用,简单实用的结构,方式如下:
whilereadmy_nomy_usermy_ipmy_name;do
…
echo“$my_no$my_user$my_ip$my_name”
…
done<<(awk'$1!
~/^#|^$/&&NR>1'配置文件.txt)#此处为进程替换(请参考以前那个PPT)
由于read命令本身的强大,它可以读取多个变量,然后交给循环内部处理,而且很容易就可以实现多线程的控制(请参考以前那个PPT),等等,不多说了
但是,whileread结构同样存在很多的不足:
效率低(read命令慢)、read占用stdin、配置文件控制依赖外部命令、多次遍历多次读取
这些弱点在小规模应用时并不明显,而大型应用的话很耽误事
(在下面的“为了安全”那一节会有更详细的介绍)
2、将配置文件存入数组
建立一个数据结构,将配置文件存进去,然后通过循环遍历,对配置信息进行处理
这样做的优点在于,处理一次配置文件之后,可以多次遍历,
即实现了:
多次遍历一次读取
例如,假设配置文件名为server.conf,格式如上面,那么处理方式可以为:
SERVER_CONF=server.conf
server_conf_now=`awk'$1!
~/^#|^$/&&NR>1'$SERVER_CONF`
num_var=`awk'{printf$1""}END{printf"\n"}'<<<"$server_conf_now"`
ip_var=`awk'{printf$2""}END{printf"\n"}'<<<"$server_conf_now"`
name_var=`awk'{printf$3""}END{printf"\n"}'<<<"$server_conf_now"`
user_var=`awk'{printf$4""}END{printf"\n"}'<<<"$server_conf_now"`
num_arg=($num_var)
ip_arg=($ip_var)
name_arg=($name_var)
user_arg=($user_var)
laodwarn_arg=($laodwarn_var)
chktpye_arg=($chktpye_var)
这样就生成了一个个的数组(遍历方法请参考:
“循环遍历”那一节)
7、为了安全(直到你失去了,才会懂得珍惜)
Bash脚本是危险的,最遭的话,他会尽当前用户的所有权限对系统进行破坏、资源占用
(这里讨论的是关于脚本编程层面上的规范,不是系统相关的设置)
1、垃圾的累积
有很多脚本会生成一些文件之类的东西
(有一些可能是不经意生成的,比如:
[root@fxstest1~]#[249>250]
#本以为这个会返回一个非0,但事实却返回了0
#为什么呢?
看看这里是不是多了一个250?
[root@fxstest1~]#ls250
250
#没说你是250,呵呵,而是生成了一个名为“250”的文件,同时返回生成文件成功,所以$?
为0
),
慢慢的,这些文件越积越多,会影响到系统的心情(这个后果比较严重)
避免的方式:
退出之后删除临时文件、尽量不用临时文件
如何保证退出之后删除文件呢?
这个问题也许并不是那么简单,因为脚本不一定那么顺利的执行完毕,
你可以确定脚本天衣无缝,但不能确定服务器机箱是否进去了一只老鼠,或者管灯掉下来砸断了你网线(这个只能用后台运行来解决了),真tmd不好说啊,
所以,只能最大可能的回收临时文件,或者干脆不回收了
(有必要的话,比如临时文件影响到了逻辑判断,可以利用crontab、rc.local专门回收这些文件)
服务器突然重启、当机,我们不考虑了,
但,天衣无缝的脚本写起来也不是那么容易,尤其是大规模的脚本
可以考虑到的问题在于:
脚本的逻辑,是不是没到删除临时文件时候就退出了、脚本执行一半被手动ctrl+c了…
(防止ctrl+c这个可以看一下trap命令的用法)
如何不使用临时文件呢?
答:
用内存!
如变量、数组、文件描述符等,都是存在内存当中的,
所以当脚本结束之后(包括非正常结束),
系统会自动回收这些资源,避免了很多麻烦(脚本没执行完系统就被弄死了,那么就没必要回收了)
(具体的方法这里不一一列举)
但,用内存的话,问题也是明显的,
在脚本执行过程中,这些内存资源没有被及时回收的话,
如果脚本比较大,逻辑复杂,也许也会出现问题
(请看下面的介绍)
2、节省内存的资源
在脚本运行过程中,资源的控制很重要,要把资源的使用控制在可以接受的范围内
(这就像炒汇,lostcut一次受不了)
上面提到尽量不使用临时文件而使用变量、数组、文件描述符等方法,
这些东西中,能存多少字符呢?
一般来说,内存多大就能存多少
所以,这就是要注意的地方,把握好尺度
文件描述符来说也是一样,可以在适当的地方关闭已经用过的描述符
3、逻辑的稳定
保证逻辑稳定的重要因素就是返回值的控制,
有的时候,不小心的改变了返回值而造成了逻辑上的错误
比如:
[!
-e“./txt”]&&exit1
这种情况的时候,当txt不存在时,返回值1并退出,
而事实上,当txt存在时,返回值也是1,然后脚本继续执行
这与“心理暗示”是逆向的,(这叫心理学,懂了吗?
)
即,文件存在可能是你希望的,而返回的却不是正常
所以有时很容易忽视,如果对逻辑没什么影响当然不必管它,
但是如果有影响的话,还是这样比较安全:
[!
-e“./txt”]&&exit1||true
4、变量的作用域问题
Bash对变量作用域的控制还是很简单,
即,只在一个脚本内部有影响,或这个脚本衍生出的子脚本
也许写程序的人不会傻到一段代码当中有变量重名的问题,
像java这种,至少犯了这样的错误,编译器也会提前告诉你
而在Bash当中就没有这好事了,它会继续执行下去,不管发生什么
尤其值得注意的是函数当中的变量,
它会不会影响外面的变量?
会不会受外部变量的影响?
所以,时不时的用local定义函数内部的函数比较安全
5、ssh的陷阱
ssh命令提供了一个shell的接口,可以运行远程主机的shell命令,
实现功能、得到输出等效果
但不要指望ssh能伺候你很爽,
保证认证key的通畅是一方面,更主要的是,不能把ssh完全当成本地shell来用,以下是一些例子
例子1,ssh运行于子层次:
[root@fxstest1~]#ssh10.4.5.213"[!
-e"/tmp/xxxxxxx"]&&exit1"
root@10.4.5.213'spassword:
[root@fxstest1~]#echo$?
1
这种方式从远程主机上得到了返回值,
但exit只退出了远程的shell,并没有退出当前shell
所以需要进行二次逻辑判断:
ssh10.4.5.213"[!
-e"/tmp/xxxxxxx"]&&exit1"&&{
true
}||exit1
同样的,在远程主机上定义的变量,不能在本地生效:
[root@fxstest1~]#ssh10.4.5.213"a=xxx"
root@10.4.5.213'spassword:
[root@fxstest1~]#echo$a
[root@fxstest1~]#
(“自动化批处理”那些脚本里面有很多应用,可以参考)
例子2,不要忽视ssh的标准输入:
这一点可能对ssh有些不公平了,因为它的标准输入与其他的命令并没有什么两样,
但为什么在ssh身上却需要注意呢?
因为:
能力越大,责任越大呗
最容易犯错的无非是与whileread结构的read冲突的例子了:
whilereadi;do
sshxxx@$#只会执行一次,因为read的stdin被ssh重执了
done 解决办法把ssh改成ssh–n关闭ssh的stdin即可,或者改变read命令的stdin 6、Bash的版本 随着Bash的发展,如今已经是3.0的时代了,但bash被广泛使用是在其2.0的时候 有很多老版本的linux操作系统仍然使用的是2.0,比如fc1、redhat9等(在fx我们系统当中,原来fx1.0的那些服务器都是Bash2.0),所以,版本问题不得不提 事实上,3.0在语法上和2.0没有任何区别,只是增加了一些语法: 1、类似perl的“=~” 2、增加了“<<<”结构,herestring 3、增加了类似于指针的变量: echo${! var} (有兴趣的话,自己研究吧…) 8、编程风格 如果你对“bash编程风格”这种说法感到可笑或是小题大做的话, 你只认为,能完成功能是bash编程的目的, 那么此节是无意义的 但劝你,还是先把你写的脚本藏好,免得让人笑话 这句是由衷的 Bash是简单的、随意的,语法不是很严格,刚刚接触的时候感到很容易,上手快 而以命令为核心的特点,会由于命令本身的强大而忽视了bash的作用 但随着学习、应用的深入,这一点却反而给了很多人烦恼(我也不烦恼啊,为啥? 答: 你新手呗
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机 脚本 常用 套路 详解