shell 基础学习
Shell工具
以下所有工具命令不会直接改变原文件,需要用重定向到文件中才能保存
grep
行过滤工具:grep [选项] '关键字' 文件名
-n 行号
-i 忽略大小写ignore
-v 取反
^key 以...开头
key$ 以...结尾
^$ 匹配空行
-A 后几行after
-B 前几行before
-C 上下文context
-w 匹配单词 ('hello' 不会匹配到'helloworld') 精确匹配
-o 只打印出关键字本身
-c 统计匹配的行数
-e 使用正则
cut
列截取工具:cut 选项 文件名
-c 以字符为单位截取
-d 自定义分隔符,默认为'\t'
-f 与-d一起使用,指定截取哪个区域
cut -d: -f1,6,7 文件 以:分割,截取第1,6,7列
cut -c1-4 文件 截取文件中每行的第1-4个字符(同理5-表示第5开始到结尾)
(head tail) -1
sort
sort用于排序,它将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们升序输出
-u 去除重复行(不管重复行连续与否)
-r 降序排列,默认升序
-o 将排序结果输出到文件中,类似重定向符号>
-n 以数字排序,默认是按字符排序
-t 分隔符
-k 第N列(与-t配合类似于cut的-d -f)
-b 忽略前导空格
-R 随机排序,每次运行结果不同
uniq
用于去除连续的重复行
-i 忽略大小写
-c 统计重复行次数
-d 只显示重复行
tee
从标准输入读取并写入到标准输出和文件,即:双向覆盖重定向(屏幕输出|文本输入)
-a 双向追加重定向
echo hello world | tee 文件
屏幕显示hello world 文件内容也是hello world
diff
用于逐行比较文件的不同
注意:diff描述两个文件不同的方式是告诉我们怎样改变第一个文件之后与 第二个文件匹配
diff [选项] 文件1 文件2
-b 不检查空格
-B 不检查空白行
-i 不检查大小写
-W 忽略所有空格
--normal 正常格式显示(默认)
-c 上下文格式显示
-u 合并格式显示
正常格式显示:c=change d=delete a=add
上下文格式:!改变 -删除 +增加
合并格式:+ - 只显示第一个文件需要增加和删除的内容
比较两个目录的不同:
- 默认情况下也会比较两个目录里相同文件的内容
- 如果只需要比较两个目录里文件的不同,不需要进一步比较文件内容,需要加-q选项
注意touch dir/file{1..5}会在该目录生成5个文件
以一个文件为标准,修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式将文件1更新成文件2
- 先找出文件不同,然后输出到一个文件 diff -uN (-N 将不存在的文件当做空文件) file1 file2 > file.patch
- 将不同内容打补丁到文件 patch file1 file.patch
- 测试验证
- paste
用于合并文件行
-d 自定义间隔符,默认是tab
-s 串行处理,非并行
tr
用于字符转换,替换和删除,主要用于删除文件中的控制字符或进行字符转换
用法1:命令的执行结果交给tr处理,其中string1用于查询,string2用于转换处理
# commands | tr 'string1' 'string2'
用法2:tr处理的内容来自文件,记住要使用"<"标准输入
# tr 'string1' 'string2' < filename
用法3:匹配string1进行相应操作,如删除操作
# tr options 'string1' < filename
options
-d 删除字符串1中所有输入字符
-s 删除所有连续重复出现的字符序列,只保留第一个,即将重复出现的字符串压缩为一个字符 aaaaabbbbb ab
压缩空格有奇效
常用的匹配字符串:一个对一个的替换
a-z或[:lower:] 匹配所有小写字母
A-Z或[:upper:] 匹配所有大写字母
0-9或[:digit:] 匹配所有数字
[:alnum:] 匹配所有字母和数字
[:alpha:] 匹配所有字母
[:blank:] 匹配所有水平空白
[:punct:] 匹配所有标点符号
[:space:] 匹配所有水平或垂直的空格
[:cntrl:] 匹配所有控制字符 \f \n \r \t
:set list 在vim中会显示控制字符
'' 或'[]'包住多个要替换的字符都是一样的
常用快捷键
^c 终止前台运行的程序
^z 将前台运行的程序挂起到后台 stopped (命令 & 表示后台运行)
^d 退出 等价exit
^l 清屏
^a |home 光标移到命令行的最前端
^e |end 光标移到命令行的后端
^u 删除光标前所有字符
^k 删除光标后所有字符
^r 搜索历史命令
常用通配符
* 匹配0或多个任意字符
? 匹配任意单个字符
[list] 匹配list中的任意单个字符,或一组单个字符 eg: file[1-12]表示file1和file2
[!list] 匹配除了list中的任意单个字符
{string1,string2,...} 匹配string1,string2或更多字符串 file{1..12}表示file1到file12
{1..6..2}表示1到6的数间隔为2的列表
bash中的引号
双引号"" 会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
单引号'' 会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
反撇号`` 反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用,只能用$()
sed
-i 原地删除 eg: sed -i "/line_number/d" file_name
Shell变量
- shell的基本语法结构
变量定义,条件判断,循环语句(for, until, while),分支语句,函数和数组
- 基本正则表达式
- 文件处理三剑客:grep, sed, awk的使用
- 使用shell脚本完成一些较复杂的任务,如服务搭建,批量处理等
以上为基本内容,还有很多更深更难的语法需要扩充学习
#! /bin/env bash 指定解释器,env bash可以找到环境变量
标准执行方式:
(1) chmod +x 脚本名字
(2) 绝对/相对路径
非标准的:没有可执行权限也可以执行脚本
bash/sh/dash 脚本名字
+x 可以显示执行过程 (用于调试!!!)
+n 用于查看脚本语法问题
source命令 # 读取该文件,获知其内容,使其在当前终端定义并生效
变量名区分大小写
变量名和值不要加入特殊字符(有空格的用括号括起来)
变量名不能以数字开头
等号两边不能有任何空格
定义变量基本方式
A=1234567
echo A
echo {A}
echo {A:2:4} 3456 切出片段,以第3个字母为起点,长度为4 切片
命令执行结果赋值给变量
A=`hostname`
A=$(hostname)
交互式定义变量
让用户自己给变量赋值,或来自文件( < filename)
read [选项] 变量名
-p 定义提示用户的信息
-n 定义字符数(限制变量值的长度)
-s 不显示(不显示用户输入的内容)
-t 定义超时时间,默认单位为妙(限制用户输入变量值的超时时间)
declare 定义有类型的变量
给变量做一些限制,固定变量的类型,比如:整型、只读
用法:declare 选项 变量名=变量值
-i 将变量看成整数 declare -i A=123
-r 定义只读变量 declare -r B=hello
-a 定义普通数组;查看普通数组
-A 定义关联数组;查看关联数组
-x 将变量通过环境导出 declare -x AAA=123456 等于 export AAA=123456
unset 取消变量
变量的分类
本地变量:当前用户自定义的变量,当前进程中有效,其他进程及当前进程的子进程无效。
环境变量:当前进程有效,并且能被子进程调用
- env查看当前用户的环境变量
- set查询当前用户的所有变量(临时变量与环境变量)
- export 变量名=变量值 或者 变量名=变量值; export 变量名
全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用
$HOME/.bashrc 当前用户的bash信息,用户登录时读取 定义别名、umask、函数等
$HOME/.bash_profile 当前用户的环境变量,用户登录时读取
$HOME/.bash_logout 当前用户退出当前shell时最后读取 定义用户退出时执行的程序等
$etc/bashrc 全局的bash信息,所有用户都生效
$etc/profile 全局环境变量信息 系统和所有用户都生效
以上文件修改后,都需要重新source让其生效或者退出重新登录
用户登录系统读取相关文件的顺序:
1. /etc/profile
2. $HOME/.bash_profile
3. $HOME/.bashrc
4. $etc/.bashrc
5. $HOME/.bash_logout
一般全局与局部冲突,以局部为主,如果在读取全局profile前设置局部变量,则局部变量被全局覆盖
系统变量(内置bash中的变量):shell本身已经固定好了它的名字和作用
$? 上一条命令执行后返回的状态,状态值为0表示执行正常,非0表示执行异常或错误
$0 当前执行的程序或脚本名
$# 脚本后面接的参数的个数
$* 脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@ 脚本后面所有参数,参数是独立的,也是全部输出
$1-$9 脚本后面的位置参数,$1表示第1个位置参数,依次类推
$(10)-$(n) 扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上括起来)
$$ 当前所在进程的进程号,如echo $$
$! 后台运行的最后一个进程号(当前终端)
!$ 调用最后一条命令历史中的参数
jobs 查看后台进程
kill -9 %数字 # 终止这个后台进程(数字为job number)
简单四则运算
$(()) echo $((1+1))
$[] echo $[10-5]
expr expr 10 / 5 # 要有空格且*要转义 不能求**幂
let n=1;let n+=1 等价于 let n=n+1 # 省略$
当要求小数运算时 只能有bc eg:echo 1+1.5|bc 或 bc模型输入
条件判断语法
- 格式1:test 条件表达式
- 格式2:[ 条件表达式 ]
- 格式3:[[ 条件表达式 ]] 支持正则
更多的判断,man test去查看,很多的参数都用来进行条件判断
(一)判断文件类型
-e 判断文件是否存在(任何类型文件)
-f 判断文件是否存在并且是一个普通文件
-d 判断文件是否存在并且是一个目录
-L 判断文件是否存在并且是一个软连接文件
-b 判断文件是否存在并且是一个块设备文件
-S 判断文件是否存在并且是一个套接字文件
-c 判断文件是否存在并且是一个字符设备文件
-p 判断文件是否存在并且是一个命名管道文件
(二)判断文件权限
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
-u 是否有suid,高级权限冒险位
-g 是否sgid,高级权限强制位
-k 是否有t位,高级权限粘滞位
-s 判断文件是否存在并且不为空
! -s 判断文件是否存在并且为空
(三)判断文件新旧
这里的新旧是指文件的修改时间
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬链接,是否指向同一个inode
(四)判断整数
-eq 相等 如:if [ "$a" -eq "$b" ]
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
< 小于(需要双括号),如:(("$a" < "$b"))
<= 小于等于(需要双括号),如:(("$a" <= "$b"))
> 大于(需要双括号),如:(("$a" > "$b"))
>= 大于等于(需要双括号),如:(("$a" >= "$b"))
(五)判断字符串
-z 判断是否为空字符串,字符串长度为0则成立
-n 判断是否为非空字符串,字符串长度不为0则成立
string1 = string2 判断字符串是否相等
string != string2 判断字符串是否不等
字符串最好用""括起来,形成一个整体
(六)多重条件判断
-a 和 && 逻辑与 [ 1 -eq 1 -a 1 -ne 0 ] [ 1 -eq 1 ] && [ 1 -ne 0 ]
-o 和 || 逻辑或 [ 1 -eq 1 -o 1 -ne 1 ] [ 1 -eq 1 ] || [ 1 -ne 1 ]
&& 前面的表达式为真,才会执行后面的代码
|| 前面的表达式为假,才会执行后面的代码
; 只用于分割命令或表达式,不考虑前面的语句是否正确执行,都会执行;号后面的内容
多个条件在一起时要从左往右依次按&&和||的方式判断
类c风格的数值比较
在(( ))中,=表示赋值,==表示判断,!=表示不等于,<
字符串比较
注意:双引号引起来,看作一个整体;= 和 == 在 [ 字符串 ] 比较中都表示判断(用于字符串的判断)
[ ] 和 [[ ]]的区别
当字符串为空时,[[ ]]可以不将字符串用双引号括起来且不报错,[]不行 eg [ $A = hell ]
&& 写在 [[ ]]之间不会报错
流程控制语句
if [ condition ];then
command
elif [ condition2 ];then
command
else
command
fi
[ 条件 ] && command
上图选择的路很多,能走的只有一条
pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
选项:
-o 仅显示找到的最小(起始)进程号
-n 仅显示找到的最大(结束)进程号
-l 显示进程名称
-p 指定父进程号;pgrep -p 4764 查看父进程下的子进程id
-g 指定进程组
-t 指定开启进程的终端
-u 指定进程的有效用户id
&>/dev/null 可以停止将输出显示到屏幕上
先执行,再用$?判断是否执行成功
ps 和 pgrep 可以用来判断一个进程是否存在
wget , curl 和 elinks --dump 用于判断一个服务是否可以正常访问
循环控制语句
for循环
固定次数的循环
(一)列表循环
for variable in {list}
do
command
command
done
或者
for variable in a b c
do
command
command
done
可以用{1..10}或sep或枚举 进行循环
(二)不带列表循环
由用户指定参数和参数的个数
for variable # 实际是 for i in '"$@"'
do
command
command
done
(三)类C风格的for循环
for ((expr1;expr2;expr3))
do
command
command
done
for ((i=1;i<=5;i++))
do
echo $i
done
continue 跳出当前循环
break 跳出循环
exit 跳出程序
练习:
- 如果目录不存在就新建
test -d /tmp/dir1 && mkdir /tmp/dir1 -p
或者
if [ ! -d /tmp/dir1 ];then mkdir /tmp/dir1 -p fi
逻辑运算符与条件语句,这两者是等价的可以相互转换
- 判断所输整数是否为质数
#!/bin/env bash
read -p "请输入一个正整数数字:" number
[ $number -eq 1 ] && echo "$number不是质数" && exit
[ $number -eq 2 ] && echo "$number是质数" && exit
for i in `seq 2 $[$number-1]`
do
[ $[$number%$i] -eq 0 ] echo "$number不是质数" && exit
done
echo "$number是质数" && exit
- 批量创建用户
批量添加5个新用户,以u1到u5命名,并统一加一个新组,组名为class,统一改密码为123
#!/bin/env bash
grep -w ^class /etc/group &>/dev/null # 精确判断是否存在class这个组
test $? -ne 0 && groupadd class
for ((i=1;i<=5;i++))
do
useradd -G class u$i
echo 123|passwd --stdin u$i
done
批量创建5个用户stu1-stu5,要求这几个用户的家目录都在/rhome
#!/bin/bash
#判断rhome是否存在
[ -f /rhome ] && mv /rhome /rhome.bak
test ! -d /rhome && mkdir /rhome
for ((i=1;i<=5;i++))
do
useradd -d /rhome/stu$i stu$i
echo 123|password --stdin stu$i
done
- 局域网内脚本检查主机网络通讯
局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件中
10.1.1.1 - 10.1.1.10
#!/bin/bash
ip=10.1.1
for ((i=1;i<=10;i++))
do
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt # >>是追加
else
echo "$ip.$i is down" >> /tmp/ip_down.txt # >>是追加
done
time ./path/demo.sh #可以用来记录执行时间
并发执行以上任务
#!/bin/bash
ip=10.1.1
for ((i=1;i<=10;i++))
do
{ # (1) 花括号包起来
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt # >>是追加
else
echo "$ip.$i is down" >> /tmp/ip_down.txt # >>是追加
}& #(2) 在后台执行
done
wait #(3) 等待所有线程执行完毕
echo "IP is ok"
while循环
while 表达式
do
command
done
死循环
while true
while : #等价
tail -f 循环读取文件尾部,动态显示更新的内容
while read ip passwd
do
command
done < ip.txt # 用循环从文件中读取
until语法结构
条件为假就进入循环,条件为真就退出循环
until expression [ 1 -eq 1 ] (( 1 >= 1 ))
do
command
command
done
新建10个user,其中后5个的home目录在rhome下
#!/bin/env bash
if [ -d /rhome ];then
echo "/rhome目录已存在"
else
mkdir /rhome
echo "/rhome目录不存在,已完成创建"
fi
i=1
until [ $i gt 10 ]
do
if [ $i -le 5 ];then
useradd -u $[1000+$i] stu$i
echo 123|passwd --stdin stu$i
else
useradd -d /rhome/stu$i stu$i
echo 123|passwd --stdin stu$i
fi
let i++
done
随机数
系统变量:RANDOM,默认会产生0~32767的随机整数
前言:要想调用变量,不管是什么变量,都要用$
#产生0~100之间的随机数
echo $[ $RONDOM%101 ]
#产生10~99之间的随机数
echo $[ $RONDOM%90+10 ] 0~89 + 10
head -随机产生的行号 file_name|tail -1
嵌套循环
for while until 都可以相互嵌套
echo默认有换行的属性,-n 取消换行
shift位移
shift 使位置参数向左移动,默认移动一位,可以使用shift 2
eg: ./demo.sh 1 2 3 4
for i
do
echo $1 # 依次输出 1 2 3 4
shift
done
expect
expect自动应答 tcl语言
需求1:A远程登录到server上什么都不做
#!/usr/bin/expect
#开启一个程序
set ip 10.1.1.1 # 定义变量 [ lindex $argv 0 ] 可以从脚本的参数中读取
set pass 123456
spawn ssh root@$ip # 执行程序
捕获相关内容
expect {
"(yes/no)?" { send "yes\r";exp_continue } # exp_continue 的作用是当匹配不到yes/no时继续
"password:" { send "$pass\r" } # 一定要回车才能继续往下走 \r 或 \n 都可以
}
interact # 不加会退出ssh登录
expect "#" # 期待匹配到登录的远端服务器终端出现#开头
send "rm -rf /tmp/*\r"
send "touch /tmp/file{1..3}\r"
send "date\r"
send "exit\r"
expect eof
- 循环 useradd username
- 登录远程主机->ssh->从ip.txt文件读取IP和密码分别赋值给两个变量
- 使用expect程序解决交互问题
#!/bin/bash
while read ip pass
do
/usr/bin/expect <<-END &>/dev/null # 声明一个结束符(什么都行),其中-表示后面的END不用顶格可以添加制表符
spawn ssh root@ip
expect {
"(yes/no)?" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }
expect eof
END # 退出expect
done < ip.txt
案例分析
- 跳板机上的yunwei用户生成钥对
- 判断账号是否存在(id yunwei)
- 判断该用户是否有密钥对文件 [ -f xxx ]
- 判断expect是否安装
- su - yunwei
- 判断局域网内的主机是否ping通(循环判断|for while until)
- 循环判断 for while
- 循环体do ... done ping 主机 如果ping通,调用expect程序自动应答推送公钥
- 测试验证是否免密登录成功
- 功能1:管理员root创建yunwei用户和安装expect软件包
#!/bin/env bash
# 实现批量推送公钥
# 判断jumper上的yunwei账号是否存在
{
id yunwei &>/dev/null
[ $? -ne 0 ] && useradd yunwei && echo 123|passwd --stdin yunwei
} &>/dev/null
# 判断expect程序是否安装
rpm -q expect
[ $? -ne 0 ] && yum -y install expect && echo "expect软件已经安装成功"
- 功能2:判断主机是否ping通且yunwei用户推送公钥
#!/bin/env bash
# 判断yunwei用户密钥对是否存在
home_dir=/home/yunwei
[ ! -f $home_dir/.ssh/id_rsa.pub ] && ssh-keygen -P '' -f $home_dir/.ssh/id_rsa &>/dev/null
#循环检查主机的网络并且进行公钥推送
ip_txt=/path_to_txt/ip.txt
# 文件中很多行,每一列用:分隔
# tr ':' ' ' < $ip_txt|while read ip passwd
# do
# {
# }&
# done
for i in `cat $ip_txt` # 一行一行读取
do
ip=`echo $i|cut -d: -f1`
pass=`echo $i|cut -d: -f2`
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip >> ~/ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect "(yes/no)" { send "yes\n";exp_continue }
expect "password:" { send "$pass\n" }
expect eof
END
else
echo $ip >> $home_dir/ip_down.txt
fi
done
# 测试验证
remote_ip=`head -1 ~/ip_up.txt`
ssh root@$remote_ip hostname
[ $? -eq 0 ] && echo "公钥推送成功"
sudo
yunwei用户sudo授权:
visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
yunwei ALL=(root) NOPASSWD:ALL,!/sbin/shutdown,!/sbin/init,!/bin/rm -rf /
解释说明:
1)第一个字段yunwei指定的是用户:可以是用户名,也可以是别名。每个用户设置一行,多个用户设置多行,也可以将
多个用户设置成一个别名后再进行设置
2)第二个字段ALL指定的是用户所在的主机:可以是ip,也可以是主机名,表示该sudo设置只在该主机上生效,
ALL表示所有主机上都生效。!限制的都是本机,也就是限制使用这个文件的主机;一般都是指定为ALL表示所有主机,
不管文件拷贝到那里都可以用。比如:10.1.1.1=...则表示只在当前主机生效。
3)第三个字段(root)括号里指定的也是用户,指定以什么用户身份执行sudo,即使用sudo后可以享有所有root
账号下的权限,如果要排除个别用户,可以在括号内设置,比如ALL=(ALL,!oracle,!pos)。
4)第四个字段ALL指定的是执行的命令:即使用sudo后可以执行所有的命令,除了关机和删除跟内容以外;也可以设置别名。NOPASSWD:ALL表示使用sudo的不需要输入密码。
5)也可以授权给一个用户组
%admin ALL=(ALL) ALL 表示admin组里的所有成员可以在任何主机上以任何用户身份执行任何命令
数组
数组分类:
- 普通数组:只能使用整数作为数组索引(元素的下标)
- 关联数组:可以使用字符串作为数组索引(元素的下标)
普通数组
定义:
- 一次赋予一个值
数组名[索引下标]=值
array[0]=v1
array[1]=v2
- 一次赋予多个值
数组名=(值1 值2 值3 ...)
array=(var1 var2 var3 var4)
array1=(`cat /etc/passwd`) # 将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "Miss Hou")
array4=(1 2 3 4 "hello world" [10]=linux)
数组的读取
${数组名[元素下标]}
echo ${array[0]} # 获取数组里第一个元素
echo ${array[*]} # 获取数组里的所有元素
echo ${#array[*]} # 获取数组里所有元素的个数
echo ${!array[@]} # 获取所有数组元素的索引下标
echo ${array[@]:1:2} # 访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素
# 查看普通数组信息
declare -a # 不仅可以定义,也可以查看当前已经定义好的数组
关联数组
声明关联数组
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
数组赋值:
- 一次赋一个值
数组名[索引or下标]=变量值
asso_array1[linux]=one
asso_array1[java]=two
asso_array1[php]=three
- 一次赋多个值
asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
- 查看关联数组
#declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one")'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Miss Hou")'
关联数组下标无序
关联数组的读取方法同普通数组
- 取出一个目录下的目录和文件:dirname和basename
# A=/root/Desktop/shell/mem.txt
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A 取出目录
/root/Desktop/shell
# basename $A 取出文件
mem.txt
- 变量内容的删除和替换
一个"%"代表从右往左去掉一个/key/
两个"%%"代表从右往左最大去掉/key/
一个"#"代表从左往右去掉一个/key/
两个"##"代表从左往右最大去掉/key/
eg:
# url=www.taobao.com
# echo ${#url} 获取变量的长度
# echo ${url#*.}
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}
任务:统计网站的连接次数
#!/bin/env bash
#count_http_80_state
#统计每个状态的个数
declare -A array1
states=`ss -ant|grep 80|cut -d' ' -f1` #类似netstat,但LISTEN ESTAB的状态值显示在开头
for i in $states
do
let array1[$i]++
done
#通过遍历数组里的索引和元素打印出来
for j in ${!array1[@]}
do
echo $j:${array1[$j]}
done
case语句
- case语句为多重匹配语句
- 如果匹配成功,执行相匹配的命令
- 语法结构
说明:pattern表示需要匹配的模式
case var in # 定义变量;var代表是变量名
pattern1) # 模式1;用|分割多个模式,相当于or
command1 # 需要执行的语句
;; # 两个分号代表命令结束
pattern2)
command2
;;
pattern3)
command3
;;
*) # default, 不满足以上模式,默认执行*)下面的语句
command4
;;
esac # esac表示case语句结束
案例:菜单提示让用户选择需要做的事
需求:模拟一个多任务界面,当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作
#!/bin/env bash
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
while true
do
read -p "请选择需要操作的内容(help h):" action
clear # 清屏
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
case $action in
h|help)
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
;;
f)
lsblk
;;
d)
df -h
;;
m)
free -m
;;
u)
uptime
;;
q)
exit
;;
esac
done
函数
shell中允许将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数
给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能
- 定义函数:
函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
function 函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
- 函数中return说明:
- return可以结束一个函数,类似于循环控制语句break(结束当前循环,执行循环体后面的代码)
- return默认返回函数中最后一个命令的状态值,也可以给定参数值,范围是0-256之间
- 如果没有return命令,函数将返回最后一个指令的退出状态值
- 调用函数
1)当前命令行调用,只在当前终端有效,使用前需要source该脚本
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
2)定义到用户的环境变量中,~/.bashrc(全局所有用户是/etc/bashrc)
自登录时生效,修改后需要source
3)脚本中调用
#!/bin/bash
menu(){
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
}
menu # 调用menu
source /path_to_file/filename.sh # 接下来可以调用其他文件的函数
案例:交互输入用户的姓名、性别、年龄
#!/bin/bash
# 该函数实现用户如果不输入内容则一直循环直到用户输入为止,并且将用户输入的内容打印出来
input_fun()
{
input_var=""
output_var=$1
while [ -z $input_vat ]
do
read -p "$output_var" input_var
done
echo $input_var
}
name=`input_fun 请输入你的姓名`
递归写法
#!/bin/bash
func()
{
read -p "$1" name
if [ -z $name ];then
func $1
else
echo $name
fi
}
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu(){
cat <<-EOF
欢迎使用Jumper-server, 请选择你要操作的主机:
1. DB1-Master
2. DB2-Master
3. Web1
4. Web2
h. help
q. exit
EOF
}
# 调用函数来打印菜单
menu
# 循环等待用户选择
while true
do
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
1)
ssh root@10.1.1.1
;;
2)
ssh root@10.1.1.2
;;
3)
ssh root@10.1.1.3
;;
h)
clear;menu
;;
q)
exit
;;
esac
done
#将脚本放到yunwei用户家目录里的.bashrc里执行:
bash ~/jumper-server.sh
exit
进一步完善:
增强跳板机的安全性,工作人员通过跳板机访问生产环境,但是不能在跳板机上停留
#!/bin/bash
# 公钥推送成功(免密)
trap '' 1 2 3 19 # 屏蔽ctrl-C的信号
#打印菜单用户选择
menu(){
cat <<-EOF
}
# 回顾信号
1) SIGUP # 重新加载配置
2) SIGINT # 键盘终端^C
3) SIGQUIT # 键盘退出
9) SIGKILL # 强制终止
15) SIGTERM # 终止(正常结束),缺省信号
18) SIGCONT # 继续
19) SIGSTOP # 停止
20) SIGTSTP # 暂停^Z
正则表达式
(Regular Expression、regex或regexp,缩写为RE)是一种字符模式,用于在查找过程中匹配指定的字符
许多程序语言都支持利用正则表达式进行字符串操作
正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的
支持正则表达式的程序如:locate | find | vim | grep | sed |awk
正则能干什么?
- 匹配邮箱、匹配身份证号码、手机号、银行卡号等
- 匹配某些特定字符串,做特定处理等等
正则当中名词解释
- 元字符
正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等 和通配符中的星号和问号是不一样的
- 前导字符
位于元字符前面的字符, abd* aooo.
第一类正则表达式
正则中普通常用的元字符
. 匹配除了换行符以外的任意单个字符
* 前导字符出现0次或连续多次
.* 任意长度的字符 ab.*
^ 行首(以...开头) ^root
$ 行尾(以...结尾) bash$
^$ 空行
[] 匹配括号里任意单个字符或一组单个字符 [abc]
[^] 匹配不包含括号里任一单个字符或一组单个字符 [^abd]
^[] 匹配以括号里任意单个字符或一组单个字符开头 ^[abc]
^[^] 匹配不以括号里任意单个字符或一组单个字符开头 ^[^abc]
# 正则中的其他元字符
\< 取单词的头
\> 取单词的尾
\< \> 单词精确匹配 等价于 grep -w
\{n\} 匹配前导字符连续出现n次
\{n,\} 匹配前导字符至少出现n次
\{n,m\} 匹配前导字符出现n次与m次之间
\{ \} 保存被匹配的字符
# vim中:%s/\{10.1.1\}.1/\1.254/g 将前{}起来的用标签\1表示 10.1.1.1 -> 10.1.1.254
\d 匹配数字(grep -P) [0-9]
\w 匹配字母数字下划线(grep -P) [a-zA-Z0-9_]
\s 匹配空格、制表符、换页符(grep -P) [\t\r\n]
扩展类正则常用元字符
grep你要用我,必须加-E或者让你兄弟egrep来找我
sed你要用我,必须加-r
+ 匹配一个或多个前导字符 bo+匹配boo bo
? 匹配零个或一个前导字符 bo?匹配b bo
| 或 匹配a或b
() 组字符(看成整体) (my|your)self: 表示匹配myself或匹配yourself
{n} 前导字符重复n次
{n,} 前导字符重复至少n次
{n,m} 前导字符重复n到m次
第二类正则表达式
[:alnum:] 字母与数字字符 [[:alnum:]]+
[:alpha:] 字母字符(包括大小写字母) [[:alpha:]]{4}
[:blank:] 空格与制表符 [[:blank:]]*
[:digit:] 数字 [[:digit:]]?
[:lower:] 小写字母 [[:lower:]]{4,}
[:upper:] 大写字母 [[:upper:]]+
[:punct:] 标点符号 [[:punct:]]
[:space:] 包括换行符,回车等在内的所有空白 [[:space:]]+
正则表达式总结
- 我要找什么?
- 找数字 [0-9] \d
- 找字母 [a-zA-Z]
- 找标点符号 [[:punct:]]
- 我要如何找?看心情找
- 以什么为首 ^key
- 以什么结尾 key$
- 包含什么不包含什么 [abc] [1] [^abc] [abc]
- 我要找多少呀?
- 找前导字符出现0次或连续多次 ab*
- 找任意单个(一次)字符 ab.
- 找任意字符 ab.*
- 找前导字符连续出现几次 {n}
- 找前导字符出现1次或多次 go+
去掉空行:
1.查找不以大写字母开头的行(三种写法)
grep '^[^A-Z]' 2.txt
grep -v '^[A-Z]' 2.txt
grep '^[^[:upper:]]' 2.txt
2.查找有数字的行(两种写法)
grep '[0-9]' 2.txt
grep -P '\d' 2.txt
3. 查找一个数字和一个字母连起来的
grep -E '[0-9][a-zA-Z]|[a-zA-Z][0-9]' 2.txt
4. 查找不以r开头的行
grep '^[^r]' 2.txt
5. 查找以点结束的
grep '\.$' 2.txt
6. 去掉空行
grep -v '^$' 2.txt
grep '^[^$]' 2.txt # 错误写法,[]中的只表示单个字符
7. 查找完全匹配abc的行
grep '\<abc\>' 2.txt
8. 查找A后有三个数字的行
grep -E 'A[0-9]{3}' 2.txt
grep 'A[0-9]\{3\}' 2.txt
9. 统计root在/etc/passwd里出现了几次
grep -o 'root' 1.txt|wc -l
10. 用正则表达式找出自己的IP地址、广播地址、子网掩码
ifconfig eth0|grep Bcast|grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\'
ifconfig eth0|grep Bcast|grep -E -o '([0-9]{1,3}.){3}[0-9]{1,3}'
ifconfig eth0|grep Bcast|grep -P -o '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' # -P后不用添加-E
ifconfig eth0|grep Bcast|grep -P -o '(\d{1,3}.){3}\d{1,3}'
ifconfig eth0|grep Bcast|grep -P -o '(\d+.){3}\d+'
也可以使用egrep 配合 [:digit:]
11. 找出文件中的ip地址并且打印替换成172.16.2.254
grep -o -E '([0-9]{1,3}.){3}[0-9]{1,3}' 1.txt|sed -n 's/192.168.0.\(254\)/172.16.2.\1/p'
12. 找出文件中的ip地址
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
13. 找出全部是数字的行
grep -E '^[0-9]+$' test
14.找出邮箱地址
grep -E '^[0-9]+@[a-z0-9]+\.[a-z]+$'
abc ↩︎