做者:Danbo 日期:2015-7-3
什么是Shell:Shell是一个命令解析器,它在OS的最外层,负责直接与用户对话。
shell在系统四大层次所处的位置:(从内向外)
1.硬件
2.Kernel
3.Shell
4.外围应用程序
什么是Shell脚本:当命令或语句不在命令行执行,而是经过一个程序文件执行时,该程序就被称为Shell脚本或Shell程序。相似DOS下的批处理。经过命令、变量和流程控制语句等有机的结合起来,就造成了一个功能强大的Shell脚本。
示例1::清空日志的三种方法:把全部命令堆积起来造成了脚本示例。
echo " " >/var/log/messages
>/var/log/messages
cat /dev/null >/var/log/messages
echo "Logs cleaned up"php
示例2:有判断的shell的脚本
#!/bin/bash
ROOT_UID=0
LOG_DIR=/var/log
if [ "$UID" -ne "ROOT_UID" ]; then
echo "You must be root to run this script"
exit 1
fi
cd $LOG_DIR || {
echo "Cannot change to necessary directory." >&2
exit 1
}
cat /dev/null > messages
echo "Logs cleaned up."
exit 0 #注意返回0表示成功,返回1表示失败。python
Shell脚本是弱类型语言,较为通用的shell有标准的sh和csh,其中sh已被bash shell取代了。Shell相较于php、python、perl的差异:shell的优点在于吃力OS底层的业务(有大量的系统命令作支撑,2000多个命令,比grep、awk、sed )。好比一键安装、报警脚本、shel开发快速。shell
规范的shell脚本开头。
#!/bin/bash
#!又称为幻数,在执行bash及哦啊本的时候,内核会根据它来肯定哪一程序来解析脚本中的内容。这一行必须在脚本的顶端的第一行,好比不是第一行则是注释。
咱们发现sh本质上是bash的软连接:bash
[root@localhost ~]# which sh
/bin/sh
[root@localhost ~]# ll /bin/sh
lrwxrwxrwx 1 root root 4 Jun 29 17:03 /bin/sh -> bashssh
Shell脚本的执行
当Shell脚本以非交互的方式运行时,它会先检查环境变量env,该便来个支出一个环境文件(.bashrc)后,从该变量环境变量文件开始执行,当读取了env文件后,Shell才开始执行Shell脚本的内容。学习
Shell脚本的执行有3中方式
①.bash script-name 或 sh script-name #当脚本自己没有x权限或脚本开始没有指定解析器。
②.path/script-name 或 ./script-name
③.source script-name 或 . script-name
使用source或者"."的话,能够将子shell中的变量传递到父shell中
例如:测试
[root@localhost uestc]# echo 'userdir=`pwd`' >test.sh
[root@localhost uestc]# cat test.sh
userdir=`pwd`
[root@localhost uestc]# sh test.sh
[root@localhost uestc]# echo $userdir优化
[root@localhost uestc]# . ./test.sh
[root@localhost uestc]# echo $userdir
/uestcthis
这是为何呢?咱们当前执行脚本的窗口是一个shell(经过echo $$来查看当前窗口的shell)。而test.sh有处于另一个shell中 。所以当咱们执行sh test.sh后,虽然tesh.sh中已经定义了userdir,可是没法将其传递到父shell(当前窗口所处的shell)中来,而. /test.sh执行的方式就能够。spa
变量可分为环境变量(全局变量)和局部变量(本地变量)。
咱们设置环境变量在用户家目录的.bash_profile文件中,或者/etc/bashrc,或者/etc/profile,或者/etc/profile环境变量能够在建立他们的shell和子shell,他们一般被称为全局变量以区别局部变量。一般环境变量应该大写。环境变量可使用export内置命令或者declare -x导出为全局变量。取消本地变量命令:unset 变量名
本地变量是在用户当前的shell生存期的脚本中使用:定义方式为:locate UESTC
定义变量的时候:"" '' 不加引号区别
不加引号:内容通常为简单连续的数字、字符串、路径名等
单引号:输出变量时引号里面是什么就输出什么
双引号:双引号中的变量会通过解析而后再输出
注意再awk则相反:双引号原样输出,单引号解析后再输出
[root@localhost uestc]# UESTC=123456
[root@localhost uestc]# awk 'BEGIN{print "$UESTC"}'
$UESTC
[root@localhost uestc]# awk 'BEGIN{print '$UESTC'}'
123456
把命令定义为变量
cmd=`date +%F`或者 cmd=$(date +%F)
当咱们对文件进行打包压缩备份时为显示什么时间备份的,咱们能够将打包时间写到文件名当中
tar -zcvf etc_$(date +%F)_backup.tar.gz /etc
Shell特殊变量
$0:得到当前执行Shell脚本的文件名,包括完整路路径;只取名字:basename $0;只取路径:dirname $0
[root@localhost uestc]# cat sh.sh
#!/bin/bash
echo $0
[root@localhost uestc]# cd scripts/
[root@localhost scripts]# sh /uestc/sh.sh
/uestc/sh.sh
$n:得到当前指定行的Shell脚本第n个参数值,n=1,2,3,4....9当大于10的时候就须要括起来$(10);
[root@localhost scripts]# cat 1.sh
#!/bin/bash
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $(10)
[root@localhost scripts]# sh 1.sh 1 2 3 4 //即取后面的形式
1 2 3 4
$*:得到当前Shell的全部参数,并将全部命令行参数视为单个字符串
$#:得到当前Shell命令行中参数的总个数
$@:得到当前Shell的全部参数,每一个参数仍是独立的。
咱们经过实例来看一下其做用
#!/bin/bash
case "$1" in
start)
start
;;
stop)
stop
;;
statue)
status portmap
;;
restart|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/portmap ] && restart || :
;;
*)
echo "Usage:$0{start|stop|status|restart|reload|condrestart}"
exit 1
esac
咱们这里看一下$#的做用
[root@localhost scripts]# set -- "I am " an uestc student
[root@localhost scripts]# echo $#
4
咱们这里看一下$*与$@的区别
[root@localhost scripts]# for i in "$@";do echo $i; done
I am
an
uestc
student
[root@localhost scripts]# for i in "$*";do echo $i; done
I am an uestc student
$$:获取当前shell的进程号
$!:得到Shell最后运行后台的PID
$?:或者执行上一个指令的返回值(0为成功;非零为失败)
$_:在此以前执行命令或脚本的最后一个参数
[root@localhost ~]# cat ba.sh
#!/bin/bash
cd /etc
tar -zcvf service.tar.gz ./services >/dev/null 2>&1
[ $? -eq 0 ] && echo Success || echo Failed
咱们这里总结一下$?返回值的意义:
0 成功
2 权限拒绝
126 找到命令可是没法执行
127 未找打目标命令
大于128 命令被系统强制结束
另外一个例子:
#!/bin/bash
echo $$ >/uestc/a.log
while true
do
uptime > /dev/null 2>&1
sleep 2
done
此时咱们cat a.log此时咱们就能看到当前脚本的PID
[root@localhost ~]# sh ba1.sh &
[1] 13038
[root@localhost ~]# cat a.log
13038
Bash内部命令变量及shift实践讲解
有些内部命令在目录列表是看不到的,能够经过man bash来查看,常见的有:echo、eval、exec、 export、read、shift、exit和点(.)
shift语句按以下方式从新命令全部的位置参数变量,即$2成为$1;$3成为$2。在程序中每使用一次shift做用是全部的位置参数依次向左移动一个位置而且是位置参数$#减1,知道其值减到为0
某些脚本加了一些选项再接参数。
eval(evalargs)读入参数args并将他们组合成一个新的命令,而后执行。
好比:eval command-line
其中command-line是在终端上键入的一条普通命令行。然而当在它前面放上eval时,其结果是shell在执行命令行以前扫描它两次。如:
pipe="|"
eval ls $pipe wc -l
shell第1次扫描命令行时,它替换出pipe的值|,接着eval使它再次扫描命令行,这时shell把|做为管道符号了。
exec:当Shell执行到exec语句,不会去建立新的子进程,而是转去执行指定的命令,当指定的明林执行完时,该进程(也就是最初的Shell)就终止了,因此Shell程序中exec后面的语句将再也不被执行。
Shell变量的字串经常使用操做
${#string} #返$string的长度 //echo ${string} | wc -m,可是这种技术比#string计数多一个
[root@localhost ~]# string=uestc
[root@localhost ~]# echo ${#string}
5
[root@localhost ~]# echo $string | wc -m
6
${string:position}:提取含有position关键字的字符串
${string:position:length}:从position以后开始提取长度为length的字串
${string#substring}:从变量string开头开始删除最短匹配substring字串
${string##substring}:从变量string开头开始删除最长匹配substring字串
${string%substring}:从变量string结尾开始删除最短匹配substring字串
${string%%substring}:从变量strig结尾开始删除最长匹配substring字串
${string/substring/replace}:使用$replace,来代替第一个匹配$substring字串
${string/#substring/replace}:若是string前缀匹配substring,就用replace来替代/substring
好比咱们批量更名:
咱们首先将要建立的名字写入到一个文件当中,好比:a.log
而后使用:for f in `cat a.log`; do touch $f ; done建立文件
咱们须要将每一个文件后面的finished去掉,该如何作?脚本以下:
#!/bin/bash
for f in `ls *.jpg`
do
mv $f `echo ${f%finished*}.jpg`
done
咱们将后缀jpg改成JPG,咱们先经过mv命令实现
#!/bin/bash
for f in `ls *.jpg`
do
mv $f `echo ${f/%jpg/JPG}`
done
经过sed命令也能够实现:
#!/bin/bash
for f in `ls *.JPG`
do
mv $f `echo $f | sed 's/JPG/jpg/g'`
done
经过rename是最简单的实现更名的方式:
rename "finished" '' * 第二种:rename .jpg .JPG *
其用法以下:
rename from to file
from:须要替换或须要处理的字符;
to:把前面from替换为to;
file:代替换的文件,能够用*处理全部文件。
${value:-word}:若是变量名存在且非空,则返回变量的值。不然,word字符串用途:字符变量未定义,则返回默认值。 范例:${vlaue:-word},若是value未定义,则表达式的值为word。
${value:=word}若是变量名非空,则返回变量值。不然,设置这个变量值为word,并返回气值。用途,若是:若是变量未定义,则设置变量为默认值,并返回默认值 。范例:${value:=word},若是value为定义,则设置value值为word,返回表达式的值也为word。
${value:?message}:若是变量赋值的话正常替换,不然将消息message送到标准错误输出。
实例:
path1="/uestc"
rm -rf ${path:-/tmp/}
这样以后就比较保险了,即便目录不存在会删除tmp目录内的临时文件。
在脚本执行以前,咱们可使用sh -x来对脚本进行调试。
生产常见中咱们能够看这两个系统脚本:
/etc/init.d/httpd
/etc/init.d/crond
咱们再学习脚本的时候能够多看一些系统脚本文件,能够给系统脚本写备注。
三种计算字符串长度的方法:
1.echo {#char}
2.echo ${char} | wc -c #这种计算方式会多计算一个换行字符"\n",比实际多1。
3.echo $(expr length "$char")
程序执行效率问题
咱们经过time+要执行的命令发现运用bash内置的命令进行执行操做时效率最高。
双括号(())运算的示例
请你设计一个加、减、乘、除等功能的计算器。经过名列给你行的传参的方式
#!/bin/bash
echo $(($1$2$3))
这个实际上是经过把后面的表达式传递到前面的脚本中执行,就是简答的把3个参数排在一块儿就OK了
Let变量的数值运算
[root@localhost ~]# i=2
[root@localhost ~]# let i=i+8
[root@localhost ~]# echo $i
10
[root@localhost ~]# i=1
[root@localhost ~]# i=i+1
[root@localhost ~]# echo $i
i+1
提示:let i=i+8 等同于((i+8)),可是后者的效率更高。
变量的数值运算与特殊应用expr命令
expr命令通常用于整数值,但也可用于字符串,用来求表达式变量的值,同时expr也是一个手工命令行计算器。
咱们以前用户expr来计算字符串的长度:echo $(expr length "$char") //双引号能够不加
1.expr也能够用在计算:
expr 2 + 2
expr 2 \* 2
注意运算符与数字以前都有空格。、
2.expr在循环中能够用于增量计算。首先循环初始化为0,而后循环值加1,反引号的用法为命令替换。最基本的一种是从expr命令接受输入并将之放入循环变量。
例如:给自变量i+1
[root@localhost ~]# i=0
[root@localhost ~]# i=`expr $i + 1`
[root@localhost ~]# echo $i
1
3.expr $[$a+$b]表达式形式,其中$a$b可为整数值
expr $[2+3] #中括号数字与运算符以前能够不加空格
5
以上这种书写形式符号两边不须要加空格。expr将其后的串解释为表达式并计算其值,运算符前须要空格。
4.其余特殊用法:此时咱们注意到ssh-cpoy-id脚本
if expr "$1" : ".*\.pub";then #匹配*.pub格式的文件若是是则为真。例如:
expr "uestc.pub" : ".*\.pub"
9 #返回非0值为真,若是返回值为0则为假。咱们注意到这个非0值实际上是返回前面字符个数。
5.为了更方面阅读咱们在纪委再加上两个语句:expr "uestc.pub" : ".*\.pub" && echo MATCH || echo Not-MATCH
经过expr判断变量是否为整数。
#!/bin/bash
read -p "Please input an integer:" a
expr $a + 0 >&/dev/null
[ $? -eq 0 ] && echo INIT||echo Chars
这种判断上输入数字是否为整数的一种重要的方式
6.经过expr计算字符串的长度
expr length "$UESTC"
bc命令的用法
1)、echo 3+4 | bc
7
2)、咱们要计算1+2+3+...+10也能够经过管道接bc
seq -s "+" 10 |bc
55
3)、保留小数点问题:经过命令scale=n
[root@localhost ~]# echo "scale=3;4.35/2.11"|bc
2.061
4)、进制转换obase=n
echo "obase=2; 8" | bc #这里是将十进制的8转化为二进制。
[root@localhost ~]# echo "obase=2;8" | bc
1000
5)、typeset -i A=1 B=3
A=A+B
echo $A
4
不过这种方式不多见
6.$[]这种方式前面咱们使用expr讲过了
[root@localhost ~]# echo $[1+2]
3
不过咱们仍是推荐使用$(())这样的用法比较好
最经常使用的-p:prompt:设置提示信息
-t timeout设置输入等待的时间,单位为秒
read -t 10 -p "Please input two number:" a b #10s后不输入退出
还有另一种方法:
echo -n "Please input two number:"
read a b
咱们写个脚本对read读入的是否是数字进行判断
#!/bin/bash
while true
do
read -p "Please input two number:" a b
expr $a + 0 >/dev/null
[ $? -ne 0 ] && continue
expr $b + 0 >/dev/null
[ $? -ne 0 ] && continue || break
done
echo "a-b=$(($a-$b))"
echo "a+b=$[$a+$b]"
将上面改成命令行传参的方式并优化
#!/bin/bash
a="$1"
b="$2"
Usage(){
echo "USAGE:sh $0 num1 num2"
exit 1
}
if [ $# -ne 2 ];then
Usage
fi
expr $1 + 0 >&/dev/null
[ $? -ne 0 ] && Usage
expr $2 + 0 >&/dev/null
[ $? -ne 0 ] && Usage
echo "a+b=$(($a+$b))"
echo "a+b=$[$a+$b]"
在bash的各类流程控制结构中一般要进行各类测试,而后根据测试结果执行不一样的操做,优点也会经过与if等条件语句相结合,使咱们能够方便的完成判断。
格式1:test <测试表达式>
格式2:[ <测试表达式> ]
格式3:[[ <测试表达式> ]]
说明:格式1有格式2等价,格式3为扩展test命令
经常使用的文件测试操做符号:
-f 文件存在
-d 目录存在
-s 文件存在且非空
-e 文件存在即为真
-r 文件存在且可读
-w 文件存在且可写
-x 文件存在且可执行
-L 文件存在且为连接
f1 -nt f2 文件1比文件2更新
f1 -ot f2 文件1比文件2更久
咱们能够经过查看/etc/init.d/nfs这个脚原本学习这些文件操做符的使用
test -f file && echo true||echo false
[ -f file ] && echo true|| echo false
[[ -f file && -d folder ]] && echo true||echo false
注意:&&或者||只能用在[]之间与[[]]以内。不过他们之间是能够相互转换的。
而且-o -a 能够单[]中
字符串测试操做符
做用:比较两个字符串是否相同,字符串长度是否为零,是否为null
-z "字符串" #若串长度为0则真,-z能够当即为zero
-n "字符串" #若串的长度不会0则为真
"串1"="串2" #相等为真,此时也可使用符号==代替=
"串1"!="串2" #不相等为真
注意:以上字符串测试操做符号必定要用""引发来!!!!
注意咱们看下面这个实例:
[root@localhost ~]# [ 2>1 ] && echo OK||echo False
False
[root@localhost ~]# [ 2<1 ] && echo OK||echo False
False
为何?咱们必须注意当使用单中括号时必须使用转义字符:
[root@localhost ~]# [ 2\<1 ] && echo OK||echo False
OK
因此说单括号咱们最好不要使用符号表示法:
[ 2 -lt 1 ] && echo OK||echo False
OK
因此咱们在[]中使用的比较 在(())和[[]]中
-eq ==
-ne !=
-gt >
-ge >=
-lt <
-le <=
逻辑操做符
在[]使用的逻辑操做符 在[[]]中使用的逻辑操做符
-a &&
-o ||
! !
咱们看一下一下示例
[ -f "$UESTC" ] $$echo 1||echo 0 #这个是条件表达式的用法:返回1为真返回0为假,这点状态变量的$?不一样
file1=/etc/services; file2=/etc/rc.local
echo $file1 $file2
[ -f "file1" ] && echo 1||echo 0
通常系统脚本中使用中会用到大量的判断语句
[ -r /etc/s\sysconfig/network ] && . /etc/sysconfig/network
判断条件后执行多条命令语句,也便是咱们刚开始经shell所用到的语句:
[ 判断 ] || {命令1 命令2 命令3}
其用法实例:cd $LOG_DIR||{echo "Cannot change to necessary directory" >&2 exit 1}
对于上面的例子咱们能够这样写:
[ 3 -ne 3 ] ||{
echo "I am a UESTC student"
echo "I am a GOOD man"
exit 1
}
若是要写在一行中,每一个命令还须要分号结尾。
脚本中编写实例:[ 3 -ne 3 ]||{echo "I am a UESTC Student";echo "LAAL";}
命令行中咱们能够用:[ 3 -ne 3 ]||(echo s="1";echo "2")
还有一个例子:[ $ERROR -eq 0 ] && echo "jdk安装成功" || (echo "jdk安装失败,请检查" && exit 1)
咱们在nfs中找到相关的应用:
单分支结构
if [条件]; then
command
fi
双分支结构