shell 脚本基础
1. 基础
基础符号
# echo $? // 上一次命令成功 会返回0 不成功返回非0
#!/bin/bash
ping -c1 www.baidu.com &>/dev/null && echo "baidu is up" || echo "baidu is down"
#! shebang 解释器 没有明确指定解释器shebang会生效 写shell脚本 可以不写 此行 不是注释
&> 标准输出和错误输出 ping的结果 重定向到 黑洞/dev/null
&> a.txt 意思是把 标准输出 和 标准错误输出 都重定向到文件file中
2>&1 意思是把 标准错误输出 重定向到 标准输出 写成2&>1是不可以的
> 默认为标准输出重定向,与 1> 相同
0表示标准输入
1表示标准输出
2表示标准错误输出
符号>& 是一个整体 不可分开,分开后就不是上述含义了。
&>和>& 语义上是没有任何区别的,但是第一中方式是最佳选择,一般使用第一种
&& 前一个命令才会执行后面的命令
|| 前面命令失败 执行
; 命令的排序 不管前面命令成功与否 都执行后面的命令
! !为历史命令记忆功能
# !815 执行第815条命令
# !vim 执行最近一个以vim开头的命令
# tail !$ $为上一条命令的最后一个参数
# !! 执行上一个命令
快捷键
# ^a Ctrl + a 光标移动到命令最前面
# ^e Ctrl + e 光标移动到命令最后面
# ^k Ctrl + k 删除光标后面的内容
# ^u Ctrl + u 删除光标前面的内容
# ^r Ctrl + r 搜索历史命令
# ^d 退出 同exit
# ^y Ctrl + y 粘贴 ^k ^u 删除的内容
# ^l Ctrl + l 清屏 同 clear
# ^s Ctrl + s 冻结
# ^q Ctrl + q 恢复冻结
通配符 \\ 在shell中 在正则表达式中不一样
* 匹配多个字符
? 匹配任意一个字符
[] 匹配括号中的任意一个字符
[abc]
[a-z]
[0-9]
[a-z0-9]
[^0-9] ^ 取反
# ls l[io]ve
() 在子shell中执行命令
# (umask 077; touch aa.txt) \\ 设置umask值 创建aa.txt并赋予权限 umask的值不会影响到当前shell
{} 集合
# mkdir -p {1,2,a} \\ 创建目录 1 2 a
# mkdir -p a{1,2} \\ 创建目录 a1 a2
# mkdir -p {1..3}{a..c} \\ 创建目录 1a 1b 1c 2a 2b 2c 3a 3b 3c
# cp a.txt{,.bak} \\ 复制a.txt 到 a.txt.bak
\ 转义符
2. 变量类型
变量名必须以字母或下划线开头 区分大小写
# vim a.sh
ip=192.168.10.11 \\ 定义一个变量ip 只在当前shell中生效
read ip \\ 从标准输入 读取
read -p "please input a ip: " ip \\ -p 给出提示 语句
if [ $? -eq 0 ]; then \\ if判断
echo "$ip is up"
else
echo "$ip is down"
fi
位置变量
系统定义的 变量1 为脚本的第一个参数 引用为 $1
$1
$2
...
${10}
自定义变量
# ip1=3.3.3.3 \\ 自定义变量 只在当前shell中有效 在脚本中定义的属于 自定义变量
# source a.sh \\ 可以加载a.sh中定义的变量 但是只在当前shell中生效
# . a.sh \\ . 同 source
echo ${NAME:-tom} \\ 变量 NAME 有值 返回NAME的值 如果没有值 显示 tom
echo ${NAME:+tom} \\ 变量 NAME 有值 返回 tom 如果没有值 那就没有值 啥都不返回
系统定义的环境变量
$HOSTNAME \\ 获取主机名
$UID \\ 获取当前用户的uid 为0 则是root用户 可以判断是不是root用户
$USER \\ 获取当前用户的 用户名
$LOGNAME \\ 获取当前用户的 用户名
环境变量
# export ip2=4.4.4.4 \\ 环境变量 仅仅对当前shell及子shell中有效 一般不使用环境变量
# export ip1 \\ 把已存在的 自定义变量 转换成 环境变量 关闭当前shell则失效
# unset ip2 \\ 删除已经定义的 环境变量ip1
# env \\ 查看所有的环境变量
# echo $PATH \\ 可以看到 PATH 路径
# vim /etc/profile \\ 环境变量 声明的 文件 对 所有用户有效 永久有效
PATH=$PATH:/new/bin \\ 可以新加 地址
export PATH
# source /etc/profile \\ 重新加载文件
# echo $PATH
# vim /etc/profile
HISTSIZE=5000 \\ 修改history 数目为5000
# source /etc/profile \\ 重新加载文件
引用变量 2种方法
$ip \\ 引用变量ip
${ip} \\ 引用变量ip 有分歧的地方 使用{}
预定义变量 \\ 可用于脚本中的 参数 统计
$0 脚本名 返回带路径的脚本名
$* 所有的参数
$@ 所有的参数
$# 参数的个数
${#i} \\ 可以返回 变量 i 的长度
$$ 当前进程的PID
$! 上一个后台进程的PID
$? 上一个命令的返回值 0表示成功
# basename /data/a.txt \\ 取文件的 文件名
a.txt
# dirname /data/a.txt \\ 取文件的 目录名
变量的赋值
1 显示赋值
ip=192.168.10.10
school="bei jing" \\ 引号里面可以有空格等
today1=`date +%F` \\ 反引号里面 可执行语句 先执行 等于 $() 命令替换
today2=$(date +%F) \\ (先执行)
2 从键盘读取
read ip \\ 从键盘输入读取
read -p "input ip: " ip \\ -p" " 为提示信息
read -t 5 -p "input ip: " ip \\ -t 5 设定时间 5秒
read -n 2 name \\ -n 2 只取前两个字符
read ip1 ip2 \\ 可以定义多个变量 空格隔开
" " 双引号 弱引用 可以引用变量的值
' ' 单引号 强引用
变量的运算
方法 1
expr 1 + 2 \\ +加 -减 \*乘 /除 %取余数
expr `$i + $n` \\ 两个变量相加
sum=`expr $sum + $i` \\ 运算并赋值
# expr 1 + 2 \* 5
注: 都有空格
默认会把计算结果输出到 屏幕
方法 2 \\ +加 -减 *乘 /除 %取余数
echo $((1+2)) \\ $(()) 里面放运算 算式
echo $(($num1+$num2)) \\ 两个变量相加
echo $((num1+num2)) \\ 两个变量相加 可以省略里面的 $符号
echo $((5-3*2))
echo $(((5-3)*2)) \\ 里面() 为先算
echo $((2**5)) \\ 2的5次方
sum=$((1+2)); echo $sum \\ 计算的值 给变量sum
注: 用于 要运算的结果
借助echo输出而已 默认没有输出
方法 3
echo $[] \\ +加 -减 *乘 /除 %取余数 用法同 $(())
方法 4
let i=2+3
let i++ 先赋值在计算
let ++i 先计算
let i=i+1
let sum=$sum+$i 用sum的值加上i的值赋给sum
let sum+=$i 用sum的值加上i的值赋给sum
let sum-=$i 用sum的值减去i的值赋给sum
let sum*=$i
let sum\=$i
注: let用于变量赋值 时候的 运算
不能加空格
小数运算
bc \\ 交互式计算器
echo "2*4" | bc \\ 一般使用传递参数 使用
echo "2^4" | bc \\ 2的四次方
echo "scale=2;6/4" \\ 保留两位小数
变量的删除
url=www.baidu.com \\ 做以下操作不会对变量进行改变
${#url} \\ 获取变量的长度 可使用 echo ${#url} 协助查看
${url#www.} \\ 删除 www.
${url#*bai} \\ 删除 www.bai *为匹配前面的字符
${url#*.} \\ 最短匹配 1个# 从前往后删除 到 第一个点 .
${url##*.} \\ 贪婪匹配 2个## 从前往后删除 到 最后一个点 .
${url%.com} \\ %从后往前删除
${url%.*} \\ 从后往前删除 到.
${url%%.*} \\ 从后往前删除 删除到最后一个点 .
索引及切片
url=www.baidu.com
0123456789 \\ 对应的索引 一次类推10 11 12 ... ...
${url:0:5} \\ 从第0个索引取5个值
www.b
${url:5:5} \\ 从第5个索引取5个值
idu.c
${url:5} \\ 从第5个索引到最后
替换
url=www.baidu.com
${url/baidu/google} \\ baidu 换个 googel
替代
${var1-baidu.com} \\ 变量var1 -为替代符号 如果变量var1有值或空值 则不替代 如果没有定义则赋予baidu.com
${var2:-baidu.com} \\ 变量var2 :-为替代符号 如果变量var2有值则不替代 如果没有定义或空值则赋予baidu.com
+
:
=
:=
?
:?
脚本中的符号
continue \\ 跳过 跳过此次循环 执行下一次循环 只在循环中有效的参数
break \\ 跳出循环 结束循环 离开循环 执行后面的代码 只在循环中有效的参数
break 1 \\ 同上 跳出 1层循环
break 2 \\ 跳出 2层循环
exit \\ 结束符号 退出脚本 后面的代码 不在执行 任何时候碰到都退出脚本
exit 1 \\ 返回1 可以任何一个数 默认返回0为成功
shift \\ 把参数 向左边 移动一位 删除一位 同 shift 1
sleep 1 \\ 延迟1秒
() 子shell
(()) 运算 数值比较
$() 命令替换 同 ` `
$(()) 运算
{} 集合
${} 变量引用 替换 替代
[] 条件测试 不支持正则
[[]] 条件测试 支持正则表达式
$[] 整数运算
# sh -vx a.sh 以调试的方式执行 查询整个执行过程
# bash -vx a.sh 以调试的方式执行 查询整个执行过程
# sh -n a.sh 检测语法
# bash -n a.sh 检测语法
执行方式:
1 bash aa.sh \\ 在子shell中执行 另开一个shell执行
./aa.sh
2 . aa.sh \\ 在当前shell中执行 希望脚本中的东西影响到当前 要在当前shell中执行
source aa.sh
3. 条件测试
1 test 条件表达式
if test -d /data/www/; then \\ -d 检测是否为目录
else
fi
2 [ 条件表达式 ] \\ [ 为 关键字 同test ]括号只为配合左边的
if [ -d /data/www/ ]; then
else
fi
3 [[ 条件表达式 ]]
文件测试 符号
-e dir|file 是否存在文件或目录
-d dir 是否存在目录
-f file 是否存在文件
-r file 当前用户对改文件是否有读权限
-x file 当前用户对改文件是否有执行权限
-W file 当前用户对改文件是否有写权限
-L file 是否为链接文件
-b 如果 FILE 存在且是一个块特殊文件则为真。
-c 如果 FILE 存在且是一个字特殊文件则为真。
-z 字符串的长度为零则为真。 字符串为空即NULL时为真。
-n 字符串的长度不为零则为真
# if echo $a | grep -q '[^0-9]' &> /dev/null ;then \\ 检测是否为数字
以 [ 为例子 同 test 和 [[
# [ -L /etc/hosts ]; echo $? \\ 是链接文件 返回0 不是非0
# [ ! -d /data ]; && mkdir /data \\ 不是目录返回真 则创建
# [ -d /data ]; || mkdir /data \\ 是目录 返回真 则不执行后面的 不是目录返回假则创建
数值比较
-eq 等于 equal 相等
-ne 不等于 not equal 不等
-gt 大于 greater than 大于
-ge 大于等于 greater than or equal 大于或等于
-lt 小于 less than 小于
-le 小于等于 less than or equal 小于或等于
((1<2)) 小于
((1==2)) 等于
((1>2)) 大于
((1>=2)) 大于等于
((1<=2)) 小于等于
((1!=2)) 不等于
((`id -u`>0)) 一个命令的结果 是否大于0
(($UID==0)) 一个命令的结果 是否等于0
字符串比较 \\ 一般都使用 " "
[ "$USER" = "root" ]
[ "$USER" == "root"]
[ "$USER" != "root"]
[ -z "$BBB"] 字符长度是为0
[ -n "$BBB"] 字符长度不为0
[ 1 -lt 2 -a 5 -gt 10 ] -a 并且 在 [ ] 里面加
[ 1 -lt 2 -o 5 -gt 10 ] -o 或者 在 [ ] 里面加
[[ 1 -lt 2 && 5 -gt 10 ]] && 并且 在 [[ ]] 里面加 支持正则表达式
[[ 1 -lt 2 || 5 -gt 10 ]] || 或者 在 [[ ]] 里面加
4. 循环 判断 语句
case语句
case "$aa" in
111)
命令
;;
222)
命令
;;
333)
命令
;;
"")
;; \\ 输入为空 执行命令为空
*)
无匹配后 执行的命令 \\ 最后一个 可以不加 ;;
esac
:和 true 一样 都是永远返回为真 可用 用在死循环的条件上 或 if判断之后 执行一个为真的命令上
if 语句
if [ 条件 ]; then
elif [ 条件 ]; then
else
fi
if test ; then \\ test 后跟 测试语句 相当于[ ]
else
fi
if [[ 条件 ]]; then \\ 支持正则表达式
else
fi
if 条件; then
else
fi
if ((条件)); then \\ 数值比较
else
fi
for 循环
shell 风格
for i in `seq 1 20`
do
done
for i \\ 没有 in 意思是取 所有的参数
for i in {1..20}
for i in 1 2 3 4 5
for i in `cat ip.txt` \\ 把文件url.txt 赋给 i
for i in `cat $1` \\ 把第1个参数文件 作为变量 赋给 i
for i in `seq 2 254`
for i in `seq $n`
c风格
for((i=1;i<=5;i++))
for((i=1;i<=$n;i++))
IFS=$'\n' \\ 定义for使用回车分隔符 默认空格 两种方式
IFS='
' \\ 第二种方法
`seq 2 254` 产生序列
seq 10 打印 序列 1-10
seq 2 10 打印 序列 从 2 - 10
seq 1 2 10 打印 序列 步长为2 1 3 5 7 9
seq -w 10 -w 等位补齐
01
02
... ...
10
seq $i 可以使用变量
if id teooo &>/dev/null; then \\ if条件 形式01
id teooo &>/dev/null
if test $? -eq 0; then \\ if条件 形式02
if [ $? -eq 0 ]; then \\ if条件 形式03
while 循环 循环不固定 一般使用while
while 条件 条件为真的时候 循环
do
done
while read line
do
done < a.txt
while : :为真或 true
do
done
unit 循环 可用于 检查 是否上线
unit 条件 与while相反 条件为假的时候循环
do
done
unit false
do
done
unit [ 1 -eq 2 ]
do
done
unit asdfasdf &>/dev/null 随便写一个不存在的命令即可
do
done
select 循环 常用于 打印菜单
select i in aa bb cc
do
done
expect 解决交互 看实例
# yum install expect
函数定义
mayann(){
}
function mayann{
}
mayann 调用
mayann 10 调用的时候 传递参数 传给$1
5. 正则表达式 RE
基本元字符
^ 行首定位符 ^love
$ 行尾定位符 love$
. 匹配单个字符 l..e
* 匹配前面的字符0到多次 ab*love
[] 匹配指定范围内的一个字符 [lL]ove \\ 只匹配一个
[-] 匹配指定范围内的一个字符 [a-z]ove [a-z0-9]ove \\ 只匹配一个
[^] 非 不在指定组内的字符 [^a-z0-9] [^a-Z] \\ a-Z 涵盖 大写和小写
\ 用来转移元字符 love\.
\< 词首定位符 \<love \\ 在这里 不是 转义符 整体是一个元字符
\> 词尾定位符 love\>
\(..\) 标签 后向引用 s#\(www.\)#\1baidu.com#g
\1 \2 调用 后项引用
x\{m\} 字符x重复出现m次 o\{5\}
x\{m,\} 字符x重复出现m次以上 o\{5,\}
x\{m,n\}字符x重复出现m到n次 o\{5,10\}
基本衍生常用组合
.* 匹配多个字符
^$ 空行 只有一个 回车 为空行
^[ \t]*$ 空格或tab制表符有0个或多个 的行
^# 注释行 以#号开始的行
^[ \t]*# #前面有有0个或多个空格或者tab制表符的行 也是注释行
扩展元字符
+ 匹配1个或多个前面的字符 [a-z]+ove
? 匹配0个或1个前面的字符 lo?ve
| 或者 a|b 匹配a或者b love|hate
() 字符组 loveadble|re love(able|rs)ov (ov)+
(..)\1 标签 后项引用 (love)able\1er
x{m} 字符x重复m次 o{5} [a-z]{9} 连续出现相同的英文字母9个
x{m,} 字符x重复至少m次 o{5,}
x{m,n} 字符x重复m到n次 o{5,10}
POSIX字符
[:alnum:] 字母与数字字符 [[:alnum:]] 使用的时候在加 [] 为 匹配指定范围内的一个字符
[:alpha:] 字母字符
[:blank:] 空格与制表符
[:digit:] 数字字母
[:lower:] 小写字母
[:upper:] 大写字母
[:punct:] 标点符号
[:space:] 换行符 回车 空格等在内的所有空白
正则表达式 练习
获取ip
# ifconfig | grep 'inet' | sed -n '1p' |awk '{print $2}'
内存剩余
# free -m | grep Mem | awk '{print $4}'
硬盘剩余
# df -h | grep '/$' | awk '{print $4}'
生成30位的随机口令
# cat /dev/urandom | tr -dc "[:alnum:]" | head -c 1
判断主机版本号
# grep -o "[0-9]\+" /etc/centos-release | head -n1
查出分区空间使用率的最大百分比值
# df | grep '/dev/sd' | grep -o "[0-9]*%" | grep -o "[0-9]\+" | sort -n | tail -1
# df | grep '/' | awk '{print $1,$5}' | sort -nr -k2 | head -n1
删除 7天以前的备份文件
find /data -mtime +7 | xargs rm -rf
find /data -mtime +7 -exec rm -rf {} \;
6. shell 脚本加密
gzexe 对 shell 脚本的加密与解密
# gzexe a.sh \\ 加密 a.sh 脚本 并同时生成源文件a.sh~
# bash -x a.sh \\ 调试 执行 以下为解密
skip=44 \\ 第44 行的时候 进行了 压缩
# tail -n +44 a.sh > a.gz \\ 生成 a.gz 文件
# gunzip a.gz
# diff a.sh~ a \\ a 为解密的文件
shc 对 shell 脚本加密 \\ 亦可先使用shc加密 在使用 gzexe加密
# yum install shc \\ epel源
# shc -v -r -f a.sh \\ 加密 脚本 不对源文件进行操作
a.sh.x.c \\ 此为C语言的 源文件
a.sh.x \\ 此为加密后的脚本 可以直接执行
# mv a.sh.x b.sh
# sh b.sh
7. shell 执行方式:
1 bash aa.sh \\ 在子shell中执行 另开一个shell执行
./aa.sh
2 . aa.sh \\ 在当前shell中执行 希望脚本中的东西影响到当前 要在当前shell中执行
source aa.sh