一、基础概念
编程语言有低级语言和高级语言两类。
高级语言也有编译型和解释型的区分,编译型语言有C,C++,jave;解释型语言有shell,perl,python
shell脚本是一种包含声明或命令,并符合一定格式的文本文件
shell作为一种过程式解释型的编程语言,它的基本组织结构有:
数据存储:变量、数组
表达式
语句:if while case
shell脚本的格式有:
#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE
shell脚本格式要求首行必须是shebang机制 ,shebang也是指明文件调用的解释器类型
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
- 创建shell脚本
第一步:使用文本编辑器来创建文本文件
第一行必须包括shell 声明序列:#!
#!/bin/bash
添加注释
注释以# 开头
第二步运行脚本:
给予执行权限,通过具体的文件路径指明文件执行。
直接运行解释器,将脚本作为解释器程序的参数运行。
- 脚本调试
检测脚本中的语法错误
bash -n /path/to/some_script
调试分布执行
bash -x /path/to/some_script
-
执行脚本
/root/danran.sh 绝对路径,可以作用于子进程 . /danran.sh 相对路径,可以作用于子进程 bash /root/danran.sh 可以作用于子进 source /root/danran.sh 不会打开子进程,仅仅作用于当前进程
二、变量: 命名的存储空间
数据的存储方式有:
字符:
数值:整型,浮点型
变量类型:
作用:
1、定义数据存储格式
2、定义参与的运算
3、表示的数据范围
类型:
字符
数值:整型,浮点数
编程语言的变量又有强类型和弱类型的区分
强类型:jave,C++
变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换,故需要定义数据类型
弱类型:bash
变量会有隐式转换,故不需要指定数据类型,所有要存储的数据都当做字符进行,不支持浮点数
逻辑运算
与:
1 && 1 = 1
1 && 0 = 0
0 && 1 = 0
0 && 0 = 0
或:
1 || 1 = 1
1 || 0 = 1
0 || 1 = 1
0 || 0 = 0
非:
!1 = 0
!0 = 1
异或:相同为假,不同为真
短路运算:
短路与:
第一个为0,结果必为0
第一个为1,第二个必须要参与运算
短路或:
第一个为1,结果必为1
第一个为0,第二个必须参与运算
三、bash中的变量的种类
根据变量的生效范围为标准:
本地变量:生效范围为当前shell进程,对当前shell之外的其他的shell进程无效,包括当前shell的子shell进程均无效;
环境变量:生效范围为当前shell进程及其子进程;
局部变量:生效范围为当前shell进程中某代码片段(通常指函数);
位置变量:$1,$2,...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数;
特殊变量:$?,$0,$*,$@,$#,$$
本地变量:
变量赋值:name=“value”
可以使用单引号:
value:
(1)可以是直接赋值:name=“filename”
(2)变量引用:name=“$username”
(3)命令引用:name=`COMMAND`,name=$(COMMAND)
变量引用:$(name),$name
"":弱引用,其中的变量引用会被替换为变量值;
'':强引用,其中的变量引用不会被替换为变量值,而保持原字符串;
显示已定义的所有变量:
set
撤销变量:
unset name
环境变量:
变量声明、赋值
export name=VALUE
declare -x name=VALUE
变量引用:$name,$(name)
显示所有环境变量:
export
env
printenv
declear -x
撤销变量:
unset name
bash中有很多内置的环境变量,PATH,SHELL,UID,HISTSIZE,PWD,OLD,HISTFILE,PS1,_
_:上一个命令的最后一个字符串
-
变量命名法则:
1、不能使用程序中的保留字,如if、for
2、只能使用数字、字母及下划线,且不能以数字开头
3、最好见名知义
只读变量:
设置只读变量:
readonly name
declare -r name
查看只读变量:
readonly -p
位置变量:
在脚本代码中调用通过命令行传递给脚本的参数:
$1,$2,....:对应调用第1,第2....个参数,$10以上的应使用括号${10} ,${12}
shift [n] 调换位置,n不能大于参数的个数
$0:调用命名本身
$*:传递给脚本的所有参数,将所有参数作为一个整体字符传递
$@:传递给脚本的所有参数,每一个参数独立传递,与$*只有在使用“”引起来时才有区别
$#:传递给脚本的参数个数
set --:清空所有位置变量参数
特殊变量:$?,$0,$*,$@,$#,$$
$?:返回状态码
$$:返回当前进程的进程编号
四、bash的配置文件
按生效范围划分:存在两类
全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc
按功能划分:存在两类
profile类:为交互式登录的shell提供配置
全局:/etc/profile,/etc/profile.d/*.sh
个人:~/.bash_profile
功用:
(1)用于定义环境变量
(2)运行命令和脚本
bashrc类:为非交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
(1)定义命令别名
(2)定义本地变量
修改文件后生效:
1、重启shell进程
2、source ~/.bashrc
. ~/.bashrc
bash退出任务
保存在~/.bash_logout 文件中(用户)
在退出登录shell 时运行
用于
创建自动备份
清除临时文件
shell登录:
交互式登录:
直接通过终端输入账号密码;
使用“su -UserName”或“su -l UserName”切换的用户
读取文件的顺序:
/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非交互式登录:
su UserName
图形界面下打开终端
执行脚本
文件读取顺序:
~/.bashrc --> /etc/bashrc --> /etc/profile.d/*.sh
编辑配置文件定义的新配置的生效方式:
(1)重新启动shell进程
(2)使用source或.命令进程
五、bash中进行算数运算
+,-,*,/,%,...
实现算数运算:
(1)let var=算数表达式
sum1=3;sum2=4;let sum=$sum1+sum2
(2)var=$(算术表达式)
(3)var=$((算术表达式))
(4)var=$(expr arg1 arg2 arg3...) expr命令,参数之间用空格隔开
sum=$(expr $num1 \* $num2)
*在某些场景下需要转义
(5)declare -i var = 数值
(6)echo '算术表达式' | bc
bash有内置的随机数生成器:$RANDOM
echo $[$RANDOM%60] 对60取模,即生成0-59之间的某个数
echo $[$RANDOM%60+1] 取1-60之间的某数
增强型赋值:
+=,-=,*=,/=,%=
count+=1 <==> count=count+1
自增,自减:
let var+=1 <==> let var++
let var-=1 <==> let var--
练习:
1、编写脚本sumid.sh,计算/etc/passwd文件中的第10个用户和第20个用户的id之和
#! /bin/bash
user10="`head -n $2 $1 | tail -n 1|cut -d: -f3`"
user20="`head -n $3 $1 | tail -n 1|cut -d: -f3`"
let sum=$user10+$user20
echo "user id sum is $sum"
2、编写脚本/sumspace.sh,传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和
#!/bin/bash
spaceline1=`grep "^[[:space:]]*$" $1 | wc -l`
spaceline2=`grep "^[[:space:]]*$" $2 | wc -l`
echo "The sum of space line:$[spaceline1+spaceline2]"
3、生成脚本模板
#!/bin/bash
[ $# -gt 1 ] &&{ echo "the args is error";exit;}
[ $# == 0 ] && read -s -p "please input script name: " name || name="$1"
echo "#!/bin/bash
# filename "$name"
# author:danran
# time is `date +%F`" >"$name"
chmod +x "$name"
vim "$name"
4、编写脚本,统计/etc、/usr、/var目录中有多少个一级子目录和文件
#!/bin/bash
# danran
# time is Mon Jun 5 13:09:12 CST 2017
line1=`ls $1 | wc -l`
line2=`ls $2 | wc -l`
line3=`ls $3 | wc -l`
let sum=$line1+$line2+$line3
echo $sum
六、条件测试
判断某需求是否满足,需要由测试机制来完成;
Nnote:专用的测试表达式需要由测试命令辅助完成测试过程;
评估布尔声明,以便用在条件性执行中
若真,则返回0
若假,则返回1
测试命令:
[:内部命令
[[:shell关键字
测试命令:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
Note:EXPRESSION前后必须有空白字符
bash的测试类型:
数值测试
-gt:大于
-ge:大于等于
-eq:等于
-ne:不等于
-lt:小于
-le:小于等于
字符串测试
==/=:等于
>:大于
<:小于
!=:不等于
~=:左侧字符是否能被右侧的PATTERN所匹配
Note:一般用于[[ ]]中
-z “STRING” 测试字符串是否为空,空为真,不空为假
-n “STRING” 测试字符串是否不空,不空为真,空为假
eg [ -z "$name" ]
[[ $name ~= ^a ]] 匹配$name是否a开头
-v VAR 检查变量VAR是否设置
eg:[ -v var ]
Note:用于字符串比较时的用到的操作数都应使用引号
文件测试
存在性测试:
-a:FILE 同-e
-e:FILE:文件存在性测试,存在为真
存在性及类别测试:
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-S FILE:存在且为套接字文件
-L FILE 或 -h FILE:存在且为链接文件
-p FILE:是否存在且为命名管道文件
先判断文件是否为链接文件,在判断文件的类型,因为测试链接文件时是检测链接原文件,故应先判断是否为链接文件
文件权限测试:(当前用户)判断文件权限时,首先判断文件是否存在
-r:文件是否存在且可读
-w:文件是否存在且可写
-x:文件是否存在且可执行
文件特殊权限测试:
-g FILE:文件存在且拥有guid权限,suid对脚本无效,脚本不是二进制文件
-u FILE:文件存在且拥有suid权限
-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 非
第二种方式:
[ EXPRESSION1 -a EXPRESSION2 ] 与
[ EXPRESSION1 -o EXPRESSION2 ] 或
!EXPRESSION 非
必须使用测试命令进行
[ -f /bin/cat -a -x /bin/cat ] && echo "true"
[ ! -r /app/a -a ! -w /app/a ] && echo true <==> [ ! \( -r /app/a -o -w /app/a \) ] && echo "true"
练习:
1、编写脚本/root/bin/excute.sh,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限否则提示用户非脚本文件
#!/bin/bash
[ $# == 0 ] && read -p "please input fimename" name || name=$1
[ -f $name ] && [[ "$name" =~ \.sh$ ]] && chmod a+x $name || echo "$name not scripts file"
2、编写脚本/bin/per.sh,判断当前用户对指定的参数文件,是否不可读并且不可写
#!/bin/bash
[ ! -r $1 -a ! -w $1 ] && echo "$1 file not read and not write"
3、编写脚本/root/bin/checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
#!/bin/bash
inode=`df -i | grep "^/dev/sd*" | tr -s ' ' '%'| cut -d'%' -f5| sort -r | head -n 1`
disk=`df | grep "^/dev/sd*" | tr -s ' ' '%'| cut -d'%' -f5| sort -r | head -n 1`
[ $inode -gt 80 ] && echo "danran"
[ $disk -gt 80 ] && echo "dan"
七、bash自定义退出状态码
exit [n]:自定义退出状态码为n
Note:脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令后面的数字n。
Note:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行最后一条命令的状态码。
进程使用退出状态来报告成功或失败
0 代表成功,1 -255 代表失败
$? 变量保存最近的命令退出状态
练习:编写脚本/root/bin/argsnum.sh,接受一个文件路径作为参数;
如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;
如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
#!/bin/bash
[ $# -lt 1 ] && (echo "please input a arge" && exit ) || echo "`grep "^$" $1 | wc -l`"
八、read
使用read 来把输入值分配给一个或多个shell 变量
-p 指定要显示的提示
eg:read -p "danran" file
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d ‘ 字符’ 输入结束符
-t N TIMEOUT 为N秒,当等待时机超过N秒时自动退出
read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,多个字符使用重定向赋值多个变量时,使用>>>三个重定向方可
- bash如何展开命令行
把命令行分成单个命令词
展开别名
展开大括号的声明({})
展开波浪符声明(~)
命令替换$() 和 ``)
再次把命令行分成命令词
展开文件通配(* 、? 、[abc] 等等)
准备I/0 重导向(< 、>)
运行命令
防止扩展:
反斜线(\ )会使随后的字符按原意解释
加引号来防止扩展
• 单引号(’ )防止所有扩展
• 双引号(” )也防止所有扩展,但是以下情况例外:
$ (美元符号) - 变量扩展
` (反引号) - 命令替换
\ (反斜线) - 禁止单个字符扩展
! (叹号) - 历史命令替换