Linux21--shell编程基础
1 shell 编程
1.1 shell 介绍
# 1 shell 介绍
1.shell 编程是一门解释型、弱类型、动态的编程语言
2.shell 就是一个命令解释器
shell分为交互式和 非交互式
# 通过 $- 来查看 $- 记录的是当前配置 打开的shell选项
himBH # 交互式shell
h # hashall 当shell运行时,会记录保存执行过的命令 hash缓存表
i # interactive 交互式shell的意思
m # monitor 监控后台进程的状态 监控模式
B # braceexpand 大括号拓展,支持{} 整体、序列
H # history 历史命令的记录
hB # 非交互式shell
# 通过 set -o 查看来确认shell选项的打开状态 set -o | grep -w on
# 2 类比
shell语法 <===> python语法
bash解释器 <===> python解释器
平台 <===> 平台
# 3 shell 脚本
1.系统命令的堆积
2.特定的格式,特定的语法,组成的一个文件
3.以.sh为结尾的
# 4 运行shell脚本
权限:
当前用户需要对脚本文件 有读和执行 r+x权限
方式:
1. bash 脚本的文件路径
2. 脚本的文件绝对路径 或 ./文件相对路径
1.2 命令优先级
# 命令的执行优先级
大致分成三类: 别名、内部命令、外部命令
==> alias # 别名 alias可以查看
==> Compound Commands 复合命令 # eg: for if while等
==> function 函数 # 可通过 set 找到 eg: set |grep cd
==> build_in 内置 # 是bash解释器 自带的功能
==> hash
==> $PATH # 外部命令 环境变量中,按冒号分割的每个路径中去搜索
==> error: command not found
2 变量
2.1 基本使用
### 交互式环境中
[root@localhost ~]# x=1 # 1 定义变量 中间不能有空格
[root@localhost ~]# x=2
[root@localhost ~]# echo $x # 2 使用变量方式一:'$变量'
[root@localhost ~]# name=egon
[root@localhost ~]# echo $name
[root@localhost ~]# echo ${name} # 3 使用变量方式二:'${变量}' 推荐使用
是将一个变量名看作是一个整体
[root@localhost ~]# echo "hello $name1" # 找不到name1的变量
hello
[root@localhost ~]# echo "hello ${name}1"
hello egon1
# 删除变量
unset VARNAME
2.2 引号对变量的影响
### 1 双引号 ---> 弱引用 保留变量
[root@localhost ~]# name=egon
[root@localhost ~]# echo "hello $name"
hello egon
### 1 单引号 ---> 强引用 都视为普通字符
[root@localhost ~]# name=egon
[root@localhost ~]# echo 'hello $name'
[root@localhost ~]# echo "hello \$name" # 或者使用转义符
hello $name
### 1 反引号 ---> 取命令的执行结果
[root@localhost ~]# today=`date +%F`
[root@localhost ~]# echo $today
2020-08-11
# $():也可以取命令的结果 与反引号的区别是 可以用于嵌套使用
[root@localhost ~]# today=$(date +%H:%M:%S)
[root@localhost ~]# echo $today
22:05:55
# eg:
[root@localhost ~]# tar czf `date +%F`_bak.tar.gz /tmp
2.3 变量作用域
### 1 变量作用域
环境变量 : 在当前shell及子shell生效!
自定义变量 : 仅在当前shell 进程生效!
# eg1:环境变量
[root@localhost ~]# x=1000
[root@localhost ~]# export x # 声明成环境变量
[root@localhost ~]# bash # 启动新的子shell
[root@localhost ~]# echo $x
1000
# eg2:自定义变量
[root@localhost ~]# x=1000 # 自定义一个全局变量
[root@localhost ~]# vim b.sh # echo $x
[root@localhost ~]# bash b.sh # 并不能打印出x的值,是启动一个子shell执行代码
[root@localhost ~]#
[root@localhost ~]# source b.sh # 能打印出x的值,source作用是将b.txt的代码,复制出来,在当前shell环境执行
1000
### 2 查看变量
set # 查看所有变量(包括自定变量和环境变量)
env # 查看环境变量
### 3 系统环境变量配置文件
1. /etc/profile
2. /etc/bashrc
3. ~/.bashrc
4. ~/.bash_profile
### 4 常见系统环境变量
[root@localhost ~]# echo $PS1 # 命令提示符
[\u@\h \W]\$
[root@localhost ~]# echo $HOSTNAME
localhost.localdomain
[root@localhost ~]# echo $USER
root
[root@localhost ~]# echo $UID
0
[root@localhost ~]# echo $SHELL
/bin/bash
[root@localhost ~]# echo $HISTSIZE # 执行命令 历史记录的保存数量
1000
[root@localhost ~]# echo $MAIL
/var/spool/mail/root
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
3 元字符
bash中的特殊字符,键盘上能敲出来的特殊字符都有其特殊意义,强调一点:元字符是被shell解释的
3.1 常见的特殊字符
# 1 ~ 家目录
# 2 .与..
. 表示当前路径
.. 表示父级路径
# 3 ! 与 ^ 都是取反
# 4 @ 无特殊意义
# 5 # 注释
# 6 $ 取变量值
注: $? # 判断上一条命令的结果是否为真,0表示true
# 7 * 任意多个字符
? 任意一个字符
[] 范围中的任意一个字符
eg:[12] [ac] [0-9] [a-z] # a到z 是不区分大小写的
# 8 _ 无特殊意义,可用于名字的声明
# 9 / 路径分隔符
# 10 & 后台运行
# 11 | 管道:把一个进程的处理结果传递给另外一个进程 详见 管道
xargs 参数传递,把上一个命令的结果作为下一个命令的参数
# 12 ( ) 在子shell中执行
$( ) 可以取命令的结果
$(( )) 整数运算
# 13 \ 转义特殊字符 取消符号的特殊意义
\\ \自身
\$ 转义$
\t 制表符
\b 退格符
\r 回车符
\n 换行符
\c 取消换行
# 14 ; && || 连接多条命令
; # 不论前一条命令运行成功与否,都会执行后续命令
&& # 前一条命令 执行成功,才会执行后续命令 与
|| # 前一条命令 执行不成功,才会执行后续命令 非
# 15 : 也是一天命令,永远为真的命令 跟true一样
while :
while true
# 16 > >> 输出重定向 覆盖 追加 详见 输出与重定向
< << 输入重定向
3.2 数学运算符
### 0 + - * / % 运算符
/ # 除法 求商
% # 除法 取余
### 1 bc是比较常用的linux计算工具了,而且支持浮点运算
yum install bc -y
[root@localhost ~]# res=`echo 1+1 | bc`
[root@localhost ~]# echo $res
2
[root@localhost ~]# res=`echo 10 % 3 | bc`
[root@localhost ~]# echo $res
1
[root@localhost ~]# res=`echo 1.2+1.3 | bc`
[root@localhost ~]# echo $res
2.5
[root@localhost ~]# res=`echo "scale=2;5.0/3.0" | bc` # scale 表示保留小点数位数,且不四舍五入
[root@localhost ~]# echo $res
1.66
[root@localhost ~]# res=`echo "scale=2;5.0/6.0" | bc` # 注意0.几 个位不会有0
[root@localhost ~]# echo $res
.83
### 2 expr不支持浮点数计算。而且要注意数字与运算符中的空格
[root@localhost ~]# res=`expr 5 / 3` # 不支持浮点计算
[root@localhost ~]# echo $res
1
[root@localhost ~]# res=`expr 1+1` # 注意:计算符号前后要有空格
[root@localhost ~]# echo $res
1+1
[root@localhost ~]# res=`expr 1 + 1`
[root@localhost ~]# echo $res
2
### 3 $(( ))、$[ ] 同 expr,不支持浮点数运算
[root@localhost ~]# echo $((1+1)) # 注意:两个 括号前后都不需要空格
[root@localhost ~]# echo $[1+1]
2
[root@localhost ~]# echo $((1.0+2.0))
[root@localhost ~]# echo $[1.0+2.0]
-bash: 1.0+2.0: 语法错误: 无效的算术运算符 (错误符号是 ".0+2.0")
### 4 let 不支持浮点数运算,而且不支持直接输出,只能赋值
[root@localhost ~]# let res=1+1 # 同js中,let声明变量差不多
[root@localhost ~]# echo $res
2
[root@localhost ~]# let res=50/5
[root@localhost ~]# echo $res
10
[root@localhost ~]# let c=1.3*3
-bash: let: c=1.3*3: 语法错误: 无效的算术运算符 (错误符号是 ".3*3"
4 条件测试
# 测试命令: test 检查文件类型 或 比较值
# 格式:
test 条件
[ 条件 ] # 注意[] 与条件 有空格隔开
[[ 条件 ]] # 常用这个 !!!
((条件)) # 涉及数字 用这个 !!! 可直接使用比较符号
# 测试结果,可通过 $? 获取,判断上一条命令执行结果是否为真
若$?值为0 表示命令执行成功 否则为失败
# 多条件比较
-a 并且
-o 或者
eg:[ 1 -lt 2 -a 2 -lt 3 ]
&& 并且 # [[ expr1 && expr2 ]] 必须使用双括号 中括号或小括号都可以
|| 或者 # (( expr1 || expr2 ))
4.1 文件状态测试
-d 目录
-s 文件长度 > 0、非空
-f 正规文件
-e 文件是否存在
-w 可写
-r 可读
-x 可执行
-L 符号连接
-u 文件有 suid 位设置
# eg
[ -d /etc/init.d ]
4.2 字符串测试
= 两个字符串相等
!= 两个字符串不相等
-z 空串
-n 非空串
=~ 判断string和右边的正则表达式pattern是否匹配
[root@MiWiFi-R3-srv ~]# var1='abc';var2='123'
[root@MiWiFi-R3-srv ~]# [ $var1 = $var2 ]
[root@MiWiFi-R3-srv ~]# echo $?
1
[root@hecs-147737 ~]# var2=''
[root@hecs-147737 ~]# [ -z $var2]
[root@hecs-147737 ~]# echo $?
0
4.3 数值测试
# 1 [] 或 [[]] 不能使用具体的符号,不然结果报错或者错误
-eq 等于
-ne 不等于
-gt 大于
-ge 大于等于
-lt 小于
-le 小于等于
[root@MiWiFi-R3-srv ~]# [ 10000 -gt 250 ]
[root@MiWiFi-R3-srv ~]# echo $?
0
# 2 或者使用 (()) 就可以使用符号
(( 10 > 20 ))
echo $?
1
5 流程控制
5.1 分支结构
5.1.1 if判断
### 单分支
if 条件; then
代码
fi
### 双分支
if 条件; then
代码1
else
代码2
fi
### 多分支
if 条件; then
代码1
elif 条件; then
代码2
elif 条件; then
代码3
else
代码4
fi
# 分支案例
#!/bin/bash
username='egon'
password='123'
read -p 'user: ' name # read 用于从标准输入读取数值 -p 后面跟提示信息,即在输入前打印提示信息
read -p 'passwd: ' passwd
# if [ $name = $username -a $passwd = $password ]; then
if [[ $name = $username && $passwd = $password ]]; then
echo 'login successful'
else
echo 'username or password error'
fi
5.1.2 case条件
# 格式
case 变量 in
变量值1)
命令序列
;;
变量值2)
命令序列
;;
变量值n)
命令序列
;;
*) # 变量 都不在变量值中,走该命令序列
命令序列
exit
esac
### case 案例
安装不同php的版本
1.菜单,PHP版本的菜单
2.提示用户根据菜单进行选择安装不同的php
3.根据用户选择进行安装不同的php版本
### 案例脚本:case-1.sh
vim case-1.sh
#!/bin/bash
# 1.准备PHP的版本菜单
cat << EOF
#######################################
1.安装PHP-5.5版本
2.安装PHP-5.6版本
3.安装PHP-7.1版本
4.退出当前安装
#######################################
EOF
# 2.提示用户根据菜单进行选择安装不同的PHP版本
read -p "请根据的上方的菜单,进行选择安装不同的PHP版本[1|2|3|4]:" Install
# 3.根据用户的选择,进行安装不同的版本
case $Install in
1)
echo "你选择了安装PHP-5.5版本.............."
echo "正在安装PHP-5.5版本,请稍后..........."
sleep 3
echo "PHP-5.5版本安装成功................."
;;
2)
echo "你选择了安装PHP-5.6版本.............."
echo "正在安装PHP-5.6版本,请稍后..........."
sleep 3
echo "PHP-5.6版本安装成功.................."
;;
3)
echo "你选择了安装PHP-7.1版本..............."
echo "正在安装PHP-7.1版本,请稍后............"
sleep 3
echo "PHP-7.1版本安装成功..................."
;;
4)
echo "你没有选择安装任何PHP版本!程序退出!"
exit
;;
*)
echo "你的选择不符合要求!请根据上方菜单进行选择[1|2|3|4]."
exit
esac
5.1.3 向脚本传递参数
# 系统预定义变量
$数字 : 获取传递给脚本 具体位置的参数 # 0 是获取自己脚本文件名
$* : 传递给脚本的 所有的参数
$@ : 传递给脚本的 所有的参数
$$ : 当前脚本的进程PID
$# : 当前脚本的参数个数
$? : 当前脚本 进程执行的状态 # 0表示真
### eg: test.sh
echo $0
echo $1
echo $2
echo $3
echo ${11}
echo '$$' $$
echo '$*' $*
echo '$@' $@
echo '$#' $#
echo '$?' $?
# 测试:
python test.sh 1 2 3 4 5 6 7 8 9 10 11
# 输出结果:
./test.sh
1
2
3
11
$$ 14312
$* 1 2 3 4 5 6 7 8 9 10 11
$@ 1 2 3 4 5 6 7 8 9 10 11
$# 11
$? 0
5.2 循环结构
### 退出循环
exit # 退出循环,退出脚本
continue # 默认退出本次循环
忽略本次循环剩余的代码,直接执行下一次循环
break # 默认退出本层循环
结束当前循环,继续执行本层循环 外面的
5.2.1 while循环
### 1 条件判断循环
while 条件测试
do
循环体
done
### 2 无限循环
while true
do
循环体
done
### 3 读入文件 循环
while read line
do
循环体
done < file
### while循环案例
1.while循环读入文件的方式,进行创建用户
2.并为其设置一个24位的随机密码,要求密码由数字、大小写字母、特殊符号组成
### 创建随机密码命令
yum install -y expect # 安装
mkpasswd
-l # 设置密码的长度
-d # 字符的最少个数
-c # 小写字母的最少个数
-C # 大写字母的最少个数
-s # 特殊符号的最少个数
mkpasswd -l 24 -d 6 -c 6 -C 6 -s 6
### 案例脚本:while-1.sh
vim while-1.sh
# 根据user.txt 创建用户脚本,并设置24位初始密码
#!/bin/bash
while read line
do
id $line &>/dev/null
if [ $? -eq 0 ]; then
echo "用户${line}已经存在"
else
Pass=$(mkpasswd -l 24 -d 6 -c 6 -C 6 -s 6)
useradd $line &>/dev/null && echo $Pass | passwd --stdin $line &>/dev/null
if [ $? -eq 0 ];then
echo "用户[$line]创建成功!初始密码设置成功!密码文件为pass.txt"
echo -e "User: $line \tPass: $Pass" >> pass.txt && chmod 400 pass.txt
else
echo "用户[$line]创建失败!"
fi
fi
done < user.txt
5.2.2 for循环
# 格式
for i in 1 2 3 # 被循环的是 以空格分开的内容
do
循环体
done
# shell的for循环 常用in列表方式
# eg:
for i in 1 2 3
for i in {1,2,3}
for i in `seq 0 3` # seq 0 3 间隔步长 # 获取从0到3,间隔多少的整数
for i in {1..9}
for i in {9..1}
for i in {a..z}
for i in {A..Z}
for i in {X..Z}
for i in $(cmd)
for i in $(find ...)
### for循环案例
1.从文件中 user.txt进行取值
2.先把用户调用出来,判断用户是否存在
3.存在则提示已经存在,不要再去设置密码
4.不存在则进行创建,再创建密码
5.显示创建结果
### user.txt
vim user.txt
aaa:thasdj
bbb:dsasda
ccc:asdads
ddd:sadads
### 案例脚本1:for-1.sh
vim for-1.sh
#!/bin/bash
for i in $(cat user.txt)
do
# 1.把用户和密码分开
User=$(echo $i | awk -F: '{print $1}')
Pass=$(echo $i | awk -F: '{print $2}')
# 2.判断用户是否存在
id $User &>/dev/null
if [ $? -eq 0 ];then
echo "用户[$User]已经存在!"
else
useradd $User &>/dev/null && echo $Pass | passwd --stdin $User &>/dev/null
if [ $? -eq 0 ];then
echo "用户[$User]创建成功! 并且密码初始化成功!"
else
echo "用户[$User]创建失败!"
fi
fi
done
### 案例脚本2:for-2.sh
vim for-2.sh
for i in $(cat user.txt)
do
User=$(echo $i | awk -F: '{print $1}')
userdel -r $User && echo "$User delete successful"
done
6 函数
### 1 定义函数
# 第一种
函数名() {
命令集合
}
# 第二种
function 函数名 {
命令集合
}
### 2 调用函数
函数名
### 3 参数传递 跟给脚本文件传递一样 按按位置传递
# eg: function-1.sh
#!/bin/bash
# hellofun
function hello(){
echo "Hello! The first parameter is '$1'."
}
hello good
# 该脚本执行的结果是:
Hello! The first parameter is 'good'.
### 4 函数状态返回
# 返回值 在shell中有两种方式
echo # 返回数据,自定义的输出数据 打印字符串
return # 命令的执行结果 返回值 0-255之间的正整数
$? 来查看 0默认表示成功,非0表示失败
# eg: function-2.sh
#!/bin/bash
fun() {
echo "123"
return 1
}
Test=$(fun) # 调用函数,并赋值给变量Test
echo "函数的状态返回码是:$?"
echo "函数的返回数据是:$Test"
# 结果
函数的状态返回码是:1
函数的返回数据是:123
### 5 函数载入和删除
set # 查看已载入的函数
unset function-name # 取消载入
7 数组
7.1 数组介绍
### 1 什么是数组
简单讲:数组就是变量的一种,变量只能存储一个值,而数组可以存储多个值
### 2 数组分类
普通数组 # 只能使用 正整数 作为数组索引 # 类似 python 列表
关联数组 # 可以使用 字符串 作为数组索引 # 类似 python 字典
7.2 基本使用
7.2.1 定义数组
### 1 定义普通数组
# 方式1:按索引位置,依次直接赋值
数组名[索引号]=字符串
# eg:
shuzu_1[0]=nginx
shuzu_1[1]=python
shuzu_1[2]=mysql
# 方式2:一次性定义多个值
shuzu_2=(oldboy oldgirl oldteacher)
# 方式3:根据命令的结果,一次性定义 默认以空白字符为分隔符
shuzu_3=($(cat user.txt))
# 方式4:自定义索引序列号
shuzu_4=(beijing shanghai [5]=wuhan [10]=nanjing)
### 2 定义关联数组
# 需要先声明
declare -A 数组名 # 声明关联数组
# 方式1:按索引位置,依次直接赋值
declare -A site
site["google"]="www.google.com"
site["runoob"]="www.runoob.com"
site["taobao"]="www.taobao.com"
# 方式2:一次性定义多个值
# eg:
declare -A site=(["google"]="www.google.com" ["runoob"]="www.runoob.com" ["taobao"]="www.taobao.com")
7.2.2 访问数组
# 1 查看已定义的所有 普通数组
declare -a
# 2 查看已定义的所有 关联数组
declare -A
# 3 查看数组 所有的值 * 和 @ 符号都可以
echo ${array[*]}
# 4 查看数组 某个索引对应的值
echo ${array[索引]}
# 5 查看数组 值的个数
echo ${#array[*]}
# 6 查看数组 所有的索引
echo ${!array[*]}
7.3 数组循环与遍历
# 案例脚本:统计Tcp连接
vim tcp11.sh
#!/bin/bash
# 1.声明关联数组
declare -A Tcp_state
# 2.定义变量 普通数组
Status=$(netstat -ant | awk 'NR>2{print $NF}')
# 3.循环賦值
for i in $Status # 根据 数组值 循环
do
let Tcp_state[$i]++ # 自增
done
# 4.索引遍历
for i in ${!Tcp_state[*]} # 根据 所以 遍历
do
echo -e "当前Tcp状态为:$i\t\t出现的次数为:${Tcp_state[$i]}"
done
# 结果:
当前Tcp状态为:ESTABLISHED 出现的次数为:3
当前Tcp状态为:LISTEN 出现的次数为:5
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律