[Linux] BASH程序设计

1. 控制结构shell


1.1 if...thenexpress

if...then控制结构的语法以下:数组

if test-command
then
  commands
fi

if 语句测试test-command返回的状态,并基于这个状态转移控制。if结构的结束由fi语句标记,例如:bash

echo -n "Word 1: "
read word1
echo -n "Word 2: "
read word2
if test "$word1" = "$word2"
then
  echo  "Match"
fi

在BASH中,test是一个内置命令,也就是说它是shell的一部分,同时还有一个单独的工具test。对于数值比较测试有如下几种测试选项:-ne(不等)、-eq(等于)、-gt(大于)、-ge(大于等于)、-lt(小于)以及-le(小于等于)。对于字符串比较能够用=(等于)、!=(不等于)来进行测试比较。特殊变量"$#"表示命令行参数的个数,例如:ide

if test $# -eq 0
then
  echo "Error"
  exit 1
fi

可使用test来判断文件参数状态或两个文件参数的关系,若是test内置命令带有"-f"选项和一个参数(第1个参数$1),那么它就可用来检查参数所指定的文件,例如:函数

if test -f "$1"
then
  echo "$1 is  a regular file"
else
  echo "$1 is not a regular file"
fi
test内置命令的选项
选项
功能
-d 检查文件是否存在以及该文件是不是文件目录。
-e 检查文件是否存在。
-f 检查文件是否存在以及该文件是不是一个普通文件。
-r 检查文件是否存在以及该文件是否可读。
-s 检查文件是否存在以及该文件是否大于0字节。
-w 检查文件是否存在以及该文件是否可写。
-x 检查文件是否存在以及该文件是否可执行。

"[]"为test的同义词,能够把test的参数用方括号括起来,例如:工具

if [$# -eq 0]
then
  echo "..."
  exit 1
fi


1.2 if...then...else oop

在if结构中引入else语句使其为分支结构,语法以下:测试

if test-command
then
  commands
else
  commands
fi

如同换行符同样,分号能够结束一条命令,所以,能够把then与if放在同一行,并在then的前面加一个分号,例如:this

if test-command; then
  commands
else
  commands
fi

若是test-command返回true状态,if结构执行then和else语句之间的命令,而后把控制转向fi后面的语句。若是test-command返回false状态,if结构执行else语句后面的命令。


1.3 if ...then...elif

if...then...elif控制结构的语法以下:

if test-commands
then
  commands
elif test-command
then
  commands
else
  commands
fi

elif语句组合了if语句与else语句,使得能够嵌套多个if...then...else结构。else语句和elif语句之间的差异在于每一个else语句必须与一个fi语句配对,而多个嵌套的elif语句只须要一个fi语句。


1.4 for...in

控制结构for...in的语法以下所示:

for loop-indexin argument-list
do
  commands
done

for...in结构把argument-list中的第1个参数赋给loop-index变量,并执行do和done语句之间的命令。 在脚本把控制传给done语句以后,结构把arguments-list的第2个参数赋给loop-index变量并再一次执行do和done之间的命令。


1.5 for

for控制结构语法以下:

for loop-index
do
  commands
done

在for结构中,loop-index用命令行参数中的每一个参数值取代,一次执行一个。除了loop-index变量的取值来源,for结构与for...in结构是相同的。for结构一般依次根据每一个参数执行一个命令序列。


1.6 while

while控制结构的语法形式以下:

while test-command
do
  commands
done

只要测试条件的返回值为真,while结构语句就要执行do与done语句之间的命令。在每次循环以前,while结构都要检查测试条件。一旦测试语句的返回值为假,while结构语句就把控制传递到done语句以后的程序段。


1.7 until

until语句与while语句的语法结构类似,区别只在于until在语句的结束测试。until的语法结构以下:

do
  until test-commands
done


1.8 break与continue

利用break和continue语句能够在for、while或until语句中产生中断。break语句能够跳出循环,把控制直接转移到done语句以后的内容。continue语句把控制传到done语句, 并继续执行循环。


1.9 case

case控制结构是一种多分支选择机制,具体选择哪一个分支依赖于测试串和某个分支类型之间的匹配状况,case控制结构的语法以下:

case test-string in
  pattern-1)
commands-1
;;
  pattern-2)
commands-2
;;
  pattern-3
commands-3
;;
esac

case结构中的匹配类型相似于一个模糊文件引用,实际上匹配类型能够包括表中的任何字符或字符串:

匹配类型
类型
功能
* 匹配任意字符串,用做默认的case匹配。
? 只匹配单个字符。
[...] 定义一个字符类,对处在方括号的每一个字符依次进行单字符匹配。
| 分离带有选择的选项,这些选项知足case结构的一个特别的分支。


1.10 select

select控制语句首先显示一个菜单,而后根据用户选择给变量赋予相应的值,最后执行一系列命令。select控制结构的语法形式以下:

select varname [in arg...]
do
  commands
done

select结构显示的内容为arg条目的菜单。假如忽略键盘输入和参数列表,select控制语句会用位置参数来取代arg条目。


2. 参数和变量


2.1 文件描述符

一个进程不管从文件中读内容仍是向文件中写内容以前必须先打开这个文件。当一个进程打开文件时,Linux中常给这个文件分配一个数字,即文件描述符。一旦打开某个文件,进程不管是读或者是写该文件都要靠文件描述符来进行操做。当进程再也不须要该文件时,它必须关闭该文件同时也要释放文件描述符。

一个典型的Linux进程在启动时包括3个已打开的文件:标准输入、标准输出和标准错误输出。在bash中使用exec内置命令打开文件,例如:

exec n> outfile
exec m< infile

第一行是打开一个输出文件outfile,并给它赋予文件描述符n,第二行是打开一个输入文件infile,并给它分配文件描述符m。符号"<&"的做用是复制一个输入文件描述符,符号">&"的做用是复制一个输出文件描述符。能够经过把两个文件描述符指向同一个文件的方法来复制文件描述符,例如:

exec n<&m


2.2 数组变量

bash支持一维数组做为变量。数组的下标是整数并以数字0做为起始,格式以下:

name=(element1 element2...)

能够按照以下方式引用数组中的某个元素:

echo ${NAMES[1]}

下标"[*]"和"[@]"的做用都是提取出整个数组元素,可是当它们加上双引号使用时工做机制却不一样。"@"符号的含义是把原数组的内容复制到一个新数组中,生成的新数组和原来是同样的。可是"*"符号是把原数组中的全部元素当成一个元素复制到新数组中,生成的新数组只有一个元素,例如:

A=("${NAME[*]}")
B=("${NAME[@]}")

把操做符"${#NAME[*]}"放在一个数组变量的前面能够返回数组中元素的个数,例如:

echo ${#NAME[*]}

把上面操做符中的"*"符号替换为数组的下标就能返回数组中对应元素内容的长度,例如:

echo ${#NAME[1]}

也能够将数组下标放在赋值语句的左边以便对相应的数组元素进行赋值,例如:

NAME[1]=John


2.3 变量局部性

进程在遇到默认变量时,通常当作是声明位置处的局部变量。除非将变量声明为可访问的全局变量,不然shell脚本不能访问用户在登陆shell中声明的变量。在bash下,export命令可使父进程的变量对子进程来讲是可以使用的,例如:

export name=John
echo "$name"
#subscript
echo "$name"

因为函数运行的环境一般与其被调用的环境相同,因此其中变量是显示的被shell和调用它的函数一块儿共享的,例如:

function name() {
  echo $myname
  myname=John
}
myname=Marry
name #Marry
echo $myname #John

在函数中使用局部变量,可使用typeset内置命令,例如:

function name() {
  typeset myname
  myname=John
  echo $myname  
}
myname=Marry
name #John 
echo $myname #Marry


2.4 特殊参数

shell把执行shell的进程的PID号存储在特殊参数"$$"中,例如:

echo $$

把PID号包含在一个文件名中对于建立一个惟一的文件名是很是有益的,这种方法经常用在shell脚本中用来建立临时文件的名称。

后台运行的进程的PID号存储在符号"$!"中,例如:

sleep 60 &
echo $!

一个进程不管因为何种缘由中止运行,它都要向父进程返回一个exit状态,返回的状态能够被认做是条件码或者返回码,"$?"中存储着上一个命令的返回状态码,例如:

ls
echo $?


2.5 位置参数

参数"$#"保存了命令行上除命令自身以外的参数的个数,例如:

echo "This script was called with $# arguments."

参数"$0"中保存了用来执行程序命令的名称,该参数被设置为0是由于它出如今命令行上第1个参数的前面,例如:

echo "The command used to run this script is $0"

命令行上的第1个参数由"$1"替换,第2个由参数"$2"替换,一直到"$n",一旦n的值超过9,数字两边就要加上大括号,例如:

echo "First 5 arguments are $1 $2 $3 $4 $5"

变量"$*"包含了全部的命令行参数,例如:

echo "All arguments are $*"

参数"$*"和"$@"除了它们在加上双引号用法不一样外,其余的用法都相同。使用参数"$*"只能产生一个参数,而参数"$@"则生成一串参数,其中每一个位置参数仍然是一个单独的参数。


2.6 左移命令行参数

利用shift内置命令能够移动每一个命令行参数,向左移动时,第1个参数被丢弃,第2个参数变成第1个参数,依次类推。已经丢弃的命令没法找回,例如:

echo "arg1=$1 arg2=$2 arg3=$3"
shift
echo "arg1=$1 arg2=$2 arg3=$3"


2.7 初始化命令行参数

set命令是用来初始化命令行参数变量的。set命令把set后跟的一个或几个参数赋值给位置参数,这些位置参数以$1打头,例如:

set this is it
echo $1 $2 $3


2.8 扩展空变量和未设置变量

表达式${name}扩大为变量name的值。若是name变量为空或尚未设置,bash就将${name}扩展成一个空串。能够经过给变量加上一个修饰符来选择几个选项:变量使用默认值、使用默认值并将其赋给变量、显示错误。

修饰符":-"使用一个默认值来替代那些空的或者没有赋值的变量,格式以下:

${name:-default}

例如:

${APP_PATH:-/home/test}

修饰符":-"不能改变理的值,但若是但愿修改脚本中空变量或未赋值变量的默认值,修饰符":="能够实现这个功能,格式以下:

${name:=default}

shell按照扩展表达式${name:-default}的方式来扩展表达式${name:=default},同时把变量name的值设置为default的值。

shell脚本中经常使用冒号":"后跟扩展表示符":="来给任意一个空变量或者未赋值的变量赋值。冒号一般给命令行上其后的符号赋值而不会去执行后面的命令,格式以下:

: ${name:=default}

有时,经过设置默认的变量值不能给脚本中某些变量提供一个合理的值,这时":?"修饰符就会显示出错误信息并停止脚本的执行同时返回退出码1,格式以下:

${name:?message}


3. 内置命令


3.1 type

使用type命令能够显示出系统命令的相关信息,例如:

type cat echo


3.2 read

经过使用read命令,脚本能够接受用户的输入并将输入信息存入到用户建立变量中。经过使用read命令,脚本能够接受用户的输入信息储存到变量中,例如:

read myvar
echo "Entered: $myvar"

read命令有一些特性可使使用read变得更加方便。若是不想指定一个变量来保存read的输入内容,bash会把用户的输入放在一个名为REPLY的变量中。经过用选项-p来显示用户提示,例如:

read -p "Go ahead:"
echo "Entered: $REPLY"

若是用户输入大于read拥有的变量数,read将按照变量的顺序先给每一个变量分配一个非空内容,到最后一个变量时,把剩下的内容所有分配到这个变量中。


3.3 exec

使用exec内置命令有两个主要的目的:第1个是使用它能够不用建立新进程来执行一个命令,第2个是使用它能够重定向来自shell脚本内部的文件描述符。通常假如shell执行的命令不是来自shell内部,那么执行这个命令就会建立一个新的进程,这个新进程继承来自父进程的环境变量而不会继承父进程中没有使用export导出的变量。相反exec执行命令时常覆盖当前的进程。

可使用以下语法来使exec运行一个命令:

exec command arguments

因为exec并不建立新进程,因此执行速度很是快,而且因为exec不能把控制返回到原程序中,因此一般把它做为最后一个命令。

使用exec也能够把来自shell脚本内部的文件描述符的信息重定向到其余文件中,例如:

exec > outfile 2> errfile

当以这种方式使用exec命令时,当前的进程不会被新的进程取代。


3.4 trap

在Linux中,信号能够用来报告用户产生的中断,还能够用来报告诸如错误的系统调用、管道中断、非法指令和其余情况等。使用trap内置命令来捕获一个或多个信号,以便于用户在收到一个特殊的信号时采起相应的动做。

能够按以下语法使用trap命令:

trap ['commands'] [signal]

可选项command指出了当脚本在捕获到由signal指定的信号后应采起的指令。signal能够是信号的名字或者信号的编号,如INT或2。若是没有command命令,那么trap命令就会把trap重置到初始化状态。

当执行过commands的内容后,shell会恢复执行commands命令离开处的脚本。在收到一个信号后,若是用户想使用trap阻止脚本退出但又不想运行任何一个显式命令,能够给commands指定一个空串,例如:

trap '' 15


3.5 kill

kill内置命令用来给一个进程或者做业发送信号,kill命令的语法格式以下:

kill [-signal] PID

signal是信号的名字或者信号的编号,PID是要接收信号的进程号,可使用%n的形式指定一个做业编号来替代PID,若是省略了signal,kill命令就发出一个TERM信号,例如:

kill -TERM %1


3.6 getopts

getopts内置命令用来解析命令行参数,语法结构以下:

getopts optstring varname [arg...]

其中,optstring是合法的字母选项列表,varname变量保存了每次接收的选项的值,arg是即将被处理的可选参数。若存在arg参数,getopts就去处理命令行参数,若optstring以冒号":"做为开始,则由脚本负责产生错误信息,不然就由getopts产生错误信息。

getopts命令使用变量OPTIND(选项索引)和OPTARG(选项参数)来保存和选项相关的值。当shell脚本启动时,OPTIND的值被设置为1,之后每次当getopts命令发现一个参数,它就增长OPTIND的值,该值与下一个将要被处理的选项的索引相等。若是选项中含有参数,bash就把参数的值赋给变量OPTARG。

为了指定某个选项含有参数,在optstring中相应的字母后面加上一个冒号,例如:

while getopts do:t:r arg
do
  case $arg in
    d) echo "-d";;
    o) echo "-o : $OPTARG";;
    t) echo "-t : $OPTARG";;
    r) echo "-r";;
  esac
done


4. 表达式


4.1 算术表达式

bash可以处理算术赋值,并可以对各类算术表达式求值,shell中有不少方法能够用来进行算术赋值,其中一种是使用let,例如:

let "VALUE = VALUE * 10 + NEW"

let语句中不须要在变量前面加美圆符号,但必须将单个的变量或者带有空格的表达式用双引号引发来。因为let的每一个参数被解释为一个独立的表达式,因此能够在一行上给多个变量进行赋值,例如:

let "COUNT = COUNT + 1" VALUE=VALUE*10+NEW

能够利用((expression))的语法结构来同时表示算术表达式和逻辑表达式,例如:

if ((30 < age && age < 60)); then
  echo "$age"
fi


4.2 逻辑表达式

条件表达式的语法形式以下:

` expression `

在expression中必须在变量的名字前面加上美圆符,执行该表达式的结果与命令test同样,返回的是一个状态,例如:

if [[ 30 < $age && $age < 60 ]]; then
  echo "$age"
fi

也可使用test命令的关系比较符:"-gt"、"-ge"、"-lt"、"-le"、"-eq"和"-ne"。操做符">"和"<"按字母顺序比较字符串,操做符"="进行类型匹配比较,好比"[[ artist = a* ]]"返回为真。


4.3 字符串模式匹配

bash提供了可操做路径名字符串以及其余字符串类型匹配操做符,这些操做符能够从字符串的前缀或后缀中删去字符串。

字符串操做符
操做符 功能
# 去除最小匹配前缀。
## 去除最大匹配前缀。
% 去除最小匹配后缀。
%% 去除最大匹配后缀。

这些操做符的语法形式以下:

${varname op pattern}

op是上表中的操做符,pattern是一个匹配类型,例如:

MYFILE=/usr/local/src/test.c
echo ${MYFILE%.c}


4.4 操做符

算术扩展和算术赋值使用了和C语言相同的语法、操做符的运算优先级以及表达式的关联关系。下表按照优先等级递减的顺序列出了这些操做符:

操做符
操做符类型
功能
后置 var++ 后置加
后置 var-- 后置减
前置 ++var 前置加
前置 --var 前置减
一元 - 一元减
一元 + 一元加
取反 ! 布尔取反
取反 ~ 二进制取反
取幂 ** 幂指数
乘法 * 乘法运算
除法 / 除法运算
取模 % 取模运算
加法 + 加法
减法 - 减法
二进制移位 << 左移
二进制移位 >> 右移
比较运算符 <= 小等于
比较运算符 >= 大等于
比较运算符 < 小于
比较运算符 > 大于
相等 = 相等
不等 != 不相等
二进制位运算符 & 二进制AND运算
二进制位运算符 ^ 二进制XOR运算
二进制位运算符 | 二进制OR运算
布尔 && 布尔AND运算
布尔 || 布尔OR运算
条件赋值 ? : 三元操做符
赋值 =、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=赋值操做
逗号 , 逗号操做符

管道操做符的优先级比全部操做符都高,例如:

cmd1 | cmd2 || cmd3 | cmd4 && cmd5 | cmd6

前置和后置操做符要与变量结合在一块儿使用,例如:

echo $((--N+3))

取模操做符取出第1个操做数被第2个除以后的余数,例如:

echo $((15%7))

使用布尔操做符所得的结果要么是0,要么是1,布尔操做符被称为短路操做符,若是仅仅经过左边的操做数就能够得出最终的结果,那么右边的操做数就能够不用赋值。

变量"$?"中保存了前面命令执行后的退出状态,例如:

true || false && false
echo $?