news 2026/7/31 16:41:57

Shell脚本入门:从零到自动化,告别重复命令操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shell脚本入门:从零到自动化,告别重复命令操作

1. 从“手忙脚乱”到“一键搞定”:为什么你需要Shell脚本

如果你在Linux或macOS的终端里,曾经为了完成一个任务,把同一串命令敲了十遍;或者,你需要在Windows的PowerShell里,每天重复执行一系列固定的文件整理操作,那么你肯定有过这样的念头:有没有办法让电脑记住这些步骤,下次点一下就能自动完成?

这就是Shell脚本要解决的问题。简单来说,Shell脚本就是一个包含了多条Shell命令的文本文件。Shell,你可以把它理解为你和操作系统内核(真正干活的“大脑”)之间的“翻译官”和“传令兵”。你输入lscdcp这些命令,Shell负责接收、解释,然后告诉内核去执行。而脚本,就是你把要给“传令兵”的一连串指令,事先写在一张“任务清单”上,下次直接把清单交给它,它就能按顺序逐一执行。

看看那些热搜词吧:npmnodeclaude命令找不到,本质是环境路径问题,一个脚本就能帮你检查和设置;adb shell反复安装APK、重启到bootloader,是安卓开发和测试的日常,写成脚本能省下大量重复劳动;用shell重命名文件、做开机自启脚本,更是自动化运维和系统管理的家常便饭。甚至那些看起来复杂的青龙脚本碧蓝航线脚本,其底层逻辑也是通过脚本模拟用户操作,实现自动化。脚本的本质,是把零散、重复的手工操作,固化、序列化,最终实现自动化。

很多人觉得脚本编程是运维或开发工程师的专利,其实不然。无论你是数据分析师需要定期处理日志,是设计师需要批量转换图片格式,还是普通用户想自动备份重要文档,Shell脚本都能成为你的得力助手。它入门门槛低,直接使用你已经在敲的命令;它威力巨大,几行代码就能完成繁琐工作。这篇文章,我就以一个十几年老鸟的视角,带你从零开始,亲手编写你的第一个Shell脚本,并理解其背后的每一个细节。我们不求面面俱到,但求你能亲手写出一个真正有用、能解决实际问题的脚本,并理解每一步“为什么”要这么做。

2. 编写你的第一个脚本:从“Hello World”到可执行文件

让我们暂时忘掉那些复杂的自动化场景,就从最经典的“Hello World”开始。这个过程看似简单,却包含了编写Shell脚本所有最核心的步骤和概念。

2.1 创建脚本文件与Shebang

首先,打开你的终端(Linux/macOS的Terminal,或Windows的Git Bash、WSL)。找一个你常用的目录,比如家目录下的scripts文件夹(如果没有,可以用mkdir ~/scripts创建一个)。

进入这个目录:cd ~/scripts。现在,我们来创建第一个脚本文件。在Shell的世界里,脚本文件通常以.sh作为扩展名,但这并不是强制要求,只是一个良好的约定,让人一眼就知道这是个Shell脚本。

使用nanovim或者你喜欢的任何文本编辑器(比如geditcode .)创建一个新文件:

nano hello_world.sh

文件的第一行,必须是这样一个特殊的注释:

#!/bin/bash

这一行被称为Shebang(或 Hashbang)。它的作用是告诉系统,当直接执行这个脚本文件时,应该使用哪个解释器来运行它。#!是固定开头,后面的/bin/bash指定了Bash Shell的路径。为什么是/bin/bash?因为在绝大多数Linux发行版和macOS上,Bash是默认的、功能最丰富的Shell。你也可以写#!/bin/sh,但sh通常是dash的符号链接,它是Bash的一个精简版,某些Bash特有的语法在sh下可能无法运行。为了兼容性和功能的完整性,我们通常直接指定bash

注意:在Windows的PowerShell中编写脚本(.ps1文件),其Shebang可能是#!/usr/bin/env pwsh,原理是类似的,都是指定解释器。但本文聚焦于Unix-like环境下的Bash Shell脚本,这是应用最广泛的场景。

2.2 编写脚本内容与赋予执行权限

在Shebang行之后,你就可以开始写命令了。我们的第一个命令是:

echo "Hello, Shell Script World!"

echo命令的作用就是将后面的字符串输出到终端屏幕。现在,你的hello_world.sh文件内容应该是这样的:

#!/bin/bash echo "Hello, Shell Script World!"

保存并退出编辑器(在nano中是Ctrl+X,然后按Y确认,再按回车)。

现在,尝试直接运行它:./hello_world.sh。你很可能会看到这样的错误信息:bash: ./hello_world.sh: Permission denied。这是因为在Unix-like系统中,一个新创建的文件默认只有读写权限,没有执行(x)权限。你必须显式地告诉系统:“这个文件是可以作为程序来执行的。”

这就需要用到chmod命令来修改文件权限:

chmod +x hello_world.sh

chmod是“change mode”的缩写,+x表示为文件增加(+)执行(x)权限。执行完这条命令后,再用ls -l hello_world.sh查看一下,你会看到文件权限变成了-rwxr-xr-x,开头的-代表是普通文件,后面三组rwx分别代表文件所有者、所属组和其他用户拥有读(r)、写(w)、执行(x)权限。现在,脚本已经具备了被执行的资格。

2.3 运行脚本的三种方式及其区别

现在再次运行:./hello_world.sh。这次,你应该能看到终端上输出了“Hello, Shell Script World!”这行字。恭喜你,你的第一个Shell脚本成功运行了!

这里./代表当前目录。系统默认只在PATH环境变量指定的目录里寻找可执行文件,当前目录通常不在PATH中(这是出于安全考虑),所以必须加上./来指明路径。

除了这种方式,还有两种常见的运行方式:

  1. 使用指定的解释器执行bash hello_world.sh。这种方式直接调用bash解释器,并把脚本文件名作为参数传给它。此时,脚本文件本身不需要有执行权限(x),因为bash命令本身是可执行的,它只是去读取并解释hello_world.sh这个文本文件的内容。Shebang行在这种情况下会被忽略,因为解释器已经由你在命令行显式指定了。
  2. 通过source或点命令执行source hello_world.sh. hello_world.sh。这种方式的特点是,脚本会在当前Shell进程中执行,而不是新建一个子Shell。这意味着脚本中设置的变量、改变的目录(cd)等,在执行完后会影响到你当前的终端环境。而前两种方式(./script.shbash script.sh)都会启动一个子Shell来执行脚本,脚本内部的环境变更不会“污染”父Shell。通常,我们运行独立任务脚本用前两种,而要加载环境配置(比如.bashrc)时,才会用source

理解这三种方式的区别,对于后续脚本调试和环境管理非常重要。一个常见的坑是:在脚本里用export设置了一个环境变量,然后用./script.sh运行,结果发现当前终端里这个变量不存在。这就是因为变量被设置在了子Shell中,子Shell结束,变量就消失了。如果想让它生效,就需要用source script.sh来运行。

3. 让脚本变得有用:变量、参数与条件判断

一个只会说“Hello World”的脚本显然没什么用。要让脚本能处理实际任务,我们必须让它能接收外部信息,并能根据情况做出不同反应。这就涉及到变量、传递参数和条件判断。

3.1 使用变量存储信息

变量就像脚本里的临时记事本,可以存放各种信息,比如文件名、路径、计数器等。在Bash中定义变量很简单:

#!/bin/bash # 定义一个变量,等号两边不能有空格! my_name="Shell Learner" backup_dir="/home/user/backups" file_count=10 # 使用变量时,需要在变量名前加上美元符号 $ echo "Hello, $my_name" echo "Backup directory is: $backup_dir" echo "We have $file_count files to process." # 也可以把命令的执行结果赋值给变量 current_time=$(date) # 方法一:推荐,更清晰 current_time=`date` # 方法二:反引号,旧式写法 echo "Current time is: $current_time" # 系统预定义的特殊变量 echo "This script is named: $0" # 脚本本身的文件名 echo "My process ID is: $$" # 当前脚本的进程ID

这里有几个关键点:

  • 赋值时等号两边不能有空格var=value是正确的,var = value会被解释为运行命令var并带两个参数=value,从而报错。
  • 引用变量要加$echo $my_name
  • 命令替换$(command)`command`可以将命令的输出捕获并赋值给变量。$(...)是更现代、更推荐的方式,因为它可以嵌套,且引号处理更清晰。
  • 变量名规范:通常使用小写字母和下划线,如backup_dir。全大写的变量名通常用于环境变量或脚本内部常量。

3.2 接收命令行参数

脚本如何知道用户想处理哪个文件呢?通过命令行参数。Bash提供了一系列特殊变量来获取这些参数:

#!/bin/bash # 假设脚本名为 process_file.sh, 这样调用:./process_file.sh file1.txt file2.txt /output echo "Script name: $0" # 输出:./process_file.sh echo "First argument: $1" # 输出:file1.txt echo "Second argument: $2" # 输出:file2.txt echo "Third argument: $3" # 输出:/output echo "All arguments: $@" # 输出:file1.txt file2.txt /output (每个参数作为独立单词) echo "All arguments as one string: $*" # 输出:file1.txt file2.txt /output (所有参数合并成一个字符串) echo "Number of arguments: $#" # 输出:3

$1,$2,$3... 分别对应命令行上的第一、第二、第三个参数。$0是脚本名本身。$#表示参数个数。$@$*都代表所有参数,但在循环处理时,$@能正确处理带空格的参数(如"my file.txt"),而$*会将所有参数视为一个整体,所以在需要遍历参数时,总是使用"$@"(带双引号)

一个实用的例子:备份指定文件到指定目录。

#!/bin/bash # backup.sh # 检查参数数量是否正确,如果不正确则提示用法并退出 if [ $# -ne 2 ]; then echo "Usage: $0 <source_file> <backup_directory>" exit 1 # 非0退出码通常表示错误 fi source_file="$1" backup_dir="$2" backup_file="${backup_dir}/$(date +%Y%m%d_%H%M%S)_$(basename "$source_file")" # 检查源文件是否存在 if [ ! -f "$source_file" ]; then echo "Error: Source file '$source_file' does not exist." exit 2 fi # 检查备份目录是否存在,不存在则创建 if [ ! -d "$backup_dir" ]; then mkdir -p "$backup_dir" echo "Created backup directory: $backup_dir" fi # 执行备份 cp "$source_file" "$backup_file" if [ $? -eq 0 ]; then echo "Backup successful: $backup_file" else echo "Backup failed!" exit 3 fi

这个脚本展示了如何用$1$2获取参数,并用$#检查参数个数。$?是一个特殊的变量,它保存了上一条命令的退出状态码。0通常表示成功,非0表示失败。cp命令成功后,$?就是0,所以我们用if [ $? -eq 0 ]来判断备份是否成功。

3.3 使用条件判断控制流程

上面的脚本中已经用到了if语句。Shell脚本中的条件判断主要依靠test命令或其等价形式[ ... ][[ ... ]]

  • [ ... ]:是test命令的另一种写法,方括号内部两端必须有空格。功能较基础。
  • [[ ... ]]:Bash的增强版测试命令,支持更强大的字符串匹配(如===~正则),且逻辑更清晰,是更推荐的方式。

常见的测试条件:

测试类型[ ]写法示例[[ ]]写法示例说明
字符串比较[ "$str1" = "$str2" ][[ $str1 == $str2 ]]字符串相等
[ "$str1" != "$str2" ][[ $str1 != $str2 ]]字符串不等
[ -z "$str" ][[ -z $str ]]字符串长度为0(空)
[ -n "$str" ][[ -n $str ]]字符串长度非0
整数比较[ $a -eq $b ](( a == b ))等于(推荐用(( ))
[ $a -ne $b ](( a != b ))不等于
[ $a -gt $b ](( a > b ))大于
[ $a -lt $b ](( a < b ))小于
文件测试[ -f "file" ][[ -f file ]]存在且是普通文件
[ -d "dir" ][[ -d dir ]]存在且是目录
[ -e "path" ][[ -e path ]]文件/目录存在
[ -r "file" ][[ -r file ]]文件可读
[ -w "file" ][[ -w file ]]文件可写
[ -x "file" ][[ -x file ]]文件可执行
逻辑组合[ cond1 -a cond2 ][[ cond1 && cond2 ]]逻辑与
[ cond1 -o cond2 ][[ cond1 || cond2 ]]逻辑或
[ ! cond1 ][[ ! cond1 ]]逻辑非

重要提示:在使用[ ]时,变量引用务必加上双引号,如[ -f "$filename" ]。否则,如果$filename包含空格或为空,命令会解析错误。[[ ]]在这方面更安全,但养成加引号的习惯总是好的。

让我们用if-elif-else结构写一个更复杂的例子,根据文件类型做不同处理:

#!/bin/bash # file_handler.sh target="$1" if [[ -z "$target" ]]; then echo "Error: Please specify a file or directory." exit 1 fi if [[ -d "$target" ]]; then echo "'$target' is a directory." echo "Listing contents:" ls -la "$target" elif [[ -f "$target" ]]; then echo "'$target' is a regular file." echo "File size: $(du -h "$target" | cut -f1)" echo "Last modified: $(date -r "$target")" # 判断文件是否可执行 if [[ -x "$target" ]]; then echo "This file is executable." fi else echo "'$target' is neither a regular file nor a directory (maybe a device file, symlink, etc.)." fi

这个脚本首先检查是否提供了参数(-z判断是否为空),然后依次判断目标是目录、普通文件还是其他类型,并执行相应的操作。elifelse if的缩写,用于连接多个条件分支。

4. 自动化核心:循环与函数

当我们需要对多个项目执行相同操作,或者把一段常用代码封装起来时,循环和函数就登场了。它们是实现复杂自动化的基石。

4.1 遍历与重复:for循环与while循环

for循环常用于遍历一个已知的列表,比如文件集合、参数列表等。

#!/bin/bash # 示例1:遍历固定字符串列表 echo "Processing fruits:" for fruit in apple banana cherry; do echo "I like $fruit" done # 示例2:遍历当前目录下所有 .txt 文件(这是处理通配符展开) echo "Processing text files:" for file in *.txt; do # 防止没有.txt文件时,循环会执行一次,file的值就是"*.txt" if [[ -f "$file" ]]; then echo "Found text file: $file" wc -l "$file" # 统计文件行数 fi done # 示例3:遍历所有命令行参数,这是处理参数最安全的方式 echo "Processing all arguments:" for arg in "$@"; do echo "Argument: $arg" done # 示例4:C语言风格的for循环,用于数字序列 for (( i=1; i<=5; i++ )); do echo "Count: $i" done

while循环则用于当某个条件为真时,持续执行循环体。它常用来读取文件内容,或者等待某个事件发生。

#!/bin/bash # 示例1:计数器 count=1 while [[ $count -le 5 ]]; do echo "Count is $count" ((count++)) # 使用双括号进行算术运算,相当于 count=$((count+1)) done # 示例2:逐行读取文件(经典用法!) config_file="app.conf" if [[ -f "$config_file" ]]; then echo "Reading config file:" line_number=1 while IFS= read -r line; do # 跳过空行和以#开头的注释行 if [[ -n "$line" && "$line" != \#* ]]; then echo "Line $line_number: $line" fi ((line_number++)) done < "$config_file" # 输入重定向,将文件内容喂给while循环 fi # 示例3:从命令输出中读取 echo "Monitoring process list for 'bash' (Ctrl+C to stop):" while true; do # 无限循环 if pgrep bash > /dev/null; then echo "$(date): Bash process is running." else echo "$(date): No bash process found." fi sleep 2 # 等待2秒 done

while IFS= read -r line是一个需要重点理解的模式:

  • IFS=将内部字段分隔符设为空,防止read命令去掉行首行尾的空白字符。
  • -r选项防止反斜杠\被解释为转义字符。
  • done < "$config_file"将文件内容重定向到while循环的标准输入,使得read命令能从文件中读取。

4.2 封装与复用:定义与使用函数

当一段代码需要被多次使用,或者为了提升脚本的可读性和结构性,我们就应该把它封装成函数。

#!/bin/bash # function_demo.sh # 定义一个无参数的函数:打印日志,带时间戳 log_message() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] $1" # $1 在这里是函数的第一个参数,不是脚本的参数 } # 定义一个有返回值的函数:检查URL是否可访问 check_url() { local url="$1" # 使用curl静默模式,只返回HTTP状态码 if curl --output /dev/null --silent --head --fail --max-time 5 "$url"; then return 0 # 成功,返回0 else return 1 # 失败,返回非0 fi } # 定义一个计算函数,利用命令替换返回值 calculate_sum() { local a=$1 local b=$2 local sum=$((a + b)) echo "$sum" # 通过echo输出结果,调用者用$(...)捕获 } # 使用函数 log_message "Script started." url_to_check="https://www.example.com" if check_url "$url_to_check"; then log_message "URL $url_to_check is accessible." else log_message "ERROR: URL $url_to_check is not accessible." fi result=$(calculate_sum 10 20) log_message "The sum of 10 and 20 is: $result" log_message "Script finished."

函数的关键点:

  • 定义function_name() { ... }function function_name { ... }。前者是POSIX标准,更通用。
  • 参数:函数内部使用$1,$2...来获取传入的参数。注意:这和脚本主体的$1$2是独立的。
  • 局部变量:使用local关键字在函数内声明变量,可以避免污染全局变量空间。这是一个非常好的习惯。
  • 返回值
    • Shell函数通过return语句返回一个退出状态码(0-255),0表示成功。这通常用于表示函数执行成败。
    • 如果需要返回一个字符串或数值结果,常用的方法是在函数内使用echo输出,然后在调用处用命令替换$(...)捕获,如上面的calculate_sum函数。
  • 调用:直接写函数名,参数跟在后面,用空格分隔。

函数让脚本变得模块化。你可以把常用的功能,比如日志记录、错误检查、发送通知等,都写成函数库(一个单独的.sh文件),然后在主脚本中用source functions_lib.sh来引入,大大提升代码的复用性和可维护性。

5. 实战:构建一个实用的日志文件清理脚本

现在,让我们把前面学到的所有知识——变量、参数、条件判断、循环、函数——组合起来,编写一个真正有用的脚本:一个自动清理旧日志文件的工具。这是系统管理中的常见需求。

5.1 需求分析与脚本设计

假设我们有一个应用程序,它每天都会在/var/log/myapp/目录下生成日志文件,命名格式为myapp-YYYY-MM-DD.log。我们希望写一个脚本,能够:

  1. 删除指定目录下,超过N天的日志文件。
  2. 可以指定要匹配的文件名模式(如*.logmyapp-*.log)。
  3. 在真正删除前,最好能先预览一下哪些文件会被删除(“试运行”模式)。
  4. 脚本应该健壮,能处理目录不存在、权限不足等情况,并给出清晰的日志。

我们将设计脚本的调用方式如下:

./clean_old_logs.sh /var/log/myapp "myapp-*.log" 30

参数1:要清理的目录。 参数2:文件名匹配模式。 参数3:保留最近多少天的文件。

加上一个可选的-n--dry-run参数来启用试运行模式。

5.2 逐步实现脚本

首先,创建脚本文件clean_old_logs.sh,并赋予执行权限。

#!/bin/bash # clean_old_logs.sh - 清理指定目录下旧文件的实用脚本 # 定义颜色输出,让日志更易读(可选) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # 日志函数 log_info() { echo -e "[INFO] $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } # 显示用法函数 show_usage() { echo "Usage: $0 [OPTIONS] <directory> <file_pattern> <days_to_keep>" echo "" echo "Options:" echo " -n, --dry-run Preview files to be deleted without actually deleting." echo " -h, --help Show this help message." echo "" echo "Example:" echo " $0 /var/log/myapp \"myapp-*.log\" 30 # Delete logs older than 30 days" echo " $0 -n /tmp \"*.tmp\" 7 # Dry-run for temp files older than 7 days" } # 初始化变量 DRY_RUN=false DIRECTORY="" PATTERN="" DAYS="" # 解析命令行参数(简单版本,更复杂的可以用getopts) while [[ $# -gt 0 ]]; do case $1 in -n|--dry-run) DRY_RUN=true shift # 移除已处理的选项 ;; -h|--help) show_usage exit 0 ;; -*) log_error "Unknown option: $1" show_usage exit 1 ;; *) # 第一个非选项参数是目录,第二个是模式,第三个是天数 if [[ -z "$DIRECTORY" ]]; then DIRECTORY="$1" elif [[ -z "$PATTERN" ]]; then PATTERN="$1" elif [[ -z "$DAYS" ]]; then DAYS="$1" else log_error "Too many arguments." show_usage exit 1 fi shift ;; esac done # 检查必需的参数是否提供 if [[ -z "$DIRECTORY" || -z "$PATTERN" || -z "$DAYS" ]]; then log_error "Missing required arguments." show_usage exit 1 fi # 验证天数是否为有效数字 if ! [[ "$DAYS" =~ ^[0-9]+$ ]]; then log_error "Days to keep must be a positive integer." exit 1 fi # 检查目录是否存在且可访问 if [[ ! -d "$DIRECTORY" ]]; then log_error "Directory '$DIRECTORY' does not exist or is not accessible." exit 1 fi if [[ ! -r "$DIRECTORY" ]]; then log_error "No read permission for directory '$DIRECTORY'." exit 1 fi log_info "Starting cleanup process..." log_info "Directory: $DIRECTORY" log_info "Pattern: $PATTERN" log_info "Days to keep: $DAYS" if [[ "$DRY_RUN" == true ]]; then log_warn "DRY-RUN MODE ENABLED. No files will be deleted." fi # 计算截止时间戳(当前时间减去DAYS天的秒数) CUTOFF_TIME=$(date -d "$DAYS days ago" +%s 2>/dev/null || date -v-${DAYS}d +%s 2>/dev/null) # 上面命令兼容Linux (date -d) 和 macOS (date -v)。如果都失败,则退出。 if [[ -z "$CUTOFF_TIME" ]]; then log_error "Failed to calculate cutoff date. Check your 'date' command." exit 1 fi DELETED_COUNT=0 ERROR_COUNT=0 # 进入目标目录,处理文件 cd "$DIRECTORY" || { log_error "Failed to cd to '$DIRECTORY'"; exit 1; } # 使用find命令查找匹配模式且修改时间早于截止时间的文件 # -name 指定模式,-mtime +$DAYS 表示修改时间在$DAYS天以前,-type f 只找普通文件 while IFS= read -r -d $'\0' file; do # 获取文件的修改时间戳 file_mtime=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null) if [[ -z "$file_mtime" ]]; then log_warn "Could not get modification time for '$file'. Skipping." ((ERROR_COUNT++)) continue fi if [[ $file_mtime -lt $CUTOFF_TIME ]]; then if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would delete: $file (modified: $(date -d @$file_mtime))" else log_info "Deleting: $file" if rm -f "$file"; then ((DELETED_COUNT++)) else log_error "Failed to delete: $file" ((ERROR_COUNT++)) fi fi fi done < <(find . -maxdepth 1 -type f -name "$PATTERN" -print0) # 注意:这里使用了进程替换 <(command) 和 find 的 -print0,可以安全处理包含空格或换行符的文件名。 # 返回原目录(虽然不是必须,但是个好习惯) cd - > /dev/null # 输出总结 if [[ "$DRY_RUN" == true ]]; then log_success "Dry-run completed. Found $DELETED_COUNT file(s) that would be deleted. $ERROR_COUNT error(s)." else log_success "Cleanup completed. Successfully deleted $DELETED_COUNT file(s). $ERROR_COUNT error(s) encountered." fi exit $((ERROR_COUNT > 0 ? 1 : 0))

5.3 脚本关键点解析与避坑指南

这个脚本虽然不长,但包含了很多Shell脚本编程的实用技巧和容易踩坑的地方:

  1. 参数解析:我们实现了一个简单的while/case循环来解析-n-h这样的选项。对于更复杂的选项(如带值的-d 30),建议使用Bash内置的getopts命令,它更标准、更强大。
  2. 输入验证:这是健壮脚本的生命线。我们检查了参数数量、目录是否存在、是否有读取权限、天数是否为数字。缺少这些检查,脚本在异常情况下会行为诡异或直接崩溃。
  3. 平台兼容性date命令计算过去时间,在Linux(GNUdate)和macOS(BSDdate)上的语法不同。我们用了||操作符尝试两种方式:date -d "$DAYS days ago" +%s(Linux)和date -v-${DAYS}d +%s(macOS)。如果都失败,脚本会报错退出。stat命令获取文件时间戳也存在类似的平台差异。
  4. 安全地处理文件名:这是最大的坑之一。如果文件名包含空格、换行符等特殊字符,简单的for file in $(find ...)会出错。我们使用了find-print0选项(用NULL字符分隔文件名)和while IFS= read -r -d $'\0'循环来读取,这是处理任意文件名的黄金标准。
  5. 进程替换< <(command)这个语法叫“进程替换”。它把command的输出作为一个临时文件描述符提供给while循环读取。这里用它来安全地传递find命令的结果。
  6. 清晰的日志与状态返回:脚本使用了不同颜色的日志级别(INFO, WARN, ERROR, SUCCESS),让运行结果一目了然。最后,脚本根据是否发生错误(ERROR_COUNT)返回不同的退出码(0成功,非0失败),这便于其他脚本或工具调用时判断本次清理是否完全成功。
  7. “试运行”模式:通过一个DRY_RUN标志,让脚本在不实际执行删除操作的情况下,列出所有将要被删除的文件。这是一个非常实用的功能,尤其是在执行破坏性操作前,让你有机会做最后确认。

你可以把这个脚本保存起来,根据你的实际需求修改(比如递归查找子目录可以去掉find-maxdepth 1,或者添加邮件通知功能)。通过这个实战项目,你不仅学会了写脚本,更学会了如何思考、设计和实现一个可靠、实用的自动化工具。这才是Shell脚本编程真正的价值所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/31 16:40:45

Avalonia UI Image控件深度解析:从核心原理到跨平台实战优化

1. 项目概述&#xff1a;Avalonia UI中的Image控件在桌面应用开发领域&#xff0c;跨平台UI框架Avalonia UI正以其现代化的架构和出色的性能吸引着越来越多的开发者。无论是开发运行在Windows、macOS、Linux&#xff0c;还是国产操作系统上的应用&#xff0c;Avalonia都提供了统…

作者头像 李华
网站建设 2026/7/31 16:39:32

【单片机毕业设计】基于 STM32 的多档位洗衣模拟控制平台搭建 基于嵌入式开发板的洗衣时序模拟系统实现(015701)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/7/31 16:39:13

【单片机毕业设计】基于 STM32 单片机的蜂鸣提醒式智能电饭煲开发 基于嵌入式技术的模拟电饭煲多模式温控系统设计(016001)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/7/31 16:36:46

UE事件分发系统:构建强类型、解耦的游戏通信框架

1. 项目概述&#xff1a;为什么要在UE里再造一个事件分发轮子&#xff1f;在虚幻引擎&#xff08;UE&#xff09;的项目开发中&#xff0c;尤其是涉及复杂游戏逻辑、多系统交互或网络同步时&#xff0c;我们经常会遇到一个经典问题&#xff1a;如何让两个或多个彼此不直接引用的…

作者头像 李华
网站建设 2026/7/31 16:34:11

YimMenu:GTA5在线模式的安全防护与增强解决方案

YimMenu&#xff1a;GTA5在线模式的安全防护与增强解决方案 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenu …

作者头像 李华