从入门到精通的5个核心技巧
📖 目录导读
- 为什么你需要关注脚本的效率写法?
- 用“函数封装”代替“重复代码”
- 活用“批量处理”与“循环优化”
- 巧用“错误处理”与“日志记录”
- 借助“外部工具”与“系统命令”
- 模块化与参数化设计
- 常见问题解答(FAQ)
为什么你需要关注脚本的效率写法?
在日常工作中,脚本(Shell、Python、PowerShell等)是自动化重复任务的利器,但很多人写脚本只关注“能不能跑”,而忽略了“跑得快不快”和“改起来方不方便”。实用的脚本应该像瑞士军刀——小巧、多功能、易于改造。

Q:为什么我的脚本经常跑着跑着就卡住,或者出现莫名其妙的问题?
A: 最常见的原因是缺乏错误处理机制和模块化设计,比如直接硬编码路径、没有前置检查、循环中嵌套太多无关操作等,下面我们就从5个方面来拆解高效脚本的写法。
技巧一:用“函数封装”代替“重复代码”
核心思想: 任何出现超过两次的代码块,都应该封装成函数,这不仅是代码整洁,更直接关系到脚本的可维护性和执行效率。
错误示范:
# 重复的备份操作 cp /data/file1 /backup/file1_20250404 cp /data/file2 /backup/file2_20250404 cp /data/file3 /backup/file3_20250404
高效写法(Shell示例):
backup_file() {
local src=$1
local dest_base="/backup"
local timestamp=$(date +%Y%m%d)
local filename=$(basename "$src")
cp "$src" "${dest_base}/${filename}_${timestamp}"
echo "[INFO] 已备份: $src"
}
# 调用
backup_file "/data/file1"
backup_file "/data/file2"
效果: 当需要修改备份路径或文件名格式时,只需改函数内部一行代码。
技巧二:活用“批量处理”与“循环优化”
核心思想: 手动处理多个相同格式的文件是效率杀手,利用for循环、find命令或数组,可以一键处理成百上千个文件。
Q:如何快速处理某个目录下所有.log文件?
A: 使用find搭配循环,比逐个写文件名高效100倍。
高效写法(Shell示例):
#!/bin/bash
# 批量压缩日志文件
find /var/log -name "*.log" -type f | while read logfile; do
if [ -f "$logfile" ]; then
gzip "$logfile" 2>/dev/null
echo "压缩完成: $logfile"
fi
done
优化点:
- 用
find而不是ls,避免文件名含空格导致的错误。 - 使用
while read逐行处理,兼容特殊字符。 - 添加
-type f只处理文件,排除目录。
Python版本更简洁:
import glob
import gzip
import shutil
for log_path in glob.glob("/var/log/*.log"):
with open(log_path, 'rb') as f_in:
with gzip.open(f"{log_path}.gz", 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f"压缩: {log_path}")
技巧三:巧用“错误处理”与“日志记录”
核心思想: 没有人希望脚本在半夜悄无声息地失败。始终假设“明天会跑失败”并做好预案。
Q:如何让脚本在出错时自动重试并通知我?
A: 使用trap命令和重试逻辑。
高效写法(Shell示例):
#!/bin/bash
MAX_RETRIES=3
RETRY_COUNT=0
LOG_FILE="/var/log/script.log"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
retry_command() {
local cmd="$1"
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if eval "$cmd" 2>&1 | tee -a "$LOG_FILE"; then
log_message "命令执行成功: $cmd"
return 0
else
RETRY_COUNT=$((RETRY_COUNT + 1))
log_message "命令失败,重试第 $RETRY_COUNT 次: $cmd"
sleep 2
fi
done
log_message "错误:命令最终失败,超出最大重试次数: $cmd"
exit 1
}
# 使用
retry_command "curl -s https://api.example.com/data"
关键点:
- 用
tee -a同时输出到终端和日志文件。 - 记录时间戳,方便排查问题产生的时间点。
技巧四:借助“外部工具”与“系统命令”
核心思想: 不要重复发明轮子。Linux系统自带的大量小工具(awk、sed、jq、parallel等)可以极大简化脚本逻辑。
Q:如何从JSON文件中提取特定字段并统计数量?
A: 用jq一行搞定,比写几十行Python循环更简洁。
高效写法(Shell示例):
# 统计所有用户中状态为active的数量 cat users.json | jq -r '.[] | select(.status=="active") | .name' | wc -l
另一个典型场景:并行处理
# 使用GNU parallel并行压缩文件(比串行快N倍)
find /data -name "*.csv" | parallel gzip {}
Q:什么时候适合用外部工具?
A: 当你的任务包含文本解析、数据过滤、正则替换或批量并行操作时,优先考虑jq、yq、parallel等专用工具。
技巧五:模块化与参数化设计
核心思想: 一个脚本永远不要硬编码任何值,路径、用户名、端口号都应该通过参数传入。
Q:如何让脚本既能用于开发环境,又能用于生产环境?
A: 使用配置文件和命令行参数。
高效写法(Python示例):
import argparse
import configparser
import os
def load_config():
parser = argparse.ArgumentParser(description='批量数据处理工具')
parser.add_argument('--env', choices=['dev', 'prod'], default='dev',
help='运行环境')
parser.add_argument('--config', default='config.ini',
help='配置文件路径')
args = parser.parse_args()
config = configparser.ConfigParser()
config.read(args.config)
section = args.env.upper()
# 从配置中读取参数
db_host = config.get(section, 'db_host', fallback='localhost')
db_port = config.getint(section, 'db_port', fallback=3306)
return db_host, db_port
if __name__ == '__main__':
host, port = load_config()
print(f"连接到数据库: {host}:{port}")
# 实际业务逻辑...
效果: 只需修改config.ini文件中的[DEV]或[PROD]段,脚本即可切换环境。
常见问题解答(FAQ)
Q1:脚本中如何优化大量的if-else分支?
A: 使用case语句替代多个if,或使用字典/哈希表映射到函数。
Q2:我的脚本要处理GB级的大文件,怎么避免内存溢出?
A: 永远不要一次性读取整个文件,使用逐行读取(如while read line或Python的for line in file)。
Q3:如何确保脚本在服务器重启后自动运行?
A: 通过cron job或其他系统服务,注意设置环境变量和PATH,因为cron的环境与交互式shell不同。
Q4:写脚本时有哪些安全注意事项?
A:
- 避免直接在脚本中写入明文密码(用环境变量或密钥管理服务)。
- 对用户输入进行转义和验证(防止注入攻击)。
- 使用
set -euo pipefail让脚本在错误时立即停止。
实用脚本的黄金法则
- 封装重复逻辑:让改动只需一处。
- 批量胜过逐个:一个循环解决所有类似文件。
- 错误处理是生命线:日志+重试+告警。
- 站在巨人肩上:优先选择现有的Linux工具。
- 参数化是关键:硬编码是技术债务的开始。
如果你能把以上5点融入日常脚本编写中,你的工作效率会肉眼可见地提升——你的同事也会更愿意阅读和重用你写的脚本。好的脚本就像一本优秀的说明书:清晰、准确、让人一看就懂。