概述
查看该linux支持的是哪种shell
脚本入门
脚本以#!/bin/bash开头(指定解析器)
vim helloworld.sh
#!/bin/bash
echo "helloworld"
bash helloworld.sh
分别使用sh bash ./ .的方式来执行shell脚本
前两种方式都是在当前shell中打开一个子shell来执行脚本内容,当脚本内容结束,则子shell关闭,回到父shell中。
第三种,也就是使用在脚本路径前加“.”或source的方式,可以使脚本内容在当前shell里执行,而无需打开子shell,这也是为什么每次修改完/etc/profile文件以后,需要source一下的原因。
开子shell与不开子shell的区别就在于,环境变量的继承关系,如在子shell中设置的当前变量,父shell是不可见的。
验证父子shell
变量
常用系统变量
$HOME、$PWD、$SHELL、$USER等
打印环境变量的值
echo $HOME
使用环境变量
ls $HOME
查看系统所有环境变量
env
或者
env | less
查看当前shell中所有变量(系统变量和用户自定义变量)
set
打印系统指定变量
printenv USER
自定义变量
1)基本语法
(1)定义变量:变量名=变量值,注意:=号前后不能有空格
(2)撤销变量:unset变量名
(3)声明静态变量:readonly变量,注意:不能unset
2)变量定义规则
(1)变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写
(2)等号两侧不能有空格
(3)在bash中,变量默认类型都是字符串类型,无法直接进行数值运算
(4)变量的值如果有空格,需要使用双引号或单引号括起来
设置变量和修改变量
查看是系统变量还是自定义变量
下图开了子bash,在里面打印变量,没有相关的值,说明设置的变量不是全局变量
升级为全局变量
在子shell修改之后,export为全局变量,exit返回父shell,发现值还是没改变,说明子bash修改的变量作用范围只在子shell生效
shell脚本查看变量的可见范围
vim helloworld.sh
#!/bin/bash
echo "helloworld"
echo $my_aaa
echo $my_new_var
chmod +x helloworld.sh
还有一种方式能在脚本里访问到局部变量,那就是在外面bash命令中,将变量升级为全局变量
export my_new_var
运算符
设置只读变量
撤销变量
任意路径执行脚本
查看环境变量PATH:echo $PATH
将shell脚本放到环境查询出来的环境变量的路径中,然后任意路径输入helloworld.sh即可执行
增加环境变量PATH的路径
vim /etc/profile
添加以下内容即可增加PATH的路径
export NODE_HOME=/usr/local/software/node-v22.6.0-linux-x64/bin
特殊变量
$n
$n(功能描述:n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含,如\${10})
vim parameter.sh
#!/bin/bash
echo '==============$n==============='
echo script name: $0
echo 1st parameter: $1
echo 2nd parameter: $2
$#
$#(功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性)
vim paramter.sh
#!/bin/bash
echo '==============$n==============='
echo script name: $(basename $0 .sh)
echo script path: $(cd $(dirname $0); pwd)
echo 1st parameter: $1
echo 2nd parameter: $2
echo '==============$#==============='
echo parameter numbers: $#
\*和\@
$*(功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成是一个整体)
$@(功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待)
vim p2.sh
#!/bin/bash
echo '================$*================='
echo $*
echo '================$@================='
echo $@
$?
$?(功能描述:最后一次执行的命令的返回状态,如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)
运算符
基本语法
"$((运算式))"或“$[运算式]”
expr计算,是把后面当成参数传入,所有每个数值和运算符之间都要加空格
expr赋值给变量
运算符计算
脚本计算
vim add.sh
#!/bin/bash
sum=$[$1 + $2]
echo sum=$sum
函数符
$(),用该符号包起来的内容会以函数的形式去运行
条件判断
1.基本语法
(1)test condition,如:test $a = hello(注意等号左右都要加空格),查看判断结果:echo $?,0为结果成功,1为结果失败
(2)[ condition ],如:[ $a = hello ](注意等号左右都要加空格),查看判断结果:echo $?,0为结果成功,1为结果失败
2.常用判断条件
(1)两个整数之间比较
-eq 等于(equal)
-ne 不等于(not equal)
-lt 小于(less than)
-le 小于等于(less equal)
-gt 大于(greater than)
-ge 大于等于(greater equal)
注:如果是字符串之间的比较,用等号“=”判断相等;用“!=”判断不等
[ 2 -lt 8 ]
echo $?
(2)按照文件权限进行判断
-r 有读的权限(read)
-w 有写的权限(write)
-x 有执行的权限(execute)
(3)按照文件类型进行判断
-e 文件存在(existence)
-f 文件存在并且是一个常规的文件(file)
-d 文件存在并且是一个目录(directory)
判断文件是否存在
[ -f /usr/local/aaa.txt ]
echo $?
判断文件是否存在的另一种写法
test -f /usr/local/aaa.txt
echo $?
字符串对比
字符串不加引号,加单引号,加双引号都可以
[ $a = hello ]
[ $a = 'hello' ]
[ $a = "hello" ]
数值对比
文件权限判断
文件类型判断
没有/home/info这个文件
多条件判断
$a是否小于20,是就打印$a<20,否就打印$a>=20
*流程控制
if判断
(1)单分支
if [ 条件判断式 ]; then 程序 fi
或者
if [ 条件判断式 ] then 程序 fi
(2)多分支
if [ 条件判断式 ] then 程序 else [ 条件判断式 ] then 程序 else 程序 fi
脚本
单分支
vim if_test.sh
下面x的意思是防止没有传入参数导致的空参报错
这里试了直接去掉x,判断式变量都带上双引号,执行脚本时没传入参数不会报错,而且执行逻辑正确
#!/bin/bash
if [ "$1"x = "chenjt"x ]
then
echo "welcome,my big brother"
fi
多分支
vim if_test.sh
#!/bin/bash
if [ "$1" = "chenjt" ]
then
echo "welcome,my big brother"
fi
# 输入第二个参数,表示年龄,判断属于哪个年龄阶段
if [ $2 -lt 18 ]
then
echo "未成年人"
elif [ $2 -lt 35 ]
then
echo "青年人"
elif [ $2 -lt 60 ]
then
echo "中年人"
else
fi
case语句
基本语法
case $变量名 in
"值1")
如果变量的值等于值1,则执行程序1
;;
"值2")
如果变量的值等于值2,则执行程序2
;;
_省略其他分支_
*)
如果变量的值都不是以上的值,则执行程序
;;
esac
注意事项:
(1)case行尾必须以单词"in",每一个模式匹配必须以右括号“)”结束。
(2)双分号“;;”表示命令序列结束,相当于java中的break。
(3)最后的"*)"表示默认模式,相当于java中的default。
vim case_test.sh
#!/bin/bash
case $1 in
1)
echo "one"
;;
2)
echo "two"
;;
3)
echo "three"
;;
*)
echo "number else"
;;
esac
for循环
基本语法1
for(( 初始值;循环控制条件;变量变化 ))
do
程序
done
vim sum_to.sh
实操:从1加到100
#!/bin/bash
for(( i=1; i<=$1; i++ ))
do
sum=$[ $sum + $i ]
done
echo $sum
基本语法2
for 变量 in 值1 值2 值3... do 程序 done
遍历linux windows macos这三个值,打印出每一个值
{}:表示一个序列
test.sh
#!/bin/bash
for param in $1
do
echo $param
done
vim parameter_test.sh
$*和$@带双引号有区别,不带双引号无区别
#!/bin/bash
echo '============$*============='
for para in "$*"
do
echo $para
done echo '============$@============='
for para in "$@"
do
echo $para
done
while循环
基本语法
while [ 条件判断式 ] do 程序 done
vim sum_to.sh
# 用while做一个实现
a=1
while [ $a -le $1 ]
do
# sum2=$[ $sum2 + $a ]
# a=$[$a + 1]
let sum2+=a
let a++
done
echo $sum2
read读取控制台输入
基本语法
read(选项)(参数)
1.选项
-p:指定读取值时的提示符
-t:指定读取值时等待时间(秒)如果-t不加表示一直等待
2.参数
变量:指定读取值的变量名
read_test.sh
#!/bin/bash
read -t 10 -p "请输入您的芳名:" name
echo "welcome, $name"
函数
系统函数
basename
基本语法
basename [string / pathname][suffix](功能描述:basename命令会删掉所有的前缀包括最后一个("/")字符,然后将字符串显示出来)。
basename可以理解为取路径里的文件名称
选项:
suffix为后缀,如果suffix被指定了basename会将pathname或string的suffix去掉
只是对路径去做切分,随便输入什么路径,不会去判断这个路径是否存在,只会取到路径最后的一个文件名
$()是用来做命令替换的,其等价于``(反引号)
vim cmd_test.sh
#!/bin/bash
filename="$1"_log_$(date +%s)
echo $filename
vim paramter.sh
$(basename $0 .sh):截取paramter.sh到.sh,结果:paramter
#!/bin/bash
echo '==============$n==============='
echo script name: $(basename $0 .sh)
echo 1st parameter: $1
echo 2nd parameter: $2
echo '==============$#==============='
echo parameter numbers: $#
dirname
基本语法
dirname 文件绝对路径(功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))
dirname可以理解为取文件的绝对路径名称
只是对路径去做切分,随便输入什么路径,不会去判断这个路径是否存在
vim paramter.sh
保证了不管在什么路径下执行该脚本,都能打印出该脚本的绝对目录
分号用于分隔命令,以下命令等同于:cd /usr/local; pwd
echo script path: (dirname $0); pwd)
#!/bin/bash
echo '==============$n==============='
echo script name: $(basename $0 .sh)
echo script path: $(cd $(dirname $0); pwd)
echo 1st parameter: $1
echo 2nd parameter: $2
echo '==============$#==============='
echo parameter numbers: $#
自定义函数
基本语法
[]:表示可选的意思
[ function ] funname[()]
{
Action;
[return int;]
}
经验技巧
(1)必须在调用函数地方之前,先声明函数,shell脚本是逐行逐行运行,不会像其他语言一样先编译。
(2)函数返回值,只能通过$?系统变量获得,可以显示地加上:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)
vim fun_test.sh
sum=a ?表示的范围只有0-255,超过255高位会被截掉,为了能计算超过255的值,这里采取重新赋值的方式
#!/bin/bash
function add(){
s=$[$1 + $2]
echo $s
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
sum=$(add $a $b)
echo "和:"$sum
echo "和的平方:"$[$sum * $sum]
综合案例-文件归档
实际生产应用中,往往需要对重要数据进行归档备份
需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将目录下所有文件按天归档保存,并将归档日期附加在归档文件名上,放在/root/archive下。
这里用到了归档命令:tar
后面可以加上-c选项表示归档,加上-z选项表示同时进行压缩,得到的文件后缀名为.tar.gz。
vim daily_archive.sh
-d 1是否是一个目录
#!/bin/bash
# 首先判断输入参数个数是否为1
if [ $# -ne 1 ]
then
echo "参数个数错误!应该输入一个参数,作为归档目录名"
exit
fi
# 从参数中获取目录名称
if [ -d $1 ]
then
echo
else
echo
echo "目录不存在!"
echo
exit
fi
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
# 获取当前日期
DATE=$(date +%y%m%d)
# 定义生成的归档文件名称
FILE=archive_${DIR_NAME}_$DATE.tar.gz
DEST=/root/archive/$FILE
# 开始归档目录文件
echo "开始归档..."
echo
tar -czf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]
then
echo
echo "归档成功!"
echo "归档文件为:$DEST"
echo
else
echo "归档出现问题!"
echo
fi
exit
chmod u+x daily_archive.sh
mkdir /root/archive
归档/root/scripts这整个目录到/root/archive目录下,名字为archive_
脚本添加到定时任务
查看定时任务
crontab -l
编辑定时任务
crontab -e
每天凌晨2点执行备份脚本 daily_archive.sh
0 2 * * * /root/scripts/daily_archive.sh /root/scripts
正则表达式
正则表达式使用单个字符串来描述、匹配一系列复合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在Linux中,grep,sed,awk等文本处理工具都支持通过正则表达式进行模式匹配。
常规匹配
常用特殊字符
^$:匹配空行
显示daily_archive.sh脚本中所有空行的行号
.*:匹配任意内容
匹配手机号
文本处理工具
cut
cut的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。
1.基本用法
cut [选项参数] filename
说明:默认分隔符是制表符
2.选项参数说明
-f:列号,提取到第几列
-d:分隔符,按照指定分隔符分割列,默认是制表符“\t”
-c:按字符进行切割后加个n表示取第几列 比如-c 3
vim cut.txt
dong shen
guan zhen
wo wo
lai lai
le le
awk
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。
1.基本用法
awk [选项参数] '/pattern1/{action1} /pattern2/{action2}...' filename
pattern:表示awk在数据查找的内容,就是匹配模式
action:在找到匹配内容时所执行的一系列命令
2.选项参数说明
-F:指定输入文件分隔符
-v:赋值一个用户定义变量
3.内置变量
FILENAME:文件名
NR:已读的记录数(行号)
NF:浏览记录的域的个数(切割后,列的个数)
以“:”为分隔符分割内容,过滤出以root开头的内容,打印/etc/passwd第7列
以“:”为分隔符分割内容,过滤出以root开头的内容,打印第1、6、7列,之间以","拼接
只显示/etc/passwd的第1列和第7列,以逗号分割,且在所有行前面添加列名user,shell在最后一行添加”the last something“
BEGIN:在所有数据行在读取之前执行
END:在所有数据执行之后执行
将/etc/passwd文件中的用户id增加数值2并输出
将文件/etc/passwd以":"为分隔符,打印文件名,已读行号和每一行分割后列的个数
查询ifconfig命令输出结果中的空行所在的行号
打印出ifconfig所有网卡netmask之后带的ip
/netmask/:表示匹配有netmask这个字符串的这一行
综合案例-发送消息
我们可以利用Linux自带的mesg和write工具,向其他用户发送消息
需求:实现一个向某个用户快速发送消息的脚本,输入用户名作为第一个参数,后面直接跟要发送的消息。脚本需要检测用户是否登录在系统中、是否打开消息功能,以及当前发送消息是否为空。
当前登录的用户信息
who am i
当前所有登录用户信息
who
消息发送功能是否是打开的
mesg
查看所有当用消息发送功能是否是打开的(+号表示消息发送功能是打开的,pts/2表示连接的控制台)
who -T
关闭mesg
mesg n
开启mesg
mesg y
发送消息给指定的登录用户
pts/2表示连接的控制台标识
write chenjt pts/2
不交互式的写法
echo aa | write chenjt pts/2
-i:ignore case,忽略大小写
-m:max count,比如-m 1,表示如果有多个相同结果,表示只取第一个结果
vim send.sh
#!/bin/bash
# 执行脚本带的参数,参数1:用户名,参数2:消息
# 查看用户是否登录
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}')
if [ -z $login_user ]
then
echo "$1 不在线!"
echo "脚本退出..."
exit
fi
# 查看用户是否开启消息功能
is_allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}')
if [ $is_allowed != '+' ]
then
echo "$1 没有开启消息功能"
echo "脚本退出..."
exit
fi
# 确认是否有消息发送
if [ -z $2 ]
then
echo "没有消息发送"
echo "脚本退出..."
exit
fi
# 从参数中获取要发送的消息
whole_msg=$(echo $* | cut -d " " -f 2-)
# 获取用户登录的终端
user_terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
# 写入要发送的消息
# 不交互式的写法
echo $whole_msg | write $login_user $user_terminal
#echo write $login_user $user_terminal
if [ $? != 0 ]
then
echo "发送失败!"
else
echo "发送成功!"
fi
exit