Shell编程

本文介绍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跳过。