本文介绍Shell编程。
基本知识
bash是在sh基础上开发的,sh是没有补齐的。
执行命令的时候,执行的语句会开启一个子进程执行命令,shell进程会切换到后台。执行完毕后shell切换到前台。
基本语法
shell变量是全大写字母加下划线组成,分为:
变量
环境变量
环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。使用env命令能够查看环境变量。
本地变量
设定本定变量:
name='LiXiaoyang'
这样就是添加本地变量。
这种方式只是添加到本地变量中,环境变量是不会存在的。
查看设置的变量:
env | grep 'name' # env打印的是环境变量
# 此处是没有输出的
set | grep 'name' # set打印的是环境变量以及本地变量。
name=''
把本地变量导出为环境变量,使用:
export '变量名'
使用export也可以直接将变量设置为环境变量:
export name='LiXiaoyang' # 使用env | grep 'name'查看
这样就可在环境变量中查到设置的name=’LiXiaoyang’变量:
env | grep 'name' # env打印的是环境变量
name='LiXiaoyang'
取出变量值:
echo $name
LiXiaoyang
注意:取的需加上$符号,不加则不会取到。定义变量的时候不需要加。
删除环境变量:
unset '变量名'
区别:
环境变量是保存在内存中的,假如说是4g内存,则环境变量是保存在3g大小处的位置。
而本地变量是保存在程序的栈中。
但在fork一个子进程时候,使用exec执行命令。就会吧程序栈中的本地变量冲掉。但是环境变量的值还在。
值存在于当前的shell进程。用set命令可以显示当前shell进程中定义的所有变量(环境,本地变量)和函数。
环境变量是任何进程都有的概念,本地变量是shell特有的概念。
查看当前sh类型
$ echo $SHELL
/usr/bin/bash
文件名代换(Globbing)* ? []
通配符:
* 匹配0个或者多个任意字符
? 匹配一个任意字符
[] 匹配括号中任意一个字符的一次出现
$ ls /dev/ttyS*
$ ls ch0?.doc
$ ls ch0[0-2].doc
$ ls ch[012] [0-9].doc
命令代换 $()
$ DATE=`date`
$ echo $DATE #DATE变量是date命令得出的时间,将命令执行的结果赋值给变量DATE。
可以使`date`或$(date)或 `date方式。
在$()命令括号的内容中的,是执行的命令。不会被赋值给变量。
算术代换 $(())
VAR=45
echo $((VAR + 3))
用2个小括号套起来,里面的VAR会被取出,并和后面数字运算。但是只能支持+,—,*,/,且是整数。
$[base#n],其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。
echo $[2#10+11] # 使用10的2进制进行运算
echo $[8#10+11] # 使用10的8进制进行运算
echo $[10#10+11]
转义字符 \ 反斜杠
创建文件名为 $ $ 的文件:
touch \$\ \$ # 得出是文件名为 $ $ ,删除的时候使用 rm \$\ \$
touch -- -hello #得出是文件名为 -hello,删除的时候使用 rm -- -hello
引号
双引号括起来的变量会被展开(取出其中的值),但是单引号引起来的不会展开(不会取出其中的值)。
括号语法
执行的语句用括号:
C@DESKTOP-0JM9OLN MINGW64 /d (master)
$ pwd # 当前所在的目录
/d
C@DESKTOP-0JM9OLN MINGW64 /d (master) # 执行语句
$ (cd ..;ls)
bin/ etc/ LICENSE.txt ReleaseNotes.html unins000.exe*
cmd/ git-bash.exe* mingw64/ tmp/ unins000.msg
dev/ git-cmd.exe* proc/ unins000.dat usr/
C@DESKTOP-0JM9OLN MINGW64 /d (master) # 当前所在的目录
$ pwd
/d
加上括号是在子进程执行不会传递给主进程本身,不使用括号会吧命令传递给父进程。
执行以上语句,实际并不是改变的当前的目录地址,使用括号是shell fork出一个子进程,子进程执行语句:exec(cmd命令)此时主进程切换到后台回收子进程wait,主进程并没有,所以当前目录并没有变。
bash内建命令
查看内建命令:
man bash-builtins
如export,if,eval,for,while,shift等。内建虽然不会创建新的进程,但是也会有exit status。*执行成功会返回0,执行失败返回的是非零。同时结束后也有一个状态码,用特殊变量$?变量读出。
C@DESKTOP-0JM9OLN MINGW64 /d (master) # 执行命令
$ cd
C@DESKTOP-0JM9OLN MINGW64 ~ (master) # 执行成功,返回的是0
$ echo $?
0
C@DESKTOP-0JM9OLN MINGW64 ~ (master) # cd一个不存在的文件夹
$ cd /dsdsds
bash: cd: /dsdsds: No such file or directory
C@DESKTOP-0JM9OLN MINGW64 ~ (master) # 返回的是非零的数字
$ echo $?
1
注意:
0,或者1,返回是上一个执行命令的值,如果是上一句执行的
echo $?``,再执行一遍,echo $?, 则最后得到的就是0,因为你的echo $?`执行是成功的。sh文件第一行加上
#! /bin/sh是指定这个解释器取执行这个文件。
可以使用source sh_file_name命令或者. sh_file_name。一样的作用。
shell脚本语法
条件测试 test 或者 []
用于测试一个条件是否成立。结果为真,命令的exit status为0.为假则为1。
itcast@ubuntu:~$ var=2
itcast@ubuntu:~$ test $var -gt 1
itcast@ubuntu:~$ echo $?
0
itcast@ubuntu:~$ test $var -gt 3
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$ [ $var -gt 3 ]
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$
注意:
使用 [ ]时候要加上空格。 [ $var -gt 3 ]
[ -d DIR_NAME ] 如果DIR存在并且是一个目录则为真
[ -f FILE_NAME ] 如果FILE存在且是一个普通文件则为真
[ -z STRING ] 如果STRING的长度为零则为真
[ -n STRING ] 如果STRING的长度非零则为真
[ STRING1 = STRING2 ] 如果两个字符串相同则为真 #常用
[ STRING1 != STRING2 ] 如果字符串不相同则为真
[ ARG1 OP ARG2 ] ARG1和ARG2应该是整数或者取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个
示例:
[ -d DIR_NAME ] # 测试是否为目录
echo $? 用命令检查上一条的命令是否执行成功
判断2个字符串是否相同,使用[ STRING1 = STRING2 ],但是要加上$:
s1=hello
s2=hello
[ “$s1” = “$s2” ] # $是取出变量 , 为了保险,在外层加上双引号,如果取出的变量是空字符串。则双引号会吧其作为字符处理
echo $? # 检查上一句是否执行成功。
注意:
为了安全,取出变量的时候必须加上双引号,防止取出的是空字符串。
逻辑运算:
带与、或、非的测试命令
[ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示逻辑反
[ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示逻辑与
[ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示逻辑或
$ VAR=abc
$ [ -d Desktop -a $VAR = 'abc' ]
$ echo $?
0
if/then/elif/else/fi
#! /bin/sh
echo "Is it morning? Please answer yes or no." # 输出内容
read YES_OR_NO # 读取获取到的内容并且赋值给YES_OR_NO,将该字符串存到一个Shell变量中
if [ "$YES_OR_NO" = "yes" ]; then # 判断
echo "Good morning!"
elif [ "$YES_OR_NO" = "no" ]; then
echo "Good afternoon!"
else
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1 # 将状态码设置为1, 执行echo $?时候的返回值
fi
exit 0
#! /bin/sh
if [ -f /bin/bash ]
then echo "/bin/bash is a file"
else echo "/bin/bash is NOT a file"
fi
if :; then echo "always true"; fi
:是一个特殊的命令,称为空命令。这个命令不做任何事情。但是exit status总是真的。
Shell还提供了&&和||语法:
test “$(whoami)” != ‘root’ && (echo you are using a non-privileged account; exit 1)
&&相当于“if…then…”,而||相当于“if not…then…”。&&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别。
test "$VAR" -gt 1 -a "$VAR" -lt 3
等价于:
test "$VAR" -gt 1 && test "$VAR" -lt 3
case
#! /bin/sh
case语法示例:
echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in # in是关键字
yes|y|Yes|YES) # |是或, 单括号只有右边的半个,左边的没有
echo "Good Morning!";; # ;;是结束符,是2个分号
[nNo]*) # 匹配括号内的字符,一个或者多个
echo "Good Afternoon!";;
*) # 匹配所有的字符
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1;;
esac # 这里是将case反过来【和if使用一样】
exit 0
使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):
case "$1" in
start)
...
;;
stop)
...
;;
reload | force-reload)
...
;;
restart)
...
*)
log_success_msg "Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"
exit 1
;;
esac
$1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数,也就是start,所以进入start)分支执行相关的命令。同理,命令行参数指定为stop、reload或restart可以进入其它分支执行停止服务、重新加载配置文件或重新启动服务的相关命令。
for/do/done
循环语句:
#! /bin/sh
for FRUIT in apple banana pear; do # apple banana pear 是列表内容
echo "I like $FRUIT" 使用
done
输出结果是:
I like apple
I like banana
I like pear
可以写成一行:
FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再比如,要将当前目录下的chap0、chap1、chap2等文件名改为chap0~、chap1~、chap2~等(按惯例,末尾有~字符的文件名表示临时文件),这个命令可以这样写:
$ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done
while/do/done
#! /bin/sh
echo "Enter password:"
read TRY
while [ "$TRY" != "secret" ]; do
echo "Sorry, try again"
read TRY
done
控制运算次数:
#! /bin/sh
COUNTER=1
while [ "$COUNTER" -lt 10 ]; do
echo "Here we go again"
COUNTER=$(($COUNTER+1))
done
break和continue
break[n]可以指定跳出几层循环,continue跳过本次循环步,没跳出整个循环。
break跳出,continue跳过。