您的位置:首页 > 运维架构 > Shell

Bash脚本编程总结

2016-03-12 00:00 543 查看

Bash脚本编程总结

  在Linux中的脚本编程,通常指的是shell脚本,即bash脚本编程;bash作为一个命令的解释器,而bash脚本是将命令集合于一个或多个文本文件中,按照预设的顺序依次执行,来完成特定的,复杂的系统管理任务;本文将介绍bash编程的基本语法。

基本格式:

#!/bin/bash

  第一行必须顶格写,用shebang定义指定的解释器来解释该脚本;其它的以#开头的行均为注释,会被解释器忽略;可用作注释该脚本版本和用途,方便管理。

示例:

#!/bin/bash# Author:Chencer
# Date:2015/10/08 14:30
# Email:ms_achencer@yahoo.com
# Version:1.0.0
# Description:A test script.

执行方式:bash脚本属于过程式编程语言;

  顺序执行

  选择执行:测试条件,可能会多个测试条件,某条件满足时,则执行对应的分支;

  循环执行:将同一段代码反复执行多次;因此,循环必须有退出条件;否则,则陷入死循环;

bash执行选项:

-n:语法测试;
-x:模拟单步执行;

bash脚本变量:

什么是变量:有名称的内存空间;

变量的命名:

赋值:Bash属于弱类型语言,任何变量无需事先声明,可直接使用,值默认都是字符型;

name=chencer name=”hello chencer”

撤销变量:

# unset varname

命名要求:

1、不能使用程序中的关键字(保留字);

2、只能使用数字、字母和下划线,且不能以数字开头

3、要见名知义

变量的类型:

数值型:

  精确数值:整数

  近似数值:浮点型

    单精度浮点,双精度浮点

字符型:char,string

布尔型:true,false

变量声明命令:

# declare
-a:array,数组;
-i:intger,整数;
-x:env,
-r:readonly,只读;
# export

类型转换:显式转换,隐式转换;

变量引用:

$name ${name}

变量的类别:

本地变量:只对当前shell进程有效的变量,对其它shell进程无效,包当前shell进程的子进程

> VAR_NAME=VALUE    :变量赋值,向变量的存储空间保存数据;
> ${VAR_NAME}    :变量引用;
“”:弱引用,变量会被替换为变量值;
‘’:强引用,字符会被直接输出;

环境变量:向变量的存储空间保存数据;

> export VAR_NAME=VALUE    :定义变量;
> export VAR_NAME    :导出变量;
> unset VAR_NAME    :撤销变量;
> readonly VAR_NAME    :只读变量;

局部变量:对shell脚本中某代码片断有效,通常用于函数本地;

> local VAR_NAME=VALUE

位置变量:用来接受变量指定位置的参数;

> $1, ..., $n, ${10}

特殊变量:

$?:
$#:传递给脚本参数的个数
$*:
$@:引用传递给脚本的所有参数

bash的配置文件:

profile类:为交互式登录的用户提供配置

/etc/profile,/etc/profile.d/*.sh   :全局;
~/.bash_profile   :用户;
功用:设定环境变量,运行命令或脚本;

bashrc类:为非交互式的用户提供配置

/etc/bashrc  :全局;
~/.bashrc :用户;
功用:设定本地变量,定义命令别名;

Bash算术运算与逻辑运算:

算术运算:

如何定义整型变量:

# let VAR_NAME=INTEGER_VALUE
例如:
# let a=3

# declare -i VAR_NAME=INTEGER_VALUE
例如:
# declare -i a=3

  注意:即使没有定义为整型变量,字符型的数字依然可以参与算术运算;bash会执行变量类型的隐式类型转换;

实现算术运算的方式:

# let VAR_NAME=ARITHMATIC_EXPRESSION
# VAR_NAME=$[ARITHMATIC_EXRESSION]
# VAR_NAME=$((EXPRESSION))
# VAR_NAME=$(expr $num1 + $num2)

算术运算符:

+:加法运算
-:减法运算
*:乘法运算;
/:除法运算
%:取模运算,取余数;
例如:5%2=1,
**:乘幂运算;
例如:2**2=4

逻辑运算:

布尔运算:

真,假

与、或、非、异或:

与运算:
真 && 真 = 真
真 && 假 = 假
假 && 真 = 假
假 && 假 = 假

或运算:
真 || 真 = 真
真 || 假 = 真
假 || 真 = 真
假 || 假 = 假

非运算:
!真 = 假
!假 = 真

bash条件测试:

命令执行成功与否即为条件测试:

test EXPR
[ EXPR ]
[[ EXPR ]]

命令都有其状态返回值:

成功:0,真
失败:1-255,假

测试类型:根据比较时的操作数的类型

  整型测试:整数比较

  字符测试:字符串比较

  文件测试:判断文件的存在性及属性等

  注意:比较运算通常只在同一种类型间进行

整型测试:

例如:[ $num1 -gt $num2 ]
-gt:测试大于
-lt:测试小于
-ge:大于等于
-le:小于等于
-eq:测试相等
-ne:测试不等

字符串测试:

双目:
例如:[[ "$str1" > "$str2" ]]
>:测试大于
<:测试小于
>=:大于等于
<=:小于等于
==:测试相等
!=:测试不等

单目:
-n String:是否不空,不空则为真,空则为假
-z String:是否为空,空则为真,不空则假

文件测试:判断文件属性等信息;

-a/-e FILE:判断存在;
-f FILE:普通文件;
-d FILE:目录文件;
-L/-h FILE:符号链接文件;
-b FILE:块设备文件;
-c FILE:字符设备文件;
-S FILE:套接字文件;
-p FILE:命名管道;
-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

Bash脚本语句:

for循环语句:有两种格式;

格式一:

For 变量名 in 列表do
循环体
done

for:遍历有限的元素列表
列表:中间包括一个或多个元素;
循环体:依赖于调用变量来实现其变化

格式二:

for ((初始条件;测试条件;修改表达式));do
循环体
done

示例:使用两种for循环格式求100以内所有正整数之和;

#!/bin/bash#

declare -i sum=0

for i in {1..100};do
let sum+=$i
done
echo “sum = $sum”

#!/bin/bash#

declare -i sum=0
for ((i=1;$i <= 100;i++));do
let sum+=$i
done
echo “sum = $sum”

补充:语句中可嵌套语句;

示例:使用for语句中嵌套if语句,求100以内所有偶数之和,和基数之和;

#!/bin/bash#

declare -i evensum=0
declare -i oddsum=0

for i in `seq 1 100`;do
if [ $[$i%2] -eq 0 ];then
let evensum+=$i
else
let oddsum+=$i
fi
done

echo "evensum = $evensum"
echo "oddsum = $oddsum"

If判断语句:

语法格式:

If 条件1;then
分支1
elif 条件2;then
分支2
……
else
分支n
fi

补充:bash编程之交互编程

> read
-p "prompt":指定一个变量接收参数;
-t timeout:超时时间;

示例:
传递一个用户给脚本;

如果用户不存在,则显示此用户不存在;

如果用户的id为0,则显示说这是管理员;

如果用户的id大于等于500,则显示说这是普通用户;

如果用户的id大于0小于500,则显示说这是系统用户;

#!/bin/bash#
read -p "Please input an username:" username
userid=`id -u $username`

if ! `id $username &>/dev/null`;then
echo "User $username not exsits."
exit 1
elif [ $userid -eq 0 ];then
echo "User $username is admin."
elif [ $userid -ge 500 ];then
echo "User $username is common user."
else
echo "User $username is system user."
fi

循环测试语句while和until:

while循环:首先进行条件测试,如果结果为真,则进入循环,执行循环体;结果为假,则退出循环;通常用于循环次数未知,或不便用for直接生成较大的列表时;

until循环:与while相反,条件测试结果为假则进入循环,为真则退出循环。

语句格式:

while 条件测试; do
循环体
done

until 条件测试; do
循环体
done

示例:
使用while和until语句,求100以内所有偶数之和;

#!/bin/bash#
declare -i num=1
declare -i evensum=0
while [ $num -le 100 ]; do
if [ $[$num%2] -eq 0 ]; then
let evensum+=$num
fi
let num++
done
echo "evensum=$evensum."

#!/bin/bash#
declare -i num=1
declare -i oddsum=0
until [ $num -gt 100 ]; do
if [ $[$num%2] -eq 0 ]; then
let oddsum+=$num
fi
let num++
done
echo "oddsum=$oddsum."

提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在;

#!/bin/bash#
read -p "Please enter a username:" username
while [ "$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."
fi
read -p "Pease enter a username again: " username
done

while循环特殊用法:遍历文件;

语句格式:

while read 变量名; do
循环体
done < /path/to/somefile

变量名:每循环一次,记忆了文件中一行文本

示例:
显示其ID号为偶数的用户的用户名、ID号和SHELL

#!/bin/bash#
while read line; do
userid=`echo $line | cut -d: -f3`
if [ $[$userid%2] -eq 0 ];then
echo $line | cut -d: -f1,3,7
fi
done < /etc/passwd

显示ID号为偶数,且ID号同GID的用户的用户名、ID和SHELL;

#!/bin/bash#
while 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
fi
done < /etc/passwd

case语句:有多个测试条件时,case语句会使得语法结构更明晰;

语句格式:

case 变量引用 in
PATTERN1)
分支1
;;
PATTERN2)
分支2
;;
...
*)
分支n
;;
esac

PATTERN:类同于文件名通配机制,但支持使用|表示或者;

a|b:a或者b
*:匹配任意长度的任意字符
?:匹配任意单个字符
[]:指定范围内的任意单个字符

示例:用户键入字符后判断其所属的类别;

#!/bin/bash#

read -p "Please enter a char:" char

case $char in
[[:digit:]])
echo "A digit."
;;
[[:alpha:]])
echo "A char."
;;
*)
echo "A special word."
;;
esac

函数:

可调用:使用函数名,函数名出现的地方,会被自动替换为函数体中的内容;

语句格式:

function F_NAME{
函数体
}

F_NAME() {
函数体
}

函数的返回值:

函数的执行结果返回值:代码的输出

  函数中的打印语句:echo,print

  函数中调用的系统命令执行后返回的结果

执行状态返回值:

  最后一次执行的命令状态结果

  自定义函数执行状态的返回值:return [0-255]

注意:return与exit;

  return:遇到return语句,返回但不退出;

  exit:遇到exit语句,返回且退出;

示例:
写一个脚本,完成如下任务,其使用形式如下所示:

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.”

  说明:script.sh是脚本文件名,在创建时,其名称可以自己随意定义,但如果其名称发生变量,上/var/lock/sussys/下的文件名也要随之而变;

#!/bin/bash#
srv=`basename $0`
lockfile=/var/lock/subsys/"$srv"

[ `id -u` -ne 0 ] && echo "Oly root." && exit 1
[ $# -ne 1 ] && echo "Please input an argument,{start|stop|restart|status}." && exit 2

start () {
if ! [ -e $lockfile ];then
touch $lockfile
echo "Starting $srv successfully."
return 0
else
echo "$srv is running,don't need start."
return 4
fi
}

stop () {
if [ -e $lockfile ];then
rm -f $lockfile
echo "Stop $srv successfully."
return 0
else
echo "$srv is stopped,don't need stop."
return 5
fi
}

status () {
if [ -e $lockfile ];then
echo "$srv is running."
return 0
else
echo "$srv is stopped."
return 0
fi
}

usage () {
echo "Argument erro,Plase input {start|stop|restart|status}."
return 0
}

case $1 in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
usage
exit 3
;;
esac

信号捕捉:

trap命令用于在shell程序中捕捉到信号,之后可以执行一段程序来处理这一信号:

trap 'COMMAND' SIGINT(表示关闭进程)

示例:
写一个脚本,能够ping探测指定网络内的所有主机是否在线,当没有执行完时可接收ctrl+c命令退出。

#!/bin/bash#

quit () {
echo "Quit..."
}
trap 'quit;exit 2' SIGINT

cnetping () {
for c in {1..254};
do
if `ping -c 1 -W 1 $1.$c &> /dev/null`;then
echo "$1.$c is up."
else
echo "$1.$c is down."
fi
done
}

bnetping () {
for b in {0..255};do
cnetping $1.$b
done
}

anetping () {
for a in {0..255};do
bnetping $1.$a
done
}

nettype=`echo $1 | cut -d"." -f1`
netmask=$2

if [ $nettype -ge 1 -a $nettype -le 126 -a $netmask -eq 8 ];then
anetping $nettype
elif [ $nettype -ge 1 -a $nettype -le 191 -a $netmask -eq 16 ];then
bnetping $(echo $1 | cut -d'.' -f1,2)
elif [ $nettype -ge 1 -a $nettype -le 223 -a $netmask -eq 24 ];then
cnetping $(echo $1 | cut -d'.' -f1-3)
else
echo "Wrong net."
exit 1
fi

循环控制:

continue:提前进入下一轮循环;用于条件语句中;
break:提前跳出当前循环;用于条件语句中;

示例:
求出1-100之内,3的正整数之和;

#!/bin/bash#
sum=0
for i in {1..100};do
if [ $[$i%3] -ne 0 ];then
continue
fi
let sum+=$i
done
echo $sum

写一个脚本,判断给定的用户是否登录了当前系统:

  如果登录了,则脚本终止;

  每5秒种,查看一次用户是否登录;

#!/bin/bash#
while true; do
who | grep "script" &> /dev/null
if [ $? -eq 0 ];then
break
fi
sleep 5
done
echo "script is logged."

shift:如果没有数字,只有shift就是跳过一个参数获取下一个参数,如果加上数字,比如shift 2,跳过两个参数获取下一个参数。

示例:
写一个脚本,使用形式如下所示

showifinfo.sh [-i INTERFACE|-a] [-v]

要求:

  -i或-a不可同时使用,-i用于指定特定网卡接口,-a用于指定所有接口;

  显示接口的ip地址;

  使用-v,则表示显示详细信息;

  显示接口的ip地址、子网掩码、广播地址;

  默认表示仅使用-a选项;

#!/bin/bash#
verbose=0
allinterface=0
ifflag=0
interface=0
while [ $# -ge 1 ];do
case $1 in
-a)
allinterface=1
shift 1
;;
-i)
ifflag=1
interface="$2"
shift 2
;;
-v)
verbose=1
shift
;;
*)
echo "wrong option"
exit 2
;;
esac
done

if [ $allinterface -eq 1 ];then
if [ $verbose -eq 1 ]; then
ifconfig | grep "inet addr:"
else
ifconfig | grep "inet addr:" | awk '{print $2}'
fi
fi

if [ $ifflag -eq 1 ]; then
if [ $verbose -eq 1 ]; then
ifconfig $interface | grep "inet addr:"
else
ifconfig $interface | grep "inet addr:" | awk '{print $2}'
fi
fi

数组:

  数组是内存中的存储空间,连续的多个存储单元,每个存储单元相当于一个变量;bash中只支持一堆数组,支持稀疏格式,参数个数没有限制;

表述方法:数组名+索引

  索引的表示方法:

a[index]    :数字索引,例如:a[0]、a[1];
a[hello]、a[hi]    :关联数字;

声明:

declare -a ARRAR_NAME   :表示普通数组;默认,可不用声明;
declare -A ARRAR_NAME   :表示数值;必须声明,bash需4.0以上版本支持,可通过bash -version查看当前版本;

数组元素赋值:

a[0]=$RANDOM    :一次对一个元素赋值;
a=(red blue yellow green)   :一次对全部元素赋值;
a=([0]=green [3]=red [2]=blue [6]=yellow) :按索引进行赋值;
logs=(/var/log/*.log)   :命令替换;
read -a ARRAY   :用户输入;

数组的访问:

  用索引访问:

ARRAY[index]

  数组的长度:

${#ARRAY[*]}
${#ARRAY[@]}

  从数组中挑选某元素:

${ARRAY[@]:offset:number}
切片:
offset: 偏移的元素个数
number: 取出的元素的个数

${ARRAY[@]:offset}:取出偏移量后的所有元素

${ARRAY[@]}: 取出所有元素

  数组复制:

要使用${ARRAY[@]}
$@: 每个参数是一个独立的串
$*: 所有参数是一个串

  从数组中删除元素:

unset ARRAY[index]

示例:
复制一个数组中下标为偶数的元素至一个新数组中

#!/bin/bashdeclare -a mylogs
logs=(/var/log/*.log)
echo ${logs[@]}

for i in `seq 0 ${#logs[@]}`; do
if [ $[$i%2] -eq 0 ];then
index=${#mylogs[@]}
mylogs[$index]=${logs[$i]}
fi
done

echo ${mylogs[@]}

生成10个随机数,升序排序

#!/bin/bash#
for((i=0;i<10;i++));do
rnd[$i]=$RANDOM
done
echo -e "total=${#rnd[@]}\n${rnd[@]}\nBegin to sort"

for((i=9;i>=1;i--));do
for((j=0;j<i;j++));do
if [ ${rnd[$j]} -gt ${rnd[$[$j+1]]} ] ;then
swapValue=${rnd[$j]}
rnd[$j]=${rnd[$[$j+1]]}
rnd[$[$j+1]]=$swapValue
fi
done
done
echo ${rnd[@]}

打印九九乘法表

#!/bin/bash#
for((i=1;i<=9;i++));do
strLine=""
for((j=1;i<=9;j++));do
strLine=$strLine"$i*$j="$[$i*$j]"\t"
[ $i -eq $j ] && echo -e $strLine && break
done
done

字符串操作:

${string:offset:length}	:字符串切片;
${string: -length}	:取尾部的指定个数的字符;

示例:

[root@script ~]# string='hello chencer'
[root@script ~]# echo $string
hello chencer
[root@script ~]# echo ${string:2:4}
llo
[root@script ~]# echo ${string: -2}
er

取子串:基于模式;

${variable#*word}:在variable中存储字串上,自左而右,查找第一次出现word,删除字符开始至此word处的所有内容;
${variable##*word}:在variable中存储字串上,自左而右,查找最后一次出现word,删除字符开始至此word处的所有内容;

${variable%word*}: 在variable中存储字串上,自右而左,查找第一次出现word,删除此word处至字串尾部的所有内容;
${variable%%world*}:在variable中存储字串上,自右而左,查找最后一次出现word,删除此word处至字串尾部的所有内容;

示例:

[root@script ~]# file='/var/log/messages'
[root@script ~]# echo $file
/var/log/messages
[root@script ~]# echo ${file#*/}
var/log/messages
[root@script ~]# echo ${file##*/}
messages
[root@script ~]# echo ${file%/*}
/var/log
[root@script ~]# echo ${file%%/*}
返回结果为空

[root@script ~]# phonenumber='010-110-8'
[root@script ~]# echo $phonenumber
010-110-8
[root@script ~]# echo ${phonenumber%%-*}
010
[root@script ~]# echo ${phonenumber##*-}
8

[root@script ~]# url="http://www.chencer.org:80"
[root@script ~]# echo $url http://www.chencer.org:80 [root@script ~]# echo ${url##*:}
80	:取端口;
[root@script ~]## echo ${url%%:*}
http	:取协议;

查找替换:

${variable/pattern/substi}:替换第一次出现;
${variable//pattern/substi}:替换所有的出现;

${variable/#pattern/substi}:替换行首被pattern匹配到的内容;
${variable/%pattern/substi}:替换行尾被pattern匹配到的内容;

pattern可以使用globbing中的元字符:* ?

示例:

[root@script ~]# userinfo=`tail -n 1 /etc/passwd`
[root@script ~]# echo $userinfo
script:x:1000:1000::/home/script:/bin/bash
[root@script ~]# echo ${userinfo/script/chencer}
chencer:x:1000:1000::/home/script:/bin/bash
[root@script ~]# echo ${userinfo//script/chencer}
chencer:x:1000:1000::/home/chencer:/bin/bash
[root@script ~]# echo ${userinfo/#script/chencer}
chencer:x:1000:1000::/home/script:/bin/bash
[root@script ~]# echo ${userinfo/%bash/chencer}
script:x:1000:1000::/home/script:/bin/chencer

查找删除:

${variable/pattern}:删除第一次出现;
${variable//pattern}:删除所有的出现;
${variable/#pattern}:删除行首匹配到的内容;
${variable/%pattern}:删除行尾匹配到的内容;

示例:

[root@script ~]# echo ${userinfo/script}
:x:1000:1000::/home/script:/bin/bash
[root@script ~]# echo ${userinfo//script}
:x:1000:1000::/home/:/bin/bash
[root@script ~]# echo ${userinfo/#script}
:x:1000:1000::/home/script:/bin/bash
[root@script ~]# echo ${userinfo/%bash}
script:x:1000:1000::/home/script:/bin/

大小写转换:

${variable^^}  :小-->大;
${variable,,}  :大-->小;

示例:

[root@script ~]# name=chencer
[root@script ~]# echo $name
chencer
[root@script ~]# echo ${name^^}
CHENCER
[root@script ~]# name=CHENCER
[root@script ~]# echo $name
CHENCER
[root@script ~]# echo ${name,,}
chencer

变量赋值操作:

${variable:-string} :variable为空或未设定,那么返回string,否则,返回variable变量的值;
${variable:=string}	:variable为空或未设定,则返回string,且将string赋值给变量variable,否则,返回variable的值;

为脚本使用配置文件,并确保某变量有可用值的方式

variable=${variable:-default vaule}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: