0%

Shell实用脚本

前言

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

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

短脚本

查看系统版本

1
lsb_release -a

查看命令属于哪个包

1、centos

1
2
yum makecache fast
yum whatprovides lsb_release

2、ubuntu

1
2
3
apt-get install -y apt-file
apt-file update
apt-file search ifconfig

创建用户

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

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

生成密钥

1
2
ssh-keygen
ssh-keygen -C "voidking@qq.com" -f ./voidking_rsa

第一条命令执行完之后,.ssh目录下生成了 id_rsa 和 id_rsa.pub。
第二条命令执行完之后,当前目录下生成了 voidking_rsa 和 voidking_rsa.pub,注释内容为 voidking@qq.com

使密钥生效

1
2
3
ssh-copy-id -i ~/.ssh/id_rsa.pub voidking@hostname
ssh-copy-id -i ~/.ssh/id_rsa.pub voidking@hostname -p 22
ssh voidking@hostname

ssh-copy-id 这个命令做的事情,是把 id_rsa.pub 中的内容,追加到 hostname:/home/voidking/.ssh/authorized_keys 文件中。
authorized_keys文件权限为600。

删除空行

过滤hostlist.txt中的空行:

1
2
3
4
5
6
7
cat hostlist.txt |tr -s '\n'
cat hostlist.txt |sed '/^$/d'
cat hostlist.txt |awk '{if($0!="")print}'
cat hostlist.txt |awk '{if(length!=0) print $0}'
grep -v "^$" hostlist.txt
# 终极方法(linux和windows空行都适用)
grep -v -e '^[[:space:]]*$' hostlist.txt

过滤windows换行符

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

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

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

去除空格

去除test.txt文本中的空格:

1
cat test.txt | tr -d ' '

空格转换行

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

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

utf8 bom 转 utf8

使用macos中编译运行windows下创建的java项目,报错 Error:(1, 1) java: 非法字符: ‘\ufeff’。
BOM(byte order mark)是为 UTF-16 和 UTF-32 准备的,用于标记字节序(byte order)。
微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开,但这样的文件在 Windows 之外的操作系统里会带来问题。
那么,怎么把utf8 bom编码文件批量转换为utf8编码文件呢?使用下面的脚本。

1
2
3
4
5
6
7
8
# 当前目录下递归所有文件
grep -r -i -l $'^\xEF\xBB\xBF' . | xargs sed -i 's/^\xEF\xBB\xBF//g'
# 当前目录下所有文件
grep -i -l $'^\xEF\xBB\xBF' * | xargs sed -i 's/^\xEF\xBB\xBF//g'
# macos
brew install gnu-sed
grep -r -i -l $'^\xEF\xBB\xBF' . | xargs gsed -i 's/^\xEF\xBB\xBF//g'
grep -i -l $'^\xEF\xBB\xBF' * | xargs gsed -i 's/^\xEF\xBB\xBF//g'

GBK编码转UTF8

打开一个文件,发现中文显示乱码,大概率编码问题。使用下面的命令进行转码:

1
iconv -f GBK -t UTF-8 inputfile > outputfile

读取某一列

读取第一列,读取最后一列。

1
2
3
echo "www.voidking.com" | awk -F'.' '{print $1}'
echo "www.voidking.com" | awk -F'.' '{print $3}'
echo "www.voidking.com" | awk -F'.' '{print $NF}'

第一列换到最后一列

已知namelist.txt:

1
2
haojin 70 80
voidking 90 100

需求:第一列name,在显示时放到最后一列。

1
cat namelist.txt | awk '{for(i=2;i<=NF;i++)printf("%s ", $i);print $1}'

循环读取单列文本

已知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

ssh执行远程命令

执行远程命令很简单,直接ssh后面跟着命令就可以了。

1
ssh root@192.168.56.100 "echo helloworld"

那如果远程命令里面包含双引号怎么办?变成单引号,或者添加转义。

1
2
ssh root@192.168.56.100 "echo 'helloworld'"
ssh root@192.168.56.100 "echo \"helloworld\""

那如果需要远程命令里使用变量怎么办?添加转义。

1
ssh root@192.168.56.100 "host=\$(hostname);echo \"\${host}\""

批量添加开机启动命令

已知主机列表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不输出错误和进度

1
2
3
4
5
6
# 不输出错误和进度
curl -s "https://www.voidking.com"
curl -sX GET "https://www.voidking.com"

# 不产生任何输出
curl -s -o /dev/null "https://www.voidking.com"

更多内容参考 curl的用法指南

curl post请求带参数

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

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

2、application/json请求:

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

其中data.json中的内容为:

1
2
3
4
{
"id":1,
"ip":"10.0.0.1"
}

3、multipart/form-data请求:

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

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中的第一列。

求交集

已知file1内容为:

1
2
3
haojin
voidking
vk

file2内容为:

1
2
3
4
5
haoshuai 95 93 80
vk 99 99 100
haojin 100 100 99
baidu 100 100 100
voidking 99 99 99

需求:file1第一列和file2第一列求交集,显示file2中交集的内容。

1
2
3
awk '{if(NR==FNR){a[$1]=$1}else if($1 in a){print $0}}' file1 file2
# or
awk '{if(NR==FNR)a[$1]=$1;else if($1 in a)print $0}' file1 file2

每两行合成一行

已知ip-port.txt内容为:

1
2
3
4
5
6
127.0.0.1
80
127.0.0.1
8080
192.168.56.101
3306

需求:把ip和对应端口放在同一行。

1
2
3
127.0.0.1 80
127.0.0.1 8080
192.168.56.101 3306

脚本:

1
2
3
4
awk 'NR%2{printf "%s ",$0;next;}1' ip-port.txt
awk 'ORS=NR%2?FS:RS' ip-port.txt
awk '{ ORS = (NR%2 ? FS : RS) } 1' ip-port.txt
awk '{ ORS = (NR%2 ? "," : RS) } 1' ip-port.txt

删除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 | 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

输出到屏幕和文件

输出内容同时到屏幕和文件:

1
echo "hello" | tee test.log

清空文件内容

需求:清空text.txt文件中的内容
脚本:

1
2
3
4
echo "" > test.txt
echo -n "" > test.txt
cat /dev/null > test.txt
:> test.txt

测试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

获取脚本绝对路径

1
scriptpath=$(cd "$(dirname "$0")"; pwd)

时间和时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 输出时间
date

# 输出时间并格式化
date "+%Y-%m-%d %H:%M:%S"

# 时间转时间戳(秒)
date "+%s"

# 时间转时间戳(微秒)
microsecond=$(($(date "+%s%N")/1000000))

# 时间戳(秒)转时间
date -d @1612351314 "+%Y-%m-%d %H:%M:%S"

根据端口找进程

已知某进程监听22端口,怎样找到该进程的pid?怎样找到该进程启动命令?

1
2
3
4
lsof -i:22
netstat -npt | grep ":22"
netstat -nlpt | grep ":22"
ps -ef |grep $pid

查看外网出口IP

curl myip.ipip.net

cp须知

1
2
3
4
5
6
unalias cp #避免cp命令被alias
cp -f #强制覆盖
cp -R #递归拷贝所有文件。对特殊文件(管道文件、块设备文件、字符设备文件)会进行创建而不是拷贝。
cp -r #递归拷贝所有文件。所有source文件当做普通文件。
cp -rf public/* voidking #递归拷贝所有文件,不包括隐藏文件
cp -rf public/. voidking #递归拷贝所有文件,包括隐藏文件

长脚本

文件拆分

需求1:大文件拆分成小文件,每50行拆分成一个文件。
脚本1:

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

linenum="50"
bigfile="bigfile.txt"
datadir="files"
prefix="smallfile."
mkdir -p ${datadir}
split -l ${linenum} ${bigfile} -d -a 2 ${datadir}/${prefix}

需求2:大文件拆分成小文件,方便按比例进行变更。
脚本2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

percent="50"
#percent="34"
bigfile="bigfile.txt"
datadir="file"
prefix="smallfile."

rm -rf ${datadir}/${prefix}*
mkdir -p ${datadir}
content=$(cat ${bigfile})
sum=$(echo "${content}" | wc -l)
linenum=$((${sum}*${percent}/100))
split -l ${linenum} ${bigfile} -d -a 2 ${datadir}/${prefix}

还原history

万万没想到,centos4.3的history -c命令,不止会清除当前登录的操作历史记录,还会清除 .bash_history 中的内容。

如果不小心清除了 .bash_history 中的内容,该怎么办?如果屏幕上还残留着history的历史记录,那么还有得救。
拷贝屏幕上的内容,到 history.txt,假设内容为:

1
2
3
244486  2021-01-08 17:35:18 cd haojin
244487 2021-01-08 17:36:01 vim main.py
244488 2021-01-08 17:38:33 python main.py

怎么恢复成标准的 .bash_history 格式呢?使用如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

cat /dev/null > bash_history
while read line
do
time=$(echo $line | awk '{print $2" "$3}')
#cmd=$(echo $line | awk '{$1="";$2="";$3="";print $0}')
cmd=$(echo $line | awk -F " " '{for (i=4;i<=NF;i++)printf("%s ", $i);print ""}')
timestamp=$(date -d "$time" +%s)
echo "#"$timestamp >> bash_history
echo $cmd >> bash_history
done < history.txt

执行完成,使用 bash_history 替换 .bash_history 即可。

执行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
  • 本文作者: 好好学习的郝
  • 本文链接: https://www.voidking.com/dev-shell-script-collection/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!源站会及时更新知识点及修正错误,阅读体验也更好。欢迎分享,欢迎收藏~