一. 基础
练习1:按照时间生成文件"2024-03-24.log"将每天的磁盘使用状态写入到对应日期的文件
#!/bin/bash
name=`date +%F`
/bin/df -h > /tmp/"$name".log
练习2:统计Nginx日志中每个IP的访问量有多少,日志格式如下
192.168.44.1 - - [21/May/2024:20:44:06 -0400] "GET /index.html HTTP/1.0" 404 169 "-" "ApacheBench/2.3" "-"/code/index.html
[root@Shell test]# cat count_nginx_ip.sh
#!/bin/bash
cat /var/log/nginx/access.log-20230506|awk '{print $1}'|sort -rn|uniq -c
[root@Shell test]# bash count_nginx_ip.sh
207 172.16.1.5
练习3:写一个脚本计算一下Linux系统所有进程占用内存的大小的和
%MEM列是进程占用物理内存的百分比。可以通过查看该值来了解进程所使用的物理内存比例大小。
VSZ(Virtual Memory Size)是虚拟内存的大小,表示进程所使用的虚拟内存空间大小。
RSS(Resident Set Size)是进程实际使用的物理内存大小,即占用的内存数,单位为KB。
我们求RSS的和
[root@Shell test]# cat process_occupies_memory_size.sh
#!/bin/bash
ps axu|awk 'NR!=1{print $6}'|grep -v ^0$|awk '{sum+= $1}END{print sum}'
[root@Shell test]# bash process_occupies_memory_size.sh
1387020
# !=1表示不等于1,也就是除了第一行之外的所有行
# {sum += $1}表示对每一行的第一个字段数值累加到变量sum中,END {print sum}表示在处理完所有输入行后,输出变量sum的值,即所有行第一个字段数值的总和。
练习4:在/backup下创建10个.txt的文件,找到/backup目录下所有后缀名是.txt的文件
批量修改txt为txt.bak,把所有的.bak文件内打包压缩为123.tar.gz,批量还原文件的名字,把增加的.bak删除掉
[root@Shell test]# cat file_handle.sh
#!/bin/bash
for i in `ls /backup`
do
cp /backup/"$i" /backup/"$i".bak
done
tar zcvf /backup/123.tar.gz /backup/*.txt.bak &> /dev/null
rm -rf /backup/*.txt.bak
[root@Shell test]# bash file_handle.sh
[root@Shell test]# ls /backup/
10.txt 1.txt 3.txt 5.txt 7.txt 9.txt
123.tar.gz 2.txt 4.txt 6.txt 8.txt
[root@Shell test]# tar tf /backup/123.tar.gz
backup/10.txt.bak
backup/1.txt.bak
backup/2.txt.bak
backup/3.txt.bak
backup/4.txt.bak
backup/5.txt.bak
backup/6.txt.bak
backup/7.txt.bak
backup/8.txt.bak
backup/9.txt.bak
二. 计算
练习1:输入两个整数计算这两个整数的相加,相减,相乘,相除,求余的结果。
[root@Shell test]# cat jisuan.sh
#/bin/bash
num1=$1
num2=$2
echo "两数相加为:$num1 + $num2 = $(awk "BEGIN{print $num1+$num2}")"
echo "两数相减为:$num1 - $num2 = $(awk "BEGIN{print $num1-$num2}")"
echo "两数相乘为:$num1 * $num2 = $(awk "BEGIN{print $num1*$num2}")"
echo "两数相除为:$num1 / $num2 = $(awk "BEGIN{print $num1/$num2}")"
echo "两数求余为:$num1 % $num2 = $(awk "BEGIN{print $num1%$num2}")"
[root@Shell test]# bash jisuan.sh 9 3
两数相加为:9 + 3 = 12
两数相减为:9 - 3 = 6
两数相乘为:9 * 3 = 27
两数相除为:9 / 3 = 3
两数求余为:9 % 3 = 0
练习2:把一个文本文档的前五行中包含字母的行删掉,同时把6到10行中全部字母删掉
[root@Shell test]# cat delete.sh
#!/bin/bash
# 指定待处理的文本文档路径
file_path="your_file_path.txt"
# 临时文件路径
temp_file="temp.txt"
# 删除前五行中包含字母的行
sed '1,5 { /[a-zA-Z]/d }' "$file_path" > "$temp_file"
# 删除6到10行中的所有字母
sed '6,10 s/[a-zA-Z]//g' "$temp_file" > "$file_path"
# 删除临时文件
rm "$temp_file"
#命令详解:
1,5:指定作用范围为从文件的第1行到第5行。
{ /[a-zA-Z]/d }:在上述范围内,如果行内包含任意英文字符(a-z或A-Z),则执行d命令删除该行。
"${file_path}":指定待处理的原始文件路径。
>:重定向符号,将命令的输出写入到指定文件。
"${temp_file}":处理后结果输出的目标文件路径。
练习3:打印下面这句话中字母数小于3的单词,I am koten I am 18
[root@Shell test]# cat print.sh
#!/bin/bash
sentence="I am koten I am 18"
echo "$sentence" | awk '{ for(i=1; i<=NF; i++) { if (length($i) < 3) print $i } }'
#具体解析如下:
'{...}':这部分是awk的处理块,对每一行输入数据执行其中的命令。
for(i=1; i<=NF; i++):NF是awk内置变量,表示当前行的字段数。这段代码是一个循环,遍历当前行的所有字段(从第1个字段到第NF个字段)。
if (length($i) < 3) print $i:length($i)计算当前字段$i的长度,如果长度小于3,则打印该字段。
练习4:写一个Shell,看看linux系统中是否有自定义用户(普通用户),若是有,一共有几个?
[root@Shell test]# cat user.sh
#!/bin/bash
# 获取系统中普通用户的数量
user_count=$(awk -F: '$3>=1000 && $1!="nobody" {print $1}' /etc/passwd | wc -l)
if [ $user_count -gt 0 ]; then
echo "系统中存在 $user_count 个自定义普通用户。"
else
echo "系统中没有自定义普通用户。"
fi
#解释
user_count=$(awk -F: '$3>=1000 && $1!="nobody" {print $1}' /etc/passwd | wc -l)
使用awk命令处理/etc/passwd文件,这是一个存储系统用户信息的文件,每行代表一个用户。
-F: 指定输入字段的分隔符为冒号,因为/etc/passwd文件中的每行用户信息由多个冒号分隔的字段组成。
$3>=1000 && $1!="nobody" 是一个条件判断,筛选出第三字段(即用户ID,UID)大于等于1000且用户名不是"nobody"的行。
{print $1} 输出符合条件的行的第一个字段,即用户名。
结果通过管道传递给wc -l命令,统计匹配行的数量,赋值给变量user_count。
练习5:写一个shell脚本来看看你使用的最多的命令是哪些,列出你最常用的命令top10
[root@Shell test]# cat history_cmd.sh
#!/bin/bash
# 指定要统计的shell历史文件路径
history_file=~/.bash_history
# 统计命令使用频率,并获取前10个最常用的命令
top_commands=$(cat "$history_file" | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10)
echo "您使用最多的命令 Top 10:"
echo "$top_commands"
练习6:编写一个脚本,计算100以内所有能被3整除数字的和
[root@Shell test]# cat 3.sh
#!/bin/bash
sum=0
# 循环遍历1到100之间的数字
for ((num=1; num<=100; num++))
do
# 判断数字是否能被3整除
if [ $((num % 3)) -eq 0 ]; then
sum=$((sum + num))
fi
done
echo "100以内所有能被3整除的数字的和为:$sum"
三. 判断
练习1:将下面的条件表达式,写成if条件语句
[ -f /etc/hosts ] && echo !
[root@Shell test]# cat if.sh
#!/bin/bash
if [ -f /etc/hosts ]; then
echo "!"
fi
练习2:写一个脚本,实现判断10.0.0.0/24网络里,当前在线用户的IP有哪些
[root@Shell test]# cat ping.sh
#!/bin/bash
network="10.0.0"
# 循环遍历IP地址范围内的所有地址
for ((i=1; i<=255; i++))
do
ip="${network}.${i}"
# 使用ping命令检查IP地址是否在线
ping -c 1 -w 1 "$ip" > /dev/null 2>&1
# 检查ping命令的返回值,如果为0则表示IP地址在线
if [ $? -eq 0 ]; then
echo "IP地址 $ip 在线"
fi
done
#解释
ping -c 1 -w 1 "$ip":对每个生成的IP地址执行一次ping命令,参数-c 1表示只发送一个ICMP Echo Request数据包,-w 1则设置超时时间为1秒。将命令的输出重定向到/dev/null是为了忽略其输出,同时2>&1将错误输出也重定向到/dev/null,即忽略所有输出。
if [ $? -eq 0 ]:检查上一条命令(即ping命令)的退出状态码(Exit Status)。在Unix/Linux系统中,命令执行成功通常返回0,失败则返回非零值。因此,[ $? -eq 0 ]就是在检查ping命令是否成功执行,如果成功则说明该IP地址在线。
echo "IP地址 $ip 在线":如果ping命令成功,则输出一条信息,表示该IP地址在线。
练习3:用shell处理以下内容,按单词出现频率降序排序,按字母出现频率降序排序。
the squid project provides a number of resources toassist users design,implement and support squid installations. Please browsethe documentation and support sections for more infomation
[root@Shell test]# cat sort.sh
#!/bin/bash
content="the squid project provides a number of resources to assist users design, implement and support squid installations. Please browse the documentation and support sections for more information."
# 按单词出现频率降序排序
word_frequency=$(echo "$content" | awk '{ for(i=1; i<=NF; i++) { count[$i]++ } } END { for(word in count) { print count[word], word } }' | sort -rn)
echo "按单词出现频率降序排序:"
echo "$word_frequency"
echo
# 按字母出现频率降序排序
letter_frequency=$(echo "$content" | grep -o . | grep -v '[. ]' | sort | uniq -c | sort -rn)
echo "按字母出现频率降序排序:"
echo "$letter_frequency"
#解释
按单词出现频率降序排序:
word_frequency=$(...):这一行首先将变量$content的内容通过管道传递给awk命令。
awk命令会遍历每一行中的每一个字段(单词),并通过数组count记录每个单词出现的次数,键为单词,值为计数。
END块会在处理完所有行后执行,此时会遍历count数组并将每个单词及其出现次数打印出来。
输出被进一步传递给sort -rn命令,按照数值(即单词出现次数)进行降序排序。
最后,word_frequency变量存储了排序后的单词及其出现频率列表。
按字母出现频率降序排序:
letter_frequency=$(...):同样地,这一部分将$content的内容通过管道操作进行处理。
grep -o .:提取出字符串中的每一个字符,包括空格和标点符号。
grep -v '[. ]':去除其中的空格和句点,仅保留字母和其它非空白字符。
sort:将所有字符排序。
uniq -c:计算连续重复字符的数量,即每个字符的出现频率。
最后再次使用sort -rn对带有计数的字符进行降序排序。
letter_frequency变量存储了排序后的字母及其出现频率列表。
练习4:使用 ps aux 查看系统进程发现有100个test.sh脚本正在运行,如何杀死所有的test.sh
[root@Shell test]# cat kill.sh
ps aux | grep test.sh | awk '{print $2}' | xargs kill
练习6: 用Shell实现,把一个文本文档中只有一个数字的行给打印出来
[root@Shell test]# cat print_num.sh
#!/bin/bash
filename="your_text_file.txt"
while read -r line; do
# 使用正则表达式匹配只有一个数字的行
if [[ $line =\~ ^[0-9]+$ ]]; then
echo "$line"
fi
done < "$filename"
练习7:写一个Shell脚本通过curl命令返回的状态码来判定所访问的网站是否正常,比如当前状态码200,才算正常
[root@Shell test]# cat curl.sh
#!/bin/bash
# 设置要访问的网站URL
website="https://www.baidu.com"
# 发送GET请求并获取状态码
status_code=$(curl -s -o /dev/null -w "%{http_code}" "$website")
# 判断状态码是否为200
if [ "$status_code" -eq 200 ]; then
echo "Website is accessible!"
else
echo "Website is not accessible! Status code: $status_code"
fi
#解释
-s 参数表示静默模式,禁止curl输出任何错误消息或进度信息。
-o /dev/null 参数告诉curl将下载的数据输出到/dev/null,这是一个特殊的设备文件,任何写入的信息都会被丢弃,这里我们并不关心下载的内容,只需要获取HTTP响应状态码。
-w "%{http_code}" 参数是curl的写入选项(write-out),它指示curl在完成请求后输出指定格式的内容。这里的"%{http_code}"是一个curl内置变量,它代表HTTP响应的状态码,如200、404、500等。
"$website" 是要请求的URL,放在双引号中以正确处理可能包含空格或其他特殊字符的情况。
四. 循环
练习2:查看磁盘、当前使用状态,如果使用率超过80%则把结果输出到/var/log/disk.err
[root@Shell test]# cat disk.sh
#!/bin/bash
# 设置磁盘使用率阈值
threshold=80
# 执行df命令获取磁盘使用状态,并使用awk过滤出使用率超过阈值的行
df_output=$(df -h | awk '$5 > threshold')
# 检查是否有超过阈值的行
if [ -n "$df_output" ]; then
echo "$df_output" >> /var/log/disk.err
echo "Disk usage exceeded the threshold and logged to /var/log/disk.err"
else
echo "Disk usage is within the threshold"
fi
练习3:脚本批量创建10个用户,密码统一为123,需要对用户输入是否为数字和输入的名字是否为空做判断
[root@Shell test]# cat useradd.sh
#!/bin/bash
count=1
while [ $count -le 10 ]
do
echo "请输入第 $count 个用户的名字:"
read username
# 判断用户名是否为空
if [ -z "$username" ]; then
echo "用户名不能为空,请重新输入!"
continue
fi
echo "请输入第 $count 个用户的密码:"
read password
# 判断密码是否为数字
if ! [[ $password =\~ ^[0-9]+$ ]]; then
echo "密码必须为数字,请重新输入!"
continue
fi
# 创建用户并设置密码
sudo useradd -m $username
echo "$username:$password" | sudo chpasswd
count=$((count + 1))
done
五. 控制与数组
练习1:生成0-100之间的随机数,并相加,直到大于1000,输出相加的结果
[root@Shell test]# cat addnum.sh
#!/bin/bash
sum=0
while [ $sum -le 1000 ]
do
# 生成0-100之间的随机数
random_number=$((RANDOM % 101))
sum=$((sum + random_number))
done
echo "和大于1000的结果为: $sum"
练习2:生成0-100之间的随机数,并相加,直到大于1000,并判断最后一个随机数字能否被3整除
[root@Shell test]# cat 3.sh
#!/bin/bash
sum=0
last_random_number=0
while [ $sum -le 1000 ]
do
# 生成0-100之间的随机数
random_number=$((RANDOM % 101))
sum=$((sum + random_number))
last_random_number=$random_number
done
echo "和大于1000的结果为: $sum"
# 判断最后一个随机数字是否能被3整除
if [ $((last_random_number % 3)) -eq 0 ]; then
echo "最后一个随机数字 $last_random_number 能被3整除"
else
echo "最后一个随机数字 $last_random_number 不能被3整除"
fi
练习3:判断/tmp/目录下是否有大于4K的文件,如果有则输出该文件的大小与创建时间
[root@Shell test]# cat 4K.sh
#!/bin/bash
path="/tmp/"
# 使用find命令查找/tmp/目录下大于4K的文件,并使用-printf选项输出文件大小和创建时间
find "$path" -type f -size +4k -printf "文件:%p,大小:%s字节,创建时间:%TY-%Tm-%Td %TH:%TM:%TS\n"
练习5:使用数组判断 I am koten welcome to my hometown 中字母数大于6的单词
[root@Shell test]# cat array_6.sh
#!/bin/bash
sentence="I am koten welcome to my hometown"
words=($sentence)
# 使用for循环遍历数组中的每个单词
for word in "${words[@]}"
do
# 统计单词中的字母数
length=${#word}
# 判断字母数是否大于6
if [ $length -gt 6 ]; then
echo "$word"
fi
done
六. 实战进阶
练习1:检测服务器中重要的文件是否被修改,如果修改则报警(使用指纹)
[root@Shell test]# cat fingerprint.sh
#!/bin/bash
# 定义重要文件的路径和对应的指纹
declare -A important_files=(
["/path/to/file1"]="fingerprint1"
["/path/to/file2"]="fingerprint2"
["/path/to/file3"]="fingerprint3"
)
# 检测文件是否被修改
for file in "${!important_files[@]}"; do
# 计算当前文件的指纹
current_fingerprint=$(md5sum "$file" | awk '{print $1}')
# 比较当前指纹和预设的指纹
if [[ "$current_fingerprint" != "${important_files[$file]}" ]]; then
echo "警报!文件 $file 已被修改!"
# 在此处添加触发报警的操作,比如发送邮件或短信通知管理员
fi
done
练习2:计算nginx日志中访问最多的10个IP使用的流量综合
[root@Shell test]# cat ip.sh
#!/bin/bash
logfile="access.log"
# 使用awk命令提取日志中的IP和流量信息,并按照IP统计流量总和
ip_traffic=$(awk '{ ip[$1] += $10 } END { for(i in ip) print i, ip[i] }' "$logfile")
# 使用sort命令对流量进行降序排序
sorted_ip_traffic=$(echo "$ip_traffic" | sort -k2 -rn)
# 使用head命令获取前10个IP和对应的流量
top_10_ip_traffic=$(echo "$sorted_ip_traffic" | head -n 10)
# 输出结果
echo "访问最多的10个IP使用的流量综合:"
echo "$top_10_ip_traffic"
练习3:防止DOS攻击,检测nginx日志,若某个IP短时间的PV过大则使用防火墙将其禁掉
[root@Shell test]# cat iptables.sh
#!/bin/bash
logfile="access.log"
threshold=100 # 设置阈值,表示短时间内的页面访问量上限
ban_time=60 # 设置禁用时间,单位为秒
# 使用awk命令提取日志中的IP信息,并统计每个IP的页面访问量
ip_pv=$(awk '{ ip[$1]++ } END { for(i in ip) print i, ip[i] }' "$logfile")
# 使用while循环遍历PV统计结果
while IFS= read -r line; do
ip=$(echo "$line" | awk '{print $1}')
pv=$(echo "$line" | awk '{print $2}')
# 判断页面访问量是否超过阈值
if [ "$pv" -gt "$threshold" ]; then
echo "警报!IP $ip 的短时间内页面访问量过大!"
# 使用防火墙命令禁用IP
iptables -A INPUT -s "$ip" -j DROP
# 等待一段时间后解禁IP
sleep "$ban_time"
iptables -D INPUT -s "$ip" -j DROP
fi
done <<< "$ip_pv"
练习4:实时监控服务器CPU使用率大于80%,磁盘使用率大于80%,内存使用率大于80%时报警
[root@Shell test]# cat cpu_disk.sh
#!/bin/bash
# 配置监控阈值
cpu_threshold=80
disk_threshold=80
memory_threshold=80
# 检查CPU使用率
check_cpu() {
cpu_usage=$(sar -u 1 1 | grep Average | awk '{print 100 - $NF}')
if [ "$cpu_usage" -gt "$cpu_threshold" ]; then
echo "警报!CPU使用率超过 $cpu_threshold%!"
# 在此处添加触发报警的操作,比如发送邮件通知管理员
fi
}
# 检查磁盘使用率
check_disk() {
disk_usage=$(df -h | awk '/\/$/ {print $5}' | sed 's/%//')
if [ "$disk_usage" -gt "$disk_threshold" ]; then
echo "警报!磁盘使用率超过 $disk_threshold%!"
# 在此处添加触发报警的操作,比如发送邮件通知管理员
fi
}
# 检查内存使用率
check_memory() {
memory_usage=$(free | awk '/Mem/ {printf "%.2f", $3/$2 * 100}')
if (( $(echo "$memory_usage > $memory_threshold" | bc -l) )); then
echo "警报!内存使用率超过 $memory_threshold%!"
# 在此处添加触发报警的操作,比如发送邮件通知管理员
fi
}
# 持续监控
while true; do
check_cpu
check_disk
check_memory
sleep 5
done
练习6:数据库分库分表备份到/tmp下。
[root@Shell test]# cat mysql.sh
#!/bin/bash
# 定义数据库连接信息
host=localhost
port=3306
user=root
password=123456
# 定义要备份的数据库和表
db_list=(db1 db2)
table_list=(table1 table2 table3)
# 遍历数据库
for db in ${db_list[@]}; do
# 遍历表
for table in ${table_list[@]}; do
# 构建备份文件名
bak_file=/tmp/${db}_${table}.sql
# 执行备份命令
mysqldump -h$host -P$port -u$user -p$password $db $table > $bak_file
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "备份 $db.$table 成功"
else
echo "备份 $db.$table 失败"
fi
done
done
向数据库中插入数据
#!/bin/bash
# send data to the the table in the MYSQL database
MYSQL=`which mysql`
if [ $# -ne 2 ]
then
echo "Usage:mtest2 emplid lastname firstname salary"
else
#脚本变量一定要用双引号,字符串变量使用单引号
statement=" insert into em_admin values(NULL, '$1', $2)"
$MYSQL emwjs -u test <<EOF
$statement
EOF
if [ $? -eq 0 ]
then
echo Data successfully added
else
echo Problem adding data
fi
fi
格式化输出数据
#!/bin/bash
#redirecting SQL output to a variable
MYSQL=`which mysql`
dbs=`$MYSQL emwjs -u test -Bse 'show tables;'`
for db in $dbs
do
echo $db
done
#使用xml输出数据
$MYSQL emwjs -u test -X -e 'select * from em_admin'
#使用table标签输出数据
$MYSQL emwjs -u test -H -e 'select * from em_admin'
连接数据库并发送命令
#!/bin/bash
#连接数据库
mysql=`which mysql
`
#发送单个命令
$mysql emwjs -u test -e "show databases;"
#发送多个命令
$mysql emwjs -u test <<EOF
show tables;
select * from em_admin;
EOF