1、 描述shell程序的运行原理:
Shell是一种具备特殊功能的程序,它提供了用户与内核进行交互操作的一种接口.它接收用户输入的命令,并把它送入内核去执行.内核是Linux系统的心脏,从开机自检时就驻留在计算机的内存中,直到计算机关闭为止,而用户的应用程序存储在计算机的硬盘上,仅当需要时才被调入内存.Shell是一种应用程序,当用户登录Linux系统时,Shell就会被调入内存执行.Shell独立于内核,它是连接内核和应用程序的桥梁,并由输入设备读取命令,再将其转为计算机可以理解的机械码,Linux内核才能执行该命令。
图1-1描述了Shell在Linux系统中的位置
Shell脚本是把命令堆砌在一起,shell通过词法分析,语法分析,语义分析,按顺序、选择或循环执行脚本中的命令。脚本运行结束,此Shell进程也即终止
2、 Shell脚本编程总结
变量:所谓变量就是可变化的量,命名内存空间,本质就是存储数据的一个或多个计算机内存地址。
bash环境:
本地变量:当前shell进程;
环境变量:当前shell进程及其子进程;
局部变量:某个函数执行过程;
位置参数变量:在脚本中引用传递给脚本的参数;在函数中引用传递给函数的参数;
特殊变量:
$$:当前shell的进程号。对于shell脚本,这是其正在执行时的进程ID
$!:前一个后台命令的进程号
$?:前一个命令执行后的返回值,0表示没有错误,非0表示有错误
$-:当前命令的引导符
$_:上条命令的执行结果
$*:所有参数列表。如"$*"用「"」括起来的情况、以"$1$2 … $n"的形式输 出所有参数。
$@:所有参数列表。如"$@"用「"」括起来的情况、以"$1""$2" … "$n" 的 形式输出所有参数。
$#:传递到脚本的参数数量
$0:正在被执行命令的名字。对于shell脚本而言,这是被激活命令的路径
$1~$n:该变量与脚本被激活时所带的参数相对应。n是正整数,与参数位置 相对应。
示例:编写一个位置参数的position.sh脚本
#!/bin/bash echo"The script name is : $0" #$0表示脚本本身 echo "Parameter #1: $1" echo "Parameter #2: $2" echo "Parameter #3: $3" echo "Parameter #4: $4" echo "Parameter #5: $5" echo "Parameter #6: $6" echo "Parameter #7: $7" echo "Parameter #8: $8" echo "Parameter #9: $9" echo "Parameter #10: $10" echo"--------------------" echo"All the command line parameters are: $*" #$*和$@一样,表示从$1开始的全部参数
变量类型:
变量类型的作用:存储空间、运算、存储格式
数值,字符;
数值:整数、浮点数;
字符:ASCII
语言对变量类型的支持力度:
强类型:一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。java、.NET、C++等都是强制类型定义的。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了;
弱类型:与强类型相反。故变量类型不严格区分;
默认存储机制:bash为字符;
bash的变量使用特点:弱类型、无须事先声明;
本地变量:
本地变量命名:
变量名=变量值;
变量名:只能包含数字、字母和下划线;且不能以数字开头;不能超过20个字符;
=:赋值符号;
变量值:我们需要给变量名赋予的值;
注意:在变量、等号和值之间不能出现空格。
引用:
弱引用: ""双引号, 其内部的变量引用会被替换为变量值;
强引用:''单引号,其变量的变量引用会保持原有字符;
命令引用:`COMMAND`反引号, 也可使用$(COMMAND),引用命令的执行结果;
声明为整型:
declare -i name[=value]
let name=value
查看所有本地变量:set命令
变量的生命周期:
创建
销毁:
自动销毁:shell进程终止;
手动销毁:unset name
环境变量:
被“导出”的本地变量
export name[=value]
declare -x name[=value]
查看所有环境变量:env,printenv, export
shell脚本:其实就是纯文本文件,我们可以编辑这个档案,然后让这个文本文件来帮我们一次执行多个指令,或者是利用一些运算与逻辑判断来帮我们达成某些功能启动脚本。
(1)bash 脚本名
(2)给脚本一个执行权限即可直接通过脚本路径运行脚本。
# ./PATH/TO/SCRIPT_FILE。
Shell脚本通常是以#!起始的文本文件,如“#!/bin/bash”,这行被称为shebang,指定了解释此脚本shell的路径,执行脚本时系统会调用这个shell来运行此脚本。字符#指明注释的开始。注释部分以#为起始,一直延续到行尾,注释行通常用于为代码提供注释信息,或者用于暂停执行某行代码,会被解释器忽略。
bash的常用选项:
-n: 检查脚本中的语法错误;
-x:调试执行脚本;
当我们需要查看脚本是否执行成功,可以在执行脚本以后可以使用 $? 命令查看上一条命令执行状态结果:
0: 成功
1-255:失败
我们也可在脚本中自定义脚本的状态结果:
在脚本中敲入命令exit [n]:n为我们想要输出的数字
注意:脚本中任何位置执行了exit命令即会终止当前shell进程;
条件测试:
界定程序执行环境;
(1) 根据运行的命令的状态结果;
(2) 测试表达式: EXPR 表达式
test EXPRESSION
[ EXPRESSION ]
` EXPRESSION `
整数测试:隐含着做数值大小比较,所以不要给变量引用加引用;
$A -gt $B:是否大于;是则为“真”,否则为“假”;
$A -ge $B: 是否大于等于;
$A -lt $B:是否小于;
$A -le $B: 是否小于等于;
$A -eq $B: 是否等于;
$A -ne $B:是否不等于;
字符串测试:ASCII数值越大,字符比较时其值越大;
"$A" > "$B":是否大于;
"$A" < "$B":是否小于;
"$A" == "$B":是否等于;
"$A" != "$B":是否不等于;
-z "$A":是否为空;空则为“真”,否则为“假”
-n "$A":是否不空;不空则“真”,空则为“假”
注意:应该使用` EXPRESSION`
文件测试:测试文件的存在性以及属性;
-e $file: 是否存在;存在则为“真”,否则为“假”;
-f $file:文件是否存在且为普通文件;
-d $file:文件是否存在且为目录;
-h $file:是否存在且为符号链接文件;
-b $file:是否存在且为块设备文件;
-c $file:是否存在且为字符设备文件;
-S $file:是否存在且为套接字文件;
-p $file: 是否存在且为管道文件;
-r $file: 当前用户对文件是否拥有读权限;
-w $file:当前用户对文件是否拥有写权限;
-x $file:当前用户对文件是否拥有执行权限;
-u $file:文件是否拥有SUID权限;
-g $file:文件是否拥有SGID权限;
-k $file:文件是否拥有sticky权限;
-O $file: 当前用户是否为指定文件的属主;
-G $file: 当前用户是否为指定文件的属组;
双目操作符:
$file1 -nt$file2: file1是否新于file2, file1的最近一次的修改时间戳是否晚于file2的;
$file1 -ot$file2: file1是否旧于file2, file1的最近一次的修改时间戳是否早于file2的;
$file1 -ef$file2:file1与file2是否指向了同一个inode,测试二者是否为同一个文件的硬链接;
特殊设备:
/dev/null: 空,bit buckets,吞下所有数据,并直接丢弃;
/dev/zero:吐出一堆0;
bash之条件判断(选择执行):
if/then ;case
if CONDITION; then
if-true-分支
fi
if CONDITION; then
if-true-分支
else
if-false-分支
fi
! CONDITION: 表示的是取反
脚本参数(位置参数变量):
# ./name.sh /etc/fstab /etc/grub.conf
$0 $1 $2
参数$0—>shell自身文件,$1—>shell中定义的第一个参数,$2—>shell中定义的第二个参数,以此类推。
特殊变量:
$?: 命令的状态结果;
$#: 传递给脚本或函数的参数的个数;
$*和$@: 引用传递给脚本或函数的参数列表;
与用户交互:
read命令:
read [options] VAR...
-p"PROMPT"
-t timeout
示例:-t5 5秒后超时退出交互。
#!/bin/bash # read -p "Plz enter ausername: " -t 5 username if [ -z "$username"]; then username="myuser" fi if id $username &>/dev/null; then echo "$username exists." else useradd $username fi
命令引用:
`COMMAND`, $(COMMAND)
引用命令的执行结果;
(1)ls `which cat`
(2) lines=$(wc -l /etc/fstab | cut -d'' -f1)
循环语句:循环就是通过循环编程对循环体进行多次运行,直到结果匹配给定的退出循环条件。
Shell中的循环语句:for,while, until
VARIABLE 定义的变量
for VARIABLE in LIST; do
循环体
done
LIST:是由一个或多个空格或换行符分隔开的字符串组成;即可使用LIST 给一个变量进行多次逐个赋值;LIST 不为空时进入循环,LIST逐个赋值直到最后一个结束后循环结束,即退出循环;
for username in user1 user2user3; do
循环体
done
示例:
添加10个用户,user1-user10;
#!/bin/bash # for username in user1 user2 user3 user4 user5; do if id $username &> /dev/null; then echo "$username exists." else useradd $username echo "Add user $username finished." fi done
LIST的生成方法:
(1) 整数列表
(a) {start..end}
(b) $(seq [start `step` end)
(2) 直接给出列表
(3) glob
(4) 命令生成
示例:数值列表
#!/bin/bash # for i in {1..10}; do if id user$i &> /dev/null; then echo "user$i exists." else useradd user$i echo "Add user user$i finished." fi done
示例:glob,即定义参数变量为绝对路径下的文件:
#!/bin/bash # for filename in /var/log/*; do file $filename done
示例:命令生成列表
#!/bin/bash # for username in $(cut -d: -f1/etc/passwd); do echo "$usernameprimary group: $(id -n -g $username)." done
Shell脚本中的算术运算:
+, -, *, /, %, **
(1) $[$A+$B]
(2) $(($A+$B))
(3) let VARIABLE=$A+$B
(4) VARIABLE=$(expr $A + $B)
示例:求100以内所以正整数之和;
#!/bin/bash # declare -i sum=0 for i in {1..100}; do sum=$[$sum+$i] done echo $sum
增强型赋值:
+=
sum=$[$sum+$i]可写为:letsum+=$i
-=, *=, /=, %=
let count=$[$count+1] --> let count+=1 --> let count++
let count=$[$count-1] --> let count-=1 --> let count--
示例:显示/etc目录下所有普通文件列表,而后统计一共有多少个文件;
测试表达式:
整数测试:-gt,-lt, -ge, -le, -eq, -ne
字符串测试:==,>, <, !=, -z, -n, =~
注意:
(1) 字符串等会比较测试:["$hostname" == 'localhost' ]—>比较命令hostname的结果是否为localhost;
(2) 模式匹配测试:[["STRING" =~ PATTERN ]]
组合测试条件:
条件间逻辑运算:
与:多个条件要同时满足;
或:多个条件满足其一即可;
非:对指定的条件取反;
表达式组合:
与:` CONDITION1 -aCONDITION2 `
或:` CONDITION1 -oCONDITION2 `
非:[ ! CONDITION ]
命令组合:
与:COMMAND1 &&COMMAND2 <-- [ EXPR1 ] && [EXPR2 ]
或:COMMAND1 ||COMMAND2
非:! COMMAND
短路操作符:&&
false&& true = false
false && false =false
第一个为假就不会运行第二个。
true && false =false
true && true = true
第一个为真则继续运行第二个,第二个为假则不会运行,为真则运行。
if COMMAND1; then
COMMAND2
Fi
短路操作符:||
true || true = true
true || false = true
两个有一个为真则真;
false || true = true
false || false = false
两个全为假则假;
if ! COMMAND1; then \\如果命令1为 假则运行命令2.
COMMAND2
fi
COMMAND1 &&COMMAND2 || COMMAND3 与和或同时存在:
if COMMAND1; then \\如果命令1位真则运行命令2,否则运行命3.
COMMAND2
else
COMMAND3
fi
示例:写一个脚本实现如下功能;获取当前主机的主机名;如果当前主机的主机名为空,或者为localhost,则将其修改为www.magedu.com
#!/bin/bash # hostname=$(hostname) if [ -z"$hostname" -o "$hostname" == "localhost" ]; then hostname www.magedu.com fi
练习:写一个脚本,打印九九乘法表;
循环嵌套
1X1=1
1X2=2 2X2=4
1X3=3 2X3=6 3X3=9
#!/bin/bash # for j in{1..9}; do for i in $(seq 1 $j); do echo -n -e"${i}X${j}=$[$i*$j]\t" done echo done
多分支的if语句:
单分支:
if CONDITION; then
if-true-分支
fi
双分支:
if CONDITION; then
if-true-分支
else
if-false-分支
fi
多分支:
if CONDITION1; then
if-CONDITION1-true-分支
elif CONDTION2; then
if-CONDITIO2-true-分支
...
else
if-ALL-false-分支
fi
示例:通过脚本参数传递一个文件路径给脚本,判断其类型;
#!/bin/bash # if [ $# -lt1 ]; then echo "Usage: $0" exit 1 fi if [ -f $1]; then echo "Rgular file." elif [ -d$1 ]; then echo "Directory." elif [ -h $1]; then echo "Symbolic link." elif [ -b$1 ]; then echo "Block special." elif [ -c$1 ]; then echo "Charactoer special." elif [ -S$1 ]; then echo "Socket file." else echo "file not exist or unknowntype." fi
示例:脚本可接受四个参数
start: 创建文件/var/lock/subsys/SCRIPT_NAME
stop: 删除此文件
restart: 删除此文件并重新创建
status: 如果文件存在,显示为"running",否则,显示为"stopped"
basename命令:
取得路径的基名;
#!/bin/bash # prog=$(basename$0) lockfile="/var/lock/subsys/$prog" #echo $lockfile if [ $# -lt 1 ];then echo "Usage: $progstart|stop|restart|status" exit 1 fi if ["$1" == "start" ]; then if [ -f $lockfile ]; then echo"$prog is started yet." else touch$lockfile && echo "Starting $prog ok..." || echo"Starting $prog failed..." fi elif ["$1" == 'stop' ]; then if [ -f $lockfile ]; then rm-f $lockfile && echo "Stop $prog ok...." || echo "Stop$prog failed..." else echo"$prog is stopped yet." fi elif ["$1" == 'restart' ]; then if [ -f $lockfile ]; then rm-f $lockfile && touch $lockfile && echo "Restarting $porgok..." else touch$lockfile && echo "$prog is stopped, Starting $prog ok...." fi elif ["$1" == 'status' ]; then if [ -f $lockfile ]; then echo"Running..." else echo"Stopped..." fi else echo "Usage: $progstart|stop|restart|sttus" exit 1 fi
case语句
简洁版多分支if语句;
使用场景:判断某变量的值是否为多种情形中的一种时使用;
语法:
case $VARIABLE in
PATTERN1)
分支1
;;
PATTERN2)
分支2
;;
PATTERN3)
分支3
;;
...
*)
分支n
;;
esac
PATTERN可使用glob模式的通配符:
*: 任意长度的任意字符;
?: 任意单个字符;
[]: 指定范围内的任意单个字符;
a|b: 多选1;
示例:提示键入任意一个字符;判断其类型;
#!/bin/bash # read -p "Plzenter a character: " char case $char in [a-z]) echo "A character." ;; [0-9]) echo "A digit." ;; *) echo "A special character." ;; esac
示例:脚本可接受四个参数
start: 创建文件/var/lock/subsys/SCRIPT_NAME
stop: 删除此文件
restart: 删除此文件并重新创建
status: 如果文件存在,显示为"running",否则,显示为"stopped"
#!/bin/bash # prog=$(basename $0) lockfile="/var/lock/subsys/$prog" #echo $lockfile if [ $# -lt 1 ];then echo"Usage: $prog start|stop|restart|status" exit 1 fi case $1 in start) if [ -f$lockfile ]; then echo "$prog isstarted yet." else touch $lockfile&& echo "Starting $prog ok..." || echo "Starting $progfailed..." fi ;; stop) if [ -f$lockfile ]; then rm -f $lockfile&& echo "Stop $prog ok...." || echo "Stop $progfailed..." else echo "$prog isstopped yet." fi ;; restart) if [ -f$lockfile ]; then rm -f $lockfile&& touch $lockfile && echo "Restarting $porg ok..." else touch $lockfile&& echo "$prog is stopped, Starting $prog ok...." fi ;; status) if [ -f$lockfile ]; then echo"Running..." else echo"Stopped..." fi ;; *) echo"Usage: $prog start|stop|restart|sttus" exit 1 esac
shell中的流程控制语句:
while循环:
while CONDTION; do
循环体
Done
进入条件:当CONDITION为“真”;
退出条件:当CONDITION为“假”;
while CONDITION; do
循环体
控制变量的修正表达式
done
示例:求100以内所有正整数之和;
#!/bin/bash # declare -i sum=0 declare -i i=1 while [ $i -le 100]; do let sum+=$i let i++ done echo "Sum:$sum."
示例:打印九九乘法表
#!/bin/bash # declare -i i=1 declare -i j=1 while [ $j -le 9 ];do while [ $i-le $j ]; do echo -e -n"${i}X${j}=$[$i*$j]\t" let i++ done echo let i=1 let j++ done
unitl循环:
until CONDITION; do
循环体
循环控制变量的修正表达式
done
进入条件:当CONDITION为“假”时
退出条件:当CONDITION为“真”时
示例:求100以内所有正整数之和
#!/bin/bash # declare -i sum=0 declare -i i=1 until [ $i -gt 100]; do let sum+=$i let i++ done echo "Sum:$sum."
循环控制:
continue [n]:提前结束本轮循环,而直接进入下一轮;
break [n]:提前结束循环;
while循环:
while CONDITION; do
.......
if CONDITION2; then
break [n]
fi
done
while CONDITION; do
......
if CONDITION2; then
continue [n]
fi
......
done
示例:求100以内所有偶数之和;
#!/bin/bash # declare -i sum=0 declare -i i=0 while [ $i -le 100]; do let i++ if [$[$i%2] -eq 1 ]; then echo "$i is aodd." continue fi let sum+=$i done echo "Sum:$sum."
死循环:
while true; do
循环体
if CONDTION; then
break
fi
done
until false; do
循环体
if CONDITION; then
break
fi
done
示例:每隔3秒钟查看当前系统上是否有名为“gentoo”的用户登录;如果某次查看gentoo登 录了,则显示gentoo已经登录;如果未登录,就显示仍然未来,并显示这是已经是第多少次 查看了;
#!/bin/bash # username=$1 declare -i count=0 while true; do if who | grep "^$username" &> /dev/null; then echo "$username is logged." break else let count++ echo "$count $username is not login." fi sleep 3 done
#!/bin/bash # declare -i count=0 username=$1 if [ $# -lt 1 ]; then echo "At lease one argument." exit 1 fi if ! id $username &> /dev/null; then echo "No such user." exit 2 fi until who | grep "^$username" &> /dev/null; do let count++ echo "$count $username is not login." sleep 3 done echo "$username is logged on."
while循环的特殊用法:
遍历文件的每一行:
while read VARIABLE;do
循环体
done
示例:找出UID为偶数的所有用户,显示其用户名和ID号;
#!/bin/bash # while read line; do userid=$(echo$line | cut -d: -f3) if [$[$userid%2] -eq 0 ]; then echo $line | cut -d:-f1,3 fi done
for循环的特殊用法:
for((expr1;expr2;expr3)); do
循环体
done
expr1: 定义控制变量,并初始赋值;
expr2: 循环控制条件;
进入条件:控制条件为“真”
退出条件:控制条件为“假”
expr3: 修正控制变量
示例:求100以内所有正整数之和;
#!/bin/bash # declare -i sum=0 for((i=1;i<=100;i++)); do let sum+=$i done echo "Sum:$sum."
函数:
function: 功能
把一段具有独立功能代码封装在一起,并给予命名;后续用到时,可直接通过给定函数名来调用整体代码;
函数作用:
代码重用;
模块化编程;
函数的使用方法:
先定义:编写函数代码
后调用:给出函数名,还可按需传递参数
定义方法:
(1) function f_name {
函数体
}
(2) f_name() {
函数体
}
调用函数:
f_name [argu1, argu2, ...]
自定义函数状态返回值:
return [#]
0:成功
1-255:失败
注意:函数代码执行时,一旦遇到return,函数代码终止运行,函数返回;
示例:此前的服务脚本
#!/bin/bash # prog=$(basename$0) #获取目录根路径 lockfile="/var/lock/subsys/$prog" #echo $lockfile if [ $# -lt 1 ];then echo"Usage: $prog start|stop|restart|status" exit 1 fi start() { if [ -f$lockfile ]; then echo "$prog isstarted yet." else touch $lockfile&& echo "Starting $prog ok..." || echo "Starting $progfailed..." fi } stop() { if [ -f$lockfile ]; then rm -f $lockfile&& echo "Stop $prog ok...." || echo "Stop $progfailed..." else echo "$prog isstopped yet." fi } restart() { if [ -f$lockfile ]; then rm -f $lockfile&& touch $lockfile && echo "Restarting $porg ok..." else touch $lockfile&& echo "$prog is stopped, Starting $prog ok...." fi } status() { if [ -f$lockfile ]; then echo"Running..." else echo"Stopped..." fi } case $1 in start) start ;; stop) stop ;; restart) restart ;; status) status ;; *) echo"Usage: $prog start|stop|restart|sttus" exit 1 esac
示例:判断用户的ID号的奇偶性;
#!/bin/bash # evenid() { if [ $# -lt1 ]; then return 1 fi if ! id $1&> /dev/null; then return 2 fi userid=$(id-u $1) if [$[$userid%2] -eq 0 ]; then echo "$1, Evenuser ID." else echo "$1, Odduser ID." fi } evenid root evenid echo $? evenidrooooooooooooot echo $?
模块化编程
功能:把脚本文件中的代码分隔为多段,放在不同的文件中
假设/root/bin/srv目录有两个文件:
(1) 函数文件
(2) 脚本文件
为脚本使用配置文件
一个文件中只定义变量
脚本文件source此变量定义的文件;
变量的作用域:
局部变量:在当脚本中存在其他变量与函数中定义的变量重复时时,需要使用local命令来使函数中的变量变为局部变量:
local VARIABLE=value
局部变量存活时间:
函数执行开始,至函数返回结束;
数组:连续的多个独立内存空间;每个内存空间相当于一个变量;
元素:数组名[索引]
bash4.0以后支持2种:
传统数组:索引时数字,从0开始 declare -aname
关联数组:索引可以自定义,使用字符串做索引 declare -A name
bash支持稀疏格式的数组{0,,,,6}
数组元素的赋值方式:
(1) 一次只赋值一个元素
array[index]=value
(2) 一次赋值全部元素
array=("val1""val2" ...)
(3)指定索引进行赋值
array=([0]="val1"[3]="val2")
(4) read -a array
引用元素:${array[index]}
长度:${#array[*]},${#array[@]}