首页  /  Linux 进阶  /  正文

Shell 应用之函数封装与 rsync 启停脚本

Linux 进阶 2026-10-02📖 17 分钟👁 —
🐚 Linux 进阶 · Shell 编程第 8 / 10 页12345678910📖 完整导航 →

一、先手动把 rsync 跑起来

rsync 是一个文件同步和传输工具,本地和远程之间都能用,只传差异部分,效率很高。

写脚本之前得先把它手动跑通一遍,不然脚本里写什么都不知道。

看软件包

centos rpm -qa rsync

ubuntu dpkg -l | grep rsync

root@VM-0-9-ubuntu:~/shell# dpkg -l | grep rsync
ii  rsync                                 3.2.7-1ubuntu1.4                        amd64        fast, versatile, remote (and local) file-copying tool

看配置文件

root@VM-0-9-ubuntu:~/shell# ls -l /etc/rsyncd.conf
ls: cannot access '/etc/rsyncd.conf': No such file or directory

# rsyncd.conf 是 rsync 服务端配置文件,默认不会自动生成
# 需要你手动创建

touch /etc/rsyncd.conf
chmod 644 /etc/rsyncd.conf

# 检查是否有 rsync 服务

启动

# rsync配置文件需要自己创建
root@VM-0-9-ubuntu:~# cat > /etc/rsyncd.conf <<EOF
uid = nobody
gid = nogroup
use chroot = no
max connections = 10
pid file = /var/run/rsyncd.pid

[data]
    path = /tmp
    comment = Test Rsync Module
    read only = no
EOF

# 启用服务
root@VM-0-9-ubuntu:~# /usr/bin/rsync --daemon
root@VM-0-9-ubuntu:~# netstat -tunl | grep 873
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN     
tcp6       0      0 :::873                  :::*      LISTEN 
# 停止
pkill rsync

每次启停都要敲这么一坨太麻烦了,所以自己写一个启停脚本。系统服务脚本都放在 /etc/init.d 下,可以先看看人家是怎么写的:

# 这是系统的脚本,但是执行时候提示需要传入下面的参数
# 可以按照这个风格开发 rsync 的启停脚本
root@VM-0-9-ubuntu:~# /etc/init.d/ufw
Usage: /etc/init.d/ufw {start|stop|restart|force-reload|status}

照着这个风格写一个 cc_rsync:

#!/bin/bash
# 判断参数个数
if [ "$#" -ne "1" ]
  then
    echo "$0 {start|stop|restart}"
    exit 1
fi

# 用户选择启动
if [ "$1" = "start" ]
  then
    /usr/bin/rsync --daemon
    sleep 2
    # 查看端口是否存活
    if [ `netstat -tunlp | grep 873 | wc -l` -ge "1" ]
      then
        echo "Rsync is started"
        exit 0
    fi
# 这里不能写 fi,会导致 elif 无法匹配到前面的 if


elif [ "$1" = "stop" ]
  then
    killall rsync &>/dev/null
    sleep 2
    if [ `netstat -tunlp|grep 873|wc -l` -eq "0" ]
      then
        echo "Rsync is stopped"
        exit 0
    fi

# 重启,先停止再启动
elif [ "$1" = "restart" ]
  then
    killall rsync &>/dev/null
    # 定义变量接收端口状态
    killpro=`netstat -tunlp|grep 873|wc -l`
    
    /usr/bin/rsync --daemon
    # 变量接收启动状态
    startpro=`netstat -tunlp|grep 873|wc -l`
    
    if [ "$killpro" -eq "0" ] && [ "$startpro" -ge "1" ]; then
        echo "Rsync is restarted"
        exit 0
    fi

    
else
# $0 获取文件名
  echo "Usage: /etc/init.d/cc_rsync {start|stop|restart}"
# 结束开始的if
fi 

root@VM-0-9-ubuntu:~# /etc/init.d/cc_rsync restart
Rsync is restarted

二、函数怎么写

if 写得多了,同一段判断在脚本里出现好几遍就很烦,这时候就该上函数了。定义有三种写法:

# 标准 shell 定义
function 函数名() {
  函数体
  命令
  # 可以不写 return 
  return 返回值

}

# 使用function关键字可以省略括号
function 函数名 {
  同上
  
}

# 方便写法
函数名() {
  同上

}

关于函数执行,有几条基本概念要记住:

执行、定义顺序、局部变量、return 与 exit 的区别等函数基础概念

同一个 shell 里调用函数,定义和执行都要写在文件里:

定义函数、往文件里写点东西、最后再调用它的完整脚本

写完直接 bash 执行 .sh 脚本就行。

把定义和执行拆到两个文件

函数只写定义不执行,放在一个文件里,另一个文件加载它:

# 1.生成my_func.sh脚本记录函数
#! /bin/bash

zzz(){
  echo "zzzzzzzzzzzzzzz"
}

# 2.检查当前shell变量(可能会查到系统变量,可以加 ^ 正则匹配开头)
set | grep ^zzz

# 3.利用. 或者 source 执行shell脚本,把变量加载到当前 shell 
source my_func.sh

# 4.验证变量在环境中
set | grep ^zzz

root@VM-0-9-ubuntu:~/shell# cat my_func.sh
#! /bin/bash

zzz(){
  echo "zzzzzzzzzzzzzzz"
}
root@VM-0-9-ubuntu:~/shell# set | grep ^zzz
root@VM-0-9-ubuntu:~/shell# source my_func.sh
root@VM-0-9-ubuntu:~/shell# set | grep ^zzz
zzz () 

# 输入 set 到底部可以看到变量的定义
zzz () 
{ 
    echo "zzzzzzzzzzzzzzz"
}

# 退出 shell 会话就会删掉 shell 中的变量,这里不再演示了

# 执行
root@VM-0-9-ubuntu:~/shell# zzz
zzzzzzzzzzzzzzz

再写另一个脚本,负责读取 my_func.sh 并且调用里面的函数:

# 条件测试
# ./root/shell/my_func.sh是有点的,相对路径,不能在绝对路径加 .
# 由于是函数,必须source或者 . 执行变量才能写入shell环境
# 因此可以改为 source
[ -f /root/shell/my_func.sh ] && source /root/shell/my_func.sh || exit

# 执行
zzz
root@VM-0-9-ubuntu:~/shell# bash my_start_func.sh
zzzzzzzzzzzzzzz
root@VM-0-9-ubuntu:~/shell# zzz
zzzzzzzzzzzzzzz

这里注意 . / 和 ./ 的区别:. /路径 是 source 的简写,点后面跟一个空格再跟文件路径,表示在当前 shell 里加载并执行脚本;./文件 是执行当前目录下的可执行文件,会另开一个子 shell 去跑,退出后变量和函数都不会保留。

把上面的步骤换成 bash 跑一遍,差别就很明显了:

# 如果exit后再次登录,执行会发现变量加载不了了
# bash my_start_func.sh 是在子 Shell 里执行的
# 脚本里的函数定义和变量在子 Shell 退出后就没了
# 上个代码块能加载是因为之前执行过. my_func.sh
root@VM-0-9-ubuntu:~/shell# bash my_start_func.sh
zzzzzzzzzzzzzzz
root@VM-0-9-ubuntu:~/shell# zzz
Command 'zzz' not found, did you mean:
  command 'zaz' from deb zaz (1.0.1-1)
  command 'ezz' from deb fex-utils (20160919-2)
  command 'zz' from deb fex-utils (20160919-2)
  command '7zz' from deb 7zip-standalone (23.01+dfsg-7)
  command 'bzz' from deb djvulibre-bin (3.5.28-2ubuntu0.24.04.2)
  command 'zgz' from deb pristine-tar (1.50)
Try: apt install <deb name>
root@VM-0-9-ubuntu:~/shell# set | grep ^zzz
zzz () 
# 必须用source才会在当前加载

三、检测网站存活

函数讲完了,拿它办点正事:监测一个 url 是不是正常。函数的参数和脚本参数一样,$1、$2、$# 都能直接用:

root@VM-0-9-ubuntu:~/shell# cat my_func.sh 
#! /bin/bash

zzz(){
  echo "zzzzzzzzzzzzzzz"
}

# 处理传入的参数
helloPyyu(){
	echo "传入参数:$1 $2 $3,个数:$#"
}


root@VM-0-9-ubuntu:~/shell# cat my_start_func.sh 
#! /bin/bash

# 条件测试
[ -f /root/shell/my_func.sh ] && source  /root/shell/my_func.sh || exit

# 执行
helloPyyu $1 $2 $3

# 处理参数一般用 bash,不污染当前终端
root@VM-0-9-ubuntu:~/shell# bash my_start_func.sh 666 77 8 
传入参数:666 77 8,个数:3
root@VM-0-9-ubuntu:~/shell# set | grep ^hello

不封装版本

一般要对脚本的功能做帮助提示,不然别人不知道怎么执行。执行方式是 bash 脚本 url:

#!/bin/bash

# 限制参数,后面多次使用,可以封装为函数
# 这里先演示不封装的
if [ "$#" -ne "1" ]
  then
    echo "Usage: $0 url"
    exit 1
fi

wget --spider -q -o /dev/null --tries=1 -T 5 $1

# 判断状态码
if [ "$?" -eq "0" ]
  then
    echo "$1正常运行"
else
  echo "$1挂了"
fi


root@VM-0-9-ubuntu:~/shell# bash check_url.sh http://168kaguyachi.site/
http://168kaguyachi.site/正常运行
root@VM-0-9-ubuntu:~/shell# bash check_url.sh 168kaguyachi.site
168kaguyachi.site正常运行

改成函数版本

参数判断和 url 检测各抽成一个函数,再仿照 C 的写法立一个 main 当入口:

#! /bin/bash
# 提示用户
usage(){
  # 用户输错了,说明函数用法
  echo "Usage: $0 url"
  exit 1
}

# 检测url
check_url(){
  wget --spider -q -o /dev/null --tries=1 -T 5 $1
  # 判断状态码
  if [ "$?" -eq "0" ]
    then
      echo "$1正常运行"
  else
    echo "$1挂了"
  fi

}

# 参考C的写法,设立main函数作为入口函数
# main也是shell函数,需要调用
main() {
  # 判断输入是否正确
  if [ "$#" -ne "1" ]
    then
      usage
  fi
  check_url $1
}

# 调用main,也要传入用户的参数(用户参数当作一个整体传入)
main $*

root@VM-0-9-ubuntu:~/shell# bash check_url2.sh 168kaguyachi.site 1331 13
Usage: check_url2.sh url
root@VM-0-9-ubuntu:~/shell# bash check_url2.sh 168kaguyachi.site
168kaguyachi.site正常运行

把输出美化一下

正经的脚本返回信息都是带颜色高亮的,这个不用自己造轮子,系统里就有现成的,直接拿来用。

centos07 上装的 mysql,用只读模式打开 /etc/init.d/mysql,搜 /log_success 就能看到实现高亮的函数长什么样,linux 内置提供了执行状态美化的函数库:

init-functions 里 log_success_msg 和 log_failure_msg 的定义

把这一整段(含前面判断 LSB 函数库在不在的那几行)复制到脚本开头:

把 LSB 函数那段粘到脚本开头,后面接自己的 usage 和检测逻辑

然后原来自己 echo 的那两行就能换成现成的函数,成功失败各有各的样式:

if 分支里的 echo 换成 log_success_msg 和 log_failure_msg

四、写一个专业点的 rsync 脚本

系统服务的启停管理脚本都放在 /etc/init.d,可以照着它的样子,把上面结果的美化日志也加进去:

vim /etc/init.d/my_rsync
#!/bin/bash
# 方便维护最好写标准的
function usage(){
  echo "Usage: $0 {start|stop|restart}"

}

function start(){
  /usr/bin/rsync --daemon
  sleep 1
  if [ `netstat -tunlp|grep rsync|wc -l` -ge "1" ]
    then
      echo "Rsync running..."
  else
      echo "启动失败"
  fi
}

function stop(){
  pkill -x rsync &>/dev/null
  sleep 1
  if [ `netstat -tunlp|grep rsync|wc -l` -eq "0" ]
    then
      echo "服务停止"
  else
      echo "停止失败"
  fi
}

function restart(){
pkill -x rsync &>/dev/null
killpro=`netstat -tunlp|grep rsync|wc -l`
/usr/bin/rsync --daemon
startpro=`netstat -tunlp|grep rsync|wc -l`
# 单括号内不能直接用 &&
  if [ "$killpro" -eq "0" -a "$startpro" -ge "1" ]
    then
      echo "服务重启"
  else
      echo "重启失败"
  fi
}

# 入口函数
# -eq 是数值比较,字符串比较应该用 =
function main() {
  if [ "$#" -ne "1" ]
    then 
      usage
  fi

  if [ "$1" = "start" ]
    then
      start
  elif [ "$1" = "stop" ]
    then
      stop
#restart函数也可以不写,换一种用法
  elif [ "$1" = "restart" ]
    then
      stop
      sleep 1
      start
  else
    usage
  fi
}

main $*

执行的时候踩了两个坑,一个是文件没有执行权限,另一个是 pkill 默认按模糊进程名匹配,my_rsync 里也包含 rsync,结果脚本把自己给匹配掉了:

root@VM-0-9-ubuntu:~/shell# netstat -tunlp|grep rsync
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      463696/rsync        
tcp6       0      0 :::873                  :::*                    LISTEN      463696/rsync        
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync stop
-bash: /etc/init.d/my_rsync: Permission denied
root@VM-0-9-ubuntu:~/shell# chmod +x /etc/init.d/my_rsync
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync stop
Terminated
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync
Usage: /etc/init.d/my_rsync {start|stop|restart}
Usage: /etc/init.d/my_rsync {start|stop|restart}
root@VM-0-9-ubuntu:~/shell# netstat -tunlp|grep rsync
# pkill 默认按模糊进程名匹配,my_rsync 包含 rsync,所以进程匹配到了自己
# 改为 pkill -x rsync 即可
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync restart
服务停止
Rsync running...
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync stop
服务停止
root@VM-0-9-ubuntu:~/shell# /etc/init.d/my_rsync start
Rsync running...

至此 shell 进阶篇彻底结束。

可喜可贺