转载https://www.toutiao.com/i6921165421349487107/?wid=1620965217918
shell脚本是帮助程序员和系统管理员完成费时费力的枯燥工作的利器,是与计算机交互并管理文件和系统操作的有效方式。区区几行代码,就可以让计算机接近按照你的意图行事。
博智互联为大家整理了100个实例,通过100个实战经典脚本实例,展示了shell脚本编程的实用技术和常见工具用法。大家只需根据自己的需求,将文中这些常见任务和可移植自动化脚本推广应用到其他类似问题上,能解决那些三天两头碰上的麻烦事。
判断用户输入的是否为数字
方法1:
#!/bin/bash
if [[ $1 =~ ^[0-9]+$ ]]; then
echo "Is Number."
else
echo "No Number."
fi
方法2:
#!/bin/bash
if [ $1 -gt 0 ] 2>/dev/null; then
echo "Is Number."
else
echo "No Number."
fi
方法3:
#!/bin/bash
echo $1 |awk '{print $0~/^[0-9]+$/?"Is Number.":"No Number."}' #三目运算符
12.14 找出包含关键字的文件
DIR=$1
KEY=$2
for FILE in $(find $DIR -type f); do
if grep $KEY $FILE &>/dev/null; then
echo "--> $FILE"
fi
done
监控目录,将新创建的文件名追加到日志中
场景:记录目录下文件操作。
需先安装inotify-tools软件包。
#!/bin/bash
MON_DIR=/opt
inotifywait -mq --format %f -e create $MON_DIR |\
while read files; do
echo $files >> test.log
done
给用户提供多个网卡选择
场景:服务器多个网卡时,获取指定网卡,例如网卡流量
#!/bin/bash
function local_nic() {
local NUM ARRAY_LENGTH
NUM=0
for NIC_NAME in $(ls /sys/class/net|grep -vE "lo|docker0"); do
NIC_IP=$(ifconfig $NIC_NAME |awk -F'[: ]+' '/inet addr/{print $4}')
if [ -n "$NIC_IP" ]; then
NIC_IP_ARRAY[$NUM]="$NIC_NAME:$NIC_IP" #将网卡名和对应IP放到数组
let NUM++
fi
done
ARRAY_LENGTH=${#NIC_IP_ARRAY[*]}
if [ $ARRAY_LENGTH -eq 1 ]; then #如果数组里面只有一条记录说明就一个网卡
NIC=${NIC_IP_ARRAY[0]%:*}
return 0
elif [ $ARRAY_LENGTH -eq 0 ]; then #如果没有记录说明没有网卡
echo "No available network card!"
exit 1
else
#如果有多条记录则提醒输入选择
for NIC in ${NIC_IP_ARRAY[*]}; do
echo $NIC
done
while true; do
read -p "Please enter local use to network card name: " INPUT_NIC_NAME
COUNT=0
for NIC in ${NIC_IP_ARRAY[*]}; do
NIC_NAME=${NIC%:*}
if [ $NIC_NAME == "$INPUT_NIC_NAME" ]; then
NIC=${NIC_IP_ARRAY[$COUNT]%:*}
return 0
else
COUNT+=1
fi
done
echo "Not match! Please input again."
done
fi
}
local_nic
MySQL数据库备份
#!/bin/bash
DATE=$(date +%F_%H-%M-%S)
HOST=192.168.1.120
DB=test
USER=bak
PASS=123456
MAIL="zhangsan@example.com lisi@example.com"
BACKUP_DIR=/data/db_backup
SQL_FILE=${DB}_full_$DATE.sql
BAK_FILE=${DB}_full_$DATE.zip
cd $BACKUP_DIR
if mysqldump -h$HOST -u$USER -p$PASS --single-transaction --routines --triggers -B $DB > $SQL_FILE; then
zip $BAK_FILE $SQL_FILE && rm -f $SQL_FILE
if [ ! -s $BAK_FILE ]; then
echo "$DATE 内容" | mail -s "主题" $MAIL
fi
else
echo "$DATE 内容" | mail -s "主题" $MAIL
fi
find $BACKUP_DIR -name '*.zip' -ctime +14 -exec rm {} \;
Nginx服务管理脚本
场景:使用源码包安装Nginx不含带服务管理脚本,也就是不能使用"service nginx start"或"/etc/init.d/nginx start",所以写了以下的服务管理脚本。
#!/bin/bash
# Description: Only support RedHat system
. /etc/init.d/functions
WORD_DIR=/usr/local/nginx
DAEMON=$WORD_DIR/sbin/nginx
CONF=$WORD_DIR/conf/nginx.conf
NAME=nginx
PID=$(awk -F'[; ]+' '/^[^#]/{if($0~/pid;/)print $2}' $CONF)
if [ -z "$PID" ]; then
PID=$WORD_DIR/logs/nginx.pid
else
PID=$WORD_DIR/$PID
fi
stop() {
$DAEMON -s stop
sleep 1
[ ! -f $PID ] && action "* Stopping $NAME" /bin/true || action "* Stopping $NAME" /bin/false
}
start() {
$DAEMON
sleep 1
[ -f $PID ] && action "* Starting $NAME" /bin/true || action "* Starting $NAME" /bin/false
}
reload() {
$DAEMON -s reload
}
test_config() {
$DAEMON -t
}
case "$1" in
start)
if [ ! -f $PID ]; then
start
else
echo "$NAME is running..."
exit 0
fi
;;
stop)
if [ -f $PID ]; then
stop
else
echo "$NAME not running!"
exit 0
fi
;;
restart)
if [ ! -f $PID ]; then
echo "$NAME not running!"
start
else
stop
start
fi
;;
reload)
reload
;;
testconfig)
test_config
;;
status)
[ -f $PID ] && echo "$NAME is running..." || echo "$NAME not running!"
;;
*)
echo "Usage: $0 {start|stop|restart|reload|testconfig|status}"
exit 3
;;
esac
用户根据菜单选择要连接的Linux主机
Linux主机SSH连接信息:
# cat host.txt
Web 192.168.1.10 root 22
DB 192.168.1.11 root 22
内容格式:主机名 IP User Port
#!/bin/bash
PS3="Please input number: "
HOST_FILE=host.txt
while true; do
select NAME in $(awk '{print $1}' $HOST_FILE) quit; do
[ ${NAME:=empty} == "quit" ] && exit 0
IP=$(awk -v NAME=${NAME} '$1==NAME{print $2}' $HOST_FILE)
USER=$(awk -v NAME=${NAME} '$1==NAME{print $3}' $HOST_FILE)
PORT=$(awk -v NAME=${NAME} '$1==NAME{print $4}' $HOST_FILE)
if [ $IP ]; then
echo "Name: $NAME, IP: $IP"
ssh -o StrictHostKeyChecking=no -p $PORT -i id_rsa $USER@$IP # 密钥免交互登录
break
else
echo "Input error, Please enter again!"
break
fi
done
done
将结果分别赋值给变量
应用场景:希望将执行结果或者位置参数赋值给变量,以便后续使用。
方法1:
for i in $(echo "4 5 6"); do
eval a$i=$i
done
echo $a4 $a5 $a6
方法2:将位置参数192.168.1.1{1,2}拆分为到每个变量
num=0
for i in $(eval echo $*);do #eval将{1,2}分解为1 2
let num+=1
eval node${num}="$i"
done
echo $node1 $node2 $node3
# bash a.sh 192.168.1.1{1,2}
192.168.1.11 192.168.1.12
方法3:
arr=(4 5 6)
INDEX1=$(echo ${arr[0]})
INDEX2=$(echo ${arr[1]})
INDEX3=$(echo ${arr[2]})
读取list.txt文件中的账号密码批量创建用户
#!/bin/bash
#提前准备账号密码文件list.txt
#文件内每行一个账号密码组,使用逗号分隔(username,password)
for line in `cat list.txt`
do
echo $line | awk -F, '{cmd="useradd "$1"; echo "$2" | passwd --stdin "$1; system(cmd)}' #awk中调用系统命令
done
显示当前计算机中所有账户的用户名
#!/bin/bash
echo "方法①,指定以:为分隔符,打印/etc/passwd文件的第1列"
awk -F: '{print $1}' /etc/passwd
echo "方法②,指定以:为分隔符,打印/etc/passwd文件的第1列"
cut -d: -f1 /etc/passwd
echo "方法③使用sed的替换功能,将/etc/passwd文件中第1个:后面的所有内容替换为空(仅显示用户名)"
sed "s/:.*//" /etc/passwd
统计当前Linux系统中可以登陆计算机的账户数量
#!/bin/bash
#方法1grep:
grep "bash$" /etc/passwd | wc -l #一般默认解释器是bash
#方法2awk:
awk -F: '/bash$/{x++} END{print x}' /etc/passwd
#方法3,更为准确
for shell in `awk -F: '{print $7}' /etc/passwd` #取出账号文件中各账号的解释器
do
if [[ "`cat /etc/shells`" =~ "$shell" ]]; then #检测取出的解释器是否包含在可登录解释器中
let n++
fi
done
echo $n
找出/etc/passwd中能登录的用户,并将对应在/etc/shadow中第二列密码提出处理
#!/bin/bash
shells=`awk -F: '$7!~"nologin"{print $1","$7}' /etc/passwd`
for shell in $shells
do
if [[ "`grep -v '^#' /etc/shells`" =~ "`echo $shell | cut -d, -f2`" ]]; then
users="$users `echo $shell | cut -d, -f1`"
fi
done
for user in $users
do
awk -F: -v u=$user '$1~u{print $1,$2}' /etc/shadow
done
自动修改计划任务配置文件
#!/bin/bash
read -p "请输入分钟信息(00-59):" min
read -p "请输入小时信息(00-24):" hour
read -p "请输入日期信息(01-31):" date
read -p "请输入月份信息(01-12):" month
read -p "请输入周几信息(00-06):" weak
read -p "请输入计划任务需要执行的命令或脚本:" program
echo "$min $hour $date $month $weak $USER $program" >> /etc/crontab
实时监视本机内存、/分区剩余空间,当剩余空间达到阈值发送报警邮件给root管理员
#!/bin/bash
disk_value=1000 #设置/分区监测空间
mem_value=500 #设置内存监测空间
disk_size=`df -m / | awk '/\//{print $4}'` #获取/的剩余硬盘空间,单位M
mem_size=`free -m | awk '/Mem/{print $4}'` #获取剩余内存空间,单位M
while :
do
if [ $disk_size -le $disk_value -o $mem_size -le $mem_value ]; then #判断条件
echo "Insufficient resources,资源不足" | mail -s "Warning" root #达到阈值发送邮件给root
fi
done
批量下载有序文件(pdf、图片、视频等)
#!/bin/bash
#本脚本进行有序的网络资料批量下载操作(如01.jpg,02.jpg,03.jpg)
url="http://www.test.com"
type=jpg
Dpath=/mnt
echo "开始下载..."
for num in `seq 10`
do
echo -n "正在下载$num.$type"
curl -s ${url}/${num}.${type} -o $Dpath/${num}.$type #-o选项,curl指定下载文件另存为
if [ $? -eq 0 ]; then
echo -e " [\e[32mOK\e[0m]"
else
echo -e " [\e[31mERROR\e[0m]"
fi
sleep 1
done
批量修改文件名
示例:
# touch article_{1..3}.html
# ls
article_1.html article_2.html article_3.html
目的:把article改为bbs
方法1:
for file in $(ls *html); do
mv $file bbs_${file#*_}
# mv $file $(echo $file |sed -r 's/.*(_.*)/bbs\1/')
# mv $file $(echo $file |echo bbs_$(cut -d_ -f2)
done
方法2:
for file in $(find . -maxdepth 1 -name "*html"); do
mv $file bbs_${file#*_}
done
方法3:
# rename article bbs *.html
统计当前目录中以.html结尾的文件总大
方法1:
# find . -name "*.html" -exec du -k {} \; |awk '{sum+=$1}END{print sum}'
方法2:
for size in $(ls -l *.html |awk '{print $5}'); do
sum=$(($sum+$size))
done
echo $sum
扫描主机端口状态
#!/bin/bash
HOST=$1
PORT="22 25 80 8080"
for PORT in $PORT; do
if echo &>/dev/null > /dev/tcp/$HOST/$PORT; then
echo "$PORT open"
else
echo "$PORT close"
fi
done
自动优化Linux内核参数
#!/bin/bash
#此脚本针对RHEL7系列
cat >> /usr/lib/sysctl.d/00-system.conf << EOF
fs.file-max=65535
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 5
net.ipv4.tcp_syn_retries = 5
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
#net.ipv4.tcp_keepalive_time = 120
net.ipv4.ip_local_port_range = 1024 65535
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernek.shmmin = 4096
kernel.sem = 5010 641280 5010 128
net.core.wmem_default=262144
net.core.wmem_max=262144
net.core.rmem_default=4194304
net.core.rmem_max=4194304
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
EOF
统计/var/log有多少个文件,并显示这些文件名
#!/bin/bash
#方法1
sum=0 #用于统计数量
for i in `ls /var/log/`
do
if [ -f $i ];then
let sum++
echo "文件名:$i"
fi
done
echo "文件总量为:$sum"
#方法2
echo "文件总量为:"`find /var/log -maxdepth 1 -type f | wc -l`
查找Linux系统中的僵尸进程
#!/bin/bash
#awk判断ps命令输出的第8列为z时,显示该进程的PID和进程命令
ps aux | awk '{if($8 == "Z" ){print $2,$11}}'
Expect实现SSH免交互执行命令
Expect是一个自动交互式应用程序的工具,如telnet,ftp,passwd等。
需先安装expect软件包。
方法1:EOF标准输出作为expect标准输入
#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect << EOF
set timeout 30
spawn ssh $USER@$IP
expect {
"(yes/no)" {send "yes\r"; exp_continue}
"password:" {send "$PASS\r"}
}
expect "$USER@*" {send "$1\r"}
expect "$USER@*" {send "exit\r"}
expect eof
EOF
方法2:
#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect -c "
spawn ssh $USER@$IP
expect {
\"(yes/no)\" {send \"yes\r\"; exp_continue}
\"password:\" {send \"$PASS\r\"; exp_continue}
\"$USER@*\" {send \"df -h\r exit\r\"; exp_continue}
}"
方法3:将expect脚本独立出来
登录脚本:
# cat login.exp
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set passwd [lindex $argv 2]
set cmd [lindex $argv 3]
if { $argc != 4 } {
puts "Usage: expect login.exp ip user passwd"
exit 1
}
set timeout 30
spawn ssh $user@$ip
expect {
"(yes/no)" {send "yes\r"; exp_continue}
"password:" {send "$passwd\r"}
}
expect "$user@*" {send "$cmd\r"}
expect "$user@*" {send "exit\r"}
expect eof
执行命令脚本:写个循环可以批量操作多台服务器
#!/bin/bash
HOST_INFO=user_info.txt
for ip in $(awk '{print $1}' $HOST_INFO)
do
user=$(awk -v I="$ip" 'I==$1{print $2}' $HOST_INFO)
pass=$(awk -v I="$ip" 'I==$1{print $3}' $HOST_INFO)
expect login.exp $ip $user $pass $1
done
Linux主机SSH连接信息:
# cat user_info.txt
192.168.1.120 root 123456
批量修改服务器用户密码
Linux主机SSH连接信息:旧密码
# cat old_pass.txt
192.168.18.217 root 123456 22
192.168.18.218 root 123456 22
内容格式:IP User Password Port
SSH远程修改密码脚本:新密码随机生成
https://www.linuxprobe.com/books
#!/bin/bash
OLD_INFO=old_pass.txt
NEW_INFO=new_pass.txt
for IP in $(awk '/^[^#]/{print $1}' $OLD_INFO); do
USER=$(awk -v I=$IP 'I==$1{print $2}' $OLD_INFO)
PASS=$(awk -v I=$IP 'I==$1{print $3}' $OLD_INFO)
PORT=$(awk -v I=$IP 'I==$1{print $4}' $OLD_INFO)
NEW_PASS=$(mkpasswd -l 8) # 随机密码
echo "$IP $USER $NEW_PASS $PORT" >> $NEW_INFO
expect -c "
spawn ssh -p$PORT $USER@$IP
set timeout 2
expect {
\"(yes/no)\" {send \"yes\r\";exp_continue}
\"password:\" {send \"$PASS\r\";exp_continue}
\"$USER@*\" {send \"echo \'$NEW_PASS\' |passwd --stdin $USER\r exit\r\";exp_continue}
}"
done
生成新密码文件:
# cat new_pass.txt
192.168.18.217 root n8wX3mU% 22
192.168.18.218 root c87;ZnnL 22
while循环检测所处网段主机的开关机状态
#!/bin/bash
i=1
while [ $i -le 254 ]
do
ping -c2 -i0.2 -W1 192.168.4.$i &>/dev/null
if [ $? -eq 0 ] ;then
echo -e "\e[32;1m192.168.4.$i is up[0m"
else
echo "192.168.4.$i is down"
fi
let i++
done
循环关闭局域网中所有主机
#!/bin/bash
#假设本机为192.168.4.100,编写脚本关闭除自己外的其他所有主机
#脚本执行,需要提前给所有其他主机传递ssh密钥,满足无密码连接
for i in {1..254}
do
[ $i -eq 100 ] && continue
echo "正在关闭192.168.4.$i..."
ssh 192.168.4.$i poweroff
done
自动添加防火墙规则(开启服务或端口
#!/bin/bash
#适用于RHEL7系列
#firewall-cmd --get-services #可以查看firewall支持哪些服务
#cat /etc/services #可以查看服务、端口的对应表
services="ssh http nfs" #设置服务
ports="22 80 2049" #设置端口号 ssh、http 、 nfs
#循环将每个服务添加到防火墙规则中
for service in $services
do
echo "添加 $service 服务到 防火墙"
firewall-cmd --add-service=${service}
done
#循环将每个端口添加到防火墙规则中
for port in $ports
do
echo "添加 $port 端口到 防火墙"
firewall-cmd --add-port=${port}/tcp
done
#将以上设置的临时防火墙规则,转换为永久有效的规则(确保重启后有效)
firewall-cmd --runtime-to-permanent
自动配置rsyncd服务器的配置文件rsyncd.conf
#!/bin/bash
#man rsyncd.conf #可查看更多选项
[ ! -d /home/ftp ] && mkdir /home/ftp
echo 'uid = nobody
gid = nobody
use chroot = yes
max connections = 4
pid file = /var/run/rsyncd.pid
exclude = lost+found/
transfer logging = yes
timeout = 900
ignore nonreadable = yes
dont compress = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2
[ftp]
path = /home/ftp
comment = share' > /etc/rsyncd.conf
一键配置VNC远程桌面服务器(无密码仅查看版本)
#!/bin/bash
#
#脚本配置的VNC服务器,客户端无需密码即可连接
#客户端仅有查看远程桌面的权限,没有鼠标、键盘的操作权限
rpm --quiet -q tigervnc-server #--quiet安静,-q查询,vnc的安装情况
if [ $? -ne 0 ];then
yum -y install tigervnc-server
fi
x0vncserver AcceptKeyEvents=0 AlwaysShared=1 AcceptPointerEvents=0 SecurityTypes=None rfbport
fdisk命令自动对磁盘分区、格式化、挂载
#!/bin/bash
#对虚拟机的vdb磁盘进行分区格式化,使用<<将需要的分区指令导入给程序fdisk
#n(新建分区),p(创建主分区),1(分区编号为1),
#两个空白行(两个回车,相当于将整个磁盘分一个区)。
#注意:1后面的两个回车(空白行)是必须的!
fdisk /dev/vdb << EOF
n
p
1
wq
EOF
#格式化刚刚创建好的分区
mkfs.xfs /dev/vdb1
#创建挂载点目录
if [ -e /data ] ;then
exit
fi
mkdir /data
#自动挂载刚刚创建的分区,并设置开机自动挂载该分区
echo "/dev/vdb1 /data xfs defaults 1 2" >> /etc/fstab
mount -a
自动创建逻辑卷
#!/bin/bash
#清屏,显示警告信息,创建将磁盘转换为逻辑卷会删除数据
clear
echo -e "\033[33m !!!!!!警告(Warning)!!!!!!\033[0m"
echo
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
echo "脚本会将整个磁盘转换为 PV,并删除磁盘上所有数据!!!"
echo "This Script will destroy all data on the Disk"
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
lsblk #显示当前的块设备
echo
read -p "请问是否继续 y/n?:" sure
[ $sure != y ] && exit #测试用户输入的是否为y,若不是则退出脚本
#提示用户输入相关参数(磁盘、卷组名等数据),并测试用户是否输入了这些值,
#若没输入,则脚本退出
read -p "请输入磁盘名称,如/dev/vdb:" disk
[ -z $disk ] && echo "没有输入磁盘名称" && exit #-z后边的字符串长度为0则为真
read -p "请输入卷组名称:" vg_name
[ -z $vg_name ] && echo "没有输入卷组名称" && exit
read -p "请输入逻辑卷名称:" lv_name
[ -z $lv_name ] && echo "没有输入逻辑卷名称" && exit
read -p "请输入逻辑卷大小:" lv_size
[ -z $lv_size ] && echo "没有输入逻辑卷大小" && exit
#使用命令创建逻辑卷
pvcreate $disk
vgcreate $vg_name $disk
lvcreate -L ${lv_size}M -n ${lv_name} ${vg_name}
#
lsblk $disk