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

上下文格式:!改变 -删除 +增加

合并格式:+ - 只显示第一个文件需要增加和删除的内容

比较两个目录的不同:

  1. 默认情况下也会比较两个目录里相同文件的内容
  2. 如果只需要比较两个目录里文件的不同,不需要进一步比较文件内容,需要加-q选项
注意touch dir/file{1..5}会在该目录生成5个文件

以一个文件为标准,修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式将文件1更新成文件2

  1. 先找出文件不同,然后输出到一个文件 diff -uN (-N 将不存在的文件当做空文件) file1 file2 > file.patch
  2. 将不同内容打补丁到文件 patch file1 file.patch
  3. 测试验证
  • 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变量

  1. shell的基本语法结构

变量定义,条件判断,循环语句(for, until, while),分支语句,函数和数组

  1. 基本正则表达式
  2. 文件处理三剑客:grep, sed, awk的使用
  3. 使用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 跳出程序

练习:

  1. 如果目录不存在就新建
test -d /tmp/dir1 && mkdir /tmp/dir1 -p
或者
if [ ! -d /tmp/dir1 ];then mkdir /tmp/dir1 -p fi

逻辑运算符与条件语句,这两者是等价的可以相互转换

  1. 判断所输整数是否为质数
#!/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
  1. 批量创建用户

批量添加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
  1. 局域网内脚本检查主机网络通讯

局域网内,把能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
  1. 循环 useradd username
  2. 登录远程主机->ssh->从ip.txt文件读取IP和密码分别赋值给两个变量
  3. 使用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

案例分析

  1. 跳板机上的yunwei用户生成钥对
    • 判断账号是否存在(id yunwei)
    • 判断该用户是否有密钥对文件 [ -f xxx ]
  2. 判断expect是否安装
  3. su - yunwei
  4. 判断局域网内的主机是否ping通(循环判断|for while until)
    • 循环判断 for while
    • 循环体do ... done ping 主机 如果ping通,调用expect程序自动应答推送公钥
  5. 测试验证是否免密登录成功
  • 功能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语句

  1. case语句为多重匹配语句
  2. 如果匹配成功,执行相匹配的命令
  • 语法结构
说明: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说明:
  1. return可以结束一个函数,类似于循环控制语句break(结束当前循环,执行循环体后面的代码)
  2. return默认返回函数中最后一个命令的状态值,也可以给定参数值,范围是0-256之间
  3. 如果没有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

正则能干什么?

  1. 匹配邮箱、匹配身份证号码、手机号、银行卡号等
  2. 匹配某些特定字符串,做特定处理等等

正则当中名词解释

  • 元字符

正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等 和通配符中的星号和问号是不一样的

  • 前导字符

位于元字符前面的字符, 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:]]+

正则表达式总结

  1. 我要找什么?
    • 找数字 [0-9] \d
    • 找字母 [a-zA-Z]
    • 找标点符号 [[:punct:]]
  2. 我要如何找?看心情找
    • 以什么为首 ^key
    • 以什么结尾 key$
    • 包含什么不包含什么 [abc] [1] [^abc] [abc]
  3. 我要找多少呀?
    • 找前导字符出现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]+$'

  1. abc ↩︎

posted @ 2021-04-01 23:36  demianzhang  阅读(96)  评论(0编辑  收藏  举报