[ubuntu入门手册]-16-Shell脚本基础

引言

这篇文件介绍linux中shell脚本语法,linux的shell脚本逻辑远没有编程语言难,有过编程语言学习的朋友可以很容易的上手,linux下的shell就像windows下的batch脚本,可以用于集成系统终端任何命令,让我们的操作更加的快捷。

文章目录

0×1.变量的定义与输出

shell脚本实际上就是将我们在终端中的命令集成起来批量的执行,用规定的语法结构来控制脚本的执行流程,系统将我们编写的sh脚本交给"解释器"执行,最常见的解释器就是bash,用下面的命令可以查看系统中所有可用的shell解释器:

					qing@qingsword.com:~$ cat /etc/shells 
					# /etc/shells: valid login shells
					/bin/sh
					/bin/dash
					/bin/bash
					/bin/rbash
					/usr/bin/tmux

					#显示当前系统默认使用的shell解释器
					qing@qingsword.com:~$ echo $SHELL
					/bin/bash
					

按照国际惯例,第一个shell脚本输出"hello world":

					#1.创建并编辑一个shell
					qing@qingsword.com:~$ vim sh01.sh

					#2.输入下面的内容
					#shell脚本标准第一行注释,代表这是一个bash脚本
					#定义字符串变量,在没有使用双引号的时候,输出特殊字符一定要使用转义反斜杠,包括空格。
					
					#!/bin/bash 
					#by www.qingsword.com
					hw=Hello\!\ world\!  
					echo $hw       #输出变量值

					#3.执行这个shell(使用bash sh01.sh也可以)
					qing@qingsword.com:~$ sh sh01.sh 
					Hello! world!   #输出

					#除了直接使用bash调用脚本外,还可以给脚本添加可执行权限,之后只需要输入脚本命令,系统会使用默认的脚本解释器去执行它,例如
					qing@qingsword.com:~$ sudo chmod +x sh01.sh
					qing@qingsword.com:~$ ./sh01.sh
					

单引号和双引号的区别:

					qing@qingsword.com:~$ vim sh02.sh

					#!/bin/bash
					#by www.qingsword.com
					name="qingsword"  #字符串变量
					hw="hello!world."
					myname0="My name is $name"
					myname1='My name is $name'
					echo $hw    #输出
					echo $myname0
					echo $myname1

					#单引号中的所有字符都将被解释成普通字符
					qing@qingsword.com:~$ sh sh02.sh 
					hello!world.
					My name is qingsword
					My name is $name
					

除了上面两种引号外,还有一种特殊的引号,esc下面,数字键1左边的那个键,能够打出一个`,包含在`中的内容,将被解释器直接执行,例如:

					#定义一个变量x,这个变量等于一句命令,这句命令被包含在`对中,脚本执行到这里时,将会直接执行语句`tail -n 2 /etc/passwd`并将这条命令执行后的返回结果保存到x变量中,使用echo就能打印出这个变量的值
					qing@qingsword.com:~$ x=`tail -n 2 /etc/passwd`
					qing@qingsword.com:~$ echo $x

					#可能在有些脚本中,会看到下面这种语法,实际上执行一下就会发现,输出与上面这种完全相同,所以`tail -n 2 /etc/passwd`就等同于$(tail -n 2 /etc/passwd),只是写法不相同
					qing@qingsword.com:~$ x=$(tail -n 2 /etc/passwd)
					qing@qingsword.com:~$ echo $x
					

0×2.键盘输入与参数传递

使用read关键字,可以读取键盘输入,这有点类似C语言的scanf,请看下面的实例:

					qing@qingsword.com:~$ vim sh03.sh

					#!/bin/bash
					#by www.qingsword.com
					echo "Please keyin your name then press Enter:"
					read name #读取键盘输入保存到name变量中
					#上面这两句可以用下面这一句代替,效果是相同的,read的-p参数用于打印一段需要显示在屏幕的文字,随后的name变量接收用户从键盘输入的值
					# read -p "Please keyin your name then press Enter:" name
					# -e参数告诉解释器,启用反斜杠的转义,那么\n就会被解释成一个换行符,如果不想echo命令输出的最后执行换行,可以再添加参数-n(每个echo语句的末尾都会自动换行)
					echo -e "Hello $name\nWelcome!" #输出

					qing@qingsword.com:~$ sh sh03.sh 
					Please keyin your name then press Enter:
					qingsword
					Hello qingsword
					Welcome! #因为echo中添加了换行符'\n',所以Welcome!换行了
					

可以在脚本后面添加变量参数传递给脚本:

					#$0代表被执行的脚本本身,$1,$2,$3表示执行时传递给脚本的参数,可以自定义任意数量的参数传递给脚本
					qing@qingsword.com:~$ vim sh04.sh

					#!/bin/bash
					#by www.qingsword.com
					echo "This script's name is:$0"
					echo "parameters:$1,$2,$3"

					qing@qingsword.com:~$ sh sh04.sh p1 p2 p3
					This script's name is:sh04.sh
					parameters:p1,p2,p3
					

0×3.dash语法错误的解决方法

当我们的shell脚本里面存在declare关键字定义的变量时,在终端下使用sh运行脚本会出现“dash语法错误”,因为ubuntu的/bin/sh连接到了dash这个文件(ll /bin/sh就知道了),并不是调用bash执行,dash的declare语法稍有不同,所以我们直接使用sh运行这个shell会出现错误,请看下面的实例:

					#declare命令可以直接在终端下定义临时变量并做运算,-i定义整数变量
					qing@qingsword.com:~$ declare -i a=1
					qing@qingsword.com:~$ declare -i b=2
					qing@qingsword.com:~$ declare -i c=$a+$b
					qing@qingsword.com:~$ echo $c
					3

					#declare定义变量类型参数
					-i 整数
					-a array数组
					-f 函数
					-r 只读
					-x 通过环境输出变量

					#创建一个shell脚本,计算一个简单加法
					qing@qingsword.com:~$ vim declare-demo.sh 

					#!/bin/bash
					#use bash $0
					#by www.qingsword.com
					declare -i result=1+2+3+4+3+2+1  
					echo "Result is:$result"

					#使用sh运行脚本,报错
					qing@qingsword.com:~$ sh declare-demo.sh 
					declare-demo.sh: 4: declare-demo.sh: declare: not found

					#使用bash调用这个sh,正常执行
					qing@qingsword.com:~$ bash declare-demo.sh  
					Result is:16
					

#dash语法错误的解决方法 三种解决方法:
1)执行时由sh build.sh变成bash build.sh。
2)更改软连接,让sh连接到bash,在终端下输入sudo ln -s /bin/bash /bin/sh -f
3)sudo dpkg-reconfigure dash 进行配置,去掉sh软连接关联,道理同(2)

0×4.if..else..fi实例

if是shell中最常用的判断语法,唯一要注意的就是,Linux脚本中if参数与符号之间必须用空格隔开,请看下面的实例:

					#读取键盘输入的if脚本实例
					qing@qingsword.com:~$ vim ifshell.sh

					#!/bin/bash
					#if demo
					#by www.qingsword.com
					echo "Please keyin 'y' to continue.."
					read yn
					#不同块元素之间必须用空格隔开,否则会报错
					if [ "$yn" = "y" ] || [ "$yn" = "Y" ] ; then 
					        echo "script is running..."
					else
					        echo "STOP!"
					fi #if结束

					#执行测试
					qing@qingsword.com:~/test$ sh ifshell.sh 
					Please y to continue..
					y
					script is running...
					qing@qingsword.com:~/test$ sh ifshell.sh 
					Please y to continue..
					Y
					script is running...
					qing@qingsword.com:~/test$ sh ifshell.sh 
					Please y to continue..
					s
					STOP!
					

携带初始参数的if脚本实例:

					#$1将读取脚本后面传递的第一个参数进行判断
					qing@qingsword.com:~$ vim ifshell.sh

					#!/bin/bash
					#by www.qingsword.com 
					if [ "$1" = "hello" ] || [ "$1" = "HELLO" ] ; then
					        echo "$1,How are you?"
					elif [ "$1" = "" ] ; then #如果参数为空
					        echo "STOP!You must input parameters."
					else
					        echo "The only accept parameter is ‘hello’."
					fi

					#测试
					qing@qingsword.com:~$ sh ifshell.sh  
					STOP!You must input parameters.
					qing@qingsword.com:~$ sh ifshell.sh sdfaf
					The only accept parameter is hello.
					qing@qingsword.com:~$ sh ifshell.sh HELLO
					HELLO,How are you?
					

一个判断系统是否开启某些端口的shell,可以将命令直接定义成变量,但是命令必须使用键盘1左边那个“点”号扩起来,请看下面的实例:

					qing@qingsword.com:~$ vim port.sh 

					#!/bin/bash
					# 检查www,ftp,ssh以及smtp+pop3是否开启
					# by www.qingsword.com
					# www 切记是1左边那个`不是单引号'
					www=`netstat -an|grep LISTEN|grep :80` 
					# 检测是否开放ftp21端口
					ftp=`netstat -antu|grep LISTEN|grep :21`
					# 检测是否开放ssh22端口
					ssh=`netstat -antu|grep LISTEN|grep :22`
					# smtp+pop3
					smtp=`netstat -antu|grep LISTEN|grep :25`
					pop3=`netstat -antu|grep LISTEN|grep :110`

					#变量值是否为空,空表示没有开放此端口
					if [ "$www" != "" ] ; then
					        echo "WWW is running"
					else
					        echo "WWW is NOT running"
					fi

					if [ "$ftp" != "" ] ; then
					        echo "FTP is running"
					else
					        echo "FTP is NOT running"
					fi

					if [ "$ssh" != "" ] ; then
					        echo "SSH is running"
					else
					        echo "SSH is NOT running"
					fi

					if [ "$smtp" != "" ] && [ "$pop3" != "" ] ; then
					        echo "SendMail is running"
					elif [ "$smtp" = "" ] && [ "$pop3" != "" ] ; then
					        echo "SendMail have some problem of your smtp"
					elif [ "$smtp" != "" ] && [ "$pop3" = "" ] ; then
					        echo "SendMail have some problem of your pop3"
					else
					        echo "SendMail is NOT running"
					fi

					#测试机上面这些服务都没开启
					qing@qingsword.com:~$ sh port.sh 
					WWW is NOT running
					FTP is NOT running
					SSH is NOT running
					SendMail is NOT running
					

if进行文件操作实例,下面是shell检测文件和文件夹的常用参数

					#-e检测是否存在某文件或文件夹
					#-f检测是否为文件
					#-d检测是否为文件夹

					qing@qingsword.com:~$ vim ifcreate.sh

					#!/bin/bash
					#by www.qingsword.com
					#在脚本运行当前目录创建删除文件或文件夹
					if [ ! -e logical ] ; then #如果不存在logical
					        touch logical     #创建文件logical
					        echo "Just make a file logical"
					        exit 1 #自定义的返回值
					elif [ -e logical ] && [ -f logical ] ; then #如果存在并且是一个文件
					        rm logical  #删除
					        mkdir logical #创建同名文件夹
					        echo "remove file logical"
					        echo "and make directory logical"
					        exit 1
					elif [ -e logical ] && [ -d logical ] ; then #如果存在并且是一个文件夹
					        rm -rf logical  #删除文件夹
					        echo "remove directory logical"
					        exit 1 
					#默认exit 0,是成功退出,而exit 1,exit 2或者其他自定义数值一般用作不成功退出返回给程序调用者
					else
					        echo "Does here have anything?" #似乎永远不会运行到
					fi

					#测试
					qing@qingsword.com:~$ sh ifcreate.sh 
					Just make a file logical
					qing@qingsword.com:~$ sh ifcreate.sh 
					remove file logical
					and make directory logical
					qing@qingsword.com:~$ sh ifcreate.sh 
					remove directory logical
					

bash shell的语法和各大编程语言大同小异,唯一比较恶心的就是空格,有时候中括号或等号和变量之间忘记空格都会报错。

0×5.case实例

case相当于一个并列的if.else,请看下面的实例:

					#提供一个选择列表,通过传入的值判断执行列表中哪一部分的内容
					qing@qingsword.com:~$ vim casesh.sh

					#!/bin/bash
					#case demo
					#by www.qingsword.com
					echo "This program will print your selection"
					case $1 in   #读取传递给脚本的第一个参数
					        one)
					                echo "Your choice is $1" ;;
					        two)
					                echo "Your choice is $1" ;;
					        three)
					                echo "Your choice is $1" ;;
					        *)   #如果不是上面三个值,进入这个分支
					                echo "Usage {one|two|three}"
					                exit 1 #自定义的程序返回值
					esac

					#测试不携带值与携带值返回的结果
					qing@qingsword.com:~$ sh casesh.sh 
					This program will print your selection
					Usage {one|two|three}
					qing@qingsword.com:~$ sh casesh.sh two
					This program will print your selection
					Your choice is two
					

0×6.for循环实例

下面这段shell实现循环累加,从1到100,最后输出结果

					qing@qingsword.com:~$ vim fordemo.sh

					#!/bin/bash
					#for demo
					#by www.qingsword.com
					declare -i total  #定义一个整数
					for ((i=1;i<=100;i=i+1)) #满足条件i<=100时继续循环
					#或for ((i=1;i<=100;i++)) 
					do
					       total=total+i  #递增
					       #或total+=i
					done
					echo "The count is $total"

					#测试,算法就是(1+100)*100/2=5050
					qing@qingsword.com:~$ bash fordemo.sh 
					The count is 5050
					

for循环遍历列表:

					qing@qingsword.com:~$ vim forindemo.sh

					#!/bin/bash
					#for in demo
					#by www.qingsword.com
					LIST="Tom John George Smith" #人名列表
					for i in $LIST #枚举列表中的值打印到屏幕
					do
					        echo $i
					done

					qing@qingsword.com:~$ sh forindemo.sh 
					Tom
					John
					George
					Smith
					

输出系统所有账户名称列表:

					qing@qingsword.com:~$ vim accountdemo.sh

					#!/bin/bash
					#account display
					#by www.qingsword.com
					#用':'符号分割/etc/passwd文件并取第一个字段,然后sort排序 
					account=`cut -d ":" -f1 /etc/passwd|sort`
					echo "The following is your linux server's account:"
					for i in $account
					do
					        echo $i
					done

					qing@qingsword.com:~$ sh accountdemo.sh 
					The following is your linux server's account:
					avahi
					avahi-autoipd
					backup
					bin
					...
					

0×7.while循环实例

用while循环实现从1加到100:

					qing@qingsword.com:~$ vim whiledemo.sh

					#!/bin/bash
					#while demo
					#by www.qingsword.com
					declare -i i=0
					declare -i s=0
					#当i不等于100时继续循环
					while [ $i != 100 ] 
					do
						i=i+1 #递增i
					 	s=s+i #递增s,每次循环加上i的当前值
					done
					echo "The count is $s"

					#包含declare就需要用bash执行
					qing@qingsword.com:~$ bash whiledemo.sh 
					The count is 5050
					

0×8.until循环实例

until实现1加到100:

					qing@qingsword.com:~$ vim untildemo.sh
					#!/bin/bash
					#until demo
					#by www.qingsword.com
					declare -i i=0
					declare -i s=0
					until [ $i = 100 ] #当i等于100时退出循环
					do
						i=i+1
						s=s+i
					done
					echo "The count is $s"

					qing@qingsword.com:~$ bash untildemo.sh 
					The count is 5050
					

until循环读取键盘值:

					qing@qingsword.com:~$ vim untiltwo.sh

					#!/bin/bash
					#by www.qingsword.com
					#当用户键入y或者Y时退出循环,until是满足条件则退出循环,for和while是满足条件继续循环
					until [ "$yn" = "y" ] || [ "$yn" = "Y" ]
					do 
					        echo "Press y/Y to stop:"
					        read yn
					done
					echo "Stop here."

					qing@qingsword.com:~$ sh untiltwo.sh 
					Press y/Y to stop:
					asdf
					Press y/Y to stop:
					23r
					Press y/Y to stop:
					y
					Stop here.
					

0×9.shell脚本调试

使用sh命令调试脚本,最常用的参数如下:

-n 参数是不执行脚本,查询脚本语法,有错误则显示
-v 在执行脚本之前,先将脚本的内容显示在屏幕上
-x 将脚本的执行过程显示出来,脚本语句前面添加+号,而在屏幕上的输出则不添加+号

					qing@qingsword.com:~$ bash -x untildemo.sh 
					+ declare -i i=0
					+ declare -i s=0
					+ '[' 0 '!=' 100 ']'
					+ i=i+1
					+ s=s+i
					...... #会将程序执行的所有过程都显示出来
					......
					+ '[' 99 '!=' 100 ']'
					+ i=i+1
					+ s=s+i
					+ '[' 100 '!=' 100 ']'
					+ echo 'The count is 5050'
					The count is 5050