shell脚本简要总结
脚本调试
bash -n /path/to/some_script
检测脚本中的语法错误
bash -x /path/to/some_script
调试执行
变量
环境变量
变量声明、赋值:
export name=VALUE
declare -x name=VALUE
变量引用:$name, ${name}
显示所有环境变量:
-
export
-
env
-
printenv
-
删除:unset name
bash有许多内建的环境变量:PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1
只读和位置变量
只读变量:只能声明,但不能修改和删除
readonly name
declare -r name
位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, …:对应第1、第2等参数,shift [n]换位置
$0: 命令本身
$*: 传递给脚本的所有参数,全部参数合为一个字符串
$@: 传递给脚本的所有参数,每个参数为独立字符串
$#: 传递给脚本的参数的个数
$@ $* 只在被双引号包起来的时候才会有差异
示例:判断给出的文件的行数 linecount="$(wc -l $1| cut -d' ' -f1)" echo "$1 has $linecount lines."
变量的运算
bash中的算术运算:help let
+, -, *, /, %取模(取余), **(乘方)
增强型赋值:
+=, -=, *=, /=, %=,++,–
实现算术运算:
(1) let var=算术表达式
(2) var=$[算术表达式]
(3) var=$((算术表达式))
(4) var=$(expr arg1 arg2 arg3 …)
(5) declare –i var = 数值
(6) echo "算术表达式" | bc
bash有内建的随机数生成器:$RANDOM(1-32767)
echo $[$RANDOM%50] :0-49之间随机数
注意:
乘法符号有些场景中需要转义,如 *
逻辑运算 && || !
短路运算:
短路与:
第一个为0,结果必定为0;
第一个为1,第二个必须要参与运算;
短路或:
第一个为1,结果必定为1;
第一个为0,第二个必须要参与运算;
退出状态
进程使用退出状态来报告成功或失败
• 0 代表成功,1-255代表失败
• $? 变量保存最近的命令退出状态
例如:
$ ping -c1 -W1 hostdown &> /dev/null
$ echo $?
bash自定义退出状态码
exit [n]:自定义退出状态码;
注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
条件测试
判断某需求是否满足,需要由测试机制来实现;专用的测试表达式需要由测试命令辅助完成测试过程;
评估布尔声明,以便用在条件性执行中
-
若真,则返回0
-
若假,则返回1
测试命令:
-
test EXPRESSION
-
[ EXPRESSION ]
-
[[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符test命令
长格式的例子:
$ test "$A" == "$B" && echo "Strings are equal"
$ test “$A” -eq “$B” \
&& echo "Integers are equal"
简写格式的例子:
$ [ "$A" == "$B" ] && echo "Strings are equal"
$ [ "$A" -eq "$B" ] && echo "Integers are equal"
bash的测试类型
数值测试:
-
-gt: 是否大于
-
-ge: 是否大于等于
-
-eq: 是否等于
-
-ne: 是否不等于
-
-lt: 是否小于
-
-le: 是否小于等于
字符串测试:
-
==:是否等于;
-
>: ascii码是否大于ascii码
-
<: 是否小于
-
!=: 是否不等于
-
=~: 左侧字符串是否能够被右侧的PATTERN所匹配
注意: 此表达式一般用于[[ ]]中;扩展的正则表达式 -
-z "STRING":字符串是否为空,空为真,不空为假
-
-n "STRING":字符串是否不空,不空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
文件测试
存在性测试
-
-a FILE:同-e
-
-e FILE: 文件存在性测试,存在为真,否则为假
存在性及类别测试
-
-b FILE:是否存在且为块设备文件
-
-c FILE:是否存在且为字符设备文件
-
-d FILE:是否存在且为目录文件
-
-f FILE:是否存在且为普通文件
-
-h FILE 或 -L FILE:存在且为符号链接文件
-
-p FILE:是否存在且为命名管道文件
-
-S FILE:是否存在且为套接字文件
文件测试
文件权限测试:
-
-r FILE:是否存在且可读
-
-w FILE: 是否存在且可写
-
-x FILE: 是否存在且可执行
文件特殊权限测试:
-
-u FILE:是否存在且拥有suid权限
-
-g FILE:是否存在且拥有sgid权限
-
-k FILE:是否存在且拥有sticky权限
文件测试
文件大小测试:
-
-s FILE: 是否存在且非空
文件是否打开:
-
-t fd: fd表示文件描述符是否已经打开且与某终端相关
-
-N FILE:文件自动上一次被读取之后是否被修改过
-
-O FILE:当前有效用户是否为文件属主
-
-G FILE:当前有效用户是否为文件属组
双目测试:
FILE1 -ef FILE2: FILE1与FILE2是否指向同一个设备上的相同inode
FILE1 -nt FILE2: FILE1是否新于FILE2
FILE1 -ot FILE2: FILE1是否旧于FILE2
组合测试条件
第一种方式:
COMMAND1 && COMMAND2 并且
COMMAND1 || COMMAND2 或者 ! COMMAND 非
如:[[ -r FILE ]] && [[ -w FILE ]]
第二种方式:
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
! EXPRESSION
必须使用测试命令进行;
示例: # [ -z “$HOSTNAME” -o $HOSTNAME "==\ "localhost.localdomain" ] && hostname www.magedu.com # [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
使用read命令来接受输入
使用read来把输入值分配给一个或多个shell变量:
-
-p 指定要显示的提示
-
-t 指定读取值时等待的时间(秒)。
read 从标准输入中读取值,给每个单词分配一个变量所有剩余单词都被分配给最后一个变量
read -p “Enter a filename: “ FILE
其他
read -n 2 var
从输入中读取两个字符并存入变量var,不需要按回车读取。read -r line
允许输入包含反斜杠read -d ":" var
用定界符“:”结束输入行。
实例
从标准输入读取输入并赋值给变量name。
#read name ------ #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量
HelloWorld ---------- #控制台输入Hello#echo $name -------#打印变量
HelloWorld等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
#read one two three
1 2 3 ---------#在控制台输入1 2 3,它们之间用空格隔开。#echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3
REPLY示例
#read #等待控制台输入,并将结果赋值给特定内置变量REPLY。
This is REPLY #在控制台输入该行。#echo $REPLY #打印输出特定内置变量REPLY,以确认是否被正确赋值。
This is REPLY
-p 选项示例
#read -p "Enter your name: " --- #输出文本提示,同时等待输入,并将结果赋值给REPLY。
Enter you name: stephen ------#在提示文本之后输入stephen#echo $REPLY
stephen
流程控制各个命令的简要语法格式
条件选择if语句
选择执行:
注意:if语句可嵌套单分支
if 判断条件;then
条件为真的分支代码
fi
双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
if 判断条件1; then
if-true
elif 判断条件2; then
if -ture
elif 判断条件3; then
if -ture
…
else
all-false
fi
根据命令的退出状态来执行命令 if ping -c1 -W2 station1 &> /dev/null; then echo 'Station1 is UP' elif grep "station1" ~/maintenance.txt &> /dev/null then echo 'Station1 is undergoing maintenance‘ else echo 'Station1 is unexpectedly DOWN!' exit 1 fi
写一个脚本 /root/bin/filetype.sh, 判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)
第一种方法: if [[ $# -lt 1 ]] then echo -e "Error: No argument.\n\tUsage: $0 FILENAME" exit 1 else if [[ -e $1 ]] then FileType=`ls -ld $1 | cut -c1` case $FileType in d) echo "$1 is a diretory" ;; -) echo "$1 is a common file" ;; l) echo "$1 is a link file" ;; *) echo "$1 is other file" esac else echo "$1: no such file or diretory." fi fi unset FileType
第二种方法
if [[ $# -lt 1 ]] then echo -e "Error: No argument.\n\tUsage: $0 FILENAME" exit 1 else if [[ ! -e $1 ]] then echo "$1: No such file or diretory" elif [[ -d $1 ]] then echo "$1 is a diretory" elif [[ -L $1 ]] then echo "$1 is a link file" elif [[ -f $1 ]] then echo "$1 is a common file" fi fi
条件判断:case语句
语法
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
…
*)
默认分支
;;
esac
例
1、编写脚本/root/bin/createuser.sh,实现如下功能:使
用一个用户名做为参数,如果指定参数的用户存在,就显示
其存在,否则添加之;显示添加的用户的id号等信息#!/bin/bash # read -p "请输入一个ID :" Id id $Id &/dev/null if [ $? -eq 0 ];then echo "用户$Id已存在" else useradd $Id echo "$Id用户已创建" id $Id fi
for循环
格式 1
for 变量名 in 列表;do
循环体
done执行机制:
依次将列表中的元素赋值给“变量名”; 每次赋值后即执
行一次循环体; 直到列表中的元素耗尽,循环结束列表可以由命令
命令
or $(命令) 生成或者指定
格式2 (c语言格式)for (( i=1 ; i<=9 ; i++ ))
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断注意:(())双括号
(( ... )): (( 表达式 )) 估值算术表达式。 表达式按照算术法则进行估值。 等价于 "let 表达式". 退出状态 如果表达式估值为0则返回 1;否则返回0。
例:
# 99乘法表 for i in `seq 9` do for j in `seq 1 $i` do echo -ne "$i*$j=$(($i*$j))\t" done echo done echo "==================== plan 2 ====================" for (( i=1 ; i<=9 ; i++ )) do for (( j=1 ; j<=i ; j++ )) do echo -ne "$i*$j=$(($i*$j))\t" done echo done unset i unset j
while循环
语法:
while CONDITION; do
循环体
done
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环
因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true; 若想死循环 while true
退出条件:CONDITION为false
例
1.编写脚本,利用变量RANDOM生成10个随机数字,输出
这个10数字,并显示其中的最大者和最小者
#!/bin/bash #qzx # i=10 a=$RANDOM max=$a min=$a while [ $i -ge 1 ] do [ $max -lt $a ] && max=$a [ $min -gt $a ] && min=$a echo "$a" a=$RANDOM let i-- done echo "最大值$max" echo "最小值$min"
2.* 金字塔
* *** ***** ******* #!/bin/bash #qzx # read -p "请输入要打印的行数: " j let xing=2*j-1 for i in `seq 1 2 $xing` do let j-- let a=j while [ $a -gt 0 ] do echo -n " " let a-- done for b in `seq 1 $i` do echo -n "*" sleep 0.1 done echo -ne "\n" done
until循环
语法 1
until CONDITION; do
循环体
done进入条件: CONDITION 为 false
退出条件: CONDITION 为 true
语法2
while循环的特殊用法(遍历文件的每一行):
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将
行赋值给变量line
语法1
#!/bin/bash #打印国际象棋棋盘 #qzx read -p "NxN:please input N " N let i=$N let j=$N until [ $i -le 0 ] do let j=N until [ $j -le 0 ] do let a=i+j let a=a%2 if [ $a -eq 0 ] then echo -ne "\033[41m \033[0m" else echo -ne "\033[42m \033[0m" fi let j=j-1 done echo -ne "\n" let i=i-1 done
语法2
#!/bin/bash # #qzx while read gai do name=$(echo "$gai" | cut -d: -f1) numb=$(echo "$gai" | cut -d: -f5) if [[ -z "$numb" ]] then chfn -f $name $name &>/dev/null chfn -p "1234567" $name &>/dev/null echo ""$name"添加成功" fi done < ~/bin/passwd
循环控制语句
continue
用于循环体中(do —–continue—- done)
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层。默认为1
break
用于循环体中(do —-break—-done)
break [N]:提前结束第N层循环,最内层为第1层。默认为1
例、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并提示该用户退出系统。
#/bin/bash until who |grep -q "^hacker\b" ;do sleep 3 done who | grep "^hacker"|tr -s ' '|cut -d' ' -f3,5 >> /var/log/login.log echo "you should logout system" | mail hacker echo "reminded and login record in /var/log/login.log"
select 循环与菜单
语法:
select variable in list
do
循环体命令
doneselect 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量 REPLY 中。
select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环。
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list ,此时使用位置参量
函数
函数由两部分组成:函数名和函数体。
语法一:
function f_name {
…函数体…
}语法二:
function f_name () {
…函数体…
}语法三:
f_name (){
…函数体…
}
函数使用
函数的定义和使用:
-
可在交互式环境下定义函数
-
可将函数放在脚本文件中作为它的一部分
-
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行;
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
函数返回值
函数有两种返回值:
函数的执行结果返回值:
(1) 使用echo或printf命令进行输出
(2) 函数体中调用命令的输出结果
函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
-
return 从函数中返回,用最后状态命令决定返回值
-
return 0 无错误返回。
-
return 1-255 有错误返回
使用函数文件和创建函数
使用函数:可以将经常使用的函数存入函数文件,然后将函数文件载入shell。
-
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
-
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数。
-
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件。、
创建函数文件
函数文件示例:
$cat functions.main #!/bin/bash #functions.main findit() { if [ $# -lt 1 ] ; then echo "Usage:findit file" return 1 fi find / -name $1 –print }
载入函数
意义: 函数文件已创建好后,要将它载入shell
定位函数文件并载入shell的格式:
. filename 或 source filename
注意:此即<点> <空格> <文件名>
这里的文件名要带正确路径
示例:上例中的函数,可使用如下命令: $ . functions.main
检查载入函数 set命令
使用set命令检查函数是否已载入。set命令将在shell中显示
所有的载入函数。
删除shell函数
现在对函数做一些改动。首先删除函数,使其对shell不可用。使用unset命令完成此功能.
命令格式为:
unset function_name
实例: $unset findit
再键入set命令,函数将不再显示
函数变量
变量作用域:
-
环境变量:当前shell和子shell有效
-
本地变量:只在当前shell进程有效,为执行脚本会启动
-
专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数。
-
局部变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有局部变量,如果其名称同本地变量,使
用局部变量。
在函数中定义局部变量的方法
local NAME=VALUE
函数的递归
-
函数直接或间接调用自身
-
注意递归层数
例:计算一个正整数的阶乘
#!/bin/bash #:0!=1,n!=(n-1)!×n fact() { if [ $1 -eq 0 -o $1 -eq 1 ] ; then echo 1 else echo "$[$1*$(fact $[$1-1])]" fi } fact $1
例:波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)写一个函数,求n阶斐波那契数列
#/bin/bash #qzx # fun(){ if [ $1 -eq 0 ] then echo "0" elif [ $1 -eq 1 ] then echo "1" else echo "$[$(fun $[$1-2])+$(fun $[$1-1])]" fi } fun $1
综合性练习
1、编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running…”
如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME
is stopped…”
其中:SCRIPT_NAME为当前脚本名
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理#/bin/bash #qzx # fun (){ if [[ -f /var/lock/subsys/SCRIPT_NAME ]] then a=1 else a=0 fi } fun1(){ fun if [ $a -eq 1 ] then echo "已经启动无需重复操作" else touch /var/lock/subsys/SCRIPT_NAME echo "启动成功" fi } fun2(){ fun if [ $a -eq 0 ] then echo "已经停止成功,无需重复操作" else rm -rf /var/lock/subsys/SCRIPT_NAME echo "停止成功" fi } fun3(){ fun [[ $a -eq 1 ]] && echo ""$0" is running..." || echo ""$0" is stopped..." } PS3="请输入 start stop restart status 所对应的数字 " select str in start stop restart status do case $str in start) fun1 ;; stop) fun2 ;; restart) fun1;fun2 ;; status) fun3 ;; *) echo "您输入的不正确。。" break ;; esac done2、编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称;
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下;
如:/bin/bash ==/mnt/sysroot/bin/bash
/usr/bin/passwd ==/mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下:
如:/lib64/ld-linux-x86-64.so.2 ==/mnt/sysroot/lib64/ldlinux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命
令,并重复完成上述功能;直到用户输入quit退出#/bin/bash #qzx # funku(){ for meihangdu in `ldd $quanming | sed -r -n 's@.*(/.*) \(.*$@\1@p'` do local lujing=`dirname $meihangdu` mkdir -p /mnt/sysroot/$lujing &>/dev/null cp -p $meihangdu /mnt/sysroot/$lujing &>/dev/null done } while true do read -p "请输入一个可执行的命令名称 " cmds which $cmds if [ $? -eq 0 ] then quanming=`which $cmds | tail -1` lujing=`dirname $quanming` mkdir -p /mnt/sysroot/$lujing &>/dev/null cp -p $quanming /mnt/sysroot/$lujing &>/dev/null funku elif [[ $cmds == "quit" ]] then break else echo "您输入的命令不对" fi done
原创文章,作者:qzx,如若转载,请注明出处:http://www.178linux.com/38543