可以直接 man bash 学习语法和相关命令。
一、什么是shell程序
以文件形式存放批量的Linux命令集合,该文件能够被Shell解释执行,这种文件就是Shell脚本程序
通常由一段Linux命令、Shell命令、控制语句以及注释语句构成
Shell 脚本的编写
Shell 脚本是纯文本文件,可以使用任何文本编辑器编写 Shell 脚本通常是以 .sh 作为后缀名 第一行:指定用哪个程序来编译和执行脚本。#!/bin/bash(shell 变量里面含 ! x 的话,要转义一下 \! x)
注释行:使用(#)符号;多行注释 <<EOD ... EOD
二、shell编程的主要内容
变量
本地变量、环境变量、位置参量
输入输出
read/echo或printf
条件测试
整数测试、逻辑测试、字符串测试
控制语句
条件/循环/分支/
函数 常用Shell程序内置指令
declare/export/eval/trap等
三、变量
(一)变量概述
(二)变量常见操作
(三)环境变量和只读变量
(四)位置参量(命令行参数)
位置参量是一组特殊的内置变量,通常被 shell 脚本用来从命令行接受参数,或被函数用来保存传递给它的参数。
执行 shell 脚本时,用户可以通过命令行向脚本传递信息,跟在脚本名后面的用空格隔开的每个字符串都称为位置参量。
在脚本中使用这些参数时,需通过位置参量来引用。例如: $1 表示第一个参数,$2 表示第二个参数,以此类推。 $9 以后需要用花
括号把数字括起来,如第 10 个位置参量以 ${10} 的方式来访问。
$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。但是当它们被双引号(" ")包含
时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。
注意 “$*” 受 $IFS 变量值的影响,如
$ IFS=:; $ set x y z $ echo $* x y z $ echo "$*" x:y:z $ echo $@ x y z $ echo "$@" x y z
使用$?判断管道的执行结果是否成功,不可信。因此在使用管道获取的参数,我们一定要增加对于参数的判断,或者,我们在sh和bash的解释器中,
增加set –o pipefail 的设置,可以让管道的执行结果是否成功,变得可信。
#例1:shell_test.sh echo “the count of parameters:$#” echo “first param=$1” echo “second param=$2” echo “params’ string=$*”
shell_test.sh This is Peter
shell_test.sh This is "Peter Piper" //如果位置参量中含有空格,则需要使用双引号
(五)数组
# 字典必须先声明
declare -A dic
dic=([key1]="value1" [key2]="value2" [key3]="value3")
#打印指定key的value
echo ${dic["key1"]}
#打印所有key值
echo ${!dic[*]}
#打印所有value
echo ${dic[*]}
四、输入输出
(一)输入--read命令
read var | 从标准输入读取一行并赋值给变量var |
---|---|
read | 标准输入读取一行并赋值给内置变量REPLY |
read -a arr | 读入一组词,依次赋值给数组arr |
read -p "please input 5 digits:" -t 10 -a arr3 // -p 提示符 -t 超时
read -s // 输入不回显
stty -echo // 输入不回显
stty echo // 输入回显
(二)输出--echo 命令
echo $num 或 echo ${num} //输出一行文本
echo -n “Hello World” // -n 去掉换行符 echo -e “\t” “Hello World” // -e 支持转义
(三)echo输出颜色与光标定位:
\33[30m -- \33[37m 设置前景色(字体颜色) \33[40m -- \33[47m 设置背景色 \33[y;xH设置光标位置
echo –e “\033[31mthis is a test” echo -e "\033[10;5H\033[31m\033[46m this is a test“ echo -e "\033[0m" // 取消全部设置
五、算术扩展
(一)单引号、双引号、反引号区别
单引号 忽略所有特殊字符 双引号 忽略大部分特殊字符($,`, \ 等字符除外) 参考这里 或者尝试 X=*; echo $X; echo '$X'; echo "$X"; 的区别 反引号 命令替换(将一个命令的标准输出插入到命令的任何位置) $() 同上 命令替换可以嵌套 如果使用反引号,则内部的反引号必须用反斜杠来转义。
echo `basename \`pwd\``
echo $(basename $(pwd))
算术运算符:
+、-、*、/(四则运算) |
---|
**、% (幂运算 和 模运算,取余数) |
<<、>> (按位左移 和 按位右移) |
&、^、| (按位与 、按位异或 和 按位或) |
=、+=、-= 、*=、/=、%= <<= 、>>=、&=、^=、|= (赋值运算) |
<、>、<=、>=、==、!= (比较操作符) |
&&、|| (逻辑与 和 逻辑或) |
算术扩展:
$[] n=5; echo $[$n+1] $(()) n=5; echo $(($n+1)) (()) ((n+=1)); echo $n expr expr 4 + 5 注意+号两边要有空格 r=`expr 4 + 5` r=`expr 4 \* 5` let // 内置命令 type let let n=n+1 let “n = n + 1” let “n=n+abc” // abc 当做0
六、条件测试
任何一种测试中,都要有退出状态(返回值),退出状态为 0 表示命令成功或表达式为真,非0 则表示命令失败或表达式为假。
状态变量 $? 中保存命令退出状态的值
grep study /etc/passwd; echo $?
grep hello /etc/passwd;echo $?
(一)测试表达式的值
表达式测试包括字符串测试、整数测试和文件测试。
通常用 test 命令来测试表达式的值
x=5; y=10 test $x -gt $y echo $?
test 命令可以用 方括号 来代替(方括号前后要留空格)
x=5; y=10 [ $x -gt $y ] echo $?
2.x 版本以上的 Bash 中可以用双方括号来测试表达式的值,此时可以使用通配符进行模式匹配。
name=Tom
[[ $name = [Tt]?? ]] echo $?
(二)字符串测试 //字符串测试最好加上" "
[ -z $str ] | 如果字符串 str 长度为0,返回真 |
---|---|
[ -n $str ] | 如果字符串 str 长度不为0,返回真 |
[ $str1 == $str2 ] | 两字符串相等 |
[ $str1 != $str2 ] | 两字符串不等 |
name=Tom; [ -z $name ]; echo $?
name2=Andy; [ $name == $name2 ] ; echo $?
(三)整数测试 //操作符两边必须留空格!
[ int1 -eq int2 ] | int1等于int2 |
---|---|
[ int1 -ne int2 ] | int1不等于int2 |
[ int1 -gt int2 ] | int1大于int2 |
[ int1 -ge int2 ] | int1大于或等于int2 |
[ int1 -lt int2 ] | int1小于int2 |
[ int1 -le int2 ] | int1小于或等于int2 |
x=1; [ $x -eq 1 ]; echo $?
整数测试也可以使用 let 命令或双圆括号:
相应的操作符为:== 、!= 、> 、>= 、< 、<=
x=1; let "$x == 1"; echo $?
x=1; (($x+1 >= 2 )); echo $?
let 和 双圆括号中可以使用算术表达式,而方括号不能 let 和 双圆括号中,操作符两边可以不留空格
(四)逻辑测试
[ expr1 -a expr2 ] | 逻辑与,都为真时,结果为真 |
---|---|
[ expr1 -o expr2 ] | 逻辑或,有一个为真时,结果为真 |
[ !expr ] | 逻辑非 |
x=1; name=Tom; [ $x -eq 1 –a –n $name ]; echo $?
[ ( $x -eq 1 ) –a ( –n $name ) ]; echo $? //error, 不能随便添加括号
可以使用模式的逻辑测试:
[[ pattern1 && pattern2 ]] | 逻辑与 |
---|---|
[[ pattern1 || pattern2 ]] | 逻辑或 |
[[ !pattern ]] | 逻辑非 |
x=1; name=Tom;
[[ $x -eq 1 && $name = To? ]]; echo $?
(五)检查空值
[ "$name" = "" ] [ ! "$name" ] [ "X${name}" = "X" ]
(六)文件测试
文件测试:文件是否存在,文件属性,访问权限等。更多文件测试符参见 man test
-f fname | fname 存在且是普通文件时,返回真 ( 即返回0 ) |
---|---|
-L fname | fname 存在且是链接文件时,返回真 |
-d fname | fname 存在且是一个目录时,返回真 |
-e fname | fname(文件或目录)存在时,返回真 |
-s fname | fname 存在且大小大于0 时,返回真 |
-r fname | fname(文件或目录)存在且可读时,返回真 |
-w fname | fname(文件或目录)存在且可写时,返回真 |
-x fname | fname(文件或目录)存在且可执行时,返回真 |
(七)括号总结
${...} | 获取变量值,${BASH:0:1} 可以代替 / |
---|---|
$(...) | 命令替换 |
$[...] | 让无类型的变量参与算术运算 |
$((...)) | 同上 |
((…)) | 算术运算 |
[ ... ] | 条件测试,等价于test命令 |
[[ ... ]] | 条件测试,支持模式匹配与通配符 |
七、条件与分支语句
(一)if条件语句
if expr1 #如果expr1为真(返回值为0)
then #那么
commands1 #执行语句块commands1
elif expr2 #若expr1不真,而expr2为真
then #那么
commands2 #执行语句块commands2
... ... #可以有多个elif语句
else # else最多只能有一个
commands4 #执行语句块commands4
fi #if语句必须以单词fi终止
几点说明:
elif 可以有任意多个(0 个或多个); else 最多只能有一个(0 个或 1 个); if 语句必须以 fi 表示结束
expr 通常为条件测试表达式;也可以是多个命令,以分号分隔,以最后一个命令的退出状态为条件值。
commands 为可执行语句块,如果为空,需使用 shell 提供的空命令 “ : ”,即冒号。该命令不做任何事情,只返回一个退出状态 0
if 语句可以嵌套使用。
(二)case 选择语句
case expr in # expr为表达式,关键词in不要忘!
pattern1) # 若expr与pattern1匹配,注意括号
commands1 # 执行语句块commands1
;; # 跳出case结构
pattern2) # 若expr与pattern2匹配
commands2 #执行语句块commands2
;; # 跳出case结构 ...... # 可以有任意多个模式匹配
*) # 若expr与上面的模式都不匹配
commands # 执行语句块commands
;; # 跳出case结构 esac # case语句必须以esac终止
几点说明:
表达式 expr 按顺序匹配每个模式,一旦有一个模式匹配成功,则执行该模式后面的所有命令,然后退出 case。
如果 expr 没有找到匹配的模式,则执行缺省值 “ *) ” 后面的命令块 ( 类似于 if 中的 else ); “ *) ” 可以不出现。
所给的匹配模式 pattern 中可以含有通配符和“ | ”。
每个命令块的最后必须有一个双分号,可以独占一行,或放在最后一个命令的后面。
八、循环语句
(一)for 循环
for variable in list
# 每一次循环,依次把列表list中的一个值赋给循环变量
do #循环开始的标志
commands #循环变量每取一次值,循环体就执行一遍
done #循环结束的标志
几点说明:
列表 list 可以是命令替换、变量名替换、字符串和文件名列表 ( 可包含通配符 )
list 里面的分隔符可以是空格、换行等等。
for 循环执行的次数取决于列表 list 中单词的个数
for 循环体中一般要出现循环变量,但也可以不出现
可以省略 in list ,此时使用“$@”
for ((exp1;exp2;exp3) //类似于C语言的for 循环 do done
LIST="rootfs usr data data2" for d in $LIST; do done
for d in seq_* ; do done
for i in ${arr[@]} ; do done
for k in $( seq 1 $count ); do done
for file in $( ls $dir); do done
(二)while 和until循环
while expr #执行expr
do #若expr的退出状态为0,进入循环,否则退出while
commands #循环体
done #循环结束标志,返回循环顶部
eg.
#使用read命令读取一行数据 cat datafile.txt | while read myline do echo "LINE:"$myline done
while read name cn_name risk #字段直接tab 分割,每行 \n 结束
do
echo $name, $cn_name, $risk
done < $filename
read 命令默认的分隔符是空白符(如空格,tab等),我们也可以使用IFS(内部字段分隔符)指定的的字符作为分隔符;
需要注意的是多个空白被当做一个空白处理,当某一行中某个字段不存在,这样会导致读到的数据对应不到正确的变量
用 perl or python 读取一行数据时,需要去掉换行符,perl 用 chomp,python 用 strip('\n')
until expr #执行expr
do #若expr的退出状态非0,进入循环,否则退出until
commands #循环体
done #循环结束标志,返回循环顶部
(三)break和continue、exit和sleep
break [n]
用于强行退出当前循环。
如果是嵌套循环,则 break 命令后面可以跟一数字 n,表示退出第 n 重循环(最里面的为第一重循环)。
continue [n]
用于忽略本次循环的剩余部分,回到循环的顶部,继续下一次循环。 如果是嵌套循环,continue 命令后面也可跟一数字 n,表示回到第 n 重循环的顶部。
exit n
exit 命令用于退出脚本或当前进程。n 是一个从 0 到 255 的整数,0 表示成功退出,非零表示遇到某种失败而非正常退出。该整数被保存在状态变量 $? 中。
sleep n 暂停 n 秒钟
(四)select循环和菜单
select variable in list
do #循环开始的标志
commands #循环变量每取一次值,循环体就执行一遍
done #循环结束的标志
select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量 REPLY 中。
select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环。
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list ,此时使用位置参量
九、函数
一个函数就是一个子程序,用于完成特定的任务,当有重复代码,或者一个任务只需要很少的修改就被重复几次执行时, 这时你应考虑使用函数。
local X // X 是局部变量
function function_name {
commands
}
或
function_name() {
commands
}
只需输入函数名即可调用该函数
函数必须在调用之前定义
如果要调用其他文件的函数,可以在开头 . 文件名 //类似文件包含,也可以使用source。
这两个命令都以一个脚本为参数,该脚本将作为当前shell的环境执行,即不会启动一个新的子进程。所有在脚本中设置的变量将成为当前Shell的一部
分。同样的,当前脚本中设置的变量也将作为脚本的环境。
sh -x xx.sh 是在一个脚本中,调用另一个脚本执行,启动一个新的子进程,-x 会输出所有的执行信息。
脚本调用脚本,要对被调用脚本的执行返回值进行判断。参数同理,需要对脚本计算的参数进行合理判断,提前发现错误,避免走入不可控制的分支。
#!/bin/bash
fun2()
{
echo "This is fun2."
echo "Now exitingfun2."
}
fun2 #调用函数fun2
#! /bin/bash
func1()
{
echo "the parameter's count:$#"
echo "the firstparameter:$1"
echo "the secondparameter:$2"
}
func1 a b
(一)字符串操作:
m 的取值从0 到${#var}-1
注:pattern,old中可以使用通配符。
${#var} | 返回字符串变量 var的长度 |
---|---|
${var:m} | 返回${var}中从第m+1个字符到最后的部分 |
${var:m:len} | 返回${var}中从第m+1个字符开始,长度为len的部分 |
${var#pattern} | 删除${var}中开头部分与pattern匹配的最小部分 |
${var##pattern} | 删除${var}中开头部分与pattern匹配的最大部分 |
${var%pattern} | 删除${var}中结尾部分与pattern匹配的最小部分 |
${var%%pattern} | 删除${var}中结尾部分与pattern匹配的最大部分 |
${var/old/new} | 用new替换${var}中第一次出现的old |
${var//old/new} | 用new替换${var}中所有的old(全局替换) |
字符串拼接:
value1=home
value2=${value1}"="
value3=${value1}${value2}
(二)随机数和 expr 命令
echo $RANDOM // 生成随机数的特殊变量
expr:通用的表达式计算命令
表达式中参数与操作符必须以空格分开,表达式中的运算可以是算术运算,比较运算,字符串运算和逻辑运算。
expr 5 % 3
expr 5 \* 3 #乘法符号必须被转义
注意:目前比较少使用,可用$[ ... ]替换
(三)shift 命令
一般用于函数或者脚本程序参数处理,特别是参数多于10以上的时候 将所有参数变量向下移动一个位置,$2变成$1,$3变成$2,依次递进,但$0保持不变
例如:
while [ "$1" != "" ]
do
echo $1
shift
done
(四)eval 命令
eval arg1 [arg2] ... [argN]
将所有的参数连接成一个表达式,并计算或执行该表达式,参数中的任何变量都将被展开。
listpage="ls -l | more"
eval $listpage
(五)trap命令
trap command signal
command 一般情况下是Linux命令 ’ ’表示发生陷阱时为空指令,不做任何动作 ’-’表示发生陷阱时采用缺省指令 signal HUP(1) 挂起;一般因终端掉线或用户退出而引发 INT(2) 中断;一般因按下”Ctrl+C”组合键引发 QUIT(3) 退出;一般因按下”Ctrl+\”组合键引发 ABRT(6) 异常中止;一般因某些严重的执行错误而引发 ALRM(14) 闹钟;一般是超时时钟到来而引发 TREM(15) 中止;一般由系统在关机的时候发出
#!/bin/bash
#安装2、3号信号
#处理代码为"rm-ftmp$$; exit0"
trap "rm -ftmp$$; exit 0" 2 3
#生成文件,文件名为tmp+当前进程号
touch tmp$$
#睡眠60秒,以便向当前进程发送信号
sleep 60
(六)declare 命令
declare或typeset内建命令(它们是完全相同的)可以用来限定变量的属性.这是在某些编程语言中使用的定义类型不严格的方式。命令declare是bash版本2之后才有的。命令typeset也可以在ksh脚本中运行。
-r只读
declare -r var1 |
---|
-i整数
declare -i number # 脚本余下的部分会把"number"当作整数看待. |
---|
-a数组
declare -a indices |
---|
-f函数
declare -f |
---|
-x export
declare -x var3 |
---|
-x var=$value
declare -x var3=373 |
---|