【汇总】shell 脚本笔记、设置开启自动启动

centos7、stream;rocky linux 8 9

/etc/rc.local;service;systemctl



一、基本语法

系统脚本写起来就是没有编程语言舒服,Windows的bat最难用,linux的shell好一点,用起来也比较别扭

1.1 空格和引号的重要性

shell脚本中的空格需要像八股文一样严格控制,比如:

# 正确,无空格,string变量被赋值
string=`cat a.txt`
# 错误,a.txt中的内容被当成命令执行
string = `cat a.txt`

# 正确
if [ -z "$STRING" ]
# 错误,没有空格,此外没有双引号也是错误的
if[-z "$STRING"]



二、脚本中调用其它命令

2.1 调用vi

脚本中如果不得不使用vi,例如使用 :set fileformat=unix 更改文件格式

你可以直接在脚本中使用vi,如 https://blog.csdn.net/ding_yingzi/article/details/81074422

#!/bin/bash
vi file.txt << EOF
 i                     # 进入 insert 模式
 Here is a document!   # 输入文本内容
 ^[                    # 意为按下ESC退出编辑模式,不能直接输入,而应当在i输入模式下先按下<c-v>, 即ctrl+v,再按 <esc> 得到
 :wq                   # 保存退出
EOF

在shell脚本中(bash编程),<<EOF 表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主Shell。而EOF可以换成任何其他字符都可以。

上述方法是模拟vi的使用,除此之外还可以使用vi的 ex 模式,使用 vi -e 或直接使用 ex 命令。只支持vi 底行输入,你所输入的内容都相当于在底行:后输入

#!/bin/bash
ex file.txt << EOF
i                     # 进入插入模式,后面的内容都会被插入,直到遇见 . 为止
Here is a document!   # 输入文本内容
.                     # 结束插入
wq
EOF

ex的另一种用法:出自stackoverflow 。优点是免去了写 << EOF 的麻烦,多条命令就追加 -c,缺点是插入内容不方便

vim file.txt -c 'set fileformat=unix' -c 'wq'



三、设置开机自启

设置脚本在开机时自动启动,有很多种方法,这里按时间线都介绍一下

Linux各种发行版本的发展可以参考 【汇总】Rocky Linux 8、9系统介绍、安装、配置、软件安装步骤

3.1 /etc/rc.local

这种方法最古老,在centos7就逐渐开始抛弃,默认写在/etc/rc.local中是不执行的,需要给/etc/rc.d/rc.local添加执行权限

不推荐这种方法,因为当其中存在bug时,会导致ssh启动不了



3.2 service chkconfig

也是被抛弃的方法,在centos 7中被逐步替换,但centos 7应该还能用,到redhat 8 9已经不可用,被systemctl代替。



3.2.1 脚本编写

在开始写脚本前你就需要知道这三件事

首先,脚本必须以固定内容开头

#!/bin/sh
#chkconfig: 2345 60 30
#description: [note]

第一行指定脚本解释器,这个一定要在第一行,而且字符间不能有空格,否则开机时代码无法执行

第二行指定脚本何时运行,2345指的时在哪些系统运行级别时运行,60是开机启动优先级,30是关机启动优先级,优先级1高99低,不知道具体是0-99还是1-100

运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动 
运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆 
运行级别2:多用户状态(没有NFS) 
运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式 
运行级别4:系统未使用,保留 
运行级别5:X11控制台,登陆后进入图形GUI模式 
运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

第三行 description 是描述

也可以参考系统脚本是怎么写的 cat /etc/init.d/network

其次,脚本最好处理好第一个参数

因为系统启动、暂停、重启服务,是通过向脚本传递参数实现的,虽然不处理参数,脚本也会被运行一遍,但是还是规范编写脚本较好

service network start 其实是向脚本传了一个 start 参数。处理参数的格式如下:

# See how we were called.
case "$1" in
start)
# 启动脚本内容
    ;;
stop)
# 停止脚本内容
    ;;
restart|force-reload)
# 重启脚本内容
    ;;
status)
# 查看状态脚本内容
    ;;
*)
# 其它参数脚本内容
echo $"Usage: $0 {start|stop|status|restart|force-reload}"

# 结束case语句
esac

exit 0

最后,需要注意路径问题

脚本必须放在 /etc/init.d/ 下,脚本中 pwd 即为此目录,即使是硬链到此目录下



3.2.2 注册管理服务

注册服务,注册成功后就能开机自启了

cd /etc/init.d/
chkconfig --add [your_sh]
chkconfig --list # 查看是否添加成功

管理服务,使用 service [your_service] start|stop|status



3.3 RedHat 8 开机启动

上述 3.1 3.2 的方法已经过时,在RedHat系8中使用systemctl管理服务

首先建立服务/应用的脚本,方便我们启动、停止、查看服务,建议直接放在/usr/bin

注意key_word这个变量一定要独一无二,根据这个来kill进程的,太大众会误杀其它进程,甚至docker容器中的进程。同时此脚本的文件名不能包含key_word,否则执行stop时会把脚本自身杀掉,从而无法继续进行下去

service_name只用于打印

使用时只需修改service_name、key_word和启动函数

#!/bin/sh

service_name=HTTP
# key_word这个变量一定要独一无二,根据这个来查找和kill进程,太大众会误杀其它进程,甚至docker容器中的进程。同时此脚本的文件名不能包含key_word,否则执行stop时会把脚本自身杀掉,从而无法继续进行下去
key_word=http.server

function findps()
{
    if [ "$1" = "print" ]
    then
        ps -ef|grep $key_word|grep -vE "(grep|$0|service.*$key_word|systemctl)"
    else
        fps=`ps -ef|grep $key_word|grep -vE "(grep|$0|service.*$key_word|systemctl)"`
    fi
}

function startps()
{
    #su -s /bin/bash -c "/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf" nobody
    #nohup python3 -m http.server >> DDNS.log 2>&1 &
}

function killps()
{
    ps -ef|grep $key_word|grep -vE "(grep|$0|service.*$key_word|systemctl)"| cut -c 9-16|xargs kill -9
}

case "$1" in
start)
findps;
if [ -z "$fps" ]
then
    startps;
    echo -e "\033[32;1mStarted successfully\033[0m"
else
    echo -e "\033[31;1m$service_name service is already running! \033[0m"
    exit 1
fi
;;
################################
stop)
findps;
if [ -z "$fps" ]
then
    echo -e "\033[31;1m$service_name service is not running! \033[0m"
    exit 1
else
    killps;
fi
echo -e "\033[32;1mStopped successfully\033[0m"
;;
#################################
restart|force-reload)
findps;
if [ -n "$fps" ]
then
    killps;
fi
echo -e "\033[32;1mStopped successfully\033[0m"

sleep 0.5

startps;
echo -e "\033[32;1mStarted successfully\033[0m"
;;
########################################
status)
findps;
if [ -z "$fps" ]
then
    echo -e "\033[31;1m$service_name service is stopped\033[0m"

else
    echo -e "\033[32;1m$service_name service is running\033[0m"
    findps print
fi
;;
########################################
*)
echo $"Usage: $0 {start|stop|status|restart|force-reload}"

esac

exit 0

接着是 .service 文件,放在/usr/lib/systemd/system/目录下

[Unit]
Description=服务名
After=network-online.target

[Service]
Type=forking
ExecStart=/usr/bin/bash /root/xx start
ExecStop=/usr/bin/bash /root/xx stop
ExecReload=/usr/bin/bash /root/xx restart
#RemainAfterExit=true

[Install]
WantedBy=multi-user.target

修改.service文件后使用 systemctl daemon-reload 生效

注意点:

  • .service文件中的脚本名用绝对路径,用真实路径,别用链接

  • 如果脚本中启动应用是用bash,如su -s /bin/bash -c "/usr/local/nginx/sbin/nginx -s reload" nobody

    那么.service文件中应该使用RemainAfterExit=true,否则systemctl在start后因为bash退出会立即执行stop



posted @ 2021-06-08 15:59  云牧青  阅读(274)  评论(0编辑  收藏  举报