资源描述
如何进行shell脚本开发
什么是shell
简单的理解为系统管理员与操作系统之间的接口;顾明思义它是操作系统的外壳,管理员通过这个接口完成所需要的任务.
shell脚本与shell命令的区别及关系
shell脚本是一个包含shell命令的ASCII码文件,特点是可以用文本处理工具查看如more 脚本名 来讯速的判断它是不是脚本.如果是命令的话则显示乱码
而命令则是可执行的二进制代码文件
Shell脚本可理解为shell命令与应用程序的集合
shell脚本用起来有时候跟shell命令一样.
unix的文件观
在unix中设备是文件,目录是文件,文件是文件:
空设备文件
/dev/null
开发脚本时需要把不必要的信息输出到这个文件,让屏幕干净.
cat /dev/null filename #清空filename
statement > /dev/null #把不需要的输出重定向到空
如何创建一个可执行的脚本
1 用touch命令建一个空文件(touch shellscrip),更改其权限为可执行chmod +x shellscrip
2 如何让操作系统懂得它是一个脚本
echo #!\/bin\/sh > shellscrip
echo echo hello world >> shellscrip
3 执行shellscrip 完成一个hello world脚本
hello world 相关的几个命令
touch 是创建文件的命令
chmod 是改变文件的权限(文件的权限位分为读写执行分别用三个字母标识共有三组用户属性第一组文件属主,第二组为同组用户,第三组为其它,注意:同组不包含属主
echo 表示显示信息
> 输出重定向
>> 追加
\ 转义字符
shellscripr 的内容
#!/bin/bsh
操作系统看到 #! 就会用紧跟在它后面的程序去解释这个的文本(它可能是脚本,可能是一个awk程序或是其它别的应用程序),这里用bsh ,则说明它是bsh的脚本,不同的shell也在这里指出,重要的是这里必需给出绝对路径(即全路径或完整路径),哪怕PATH变量有指出也要给绝对路径.
仅第一次出现是这个含义,往后再现”#!”字符串shell解释为注释行(注释行以#号开始)并不会改变当前的shell.:
#!/bin/sh #!/bin/csh
echo $status echo $status
#!/bin/csh #!/bin/sh
echo $status echo $status
以上两组动作将有不同的结果表明第二次出现的 #! 指定的shell并不会改变当的shell
常见的Shell
bsh (Bourne Shell)
ksh (Korn Shell)
csh ( C Shell)
最常用的Shell是sh,大部分的Unix系统都将sh连接到bsh;IBM将sh连接到ksh
Shell程序也就是一系列的Unix命令的组合,相当于DOS系统下的批处理命令。Unix命令既可以是内部命令,也可以是已经编译好的高级语言程序,甚至可以是其它Shell程序
Shell程序支持分支与循环结构,可以进行信号处理
Shell程序可以加注释,注释以“#”号开头,到行尾结束
怎样才能知道用什么Shell来执行我编写的Shell脚本?参照下面三个原则:
如果脚本文件的第一个字符不是“#”号,则启动bsh执行此脚本
如果脚本文件的第一个字符是“#”号,但第二个字符不是“!”,则启动csh执行此脚本
如果脚本文件的前两个字符为“#!”,则后面必须是Shell的全路径,启动指定的Shell执行此脚本,如:
#!/usr/bin/ksh
求条件表达试的值 test 或 [ ]
不支持控制符的判断.规避方法 [ X$str = Xstring ]
如果是含路径的字符串用basename,与dirname命令取得文件名与父目录名,因为对含路径的字符串进行处理时test命令是会报错的
test 表达式 或 [ 表达式 ]
name=Tom #bsh(这种赋值方式不适合csh,tcsh)
test $name = Tom #等号两边必需留空格字符串比较
echo $? #查看测试结果
[ $name = Tom ] #方括号两边内侧须留空格
字符串,整型和文件的测试
字符串测试
string1 = string2 #字符串1等于字符串2 (等号两边的空格是必需的)
string1 != string2 #字符串1不等于字符串2 (不等号两边的空格是必需的)
string #不是空串
-z string #字符串长度为零
-n string #字符串长度不为零
整型测试
int1 –eq int2 # Int1等于 int2
int1 –ne int2 # Int1 不等于 to int2
int1 –gt int2 # Int1 大于 int2
int1 –ge int2 # Int1 大于等于 int2
int1 –lt int2 # Int1 小于 int2
int1 –le int2 # Int1 小于等于int2
逻辑测试
expr1 -a expr2 逻辑与
expr1 -o expr2 逻辑或
! expr 逻辑非
文件测试
-b filename 是块文件
-c filename 字符文件
-d filename 目录文件
-f filename 普通文件但非目录文件
-g filename 设置gid的文件
-k filename 存在粘性位的文件
-p filename 命名管道文件
-r filename 有读权限的文件
-s filename 文件大小非零字节
-u filename 设置uid的文件
-w filename 有写权限的文件
-x filename 有执行权限的文件
Shell中的if语句的语法如下(这里不包含csh,tchs):
if 命令1 #如果命令1执行成功(返回0)
then #则执行list1命令列表
list1 #如果执行不成功(返回非0值)
elif 命令2 #执行命令2
then #如果命令2执行成功
list2 #则执行list2命令列表
else #如果命令2执行不成功
list3 #则执行list3命令列表
fi
if语句以fi结尾
Shell中case语句的语法如下:
case word in #如果word与 pattern1匹配,
pattern1) #则执行list1命令列表
list1 #如果word与 pattern2匹配,
;; #则执行list2命令列表
pattern2) #…………
list2
;;
…………
esac
case语句以esac结尾,每个分支后两个“;”是必须的
case语句中的pattern可以使用如下结构
“或”结构,如“a|b|c”匹配a或b或c
[范围],如“[0-9]”匹配单个数字
“?”匹配任何单个字符
“*”匹配任何零个或多个字符
使用“*”,相当于C语言default分支
以上结构可以组合使用,如:
“[0-9]|0x[0-9A-Fa-f]”
Shell中while循环的语法如下:
while command
do
list
done #循环语句必须以done结尾
1、执行命令command
2、如果执行成功(返回值为0),
则执行list命令列表
3、重复1,直至command命令失败(返回非零值)
Shell中until循环的语法如下:
until command
do
list
done #循环语句必须以done结尾
1、执行命令command
2、如果执行失败(返回值非0),
则执行list命令列表
3、重复1,直至command命令执行成功(返回0)
Shell中for循环的语法如下:
for name in word1 word2 ... wordN
do
list
done #循环语句必须以done结尾
name为变量名,word1到wordN是由空格分开的单词列表
for循环每次执行时,变量name被设为列表中的下一单词
for循环常用于处理文件集合
例如:修改一个目录下所有文件的文件名,在文件名后加上“.bak”
for file in $HOME/*
do
mv $file ${file}.bak
done
ksh中Select语句的语法如下:
select name in word1 word2 ... wordN
do
list
done #循环语句必须以done结尾
name为变量名,word1到wordN是由空格分开的单词列表
1、select语句打印word1…wordN到标准出错设置(文件描述符为2),前面增加一个数字。
2、打印PS3提示符,并且从标准输入设备读一行
3、如果该行与所列word前面的数字一致,则变量name被设置成相应的word
4、如果该行为空,则重新打印select列表
5、否则,变量name被设置成空
6、读到的内容存放在变量REPLY中
7、每输入一次选择,命令列表list被执行一次,直到遇到break或EOF
8、如果在命令列表list中将变量REPLY设成空,则在打印PS3提示符前,打印select列表
Select语句应用示例:
select SEL in Menu1 Menu2 Exit
do
case "$SEL" in
Menu1)
echo "You Select Menu1."
REPLY=""
;;
Menu2)
echo "You Select Menu1."
REPLY=""
;;
Exit)
break
;;
*) echo "Select Error."
REPLY=""
;;
esac
done
Shell中的特殊字符
* ? [ ] ' " \ $ ; & ( ) | ^ < > 换行 空格 tab
单引号“'”:单引号内的所有特殊字符都失去其特殊含义,包括换行符
“'”必须成对出现。一对“'”之间不能嵌入“'”,前面用“\”转义也不可以
反引号“`”:反引号内是一组命令。Shell将反引号替换为该命令的输出。反引号对特殊字符的处理与双引号基本相同
双引号“"”:双引号内的大部分字符失去特殊含义,下面的字符除外:
$ $用于变量替换
` 反引号用于命令替换
\$ $作为美元符,而不具备特殊含义
\` `作为反引号,而不具备特殊含义
\" 使用嵌入的双引号
\\ 使用嵌入的反斜线
其它的\字符都作为普通字符
定义变量
name=value 等号中间不能有空白
变量名只能包含字母、数字或者下划线,并且只能以字母或下划线开头
引用变量时,在变量名前加 $符
特殊的变量
$$ 当前执行Shell的进程ID
$1 … $9 命令行参数
$@或$* 命令行参数一整行
环境变量:子进程可以继承父进程的环境变量,在sh下,用export命令将一个变量设为环境变量
在ksh中,${#name} 为变量name值的长度
测试退出状态
echo $status csh 返回0正确,非0出错
echo $? bsh,ksh 返回0正确,非0出错
例如:
cat /etc/passwd |grep $LOGNAME
echo $?
Shell脚本如何验证参数个数:
#!/bin/ksh
if [ $# -ne 2 ]
then
echo "输入不正确!"
echo "正确方式: ./脚本名称 整型数1 整型数2 "
exit
fi
echo $1+$2=`expr $1 + $2`
这里#号变量表示参数个数,不包含文件名
$1 取第1个参数
$2 取第2个参数
$N取第N个参数
$0 取脚本名
脚本的调试
错误的类型
运行时错误
运行时错误一般发生在脚本开始运行后遇到一些意外的情况,发使用了错误的脚本名
权限问题,路径问题或语法错误,如引用不匹配或词拼写错误.
命名惯例
在shell提示符键入一个命令,shell将搜索unix/linux路径以查找这条命令.当在提示符处键入一个脚本名时,它被当作另一个命令看待.shell将搜索路径以寻找它.现在假定你将一个脚本名为某个unix命令”ls”.哪个ls将被执行?这取决于在搜索路径中先找到的是哪一个,很可能执行的是unix命令而非您的脚本.显然脚本不应该被命名为ls或cat,但对一些类似test或script的不常用的命令就不是那么明显了..例如unix的test命令并不产生输出,所以命名问题比较复杂.更要注意避免将其命名为test和script这些不常用的命令.使用which命令可以知道脚本命名是否与unix/linux命令同名.它将显示找到的命令程序路径,也可以用./启动脚本,这样当前工作目录中的脚本将会被运行.
权限不足
普通文件没有执行权限.如果直接命令运行脚本则必需将它的权限打开
chmod +x
路径问题
PATH变量中如果没有(.)目录则将收到错误的信息:command not found
可能导至语法错误的原因
未定义变量与误定变量
众所周知,shell使用变量时并不需要预先进行声名.当在程序中使用一个变量
时,该变量被自动创建.尽管看起来这种方式比显式地定义每个变量更加方便,但它
却同时带来不良的后果:一个经意的拼写错误可能会创建本来无须创建的多余变量,
再加上unix/linux是区分大小定的.即使只是将大写变成了小写也可能导致程序失败
未完成的编程语句
在sh,bash,ksh中: 在csh,tchs中:
if [ expression ] if ( expression )
Then then
Statement statement
Statement statement
fi endif
当然shell的调试技术不仅仅是这些,这里先介绍这几个.
工具介绍
1) 命名管道 |
语法:命令1 | 命令2
将命令1的执行结果送到命令2,做为命令2的输入。
例如:
ls -Rl | more 以分页方式列出当前目录及其子目录下所有文件的名称。
cat file1 | more 以分页方式列出文件file1 的内容
管道应用举例
ls -l | grep ^d #列出当前目录下的所有目录
ls –l | grep ^- #列出前目录下的普通文件
ls -l *.unl #列出当前目录下的.unl文件
2) 查找 find
find命令用于搜索目录树,并对目录树上的所有文件执行某种操作,参数是目录名表(指出从哪些起点开始搜索),还可给出一个或多个选项,规定对每个文件执行什么操作。
find ./ -print 将列出当前工作目录下的目录树的每一个文件
find / -user bob -print 将列出在系统中可找到的属于bob用户的所有文件
find ./ -perm 755 将列出/usr/bob 目录树下所有存取许可为755的文件。
find ./ -name '*.txt' -exec rm {} \; 删除当前目录下包含子目录中的所有找到的*.txt文件.-exec COMMAND \; 允许对所找到的每个文件运行指定的命令COMMAND
3) 文件内容查找 grep
grep abc file1 寻找文件f i l e 1中包含字符串abc 所在行的文本内容。
grep number *查找当前目录下所有文件中包含number(大小写不敏感) 所在行的文本内容及文件名
grep –n : 显示行号,文件中可以不必包含行号
4) 更改密码
语法: passwd
Old password: <输入旧密码>
New password: <输入新密码(最好为6~8字,英文字母与数字混合) >
Retype new password: <再输入一次密码>
------ 只有root用户才更改其它用户的密码
5) 改变当前用户
语法: su [-] 用户名
例如:
su user 进入用户user 的帐号。
passwrod : <输入用户user 的密码>
“-“ 号的作用加载新用户的环境,如果不包含-号则用当前的用户环境
6) 查看自己用户名
语法: whoami 查看当前的用户名。若已执行过su命令,则显示出此用户的用户名。
7) 创建文件与目录
touch file
mkdir folder
-m 给新建的文件夹赋权限位,如果不指定则按umask给定默认值
-p 可以建立级联目录,如果不带这个参数,新建目录的父目录必需已存在.
8) 删除命令rm 与rmdir
删除一个目录树 rm –rf qq 慎用
rmdir [-m] [-p] folder
9) 确定当前运行shell的方法
ps | grep $$ | grep -v grep | awk '{print $4}'
$$:指当前运行的shell,第二个$是个系统变量;
10) 别名alias
alias 显示当前shell的所有别名
alias periodic 'echo You have worked an hour, nonstop' csh的使用方法
alias cl='clear' ksh 的使用方法
alias lls 'ls -l;ls' 完成批命令功能各命令间用分号分开
alias lls='ls -l;ls'
-------命令因不同的shell而有所不同;
它还可以给有执行权限的脚本起别名.
这样别名与脚本一样有着无限的可能.
11) 计算文件中的指定行,字,字节或字符数 wc
wc –l file 返回该文件的行数
wc –l * 返回当前目录下的所有文件的行数
12) 文件权限的设定 chmod
chmod [-R] mode name
name:文件名或目录名。
mode: 3个8位数字或rwx的组合。r-read (读),w-write (写),x-execute (执行),u-user (当前用户),g-group(组),o-other(其他用户)。
chmod 755 dir1 对于目录dir1,设定成任何使用者皆有读取及执行的权利,但只有所有者可做修改。
chmod u+x file2 对于文件file2,增加当前用户可以执行的权利。
chmod g+x file3 对于文件file3,增加工作组使用者可执行的权利。
chmod o-r file4 对于文件file4, 删除其他使用者可读取的权利。
查看系统中的进程
13) 查看当前进程的状态
ps或ps -X 查看系统中,属于自己的进程。
ps -au 查看系统中,所有用户的进程。
ps -aux 查看系统中,包含系统内部的及所有用户的进程。
14) 在后台执行进程的方式
语法:命令&
例如:sleep 10& 将sleep10的工作置于后台执行。
按下Control+Z键,暂停正在执行的进程;键入bg命令,将暂停的进程置于后台继续执行。
15) 查看正在后台中执行的进程
语法:jobs
16) 结束或终止后台中的进程
语法:kill %n
n:利用jobs命令查看出的后台作业号
例如:
kill % 终止在后台中的第一个进程。
kill %2 终止在后台中的第二个进程。
17) 显示文档帮助
语法: man 命令名
例如:
man ls
项目知识积累
1) 使用 sed 与 cat 除去空白行
$ sed "s/^ *//g" |sed ‘s/^$//’ input.file 说明:^后是一个空格
$ cat -s /etc/X11/XF86Config | sed '/^[[:space:]]*$/d' 说明:包括tab前导行
2) 计算脚本运行时间( 常用在脚本的性能测试)
sh 的系统变量$SECONDS,保存脚本运行时间,单位为秒;
#!/bin/sh
sleep 3
echo run_time=$SECONDS
3) read从文本中逐条取行记录
awk '{print $0 }' ${file_name} | while read line
do
operator $line
command statement
done
特别说明:
cat 处理文件,会解析文件中的“回车”符然后输出,如果没有则不输出;
awk 处理一个文件,文件末尾没有回车符的则会自动添加;
wc -l 统计“回车”符的个数为记录条数,如果文件末尾没有回车符则该记录将被忽略;
4) awk中匹配全数字
echo "1234567890" |awk '{ if ($1 ~ /^([0-9])+$/) print "True" }'
5) awk 的输出命令
1 print 打印换行符;2 printf 不打印换行符;
6) 内存库访问
echo "unload to ‘tatablename.unl’ select * from tatablename " | mdsql;
单引号内可以是变量,带路径
echo "load from tatablename.unl insert into tatablename "| mdsql;
7) 物理库访问
echo "unload to tatablename.unl select * from systables;" | dbaccess dbname
echo "load from tatablename.unl insert into systables;"|dbaccess ocs_chg1
文件名可以带路径
8) awk不用输入文件测试法
awk 'BEGIN { print "\r\n" >"qq" }'
9) 如何处理“回车”符
Windows下的回车与换行,是两个控制符号十六进制码(0D0A),而在unix下则只有一个符号;
#方法一:“输入不可打印字符”通过sed进行替换,其中 ^M 需要在unix下用vi编辑器通过命令"Ctrl+v+m"才能打出来!
如:sed s/^M/HEREIS_M_ENTER/g ${InPutFile} > ${f_temp1}
但是这里隐含一个问题,一但这个脚本以ASCII方式包传到windows下(强调一点没有进行过任何编辑),1 回车符“^M”就有可能被替换成回车换行符;2 打成tar包后bin方式传回,再打开查看也会被换成回车换行符,此刻再传回unix,脚本运行报错,乱码;
#方法二:“以明文方式表示不可打印字符”,通过awk中"\r"来表示回车符:
awk -F "\r" '{
strtmp=""
for (i=1;i<=NF;i++ )
{
strtmp=sprintf("%s%s",strtmp,$i);
}
print strtmp >"tt"
}'
10) HP服务器挂载块设备相关命令
1) 挂载命令: mount
举例:mount -F nfs /dev/vg00/lv_tellin /tellin
参数说明:
“-F”: 指定文件系统为nfs 这里为nfs文件系统
“/dev/vg00/lv_tellin” 块设备文件
“/tellin” 挂载点
2) 查看块设备的文件系统命令fstyp
举例:fstyp /dev/vg00/lv_tellin
参数说明:
“/dev/vg00/lv_tellin” 块设备文件
3) 检查文件系统的一致性并且以交互方式修复文件系统命令 fsck
举例:fsck -F nfs /dev/vg00/lv_tellin
参数说明:
以nfs 的文件系统来修复 /dev/vg00/lv_tellin块设备文件
4) 卸载块设备命令umount
举例: umount /dev/vg00/lv_tellin
参数说明:
“/dev/vg00/lv_tellin” 块设备文件
11) varyonvg unix命令
12) 对行进行编号
$ cat -b /etc/X11/XF86Config
$ cat -n /etc/X11/XF86Config
13) 显示非打印字符
$ cat -t /etc/X11/XF86Config
$ cat -E /etc/X11/XF86Config
$ cat -v /etc/X11/XF86Config
14) 在sh中打印当前行代码行号
echo $LINENO 调试用
15) informix 工具
dbschema
systables 系统表可查所有表信息
syscolumns 系统表可查所有列信息
16) 内存表识别
通过dbfield_dict.memresident字段识别
1 驻留内存
0 不驻留字段
sql=” select a.memresident from dbfield_dict a , dbtable_dict b where a.tableid = b.tableid and b.tablename='表名' and a.memresident=1;”
echo "${sql}" | dbaccess sysdb |xargs | awk '{print $NF}'
对结果进行验证,等于1则为内存表;
另:memresident在dbfield_dict表的第10个字段.
识别内存表可以写sql查询再判断,别一种方案是把表下载下来,再通过字段访问来唯一确定该值,但是对文件按条件查询不是很方便.
17) 视图表识表识别
Sql=”select tabletype from dbtable_dict where tablename='表名';”
echo "${sql}" | dbaccess sysdb |xargs | awk '{print $NF}'
结果为3--系统参数主表 ,4--系统参数从表;即3和4都为视图;
如:OCS_SMSID2FILELIST表既是视图又是内存表;
18) 文本文件内容纵向列合并
paste -d "\0" field_2 field_4
把文件field_2, field_4列合并分隔符为空字符串.
19) 内存表重建
mdalter -droptable表名 #删表
mdinit -table 表名 -n ${CBP_NODEID} #建表
mdsql -notrans # not to start transactions automatically while executing record update.
20) 物理库上的字段类型识别
原理:联查syscolumns,systables 两张表来识别类型其中类型为
266
269
10
的为符串构造sql语句的时候需要加引号,其它类型不需要。
sql_cmd="unload to ${tmp_file_dir}/tbl_filed_type.tmp select a.colname ,a.coltype from syscolumns a, systables b where a.tabid = b.tabid and b.tabname = '${lower_tablname}'"
21) 取指定某年某月的最后一天值
cal 月 年 |xargs |awk '{print $NF}'
注:
1) 对月年的验证,命令执行后查退出码结果是否正确;
2) 正确的日期的范围:[1, 最后一天值]
检查年月日的合法性:
22) 取得今天与昨天的日期
运行函数后TODAY—今天、YESTERDAY--昨天;
23) 进程唯一性检查
来自现网运行的DCCProxy进程唯一性检查
24)
展开阅读全文