#!/bin/bash

# 系统更新日志文件路径
LOG_FILE="/var/log/weekly_system_update.log"

# 记录开始时间
echo "=== 系统更新开始于: $(date) ===" >> $LOG_FILE

# 检查操作系统类型并执行相应的更新命令
if [ -f /etc/os-release ]; then
    . /etc/os-release
    if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ] || [ "$ID_LIKE" = "debian" ]; then
        echo "检测到Debian/Ubuntu系统,执行apt更新..." >> $LOG_FILE
        apt update -y >> $LOG_FILE 2>&1
        apt upgrade -y >> $LOG_FILE 2>&1
        apt autoremove -y >> $LOG_FILE 2>&1
        apt autoclean >> $LOG_FILE 2>&1
    elif [ "$ID" = "centos" ] || [ "$ID" = "rhel" ] || [ "$ID_LIKE" = "rhel" ]; then
        echo "检测到CentOS/RHEL系统,执行yum更新..." >> $LOG_FILE
        yum check-update >> $LOG_FILE 2>&1
        yum update -y >> $LOG_FILE 2>&1
        yum autoremove -y >> $LOG_FILE 2>&1
    elif [ "$ID" = "arch" ] || [ "$ID_LIKE" = "arch" ]; then
        echo "检测到Arch Linux系统,执行pacman更新..." >> $LOG_FILE
        pacman -Syu --noconfirm >> $LOG_FILE 2>&1
    else
        echo "不支持的操作系统: $ID" >> $LOG_FILE
        echo "=== 系统更新失败于: $(date) ===" >> $LOG_FILE
        echo "" >> $LOG_FILE
        exit 1
    fi
else
    echo "无法检测操作系统类型" >> $LOG_FILE
    echo "=== 系统更新失败于: $(date) ===" >> $LOG_FILE
    echo "" >> $LOG_FILE
    exit 1
fi

# 记录结束时间
echo "=== 系统更新完成于: $(date) ===" >> $LOG_FILE
echo "" >> $LOG_FILE

exit 0



---------------------------------------------------------------------------------------------

================================================================================================================================

一、脚本头部声明:#!/bin/bash

bash

#!/bin/bash
  • 格式意义:这行称为 "Shebang"(#! 符号的音译),是脚本的第一行,用于指定执行该脚本的解释器。

  • 为什么这样用

    • Linux 系统中,文件本身不区分可执行类型,Shebang 告诉系统 "用 bash 解释器来运行这个脚本"。

    • 如果不写这行,系统可能会用默认的sh( Bourne Shell )执行,但sh功能比bash简单,很多 bash 独有的语法(如数组、高级条件判断)会失效。

    • 确保脚本在不同环境下的兼容性(只要系统安装了 bash,就能正确执行)。

二、变量定义:LOG_FILE="/var/log/weekly_system_update.log"

bash

LOG_FILE="/var/log/weekly_system_update.log"


  • 格式意义:定义一个名为LOG_FILE的变量,值为日志文件的路径。

  • 为什么这样用

    • 变量命名:用大写字母命名变量是 Shell 脚本的惯例,方便区分变量和命令。

    • 路径选择/var/log是 Linux 系统标准的日志存放目录(权限为 root 可写,普通用户只读),符合 Linux 文件系统规范(FHS 标准)。

    • 复用性:后续所有需要写日志的地方直接引用$LOG_FILE,如果要修改日志路径,只需改这一行,无需全局替换,提高可维护性。

三、日志记录:echo "=== 系统更新开始于: $(date) ===" >> $LOG_FILE

bash

echo "=== 系统更新开始于: $(date) ===" >> $LOG_FILE


  • 格式拆解

    • echo "内容":输出字符串到标准输出(默认是终端)。

    • $(date):命令替换(Command Substitution),执行date命令并将结果嵌入字符串中(获取当前时间)。

    • >> $LOG_FILE:输出重定向,将echo的内容追加到LOG_FILE变量指定的文件中(>>是追加,>是覆盖,这里用追加避免覆盖历史日志)。

  • 为什么这样用

    • ===包裹时间,是为了在日志中清晰分隔每次更新的记录(方便后续用grep等工具检索)。

    • 记录时间是为了追溯 "某次更新发生在何时",便于排查问题(比如更新后系统异常,可通过时间定位是哪次更新导致的)。

四、操作系统检测:if [ -f /etc/os-release ]; then . /etc/os-release

bash

if [ -f /etc/os-release ]; then
    . /etc/os-release


  • 格式拆解

    • if [ 条件 ]; then:Shell 的条件判断语法([ ]test命令的简写,用于判断条件是否成立)。

    • -f /etc/os-release-ftest命令的选项,用于检查文件是否存在且是普通文件。

    • . /etc/os-release.source命令的简写,用于在当前 Shell 环境中加载指定文件的内容(让文件中的变量生效)。

  • 为什么这样用

    • /etc/os-release是现代 Linux 发行版(Ubuntu、CentOS、Arch 等)都会自带的文件,里面定义了发行版的标识(如ID=ubuntuID_LIKE=debian),是检测系统类型的标准方式(比手动判断lsb_releaseuname更可靠)。

    • source该文件后,脚本中就能直接使用文件里的变量(如$ID$ID_LIKE),无需手动解析文件内容,简化代码。

五、发行版分支判断:if [ "$ID" = "ubuntu" ] || [ "$ID_LIKE" = "debian" ]

bash

if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ] || [ "$ID_LIKE" = "debian" ]; then
    # apt相关命令
elif [ "$ID" = "centos" ] || [ "$ID_LIKE" = "rhel" ]; then
    # yum相关命令


  • 格式拆解

    • [ "$ID" = "ubuntu" ]:字符串比较,判断$ID变量的值是否等于 "ubuntu"(=是比较运算符,变量加双引号"$ID"是为了防止变量值为空或含空格时导致语法错误)。

    • ||:逻辑运算符 "或",表示多个条件中只要有一个成立,就执行后续命令。

  • 为什么这样用

    • 不同 Linux 发行版的包管理器不同(Debian 系用apt,RHEL 系用yum/dnf,Arch 系用pacman),必须针对性执行命令。

    • 同时检查$ID$ID_LIKE$ID是发行版自身的标识(如ubuntu),$ID_LIKE是 "相似的发行版"(如 Ubuntu 的ID_LIKE=debian),这样可以覆盖更多衍生发行版(比如 Linux Mint 的ID=mint,但ID_LIKE=debian,也能被归为 Debian 系)。

六、更新命令与输出重定向:apt update -y >> $LOG_FILE 2>&1

bash

apt update -y >> $LOG_FILE 2>&1


  • 格式拆解

    • apt update -yapt是 Debian 系的包管理器,update更新软件源列表,-y自动确认所有提示(避免脚本执行时卡在交互确认环节)。

    • >> $LOG_FILE:将命令的标准输出(stdout,如正常的更新进度信息)追加到日志文件。

    • 2>&1:将标准错误(stderr,如错误提示)重定向到标准输出(stdout),最终和标准输出一起写入日志(确保错误信息也被记录,方便排查失败原因)。

  • 为什么这样用

    • 脚本是自动化执行的(非交互式),-y选项避免了 "是否继续?[Y/n]" 的等待,确保脚本能一直运行到结束。

    • 所有输出(包括错误)写入日志,是自动化脚本的核心需求 —— 如果更新失败,能通过日志回看 "哪里出了问题"(比如网络错误、依赖冲突)。

七、错误处理:else分支与exit 1

bash

else
    echo "不支持的操作系统: $ID" >> $LOG_FILE
    echo "=== 系统更新失败于: $(date) ===" >> $LOG_FILE
    echo "" >> $LOG_FILE
    exit 1


  • 格式意义:当所有发行版条件都不匹配时,执行该分支,记录错误信息并退出。

  • 为什么这样用

    • exit 1:表示脚本 "异常退出"(exit 0表示正常退出),在定时任务(如 crontab)中,非 0 退出码可以被监控工具捕获,用于报警(比如通知管理员 "脚本执行失败")。

    • 明确记录 "失败时间" 和 "失败原因",避免后续排查时毫无头绪。

八、日志分隔:echo "" >> $LOG_FILE

bash

echo "" >> $LOG_FILE


  • 格式意义:在每次更新记录的末尾添加一个空行。

  • 为什么这样用:让多次更新的日志之间有明显分隔,用cat /var/log/weekly_system_update.log查看时更易读(不会所有记录挤在一起)。

================================================================================================================================

下面是python脚本

================================================================================================================================

#!/usr/bin/env python3
import os
import subprocess
import datetime
import platform

# 日志文件路径
LOG_FILE = "/var/log/weekly_system_update.log"

def log(message):
    """记录日志信息"""
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(LOG_FILE, "a") as f:
        f.write(f"[{timestamp}] {message}\n")

def run_command(command):
    """执行系统命令并返回结果"""
    try:
        result = subprocess.run(
            command, 
            shell=True, 
            check=True, 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE,
            text=True
        )
        return True, result.stdout
    except subprocess.CalledProcessError as e:
        return False, e.stderr

def main():
    log("=== 系统更新开始 ===")
    
    # 检测操作系统
    os_name = platform.system()
    if os_name != "Linux":
        log(f"错误: 不支持的操作系统 {os_name},仅支持Linux")
        log("=== 系统更新失败 ===")
        log("")
        return
    
    # 检测Linux发行版
    distro = ""
    try:
        with open("/etc/os-release", "r") as f:
            for line in f:
                if line.startswith("ID="):
                    distro = line.strip().split("=")[1].strip('"')
                    break
    except Exception as e:
        log(f"无法检测Linux发行版: {str(e)}")
        log("=== 系统更新失败 ===")
        log("")
        return
    
    log(f"检测到Linux发行版: {distro}")
    
    # 根据发行版执行相应的更新命令
    success = True
    if distro in ["ubuntu", "debian", "linuxmint"]:
        log("执行apt更新...")
        success, output = run_command("apt update -y")
        if success:
            log("apt更新源完成")
            success, output = run_command("apt upgrade -y")
            if success:
                log("apt升级完成")
                run_command("apt autoremove -y")
                run_command("apt autoclean")
            else:
                log(f"apt升级失败: {output}")
        else:
            log(f"apt更新源失败: {output}
    
    elif distro in ["centos", "rhel", "fedora"]:
        log("执行yum更新...")
        success, output = run_command("yum update -y")
        if success:
            log("yum更新完成")
            run_command("yum autoremove -y")
        else:
            log(f"yum更新失败: {output}")
    
    elif distro == "arch":
        log("执行pacman更新...")
        success, output = run_command("pacman -Syu --noconfirm")
        if success:
            log("pacman更新完成")
        else:
            log(f"pacman更新失败: {output}")
    
    else:
        log(f"不支持的Linux发行版: {distro}")
        success = False
    
    # 记录更新结果
    if success:
        log("=== 系统更新成功完成 ===")
    else:
        log("=== 系统更新失败 ===")
    log("")

if __name__ == "__main__":
    main(

一、脚本头部声明

python

运行

#!/usr/bin/env python3


  • 这是 Python 脚本的 "Shebang" 声明,用于指定脚本的解释器为python3

  • 与 Shell 脚本的#!/bin/bash作用类似,确保系统能正确找到 Python 解释器来执行脚本。

  • 使用/usr/bin/env python3而非直接写/usr/bin/python3,是为了适配不同系统中 Python3 的安装路径(更具兼容性)。

二、导入依赖模块

python

运行

import os
import subprocess
import datetime
import platform


  • os:提供与操作系统交互的功能(本脚本未直接使用,但属于常用系统模块)。

  • subprocess:核心模块,用于在 Python 中执行系统命令(如aptyum等更新命令)。

  • datetime:用于处理时间,生成日志的时间戳。

  • platform:用于获取系统基本信息(如判断是否为 Linux 系统)。

三、日志文件配置

python

运行

# 日志文件路径
LOG_FILE = "/var/log/weekly_system_update.log"


  • 定义日志文件的存储路径,与 Shell 脚本保持一致(/var/log是 Linux 标准日志目录)。

  • 使用变量存储路径,便于后续维护(如需修改路径,只需改这一行)。

四、核心函数定义

1. log(message):日志记录函数

python

运行

def log(message):
    """记录日志信息"""
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(LOG_FILE, "a") as f:
        f.write(f"[{timestamp}] {message}\n")


  • 功能:将消息以 "[时间戳] 消息内容" 的格式写入日志文件。

  • 细节

    • datetime.datetime.now().strftime(...):生成格式化的当前时间(如2023-10-01 15:30:00),便于日志追溯。

    • with open(...) as f:使用上下文管理器打开文件,自动处理文件关闭(避免资源泄露)。

    • "a"模式:以 "追加" 方式写入,保留历史日志(不会覆盖之前的记录)。

    • 函数文档字符串"""记录日志信息""":说明函数功能,符合 Python 代码规范。

2. run_command(command):系统命令执行函数

python

运行

def run_command(command):
    """执行系统命令并返回结果"""
    try:
        result = subprocess.run(
            command, 
            shell=True, 
            check=True, 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE,
            text=True
        )
        return True, result.stdout
    except subprocess.CalledProcessError as e:
        return False, e.stderr


  • 功能:执行传入的系统命令(字符串形式),并返回执行结果(成功 / 失败状态 + 输出内容)。

  • 关键参数解析

    • command:要执行的系统命令(如"apt update -y")。

    • shell=True:允许以字符串形式传入命令(否则需要以列表形式传入,如["apt", "update", "-y"])。

    • check=True:当命令执行失败(退出码非 0)时,抛出CalledProcessError异常。

    • stdout=subprocess.PIPEstderr=subprocess.PIPE:捕获命令的标准输出和错误输出(不直接打印到终端)。

    • text=True:将输出内容转为字符串(而非字节流),方便后续处理。

  • 异常处理:捕获命令执行失败的异常,返回False和错误信息(便于日志记录失败原因)。

五、主逻辑函数 main()

这是脚本的核心执行逻辑,分为以下步骤:

1. 记录更新开始

python

运行

log("=== 系统更新开始 ===")


  • 写入日志标记,标识一次更新周期的开始(便于日志分隔和检索)。

2. 操作系统类型检测

python

运行

os_name = platform.system()
if os_name != "Linux":
    log(f"错误: 不支持的操作系统 {os_name},仅支持Linux")
    log("=== 系统更新失败 ===")
    log("")
    return


  • platform.system():获取操作系统名称(如LinuxWindowsDarwin(macOS))。

  • 仅允许在 Linux 系统上执行更新,非 Linux 系统直接记录错误并退出。

3. Linux 发行版检测

python

运行

distro = ""
try:
    with open("/etc/os-release", "r") as f:
        for line in f:
            if line.startswith("ID="):
                distro = line.strip().split("=")[1].strip('"')
                break
except Exception as e:
    log(f"无法检测Linux发行版: {str(e)}")
    log("=== 系统更新失败 ===")
    log("")
    return

log(f"检测到Linux发行版: {distro}")


  • 原理:通过读取/etc/os-release文件(Linux 标准发行版信息文件)获取发行版 ID(如ubuntucentos)。

  • 步骤

    • 循环读取文件内容,找到以ID=开头的行(如ID=ubuntu)。

    • 解析该行内容,提取等号后的发行版名称(去除引号和空格)。

  • 异常处理:如果文件不存在或读取失败(如权限问题),记录错误并退出。

4. 根据发行版执行更新命令

根据检测到的发行版(distro),执行对应包管理器的更新命令:

(1)Debian/Ubuntu 系列(apt包管理器)

python

运行

if distro in ["ubuntu", "debian", "linuxmint"]:
    log("执行apt更新...")
    success, output = run_command("apt update -y")
    if success:
        log("apt更新源完成")
        success, output = run_command("apt upgrade -y")
        if success:
            log("apt升级完成")
            run_command("apt autoremove -y")  # 清理无用依赖
            run_command("apt autoclean")      # 清理缓存安装包
        else:
            log(f"apt升级失败: {output}")
    else:
        log(f"apt更新源失败: {output}")


  • 分步骤执行:先更新源列表(apt update),再升级软件包(apt upgrade),最后清理冗余文件。

  • 每步都检查执行结果,失败时记录具体错误信息(通过run_command返回的output)。

(2)RHEL/CentOS 系列(yum包管理器)

python

运行

elif distro in ["centos", "rhel", "fedora"]:
    log("执行yum更新...")
    success, output = run_command("yum update -y")
    if success:
        log("yum更新完成")
        run_command("yum autoremove -y")  # 清理无用依赖
    else:
        log(f"yum更新失败: {output}")


  • yum update -y一次性完成源更新和软件升级(yumapt的命令逻辑差异)。

(3)Arch Linux 系列(pacman包管理器)

python

运行

elif distro == "arch":
    log("执行pacman更新...")
    success, output = run_command("pacman -Syu --noconfirm")
    if success:
        log("pacman更新完成")
    else:
        log(f"pacman更新失败: {output}")


  • pacman -Syu:同步源并升级所有软件包(-S同步,-y刷新源,-u升级)。

  • --noconfirm:自动确认所有提示(非交互式执行必需)。

(4)不支持的发行版

python

运行

else:
    log(f"不支持的Linux发行版: {distro}")
    success = False


  • 对未在列表中的发行版,标记为失败并记录日志。

5. 记录更新结果

python

运行

if success:
    log("=== 系统更新成功完成 ===")
else:
    log("=== 系统更新失败 ===")
log("")


  • 根据success变量(标记整个更新过程是否成功),记录最终结果。

  • 末尾添加空行,分隔多次更新的日志(增强可读性)。

六、脚本入口

python

运行

if __name__ == "__main__":
    main()


  • 这是 Python 脚本的标准入口写法:当脚本被直接执行时(而非被导入为模块),调用main()函数启动逻辑。











设置定时任务

无论选择哪种脚本,都需要通过 crontab 设置每周执行一次:


  1. 首先给脚本添加执行权限:

    bash

    # 对于Shell脚本
    chmod +x weekly_update.sh
    
    # 对于Python脚本
    chmod +x weekly_update.py
    

  2. 将脚本移动到合适的位置,例如:

    bash

    sudo mv weekly_update.sh /usr/local/bin/
    # 或
    sudo mv weekly_update.py /usr/local/bin/
    

  3. 编辑 crontab 配置:

    bash

    sudo crontab -e
    

  4. 添加以下内容(每周日凌晨 3 点执行):

    bash

    # 对于Shell脚本
    0 3 * * 0 /usr/local/bin/weekly_update.sh
    
    # 对于Python脚本
    0 3 * * 0 /usr/local/bin/weekly_update.py
    

  5. 保存并退出编辑器


这样系统就会每周自动执行一次更新,并将更新记录保存在/var/log/weekly_system_update.log文件中,方便后续查看更新情况。你可以根据需要调整 crontab 中的时间设置。