0%

Shell实用脚本

前言

好多shell脚本,写过就扔了,感觉很可惜。本文会不断更新,用于记录自我感觉良好的脚本。以后再遇到相似的需求,方便进行参考查找。

如果想要系统学习Bash,推荐学习阮一峰前辈的《Bash 脚本教程》

短脚本

创建用户

创建voidking用户,并添加sudo权限:

1
2
3
useradd -m voidking -s /bin/bash
passwd voidking
adduser voidking sudo

过滤空行

过滤hostlist.txt中的空行(linux和windows空行都进行过滤):

1
grep -v -e '^[[:space:]]*$' ng.txt

过滤windows换行符

在windows里编辑好的文件,上传到linux后发现多了^M。以过滤hostlist.txt中的^M为例:

1
sed -i 's/^M//g' hostlist.txt

注意,直接复制粘贴上面的命令是无效的。^M的输入方式是 Ctrl + V ,然后 Ctrl + M 。

空格转换行

把test.txt文本中的空格变成换行符。

1
2
cat test.txt | tr ' ' '\n'
cat test.txt | sed 's/ /\n/g'

循环读取单列文本

已知hostlist.txt为:

1
2
www.baidu.com
www.voidking.com

需求:批量查询主机名或者主机IP

脚本:

1
for i in `cat hostlist.txt`;do host $i;done

文本比较

需求:两个姓名列表,需要对比出两个文件中相同的姓名和不同的姓名。

脚本:

1
2
3
cat file1 > file.txt
cat file2 >> file.txt
cat file.txt | sort |uniq -c | sort -n > result.txt

批量添加开机启动命令

已知主机列表hosts.txt内容为:

1
2
192.168.56.101
192.168.56.102

需求:给主机列表中的机器添加开机启动命令

脚本:

1
for i in `cat hosts.txt`; do ssh $i "echo '/home/voidking/start.sh' | sudo tee -a /etc/rc.local";done

curl post请求带参数

1、application/x-www-form-urlencoded请求:

1
2
3
id=1
ip=10.0.0.1
curl -d "id=${id}&ip=${ip}" http://rap2api.taobao.org/app/mock/241888/updateip

2、application/json请求:

1
curl -H "Content-type: application/json" -X POST -d '{"id":"'${id}'","ip":"'${ip}'"}' http://rap2api.taobao.org/app/mock/241888/updateip

3、multipart/form-data请求:

1
curl -F "id=${id}" -F "filename=@file.tar.gz" http://rap2api.taobao.org/app/mock/241888/updateip

EPEL源

EPEL(ExtraPackagesforEnterpriseLinux)是基于Fedora的一个项目,为RedHat系的操作系统提供额外的高质量软件包。
yum install epel-release

安装epel源之后,可以安装很多原本 No package xxx available 的软件,比如jq:
yum install jq

联合查询

使用shell,能否实现类似于SQL的联合查询?必须可以。

已知file1的内容为:

1
2
1 realname
2 nickname

file2的内容为:

1
2
3
4
5
voidking nickname
haojin realname
hankin nickname
jinhao realname
vk nickname

需求:根据file1和file2的第二列,把file1和file2合并成一个文件。

1
2
3
4
5
2 voidking nickname
1 haojin realname
2 hankin nickname
1 jinhao realname
2 vk nickname

这个需求使用awk命令来实现。
NR,表示awk开始执行程序后所读取的数据行数。
FNR,与NR功用类似,不同的是awk每打开一个新文件,FNR便从0重新累计。
NR==FNR:用于在读取两个或两个以上的文件时,判断是不是在读取第一个文件。
awk处理多个文件的语法:

1
awk -F 分隔符 'BEGIN { 初始化 } { 循环执行部分 } END { 结束处理 }' file_list1 file_list2

其中BEGIN和END可以省略,-F也可以使用默认,循环执行部分,是按行对文件进行处理的。

脚本:

1
2
awk -F " " 'NR==FNR{a[$2]=$0;next}{print a[$2]" "$1}' file1 file2 \
| awk '{print $1" "$3" "$2}'

由NR=FNR为真时,判断当前读入的是第一个文件file1,执行第一个花括号内的内容。
把file1中每行记录都存入数组a,并使用file1的第2个字段作为下标。

由NR=FNR为假时,判断当前读入了第二个文件file2,执行第二个花括号内的内容。
file2中的每行,根据file2的第2个字段打印数组a中的内容,同时打印file2中的第一列。

删除10天前的日志

需求:找出10天前的日志并删除。
脚本:

1
2
find /usr/local/tomcat/logs/ -type f -mtime +10 -exec rm -rfv {} \;
find /usr/local/tomcat/logs/ -type f -mtime +10 | xargs rm -rfv;

计算一列的和

已知fruit.txt内容为:

1
2
3
4
apple 10
orange 7
banana 0
watermelon 1

第一列是水果名称,第二列是水果数量。

需求1:计算水果的总数。

1
2
awk '{sum += $2};END {print sum}' fruit.txt
cat fruit.txt | awk '{sum += $2};END {print sum}'

需求2:计算存在多少种水果。

1
cat fruit.txt | echo "$biz" | awk '{if ($2>0) (sum += 1); else (sum += 0)};END{print sum}'

重定向

0 标准输入
1 标准输出,默认指向屏幕
2 错误输出,默认指向屏幕
/dev/null 黑洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 标准输出重定向到文件
ls >out.txt
cat out.txt
# 等同于
ls 1>out.txt
cat out.txt

# 错误输出重定向到文件
ls 2>out.txt
cat out.txt
ls xxx 2>out.txt
cat out.txt

# 标准输出和错误输出都重定向到文件
(ls xxx || ls) >out.txt 2>&1
cat out.txt
# 等同于
(ls xxx || ls) &>out.txt
cat out.txt

# 标准输出和错误输出都丢弃
(ls xxx || ls) &>/dev/null
# 等同于
(ls xxx; ls) &>/dev/null

测试mtu值

MTU是Maximum Transmission Unit的缩写,表示最大传输单元,MTU的单位是字节。大部分网络设备的MTU都是1500。
把本机的MTU设成比网关的MTU小或相同,就可以减少丢包。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率。
把数据包长度加上数据包头28字节,就得到MTU的值。

1
2
ping -c 3 -s 1472 -M do www.baidu.com
ping -c 3 -s 1473 -M do www.baidu.com

长脚本

循环读取多列文本

已知mobile.txt为:

1
2
haojin 17625160000
voidking 17625160001

需求:拼接成SQL,实现根据name修改mobile。

脚本:

1
2
3
4
5
6
7
8
#!/bin/bash

grep -v "^$" mobile.txt | while read line
do
name=`echo $line | awk '{print $1}'`
mobile=`echo $line | awk '{print $2}'`
echo "update user set mobile=\"${mobile}\" where name=\"${name}\";"
done

PS:不能使用for line in cat 'mobile.txt',因为这种方法会按照空格或换行切分文本。

参数提示和校验

需求:有一个脚本,需要appname、ip、hostname、domain四个参数,并且都不能为空。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/bash

# appname、ip、hostname、domain
while getopts "a:i:H:d:" opt
do
case $opt in
a)
appname=$OPTARG
echo "appname=${appname}"
;;
i)
ip=$OPTARG
echo "ip=${ip}"
;;
H)
hostname=$OPTARG
echo "hostname=${hostname}"
;;
d)
domain=$OPTARG
echo "domain=${domain}"
;;
h)
echo "-a -i -H -d are requierd"
echo "-a: appName"
echo "-i: ip"
echo "-h: hostname"
echo "-d: domain"
exit 0
;;
\?)
echo "-a -i -H -d are requierd"
echo "-a: appName"
echo "-i: ip"
echo "-h: hostname"
echo "-d: domain"
exit 1
;;
esac
done

checkOpts () {
key=$1
value=$2

if [[ ${value} == "" ]]
then
echo -e "\033[31mFATAL: ${key} should not be empty! \033[0m"
exit 1001
fi
}

checkOpts "appname" ${appname}
checkOpts "ip" ${ip}
checkOpts "hostname" ${hostname}
checkOpts "domain" ${domain}

# logic code

执行SQL语句

需求:mysql数据库,获取数据库vk中的app表中所有记录的name,写入到 name.txt 文件。

脚本:

1
2
3
4
5
6
#!/bin/bash

get_all_name="select name from app where app.deleted=0;"

all_name=$(mysql -h192.168.56.100 -uroot -pmypassword -s -e "use vk;${get_all_name}")
echo "${all_name}" > name.txt