shell脚本编程
默认情况下,echo在每次调用后会添加一个换行符
双引号"" 双引号允许shell解释字符串中出现的特殊字符
单引号'' 单引号不会对字符串中出现的特殊字符做任何解释
echo "hello world! \$PATH"
echo 'hello world! $PATH'
如果需要打印像$这样的特殊字符,那就不要将其放入双引号中,而是使用单引号,或是在特殊字符之前加上一个反斜线\,用来转义
1.在echo中转义换行符
默认情况下,echo会在输出文本的尾部追加一个换行符,可以使用-n选项来禁止。
echo使用转义序列时,需要使用echo -e "包含转义序列的字符串"这种形式
echo -e "1\t2\t3"
2.打印彩色输出
脚本可以使用转义序列在终端中生成彩色文本,文本颜色是由对应的色彩码来描述的。其中包括:
重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37
echo -e "\e[1;31m This is red text \e[0m"
其中\e[1;31m就是一个转义字符串,可以将颜色设置为红色,\e[0m将颜色重新置回。只需要将31替换成想要的色彩码就行了
对于彩色背景,经常使用的颜色码是:重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47
echo -e "\e[1;42m Green Background \e[0m"
假设有一个叫做gedit的应用程序正在运行,我们可以使用pgrep来获得gedit的进程ID
可以使用等号操作符为变量赋值 varName=value,varName是变量名,value是赋给变量的值。如果value不包含任何空白字符(例如空格),那么就不需要将其放入引号,否则必须使用单引号或双引号。
注意:var = value不同于var=value。把var=value写成var = value是一个常见的错误。两边没有空格的等号是赋值操作符,加上空格的等号表示的是等量关系测试。
fruit=apple
count=5
echo "we have $count ${fruit}(s)"
因为shell使用空白字符来分隔单词,所以我们需要加上一对花括号来告诉shell这里的变量名是fruit
获得字符串的长度:length=${#var} length就是字符串所包含的字符数
修改bash的提示字符串
当我们打开终端或者运行shell时,会看到类似于user@hostname:/home/$的提示字符串。不同的GNU/Linux发布版中的提示字符串及颜色各不相同。我们可以利用PS1环境变量来定义主提示字符串。默认的提示字符串是在文件~/.bashrc中的某一行定义的,我们可以修改PS1的值来修改提示字符串
shell脚本编程基础
shell编程:过程式、解释执行
编程语言的基本结构:
各种系统命令的组合
数据存储:变量、数组
表达式:a+b
语句:if
shell脚本:
包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
shell脚本的用途有:
自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本文件
创建shell脚本
第一步:使用文本编辑器来创建文件
第一行必须包括shell声明序列:#!
#!/bin/bash
添加注释
注释以#开头
第二步:运行脚本
给与执行权限,在命令行上指定脚本的绝对或相对路径
直接运行解释器,将脚本作为解释器程序的参数运行
脚本调试
1.检测脚本中的语法错误
bash -n /path/to/script
2.调试执行
bash -x /path/to/script
变量:命令的内存空间
数据存储方式:
字符
数值:整形,浮点型
变量命名法则:
1.不能使用程序中的保留字,例如if,for
2.只能使用数字、字母及下划线,且不能以数字开头
3.见名知义
4.统一命名规则:驼峰命名法(大小写)
变量类型
作用
1:数据存储格式
2:参与的运算
3:表示的数据范围
类型:字符、数值:整型、浮点型
bash中变量的种类
根据变量的生效范围等标准划分为下面变量类型:
局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效(echo $变量名)
环境(全局变量):生效范围为当前shell进程及其子进程(export|declare -x|env 特性:只能向下传递)
本地变量:
位置变量:
特殊变量:$$(表示当前进程的进程编号 pstree -p查看进程树),$?
局部变量
变量赋值:name='value'
可以使用引用value:
(1)可以是直接字串,name="root"
(2)变量引用:name="$USER"
(3)命令引用:name='COMMAND' name=$(COMMAND)
变量引用:$(name) $name
"":弱引用,其中的变量引用会被替换为变量值
'':强引用,其中的变量引用不会被替换为变量值,而保持原字符串
删除变量:unset
环境变量
变量声明、赋值:
export name=VALUE
declare -x name=VALUE
变量引用:$name
显示所有环境变量
env
printenv
export
declare -x
删除变量
unset name
bash内建的环境变量
PATH
SHELL
USER
UID
HOME
PWD
LANG
HOSTNAME
HISTSIZE
只读和位置变量
只读变量:只能声明,但不能修改和删除
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly -p
(umask 666;touch /data/f1):小括号用于一次性任务
x=1;echo "pid=$$";(echo "subpid=$$";echo "subx=$x";x=2;echo "subx2=$x";);echo x=$x
位置变量:在脚本中调用通过命令行传递给脚本的参数
$1,$2,...:对应第1,第2等参数,shift[n]换位置
$0:命令本身
$*:传递给脚本的所有参数,全部参数合为一个字符串
$@:传递给脚本的所有参数,每个参数为独立字符串
$#:传递给脚本的参数的个数
$@ $*只有在被双引号包起来的时候才会有差异
set --清空所有位置变量
#!/bin/bash
scp $1 192.168.189.135:/root/bin
shift[n]的用法
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
shift 2
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
退出状态
进程使用退出状态来报告成功或失败
0代表成功,1~255表示失败
$? 变量保存最近的命令退出状态
退出状态码
bash自定义退出状态码
exit [n]:自定义退出状态码
注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
注意:如果未给脚本指定退出状态码,整个脚本的退出状态取决于脚本中执行的最后一条命令的状态码
基础逻辑运算
算数运算
bash中的算数运算:
+,-,*,/,%取模(取余数)
实现算数运算:
1)let var=算数表达式
2)var=$[算数表达式]
3)var=$((算数表达式))
bash有内建的随机数生成器:$RANDOM(0-32767)
echo $[$RANDOM%50]:0-49之间的随机数
逻辑判断
& 并且 and
0&0=0
0&1=0
1&0=0
1&1=1
| 或者 or
0|0=0
0|1=1
1|0=1
1|1=1
&& 短路与 cmd1 && cmd2 如果cmd1为假,则cmd2不需要执行;如果cmd1为真,则cmd2要执行
cmd1 && cmd2 || cmd3 cmd1为真,执行cmd2;cmd1为假,执行cmd3
||短路或 cmd1||cmd2 如果cmd1为真,则cmd2不需要执行;如果cmd1为假,则cmd2要执行
cmd1 || ( cmd2 ;cmd3 ) 如果cmd1为true,则执行cmd2和cmd3
cmd1 || { cmd2 ; cmd3 } exit时退出
XOR 异或
0^0=0
0^1=1
1^0=1
1^1=0
两个数互换:let a=a^b;let b=a^b;let a=a^b
非:!
!1=0
!0=1
条件测试
判断某需求是否满足,需要由测试机制来实现
专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件性执行中
若真,则返回0
若假,则返回1
测试命令:
test EXPRESSION help test
[ EXPRESSION ]
[[ EXPRESSION ]]
[]和[[]]的区别:
是否用正则表达式,用正则表达式时,用[[]]
注意:EXPRESSION前后必须有空白字符
()、(())、[]、[[]]、{}的区别:
[]和test命令一样,用变量时建议加双引号引起来
-a FILE:True if file exists
-d FILE:True if file is a directory
-f FILE:True if file exists and is a regular file
-z STRING:True if string is empty
-n STRING/STRING:True if string is not empty
():用于多个命令组
false || ( echo cmd1; echo cmd2)
{} flase || { echo cmd1 ; exit ;} 会退出当前shell
false || ( echo cmd1 ; exit ) 不会退出当前shell,相当于开了一个子shell
常用逻辑运算符:
-eq:等于,用于比较数字,即equal
-ne:不等于,用于比较数字,not equal
-lt:小于,letter
-gt:大于,greater
-le:小于等于
-ge:大于等于
EXPR1 -a EXPR2 True if both expr1 and expr2 are true
[ -r /etc/issue -a -w /etc/passwd ] && echo "read and write"
EXPR1 -o EXPR2 True if either expr1 or expr2 is true
[ -r /etc/issue -o -w /etc/passwd ] && echo "read or write"
bash的字符串测试
= 是否等于
> ascii码是否大于assci码
< 是否小于
!= 是否不等于
=~ 左侧字符串是否能被右侧的PATTERN所匹配
注意:此表达式一般用于[[]]中;扩展的正则表达式
-z "STRING" 字符串是否为空,空为真,不空为假
-n "STRING" 字符串是否不空,不空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
使用read命令来接受输入
-p:指定要显示的提示
-s:静默输入,一般用于密码
-n N:指定输入的字符长度N,到达长度N自动退出
-d '字符':输入结束符时退出
-t N:timeout为N秒,到了N秒时自动退出
read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
read -p "input your name:" name
判断yes no
ans="yes";[[ $ans =~ ^([Yy]|[Yy][Ee][Ss])$ ]] && echo true
ans="Yes";[[ $ans =~ ^([Yy]([Ee][Ss])?)$ ]] && echo true
ans="No";[[ $ans =~ ^([Nn][Oo]?)$ ]] && echo true
防止扩展
反斜线(\)会使随后的字符按原意解释
echo your cost:\$5.00
加引号来防止扩展
单引号(')防止所有扩展
双引号(")也防止所有扩展,但是下列情况除外
$(美元符号)——变量扩展
`(反引号)——命令扩展
\(反斜线)——禁止单个字符扩展
!(叹号)——历史命令扩展
bash的配置文件
按生效范围划分,存在两类
全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人(用户)配置:
~/.bash_profile
~/.bashrc
shell登录的两种方式
交互式登录:
1)直接通过终端输入账号密码登录
2)使用"su - username"切换用户
执行顺序:/etc/profile-->/etc/profile.d/*.sh-->~/.bash_profile-->~/.bashrc-->/etc/bashrc
非交互式登录:
1:su username
2)图形界面下打开的终端
执行顺序:~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh
.和source与bash的区别
用bash执行脚本的时候相当于在当前shell的子进程中运行的,不会对当前的环境造成影响
用source执行脚本的时候相当于在当前shell中运行,会对当前的环境造成影响
profile类和bashrc类
按功能划分,存在两类:
profile类和bashrc类
profile类:为交互式登录的shell提供配置
全局:/etc/profile,/etc/profile.d/*.sh
个人:~/.bash_profile
功用:
1)用于定义环境变量
2)运行命令或脚本
bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
1)定义命令别名和函数
2)定义本地变量
编辑配置文件生效
修改profile和bashrc文件后需生效
两种方法:
1:重新启动shell进程
2:.或source
bash退出任务
保存在~/.bash_logout文件中(用户)
在退出登录shell时运行
shell编程进阶
条件选择if语句
选择执行:
注意:if语句可嵌套
单分支
if 判断条件;then
条件为真的分支代码
fi
双分支
if 判断条件;then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
if 判断条件1;then
条件为真的分支代码
elif 判断条件2;then
条件为真的分支代码
elif 判断条件3;then
条件为真的分支代码
else
以上条件都为假的分支代码
fi
条件判断:case语句
case支持glob风格的通配符:
*:任意长度任意字符
?:任意单个字符
[]:执行范围内的任意单个字符
a|b:a或b
case 变量引用 in
PART1)
分支1
;;
PART2)
分支2
;;
...
*)
默认分支
;;(最后这个可以省略)
esac
循环
循环执行
将某代码段重复运行多次
重复运行多少次
循环次数事先已知
循环次数事先未知
有进入条件和退出条件
for,while,until
for循环(循环次数已知)
for 变量名 in 列表;do
循环体
done
执行机制:
依次将列表中的元素赋值给"变量名";每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束
列表生成方式:
1直接给出列表,用空格隔开
2整数列表:
a){start...end}
b)$(seq[start[step]]end)
3返回列表的命令
$(COMMAND) 或反向单引号 ``
4使用glob,如:*.sh
5变量引用
$@(单个变量),$*(整体的)
{1..100..3} 以3为步进,取1到100
seq 1 3 100 以3为步进,取1到100
{1..100} 1到100
while循环(循环次数未知)
while CONDITION;do
循环体
done
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为"true",则执行下一次循环;直到条件测试状态为"false"终止循环
因此:CONDITION一般应该有循环控制变量;而此变量的值会在循环体不断的被修正
进入条件:CONDITION为true
退出条件:CONDITION为false
循环控制语句continue
用于循环体中
continue[N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
#!/bin/bash
for i in {1..3};do
for j in {1..10};do
if [ $j -eq 5 ];then
continue 1
fi
echo "j=$j"
done
echo
done
#!/bin/bash
for i in {1..3};do
for j in {1..10};do
if [ $j -eq 5 ];then
continue 2
fi
echo "j=$j"
done
echo
done
循环控制语句break
用于循环体中
break[N]:提前结束第N层循环,最内层为第一层
#!/bin/bash
for i in {1..3};do
for j in {1..10};do
if [ $j -eq 5 ];then
break 1
fi
echo "j=$j"
done
echo
done
#!/bin/bash
for i in {1..3};do
for j in {1..10};do
if [ $j -eq 5 ];then
break 2
fi
echo "j=$j"
done
echo
done
循环控制shift命令
shift [n]
用于将参量列表list左移指定次数,缺省为左移一次
参量列表list一旦被移动,最左端那个参数就从列表中删除。while循环遍历位置参量列表时,常用到shift
#!/bin/bash
while [ $# -gt 0 ];do
echo "$*"
shift
done
创建无限循环
while true;do
循环体
done
until false;do
循环体
done
while的特殊用法(遍历文件的每一行)
while read line;do
循环体
done < /PATH/FROM/SOMFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将值赋给变量line
对文件逐行操作
#!/bin/bash
while read line;do
uid=`echo $line|cut -d: -f3`
user=`echo $line|cut -d: -f1`
if [ $[uid%2] -eq 0 ];then
echo "$uid:$user"
fi
done </etc/passwd
对命令结果逐行操作
#!/bin/bash
df|while read disk;do
if echo $disk|grep "^/dev/sd" >/dev/null;then
used=`echo $disk | sed -r 's/.* ([[:digit:]]+)%.*/\1/'`
device=`echo $disk|cut -d" " -f1`
[ $used -ge 10 ] && echo "$device will be full,used $used%"
fi
done
特殊用法
help for
select循环与菜单
select variable in list
do
循环体命令
done
select循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量REPLY中
PS3="Please choose the menu(1-3):"
select menu in baoyu yanwo renshen;do
case $menu in
baoyu)
echo "$REPLY:$menu price is 1000"
break
;;
yanwo)
echo "$REPLY:$menu price is 2000"
break
;;
renshen)
echo "$REPLY:$menu price is 3000"
break
;;
*)
echo "the menu is empty"
esac
信号捕捉trap
trap'触发指令'信号
自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作
trap''信号
忽略信号的操作
trap'_'信号
恢复原信号的操作
trap -p
列出自定义信号操作
#!/bin/bash
trap 'echo press ctrl+c' 2
trap -p
for i in {1..10};do
echo $i
sleep 0.5
done
trap '' 2
trap -p
for i in {10..20};do
echo $i
sleep 0.5
done
trap '-' 2
trap -p
for i in {20..30};do
echo $i
sleep 0.5
done
函数
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于:
shell程序在子shell中运行
而shell函数在当前shell中运行,因此在当前shell中,函数可以对shell中变量进行修改
定义函数
函数由两部分组成:函数名和函数体
help function
语法一:
f_name () {
...函数体...
}
语法二:
function f_name {
...函数体...
}
语法三:
function f_name () {
...函数体...
}
函数的定义和使用(函数的优先级比别名和命令高)
可在交互式环境下定义函数
declare -f 查看当前shell的所有函数
unset func 删除函数
如果函数中有局部变量,如果其名称同本地变量,在函数中定义本地变量的方法
local NAME=VALUE
local定义的变量只在函数中有效
declare -i在函数中相当于local
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
函数有两种返回值:
函数的执行结果返回值:
1.使用echo等命令进行输出
2.函数体中调用命令的输出结果
函数的退出状态码:
1.默认取决于函数中执行的最后一条命令的退出状态码
2.自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
使用函数文件
/etc/init.d/functions 定义着许多函数
可以将经常使用的函数存入函数文件,然后将函数文件载入shell
source/. /PATH/functions
文件名可以任意选取,但最好与相关任务有某种联系
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set/declare -f命令查看所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
删除shell函数
现在对函数做一些改动后,需要先删除函数,使其对shell不可用,使用unset命令完成删除函数
命令格式为:unset function_name
全局函数(环境函数)——使子进程也可以使用
声明:export -f function_name
查看:export -f或declare -xf
函数参数
函数可以接受参数:
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;func1 arg1 arg2...
在函数体当中,可食用$1,$2,...调用这些参数;还可以使用$@,$*,$#等特殊变量
数组
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引:编号从0开始,属于数值索引
注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0之后的版本支持
bash的数组支持稀疏格式(索引不连续)
声明数组:
declare -a ARRAY_NAME:普通数组可以不声明
delcare -A ARRAY_NAME:关联索引的数组即关联数组,使用前要声明
注意:两者不可互相转换
数组元素的赋值
1.一次只赋一个元素
array_name[index]=value
weekdays[0]="sunday"
2.一次赋值全部元素
array_name=("val1""val2""val3"...)
3.只赋值特定元素
array_name=([0]="val1" [3]="val2"...)
显示所有数组:declare -a
脚本常用命令
echo
date
练习:
1.编写一个脚本,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其他信息
#!/bin/bash
read -p "Please input yes or no:" ANS
if [ -z "$ANS" ];then
echo "Do not input empty string"
else
case $ANS in
[Yy]|[Yy][Ee][Ss])
echo "your answer is yes"
;;
[Nn]|[Nn][Oo])
echo "your answer is no"
;;
*)
echo "your answer is wrong"
esac
fi
2.编写一个脚本,提示输入正整数的值,计算1+2+...+n的总和
#!/bin/bash
read -p "Please input a number:" num
declare -i sum=0
for i in `seq $num`;do
let sum+=i
done
echo "The sum is $sum"
3.编写一个脚本,打印九九乘法表
#!/bin/bash
for i in {1..9};do
for j in `seq 1 $i`;do
result=$[$i*$j]
echo -e "${j}x${i}=$result\t\c"
done
echo
done
4.编写一个脚本,求100以内奇数之和
#!/bin/bash
declare -i sum=0
declare -i a
for a in {1..100};do
if [ $[a%2] -eq 1 ];then
let sum+=a
fi
done
echo "The sum is $sum"
5.编写一个脚本,打印一个矩形
#!/bin/bash
read -p "Please input the high:" high
read -p "Please input the width:" width
for i in `seq 1 $high`;do
for j in `seq 1 $width`;do
color=$[RANDOM%7+31]
echo -e "\033[1;${color}m*\033[0m\c"
done
echo
done
6.编写一个脚本,打印出国际象棋盘
#!/bin/bash
for i in {1..8};do
for j in {1..4};do
if [ $[i%2] -eq 0 ];then
echo -e "\033[1;43m \033[0m\033[1;41m \033[0m\c"
else
echo -e "\033[1;41m \033[0m\033[1;43m \033[0m\c"
fi
done
echo
done
7.监控httpd服务是否启动,如果暂停服务后立即重启
#!/bin/bash
while true;do
if killall -0 httpd &>/dev/null;then
:
else
systemctl restart httpd &>/dev/null
echo at `date "+%F +%T"` restart httpd >> /data/httpd.log
fi
sleep 10
done
8.编写一个脚本,打印等腰三角形
9.编写一个脚本,随机生成10以内的数字,实现猜字游戏,提示比较大或者比较小,相等则退出
#!/bin/bash
rand=$[RANDOM%11]
while read -p "Please input a number:" num;do
[[ $num =~ ^[[:digit:]]+$ ]] || { echo "Please input a 0-10 digit!";continue; }
if [ $num -gt $rand ];then
echo more
elif [ $num -lt $rand ];then
echo litter
else
break
fi
done
echo "You are right"
10.