Linux Bash语法总结
在学习Linux的过程中,无可避免的会碰到一个既让人喜欢、又令人十分头疼的神奇的东西--bash编程,也就是shell脚本。那么什么是shell脚本呢?shell是一个命令语言解释器,而shell脚本则是Linux命令的集合,按照预设的顺序依次解释执行,来完成特定的、较复杂的系统管理任务,类似于windows中的批处理文件。本篇博文主要介绍bash编程的基础语法讲解。
一、bash编程之变量
1)bash变量类别
本地变量:只对当前shelll进程有效的变量,对其他shell进程无效,包含当前shell进程的子进程。
2)变量赋值:
即向变量的存储空间保存数据,如下
[root@localhost ~]# VAR_NAME=VALUE
3)变量的引用
格式为:${VAR_NAME}
" ":弱引用,里面的变量会被替换;' ':强引用,里面的所有字符都是字面量,直接输出,所见即所得;
4)环境变量
对当前shell进程及其子shell有效,对其他的shell进程无效!
定义:VAR_NAME=VALUE导出:export VAR_NAME撤销变量:unset VAR_NAME只读变量:readonly VAR_NAME
5)局部变量
在shell脚本中定义,只可以在shell脚本中使用!
6)位置变量
$1,$2...,${10}
7)特殊变量
shell对一些参数做特殊处理,这些参数只能被引用而不能被赋值!
$#:传递到脚本的参数个数$*:显示所有向脚本传递的参数 //与位置变量不同,此选项参数可超过9个$$:获取当前shell的进程号$!:执行上一个指令的进程号$?:获取执行的上一个指令的返回值 //0为执行成功,非零为执行失败$-:显示shell使用的当前选项,与set命令功能相同$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数
8)查看变量
set:查看当前shell进程中的所有变量;export、printenv、env:查看当前shell进程中的所有环境变量;
9)变量命名规则
1)不能使用程序中的关键字;2)只能使用数字、字母和下划线,不可使用数字开头;3)系统变量默认都是大写,自定义变量尽量不要与系统变量冲突;4)尽量做到见名知意;
10)变量类型
1)数值型:精确数值(整数)、近似数值(浮点数);2)字符型:char、string;3)布尔型:true、false;
11)类型转换
1)显示转换;2)隐式转换;
二、bash的配置文件
功能:设定本地变量、定义命令别名。
1)profile类
profile类:为交互式登录的用户提供配置!
全局:/etc/profile、/etc/profile.d/*.sh用户:~/.bash_profile
2)bashrc类
bashrc类:为非交互式的用户提供配置!
全局:/etc/bashrc用户:~/.bashrc
1)bash编程即编写格式及执行方式
1)编写格式
shell脚本第一行必须顶格写,用shabang定义指定的解释器来解释该脚本。
#!/bin/bash //!即为shebang//其它的以#开头的行均为注释,会被解释器忽略,可用来注释脚本用途及版本,方便使用管理。
2)执行方式
bash编程属于面向过程编程,执行方式如下:
1)顺序执行:按命令先后顺寻依次执行;2)选择执行:测试条件,可能会多个测试条件,某条件满足时,则执行对应的分支;3)循环执行:将同一段代码反复执行多次,因此,循环必须有退出条件;否则,则陷入死循环;
3)bash执行选项
1)bash -n SHELLNAME #语法测试,测试是否存在语法错误;2)bash -x SHELLNAME #模拟单步执行,显示每一步执行过程;
2)bash之算数运算与逻辑运算
1)算数运算
定义整型变量:
1)et VAR_NAME=INTEGER_VALUE //例如:let a=32)declare -i VAR_NAME=INTEGER_VALUE //例如:declare -i a=3
实现算术运算的方式:
let VAR_NAME=ARITHMATIC_EXPRESSIONVAR_NAME=$[ARITHMATIC_EXRESSION]VAR_NAME=$((EXPRESSION))VAR_NAME=$(expr $num1 + $num2)
算法运算符:
+:加法-:减法*:乘法/:整除%:取余数**:乘幂
注意:即使没有定义为整型变量,字符型的数字依然可以参与算术运算,bash会执行变量类型的隐式类型转换。
2)逻辑运算
布尔运算:真,假与运算:真 && 真 = 真 真 && 假 = 假 假 && 真 = 假 假 && 假 = 假或运算:真 || 真 = 真 真 || 假 = 真 假 || 真 = 真 假 || 假 = 假非运算:!真=假 !假=真
3)bash编程之条件测试语句
1)bash条件测试
整型测试:整数比较
例如 [ $num1 -gt $num2 ]-gt: 大于-lt: 小于-ge: 大于等于-le: 小于等于-eq: 等于-ne: 不等于
字符测试:字符串比较
双目:例如[[ "$str1" > "$str2" ]]>: 大于则为真<: 小于则为真>=:大于等于则为真<=:小于等于则为真==:等于则为真!=:不等于则为真单目:-n String: 是否不空,不空则为真,空则为假-z String: 是否为空,空则为真,不空则假文件测试:判断文件的存在性及属性等-a FILE:存在则为真;否则则为假;-e FILE:存在则为真;否则则为假;-f FILE: 存在并且为普通文件,则为真;否则为假;-d FILE: 存在并且为目录文件,则为真;否则为假;-L/-h FILE: 存在并且为符号链接文件,则为真;否则为假;-b: 存在并且为块设备,则为真;否则为假;-c: 存在并且为字符设备,则为真;否则为假-S: 存在并且为套接字文件,则为真;否则为假-p: 存在并且为命名管道,则为真;否则为假-s FILE: 存在并且为非空文件则为值,否则为假;-r FILE:文件可读为真,否则为假-w FILE:文件可写为真,否则为假-x FILE:文件可执行为真,否则为假file1 -nt file2: file1的mtime新于file2则为真,否则为假;file1 -ot file2:file1的mtime旧于file2则为真,否则为假;组合条件测试:在多个条件间实现逻辑运算与:[ condition1 -a condition2 ] condition1 && condition2或:[ condition1 -o condition2 ] condition1 || condition2非:[ -not condition ] ! condition与:COMMAND1 && COMMAND2COMMAND1如果为假,则COMMAND2不执行或:COMMAND1 || COMMAND2COMMAND1如果为真,则COMMAND2不执行非:! COMMAND
2)条件测试之if语句
1、if语句之单分支语句结构:if 测试条件;then 选择分支fi表示条件测试状态返回值为值,则执行选择分支例:写一个脚本,接受一个参数,这个参数是用户名;如果此用户不存在,则创建该用户;#!/bin/bashif ! id $1 &> /dev/null;then useradd $1fi2、if语句之双分支语句结构:if 测试条件;then 选择分支1else 选择分支2fi两个分支仅执行其中之一例:通过命令行给定一个文件路径,而后判断:如果此文件中存在空白行,则显示其空白行的总数;否则,则显示无空白行;#!/bin/bashif grep "^[[:space]]*$" $1 &> /dev/null; then echo "$1 has $(grep "^[[:space]]*$" $1 | wc -l) blank lines."else echo "No blank lines"fi注意:如果把命令执行成功与否当作条件,则if语句后必须只跟命令本身,而不能引用。3、if语句之多分支语句结构:if 条件1;then 分支1elif 条件2;then 分支2elif 条件3;then 分支3 ...else 分支nfi例:传递一个用户名给脚本:如果此用户的id号为0,则显示说这是管理员;如果此用户的id号大于等于500,则显示说这是普通用户;否则,则说这是系统用户。#!/bin/bashif [ $# -lt 1 ]; then echo "Usage: `basename $0` username" exit 1fiif ! id -u $1 &> /dev/null; then echo "Usage: `basename $0` username" echo "No this user $1." exit 2fiif [ $(id -u $1) -eq 0 ]; then echo "Admin"elif [ $(id -u $1) -ge 500 ]; then echo "Common user."else echo "System user."fi
3)bash交互式编程
read [option] "prompt"-p:直接指定一个变量接受参数-t timaout:指定等待接受参数的时间-n:表示不换行例:输入用户名,可返回其shell#!/bin/bashread -p "Plz input a username: " userNameif id $userName &> /dev/null; then echo "The shell of $userName is `grep "^$userName\>" /etc/passwd | cut -d: -f7`."else echo "No such user. stupid."fi
4)条件测试与case语句
case语句:有多个测试条件时,case语句会使得语法结构更明晰语句结构:case 变量引用 inPATTERN1) 分支1;;PATTERN2) 分支2;;...*) 分支n;;esac
PATTERN:类同于文件名通配机制,但支持使用|表示或者
a|b: a或者b*:匹配任意长度的任意字符?: 匹配任意单个字符[]: 指定范围内的任意单个字符例:写一个脚本,完成如下任务,其使用形式如下所示:script.sh {start|stop|restart|status}其中:如果参数为空,则显示帮助信息,并退出脚本;如果参数为start,则创建空文件/var/lock/subsys/script,并显示"starting script successfully."如果参数为stop,则删除文件/var/lock/subsys/script,并显示"Stop script successfully."如果参数为restart,则删除文件/var/locksubsys/script并重新创建,而后显示"Restarting script successfully."如果参数为status,那么:如果文件/var/lock/subsys/script存在,则显示"Script is running…",否则,则显示"Script is stopped."#!/bin/bashfile='/var/lock/subsys/script'case $1 instart) if [ -f $file ];then echo "Script is running..." exit 3 else touch $file [ $? -eq 0 ] && echo "Starting script successfully." fi ;;stop) if [ -f $file ];then rm -rf $file [ $? -eq 0 ] && echo "Stop script successfully." else echo "Script is stopped..." exit 4 fi ;;restart) if [ -f $file ];then rm -rf $file [ $? -eq 0 ] && echo "Stop script successfully" else echo "Script is stopped..." exit 5 fi touch $file [ $? -eq 0 ] && echo "Starting script successfully" ;;status) if [ -f $file ];then echo "Script is running..." else echo "Script is stopped." fi ;;*) echo "`basename $0` {start|stop|restart|status}" exit 2 ;; esac
4)bash编程之循环语句
1)循环之for循环
1、for语句格式一语句结构:for 变量名 in 列表; do 循环体done列表:可包含一个或多个元素循环体:依赖于调用变量来实现其变化循环可嵌套退出条件:遍历元素列表结束例:求100以内所有正整数之和#!/bin/bashdeclare -i sum=0for i in {1..100}; do let sum+=$idoneecho $sum2、for语句格式二for ((初始条件;测试条件;修改表达式)); do 循环体done先用初始条件和测试条件做判断,如果符合测试条件则执行循环体,再修改表达式,否则直接跳出循环。例:求100以内所有正整数之和(for二实现)#!/bin/bashdeclare -i sum=0for ((counter=1;$counter <= 100; counter++)); do let sum+=$counterdoneecho $sum
2)循环之while语句
while循环语句适用于循环次数未知,或不适用for直接生成较大的列表!
语句结构:while 测试条件; do 循环体done测试条件为真,进入循环;测试条件为假,退出循环例1:求100以内所有偶数之和,要求使用取模方法#!/bin/bashdeclare -i counter=1declare -i sum=0while [ $counter -le 100 ]; do if [ $[$counter%2] -eq 0 ]; then let sum+=$counter fi let counter++ doneecho $sum例2:提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在;显示完成之后不退出,再次重复前面的操作,直到用户输入q或quit为止#!/bin/bashread -p "Plz enter a username: " userNamewhile [ "$userName" != 'q' -a "$userName" != 'quit' ]; do if id $userName &> /dev/null; then grep "^$userName\>" /etc/passwd | cut -d: -f3,7 else echo "No such user." firead -p "Plz enter a username again: " userNamedonewhile特殊用法:遍历文本文件语句结构:while read 变量名; do 循环体done < /path/to/somefile变量名,每循环一次,记忆了文件中一行文本例:显示ID号为偶数,且ID号同GID的用户的用户名、ID和SHELLwhile read line; do userID=`echo $line | cut -d: -f3` groupID=`echo $line | cut -d: -f4` if [ $[$userID%2] -eq 0 -a $userID -eq $groupID ]; then echo $line | cut -d: -f1,3,7 fidone < /etc/passwd
3)循环之until语句
语句结构:until 测试条件; do 循环体done测试条件为假,进入循环;测试条件为真,退出循环例:求100以内所有偶数之和,要求使用取模方法(until实现)#!/bin/bashdeclare -i counter=1declare -i sum=0until [ $counter -gt 100 ]; do if [ $[$counter%2] -eq 0 ]; then let sum+=$counter fi let counter++doneecho $sum例:提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在;显示完成之后不退出,再次重复前面的操作,直到用户输入q或quit为止(until实现)#!/bin/bashread -p "Plz enter a username: " userNameuntil [ "$userName" = 'q' -a "$userName" = 'quit' ]; do if id $userName &> /dev/null; then grep "^$userName\>" /etc/passwd | cut -d: -f3,7elseecho "No such user."firead -p "Plz enter a username again: " userNamedone
4)循环之循环控制和shift
循环控制命令:1)break:提前退出循环;2)break [N]: 退出N层循环;N省略时表示退出break语句所在的循环;3)continue: 提前结束本轮循环,而直接进入下轮循环;4)continue [N]:提前第N层的循环的本轮循环,而直接进入下轮循环;
5)死循环
#while体while true; do 循环体done#until体until false; do 循环体done例1:写一个脚本,判断给定的用户是否登录了当前系统(1) 如果登录了,则脚本终止;(2) 每5秒种,查看一次用户是否登录;#!/bin/bashwhile true; do who | grep "gentoo" &> /dev/null if [ $? -eq 0 ];thenbreak fi sleep 5doneecho "gentoo is logged."
6)shift
位置参数可以用shift命令左移,比如shift 3 表示原来的$4现在变成$1,原来的$5变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。
我们知道,对于位置变量或命令行参数,其个数必须是确定的,或者当shell程序不知道其个数时,可以把所有参数一起复制给"$*"。若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等。在 shift 命令执行前变量 $1 的值在 shift 命令执行后就不可用了。
实例一如下:
[root@localhost ~]# cat 1.sh#!/bin/bashwhile [ $# -ne 0 ]doecho "第一个参数为:$1 参数个数为:$#"shiftdone[root@localhost ~]# sh 1.sh 1 2 3 4第一个参数为:1 参数个数为:4第一个参数为:2 参数个数为:3第一个参数为:3 参数个数为:2第一个参数为:4 参数个数为:1
从上面例子中可以看出shift命令每执行一次,变量的个数($#)减1,而变量的值提前一位。
实例二如下:
[root@localhost ~]# cat 2.sh#!/bin/bashif [ $# -eq 0 ]thenecho "Usage:2.sh 参数"exit 1fisum=0while [ $# -ne 0 ]dosum=`expr ${sum} + $1`shiftdoneecho "sum is:${sum}"[root@localhost ~]# sh 2.sh 10 20 30sum is:60
shift命令还有一个重要用途,Bash定义了9个位置变量,从$1到$9,这并不意味这用户在命令行只能使用9个参数,借助shift命令可以访问多于9个的参数。
shift命令一次移动到参数的个数由其所带的参数指定,例如当shell程序处理完前9个命令行参数后,可以使用shift 9命令把$10移动到$1。
5)bash编程之函数
语法结构:
function F_NAME { 函数体 }或 F_NAME() { 函数体 }
可调用:使用函数名,函数名出现的地方,会被自动替换为函数;函数的返回值:函数的执行结果返回值:代码的输出函数中使用打印语句:echo, printf函数中调用的系统命令执行后返回的结果执行状态返回值:默认取决于函数体执行的最后一个命令状态结果自定义退出状态码:return [0-255]
注意:函数体运行时,一旦遇到return语句,函数即返回!
1)函数可以接收参数
在函数中调用函数参数的方式同脚本中调用脚本参数的方式:
位置参数$1, $2, …$#, $*, $@
实例:
要求如下:
1)提示用户输入一个可执行命令;
2)获取这个命令所依赖的所有库文件(使用ldd命令);
3)复制命令之/mnt/sysroot目录;
4)复制各库文件至/mnt/sysroot对应的目录中;
[root@localhost ~]# cat 1.sh#!/bin/bashtarget=/mnt/sysroot/[ -d $target ] || mkdir $targetpreCommand() { if which $1 &> /dev/null; then commandPath=`which --skip-alias $1` return 0 else echo "No such command." return 1 fi}commandCopy() { commandDir=`dirname $1` [ -d ${target}${commandDir} ] || mkdir -p ${target}${commandDir} [ -f ${target}${commandPath} ] || cp $1 ${target}${commandDir}}libCopy() { for lib in `ldd $1 | egrep -o "/[^[:space:]]+"`; dolibDir=`dirname $lib`[ -d ${target}${libDir} ] || mkdir -p ${target}${libDir}[ -f ${target}${lib} ] || cp $lib ${target}${libDir} done} read -p "Plz enter a command: " commanduntil [ "$command" == 'quit' ]; do if preCommand $command &> /dev/null; then commandCopy $commandPath libCopy $commandPath fi exit 1done[root@localhost ~]# sh 1.shPlz enter a command: cat[root@localhost ~]# ls /mnt/sysroot/bin/cat[root@localhost ~]# ls /mnt/sysroot/bin lib64
2)bash编程之信号捕捉
trap命令用于在shell程序中捕捉到信号,之后可以由三种反应方式:
1)执行一段程序来处理这一信号;
2)接收信号的默认操作;
3)忽略这一信号;
示例:
写一个脚本,能够ping探测指定网络内的所有主机是否在线,当没有执行完时可接收ctrl+c命令退出。
[root@localhost ~]# cat 1.sh#!/bin/bashquitScript() { echo "Quit..."} trap 'quitScript; exit 5' SIGINTcnetPing() { for i in {1..254}; do if ping -c 1 -W 1 $1.$i &> /dev/null; then echo "$1.$i is up." else echo "$1.$i is down." fi done}bnetPing() { for j in {0..255}; do cnetPing $1.$j done}anetPing() {for m in {0..255}; dobnetPing $1.$mdone}netType=`echo $1 | cut -d"." -f1`if [ $netType -ge 1 -a $netType -le 126 ]; thenanetPing $netTypeelif [ $netType -ge 128 -a $netType -le 191 ]; thenbnetPing $(echo $1 | cut -d'.' -f1,2)elif [ $netType -ge 192 -a $netType -le 223 ]; thencnetPing $(echo $1 | cut -d'.' -f1-3)elseecho "Wrong"exit 2fi[root@localhost ~]# sh 1.sh 192.168.1.1192.168.1.1 is down.192.168.1.2 is down.192.168.1.3 is down.192.168.1.4 is down.192.168.1.5 is down.192.168.1.6 is down.192.168.1.7 is down.192.168.1.8 is down.192.168.1.9 is down.192.168.1.10 is up.192.168.1.11 is down.^CQuit...
----------本次博文到此结束,感谢阅读----------