2026/8/24 5:28:37

Shell脚本编程从入门到实战:自动化运维与文本处理三剑客

Shell脚本编程从入门到实战:自动化运维与文本处理三剑客 在日常运维和开发工作中你是否经常需要重复执行一系列命令比如批量修改文件名、定时备份日志、自动化部署服务手动操作不仅效率低下还容易出错。Shell脚本正是解决这类问题的利器它能将复杂的命令行操作封装成可重复执行的自动化程序是Linux系统管理和后端开发的必备技能。本文将从零开始手把手带你掌握Shell脚本编程的核心。我们将从最基础的变量定义、流程控制循环、条件判断讲起逐步深入到函数封装和文本处理“三剑客”grep、awk、sed的实战应用。无论你是刚接触Linux的新手还是希望系统化提升脚本编写能力的运维工程师或开发者都能通过本文的完整示例和避坑指南构建一套属于自己的自动化工具箱。1. Shell脚本基础概念、环境与第一个脚本1.1 Shell是什么为什么需要Shell脚本简单来说Shell是用户与Linux内核之间的“翻译官”。我们在终端Terminal里输入的命令如ls、cd由Shell接收并解释然后调用相应的系统功能执行。常见的Shell有BashBourne-Again Shell、Zsh等其中Bash是大多数Linux发行版的默认Shell也是本文教学的重点。Shell脚本则是一个包含了一系列Shell命令的文本文件。通过编写脚本我们可以自动化重复任务如日志清理、数据备份、服务监控。简化复杂操作将多步命令组合成一个命令。实现流程控制根据条件执行不同命令或循环处理大量数据。提高可维护性脚本即文档方便他人理解和复用。1.2 环境准备与第一个脚本在开始之前请确保你有一个可用的Linux环境。这可以是物理机或虚拟机安装的Linux系统如Ubuntu, CentOS。Windows系统上的WSLWindows Subsystem for Linux。云服务器如阿里云ECS、腾讯云CVM。打开你的终端让我们创建并运行第一个脚本。步骤1创建脚本文件Shell脚本通常以.sh作为扩展名。使用touch命令创建一个新文件并用vim或nano等编辑器打开它。# 创建一个名为 hello_world.sh 的脚本文件 touch hello_world.sh # 使用vim编辑器打开如果不会用vim可以用 nano hello_world.sh vim hello_world.sh步骤2编写脚本内容在打开的编辑器里输入以下内容#!/bin/bash # 这是一个注释我的第一个Shell脚本 echo Hello, CSDN! echo 当前时间是$(date)第一行#!/bin/bash称为“Shebang”或“Hashbang”。它告诉系统使用/bin/bash这个解释器来执行本脚本。这是脚本文件的第一行且必须顶格写。第二行以#开头这是注释用于解释代码不会被执。echo命令用于向屏幕输出一行文本。$(date)这是“命令替换”。先执行date命令获取当前时间然后将结果替换到echo命令中。步骤3赋予脚本执行权限新创建的文件默认没有执行权限。使用chmod命令修改权限。# 为脚本文件添加可执行权限 chmod x hello_world.sh # 查看文件权限 ls -l hello_world.sh # 你应该能看到类似 -rwxr-xr-x 的输出其中的 ‘x‘ 代表可执行步骤4运行脚本有三种常见方式运行脚本# 方法1使用绝对路径或相对路径前提是有x权限 ./hello_world.sh # 方法2使用bash解释器直接执行即使没有x权限也可以 bash hello_world.sh # 方法3使用source命令或 . 命令在当前Shell环境中执行会影响当前环境变量 source hello_world.sh # 或 . hello_world.sh推荐使用方法1。如果看到输出“Hello, CSDN!”和当前时间恭喜你第一个脚本成功运行了2. Shell编程核心变量与参数变量是存储数据的容器是脚本编程的基石。2.1 变量的定义与使用在Bash中定义变量很简单但有一些语法规则。#!/bin/bash # 定义变量变量名值等号两边不能有空格 my_nameShell Learner urlhttps://csdn.net count100 # 使用变量需要在变量名前加上美元符号 $ echo 我的名字是$my_name echo 访问地址$url echo 数量是$count # 另一种使用方式${变量名}花括号常用于界定变量名边界 echo 我是${my_name}欢迎你 # 如果没有花括号 echo “我是$my_name欢迎你” 也能工作但下面这种情况就必须用 suffix.txt filenamelog${suffix} # 正确得到 log.txt # filenamelog$suffix # 错误Bash会尝试寻找名为 $suffix 的变量而不是 $suffix重要规则与坑点命名规则只能包含字母、数字和下划线且不能以数字开头。赋值无空格varvalue正确var value错误会被解析为命令var带参数和value。默认类型是字符串即使你写了count100Bash也将其视为字符串“100”。在算术运算时它会进行隐式转换。只读变量使用readonly声明尝试修改会报错。readonly PI3.14 PI3.1415 # 报错bash: PI: readonly variable删除变量使用unset命令但不能删除只读变量。unset url echo $url # 输出为空2.2 特殊变量与脚本参数Shell提供了一系列特殊的预定义变量用于获取脚本运行时的上下文信息。变量含义$0当前脚本的文件名。$1,$2,$3...脚本的第1、2、3...个参数。$#传递给脚本的参数个数。$*以一个单字符串显示所有参数。如“$*”用“括起来的情况、以“$1 $2 … $n”的形式输出所有参数。$与$*类似但在被双引号“”包裹时每个参数作为独立的字符串。$?上一个命令的退出状态。0表示成功非0表示失败。$$当前Shell进程的进程IDPID。$!最后一个后台运行的进程的PID。实战示例创建处理参数的脚本#!/bin/bash # 文件名args_demo.sh echo “脚本名称$0” echo “第一个参数$1” echo “第二个参数$2” echo “参数总个数$#” echo “所有参数作为一个字符串$*” echo “所有参数每个参数独立” for arg in “$”; do echo “ - $arg” done # 测试上一个命令是否成功 ls /tmp /dev/null 21 echo “ls /tmp 命令的退出状态是$?”运行这个脚本./args_demo.sh apple banana cherry输出将显示参数被如何解析。理解$和$*的区别对于编写健壮的、能处理带空格参数的脚本至关重要。2.3 环境变量与作用域环境变量是对所有Shell会话和子进程都可见的全局变量。常见的环境变量有PATH命令搜索路径、HOME用户家目录、USER当前用户名等。#!/bin/bash # 查看环境变量 echo “当前用户$USER” echo “家目录$HOME” echo “PATH变量$PATH” # 定义普通变量局部变量 local_var“I am local” # 将其导出为环境变量 export local_var # 或者定义时直接导出 export GLOBAL_VAR“I am global” # 在子Shell中测试 bash -c ‘echo “在子Shell中local_var: $local_var, GLOBAL_VAR: $GLOBAL_VAR”’作用域总结普通变量只在当前Shell脚本中有效。export导出的变量对当前Shell及其所有子进程有效。子Shell无法修改父Shell的变量反之亦然。3. 流程控制让脚本学会判断与循环流程控制是脚本拥有“智能”的关键。3.1 条件判断if语句if语句根据条件执行不同的代码块。条件判断通常使用test命令或其等价符号[ ]、[[ ]]。基础语法if [ condition ]; then # 条件为真时执行的命令 elif [ another_condition ]; then # 另一个条件为真时执行的命令 else # 所有条件都为假时执行的命令 fi常用条件判断符类别符号在 [ ] 内含义示例字符串比较或等于[ “$a” “$b” ]!不等于[ “$a” ! “$b” ]-z字符串长度为0[ -z “$str” ]-n字符串长度非0[ -n “$str” ]整数比较-eq等于[ $a -eq $b ]-ne不等于[ $a -ne $b ]-gt大于[ $a -gt $b ]-lt小于[ $a -lt $b ]-ge大于等于[ $a -ge $b ]-le小于等于[ $a -le $b ]文件测试-e文件/目录存在[ -e /path/to/file ]-f存在且为普通文件[ -f /path/to/file ]-d存在且为目录[ -d /path/to/dir ]-r可读[ -r /path/to/file ]-w可写[ -w /path/to/file ]-x可执行[ -x /path/to/file ]逻辑运算-a或与[ $a -gt 0 -a $a -lt 10 ]或[[ $a -gt 0 $a -lt 10 ]]-o或 !非[ ! -f /path/to/file ]注意[ ]是test命令的另一种形式内部两边必须有空格。[[ ]]是Bash的扩展测试命令功能更强大支持正则匹配~且逻辑运算符、||更安全推荐在Bash中使用。实战示例备份文件#!/bin/bash # 文件名backup_file.sh source_file“/etc/hosts” backup_dir“/tmp/backup” backup_file“${backup_dir}/hosts.backup.$(date %Y%m%d%H%M%S)” # 判断源文件是否存在且可读 if [ ! -f “$source_file” ]; then echo “错误源文件 $source_file 不存在” exit 1 # 非0退出码表示错误 fi if [ ! -r “$source_file” ]; then echo “错误源文件 $source_file 不可读” exit 1 fi # 判断备份目录是否存在不存在则创建 if [ ! -d “$backup_dir” ]; then echo “备份目录 $backup_dir 不存在正在创建...” mkdir -p “$backup_dir” if [ $? -ne 0 ]; then # 检查mkdir命令是否成功 echo “创建备份目录失败” exit 1 fi fi # 执行备份 cp “$source_file” “$backup_file” if [ $? -eq 0 ]; then echo “备份成功文件保存在$backup_file” else echo “备份失败” fi3.2 循环处理for, while, until循环用于重复执行一段代码。1. for循环用于遍历一个列表。# 语法1遍历值列表 for var in item1 item2 … itemN; do commands done # 语法2遍历命令输出例如ls的结果 for file in $(ls *.txt); do echo “处理文件$file” done # 语法3C语言风格适用于数字序列 for (( i0; i10; i )); do echo “i $i” done实战示例批量重命名文件#!/bin/bash # 将当前目录下所有 .log 文件重命名为 .log.bak for file in *.log; do # 防止没有.log文件时file被赋值为“*.log”字符串 if [ -f “$file” ]; then mv “$file” “${file}.bak” echo “已重命名$file - ${file}.bak” fi done2. while循环当条件为真时持续循环。while [ condition ]; do commands done实战示例监控进程是否存在#!/bin/bash # 每隔5秒检查一次nginx进程是否在运行 process_name“nginx” while true; do if pgrep “$process_name” /dev/null; then echo “[$(date)] $process_name 正在运行。” else echo “[$(date)] 警告$process_name 进程不存在” # 这里可以加入重启服务的命令 # systemctl restart nginx fi sleep 5 # 暂停5秒 done3. until循环与while相反当条件为假时持续循环。直到条件为真才停止。until [ condition ]; do commands done实战示例等待某个端口被监听#!/bin/bash # 等待本地的8080端口被监听最多等待60秒 host“localhost” port8080 timeout60 count0 until nc -z “$host” “$port” /dev/null 21; do sleep 1 ((count)) echo “等待 ${host}:${port} 启动... (${count}秒)” if [ $count -ge $timeout ]; then echo “超时${host}:${port} 在 ${timeout} 秒内未启动。” exit 1 fi done echo “${host}:${port} 已就绪”3.3 case语句多分支选择case语句比多层if-elif更清晰适用于匹配固定模式。case $variable in pattern1) commands1 ;; pattern2) commands2 ;; *) default_commands ;; esac实战示例简单的服务管理脚本#!/bin/bash # 文件名service_manager.sh action“$1” # 第一个参数start, stop, status case “$action” in start) echo “正在启动服务...” # systemctl start my_service echo “服务启动命令已执行。” ;; stop) echo “正在停止服务...” # systemctl stop my_service echo “服务停止命令已执行。” ;; status) echo “正在检查服务状态...” # systemctl status my_service echo “服务状态查询命令已执行。” ;; restart) echo “正在重启服务...” # systemctl restart my_service echo “服务重启命令已执行。” ;; *) # 匹配其他所有情况 echo “用法$0 {start|stop|status|restart}” exit 1 ;; esac4. 函数封装可复用的代码块函数将一组命令组织成一个单元提高代码的复用性和可读性。4.1 定义与调用函数#!/bin/bash # 定义函数方式1 function say_hello { echo “Hello, $1!” # $1 是函数的第一个参数 } # 定义函数方式2更便携符合POSIX greet_user() { local name“$1” # 使用local关键字定义局部变量避免污染全局 local hour$(date %H) if [ $hour -lt 12 ]; then echo “早上好$name” elif [ $hour -lt 18 ]; then echo “下午好$name” else echo “晚上好$name” fi } # 调用函数 say_hello “World” say_hello “CSDN Reader” greet_user “张三” greet_user “李四”4.2 函数参数与返回值Shell函数通过位置参数$1,$2...接收参数通过return返回一个整数状态码0成功非0失败或者通过echo输出结果。#!/bin/bash # 计算两个数的和 add() { local num1$1 local num2$2 local sum$((num1 num2)) echo $sum # 通过标准输出返回结果 } # 检查文件是否存在并返回布尔状态 file_exists() { local fname“$1” if [ -f “$fname” ]; then return 0 # 真成功 else return 1 # 假失败 fi } # 调用 result$(add 10 20) # 命令替换捕获函数echo的输出 echo “10 20 $result” if file_exists “/etc/passwd”; then echo “/etc/passwd 文件存在。” else echo “/etc/passwd 文件不存在。” fi # 注意函数return的值保存在 $? 中但只能用于判断不能像其他语言那样赋值。4.3 实战一个实用的日志分析函数假设我们需要一个函数能从指定的日志文件中提取出包含“ERROR”关键词的行并统计数量。#!/bin/bash # 文件名log_analyzer.sh analyze_errors() { local log_file“$1” local keyword“${2:-ERROR}” # 第二个参数默认为“ERROR” if [ ! -f “$log_file” ]; then echo “错误日志文件 ‘$log_file’ 不存在。” 2 # 错误信息输出到标准错误 return 1 fi if [ ! -r “$log_file” ]; then echo “错误日志文件 ‘$log_file’ 不可读。” 2 return 1 fi echo “正在分析文件$log_file关键词$keyword” echo “” # 使用grep查找并高亮显示 grep -n -i --colorauto “$keyword” “$log_file” echo “” local error_count$(grep -c -i “$keyword” “$log_file”) echo “总计发现 ‘$keyword’ 行数$error_count” return 0 } # 使用函数 analyze_errors “/var/log/syslog” analyze_errors “/var/log/auth.log” “Failed”5. 文本处理三剑客grep, sed, awk这三个工具是Shell脚本处理文本数据的核心功能强大组合使用能解决绝大多数文本处理需求。5.1 grep强大的文本搜索工具grep用于在文件中搜索匹配指定模式的行。常用选项-i忽略大小写。-v反向选择输出不匹配的行。-n显示匹配行的行号。-c只显示匹配到的行数。-r或-R递归搜索目录下的所有文件。-l只列出包含匹配项的文件名。-E使用扩展正则表达式等同于egrep。--colorauto高亮显示匹配到的文本。实战示例# 在文件中搜索包含“error”的行忽略大小写 grep -i “error” /var/log/syslog # 搜索不包含“INFO”的行并显示行号 grep -v -n “INFO” app.log # 统计当前目录下所有.py文件中“import”出现的次数 grep -c “import” *.py # 使用正则表达式搜索以“192.168”开头的IP地址 grep -E “^192\.168\.[0-9]\.[0-9]” access.log # 递归搜索目录下所有文件找出包含“TODO”或“FIXME”的文件名 grep -r -l -E “TODO|FIXME” /path/to/project/5.2 sed流编辑器用于文本替换、删除、插入sed以行为单位处理文本流尤其擅长替换操作。基础语法sed [选项] ‘命令’ 文件常用命令s/pattern/replacement/flags替换。s表示替换pattern是要匹配的模式replacement是替换后的文本flags是标志如g表示全局替换i表示忽略大小写。d删除匹配的行。p打印匹配的行通常与-n选项一起使用只打印匹配行。实战示例# 将文件中的“foo”替换为“bar”只替换每行第一个 sed ‘s/foo/bar/’ file.txt # 全局替换每行所有匹配项 sed ‘s/foo/bar/g’ file.txt # 删除所有空行 sed ‘/^$/d’ file.txt # 删除包含“debug”的行 sed ‘/debug/d’ file.txt # 在原文件上直接修改危险务必先备份-i 选项 sed -i.bak ‘s/old/new/g’ file.txt # 先备份为file.txt.bak再修改file.txt # 打印第10到20行 sed -n ‘10,20p’ file.txt # 在每一行行首添加注释符 # sed ‘s/^/# /’ file.txt5.3 awk强大的文本分析工具更像一门编程语言awk将文件逐行读入以空格默认为分隔符将每行切片然后对切片部分进行处理。awk ‘pattern {action}’ file内置变量$0整行内容。$1,$2...第1、2...个字段。NF当前行的字段数。NR当前处理的行号Record Number。FS输入字段分隔符默认是空格或制表符。OFS输出字段分隔符默认是空格。实战示例# 打印文件的第一列和第三列 awk ‘{print $1, $3}’ data.txt # 打印行号大于10的行 awk ‘NR 10 {print NR, $0}’ data.txt # 以冒号‘:’为分隔符打印/etc/passwd的第一列用户名和最后一列Shell awk -F:‘{print $1, $NF}’ /etc/passwd # 计算文件第一列数字的总和 awk ‘{sum $1} END {print “总和为”, sum}’ numbers.txt # 统计文件行数、单词数、字符数模拟wc命令 awk ‘{ lines; words NF; chars length($0) 1 } END { print “行数:”, lines, “单词数:”, words, “字符数:”, chars }’ file.txt # 处理CSV文件以逗号分隔 awk -F,‘{print “姓名”, $1, “ 分数”, $2}’ scores.csv三剑客组合使用示例分析Nginx访问日志找出访问量最高的5个IP。# 假设日志格式为$remote_addr - $remote_user [$time_local] “$request” ... awk ‘{print $1}’ access.log | sort | uniq -c | sort -rn | head -5awk ‘{print $1}’提取每行第一个字段IP地址。sort对IP地址排序为uniq -c做准备。uniq -c统计每个IP出现的次数。sort -rn按次数-n倒序-r排序。head -5取前5行。6. 脚本调试与错误处理编写健壮的脚本必须考虑错误处理和调试。6.1 调试技巧使用set命令#!/bin/bash set -x # 开启调试模式打印每条执行的命令及其参数 echo “开始调试” var“test” echo $var set x # 关闭调试模式 set -e # 脚本中任何命令失败返回非0就立即退出。非常有用 set -u # 遇到未定义的变量时报错并退出。 set -o pipefail # 管道命令中任何一个失败整个管道就失败。 # 通常一起使用 # set -euo pipefail使用trap捕获信号在脚本退出、被中断时执行清理操作。#!/bin/bash cleanup() { echo “正在清理临时文件...” rm -f /tmp/mytemp.* echo “清理完成。” } # 捕获EXIT信号脚本退出时、INT信号CtrlC、TERM信号kill命令 trap cleanup EXIT INT TERM # 脚本主体 touch /tmp/mytemp.$$ echo “临时文件已创建/tmp/mytemp.$$” sleep 106.2 错误处理与日志检查命令返回值$?cp source.txt dest.txt if [ $? -ne 0 ]; then echo “复制文件失败” 2 exit 1 fi将错误输出重定向到日志文件# 将标准输出和标准错误都追加到日志文件 some_command /var/log/my_script.log 21 # 或者分开记录 some_command /var/log/my_script.stdout.log 2 /var/log/my_script.stderr.log7. 综合实战自动化备份与清理脚本我们将结合前面所学编写一个相对完整的实战脚本定期备份指定目录并清理过期的旧备份。#!/bin/bash # 文件名auto_backup.sh # 功能备份指定目录并清理N天前的旧备份 # 用法./auto_backup.sh source_dir backup_root_dir [keep_days] # 配置部分 # 使用命令行参数或默认值 SOURCE_DIR“${1:-/home/user/data}” # 要备份的源目录默认 /home/user/data BACKUP_ROOT“${2:-/backups}” # 备份根目录默认 /backups KEEP_DAYS“${3:-30}” # 保留多少天的备份默认30天 # # 开启严格模式 set -euo pipefail # 定义日志函数 log() { local level“$1” local message“$2” echo “[$(date ‘%Y-%m-%d %H:%M:%S’)] [$level] $message” } log “INFO” “ 开始备份任务 log “INFO” “源目录$SOURCE_DIR” log “INFO” “备份根目录$BACKUP_ROOT” log “INFO” “保留天数$KEEP_DAYS” # 1. 检查源目录是否存在 if [ ! -d “$SOURCE_DIR” ]; then log “ERROR” “源目录 $SOURCE_DIR 不存在任务终止。” exit 1 fi # 2. 创建备份根目录如果不存在 if [ ! -d “$BACKUP_ROOT” ]; then log “WARN” “备份根目录 $BACKUP_ROOT 不存在正在创建...” mkdir -p “$BACKUP_ROOT” if [ $? -ne 0 ]; then log “ERROR” “创建备份根目录失败” exit 1 fi fi # 3. 生成备份文件名带时间戳 BACKUP_NAME“data_backup_$(date ‘%Y%m%d_%H%M%S’).tar.gz” BACKUP_PATH“${BACKUP_ROOT}/${BACKUP_NAME}” # 4. 执行备份使用tar压缩 log “INFO” “正在创建备份文件$BACKUP_PATH” tar -czf “$BACKUP_PATH” -C “$(dirname “$SOURCE_DIR”)” “$(basename “$SOURCE_DIR”)” 2/dev/null if [ $? -eq 0 ]; then BACKUP_SIZE$(du -h “$BACKUP_PATH” | cut -f1) log “INFO” “备份成功文件大小$BACKUP_SIZE” else log “ERROR” “备份过程失败” exit 1 fi # 5. 清理过期备份 log “INFO” “开始清理超过 ${KEEP_DAYS} 天的旧备份...” # find 命令在BACKUP_ROOT下查找名称匹配‘data_backup_*.tar.gz’且修改时间超过KEEP_DAYS天的文件然后删除 find “$BACKUP_ROOT” -name “data_backup_*.tar.gz” -type f -mtime “$KEEP_DAYS” -delete if [ $? -eq 0 ]; then log “INFO” “旧备份清理完成。” else log “WARN” “清理旧备份时可能遇到问题。” fi log “INFO” “ 备份任务完成 使用说明保存脚本为auto_backup.sh并赋予执行权限。可以带参数运行./auto_backup.sh /path/to/source /path/to/backup 7也可以使用默认配置运行./auto_backup.sh可以结合crontab实现定时自动备份# 每天凌晨2点执行备份 crontab -e # 添加一行 0 2 * * * /path/to/auto_backup.sh /home/app/data /backups 30 /var/log/auto_backup.log 218. 编写Shell脚本的常见“坑”与最佳实践8.1 常见坑点变量未加引号当变量值包含空格或特殊字符时会引发单词分割或路径名扩展。错误rm $MY_FILES(如果MY_FILES”file1.txt file2.txt”会删除两个文件)正确rm “$MY_FILES”(只会删除一个名为file1.txt file2.txt的文件通常这不是你想要的。应该用数组处理多个文件)文件名包含空格循环for file in $(ls *.txt)会错误地将带空格的文件名拆开。应使用for file in *.txt或find命令配合while read。使用反引号反引号用于命令替换但嵌套时难以阅读。推荐使用$(command)格式。在脚本中使用cd改变当前目录会影响后续命令。如果需要应在子Shell中执行(cd /some/dir command)或使用pushd/popd。忽略命令返回值特别是rm,cp,mv,mkdir等可能失败的命令一定要检查$?或使用set -e。使用进行字符串比较在[ ]中和等效。但在[[ ]]中支持模式匹配。为了一致性字符串比较建议使用。8.2 最佳实践脚本开头总是使用#!/bin/bash并设置set -euo pipefail以增强健壮性。添加注释在文件头说明脚本用途、作者、参数、示例。对复杂逻辑添加行内注释。使用函数将重复代码或独立功能封装成函数使主流程清晰。变量命名使用小写字母和下划线如backup_dir。环境变量或常量可使用大写如PATH,MAX_RETRIES。局部变量在函数内使用local声明变量避免污染全局命名空间。错误处理检查重要命令的返回值并向标准错误2输出有意义的错误信息。使用日志重要的操作和错误记录到日志文件或系统日志logger命令方便排查。参数验证脚本接收外部参数时必须验证其有效性如目录是否存在、参数数量是否正确。代码风格统一缩进推荐2或4个空格合理使用空行分隔逻辑块。测试在安全的环境如测试目录、虚拟机中充分测试脚本特别是涉及rm、格式化的危险操作。掌握Shell脚本编程意味着你获得了自动化处理Linux系统任务的强大能力。从简单的文件操作到复杂的日志分析和系统监控脚本都能大幅提升效率。学习的关键在于多动手、多实践从解决身边的小问题开始逐步积累自己的脚本库。当你下次再遇到重复性的命令行工作时不妨停下来想一想“能不能写个脚本让它自动完成”