Shell 代码片段集合
默认 Bash 3.2+,外部工具按 macOS/BSD 与 GNU/Linux 差异标注。随机串、CSV 和批量文本操作使用 Python 3.8+;先
command -v python3检查,不假设系统预装。函数正常返回 0,失败返回非零(端口查询 1 表示未发现);诊断写 stderr。复制函数定义后单独调用,示例路径需按实际替换。
副作用约定:重命名、文本替换默认只预览,最后传 --apply 才写入;备份创建新文件;清理函数会删除匹配项,先使用对应的 find 查询;发送信号和监控均需人工确认目标。所有关键写入显式检查状态,不依赖 set -e。
# 📋 目录
# pyenv
#!/usr/bin/env bash
set -e
[ -n "${PYENV_DEBUG:-}" ] && set -x
program="${0##*/}"
export PYENV_ROOT="$HOME/.pyenv"
exec "/usr/local/opt/pyenv/bin/pyenv" exec "$program" "$@"
2
3
4
5
6
7
8
这是一个 Bash 脚本, 用于在 Pyenv 环境中执行 Python 程序。以下是对脚本的解释:
#!/usr/bin/env bash: 这是一个 Shebang 行, 它告诉操作系统使用 Bash 来执行这个脚本set -e: 这个命令将在脚本中启用"strict mode", 在适用上下文遇非零状态时退出;if、&&/|| 等有例外,见 错误处理指南[ -n "${PYENV_DEBUG:-}" ] && set -x: 如果存在名为 PYENV_DEBUG 的环境变量并且其值非空, 则启用调试模式, 该模式会输出每个命令的执行program="${0##\*/}": 这一行提取了脚本文件的名称, 将其存储在变量 program 中export PYENV_ROOT="$HOME/.pyenv": 这一行设置了 PYENV_ROOT 环境变量, 指定 Pyenv 的根目录exec "/usr/local/opt/pyenv/bin/pyenv" exec "$program" "$@": 这是实际执行 Python 程序的命令。它使用 pyenv 工具来执行 Python 程序, " @"表示传递给脚本的所有参数将传递给 Python 程序表 示 要 执 行 的 程 序 的 名 称
总之, 这个脚本的目的是在 Pyenv 环境中执行 Python 程序, 它设置了一些环境变量, 并使用 pyenv 来确保 Python 程序在正确的环境中运行。如果你需要在 Pyenv 中运行 Python 程序, 并且希望通过脚本来管理它, 这个脚本可以派上用场
# v2ray 安装脚本学习
原笔记标识:
install-install-v2ray_study_shell_cache。当前库中未附脚本正文,保留此检索线索;尚不足以作为可执行安装指南。补回原脚本后再按参数解析、下载校验、提权与失败清理逐段解读。
# gen_random_string
gen_random_string() {
python3 - "${1:-16}" <<'PYCODE'
import secrets, string, sys
try:
length = int(sys.argv[1])
if not sys.argv[1].isascii() or not sys.argv[1].isdigit() or not 1 <= length <= 1048576:
raise ValueError('长度应为 1..1048576 的十进制整数')
alphabet = string.ascii_letters + string.digits
print(''.join(secrets.choice(alphabet) for _ in range(length)))
except ValueError as exc:
print(exc, file=sys.stderr)
sys.exit(2)
PYCODE
}
# 直接使用短路操作
gen_random_string "$@" || { echo "Failed to generate random string."; exit 1; }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
旧实现 LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | head -c N 的知识点仍保留:head 提前退出可能使 tr 收到 SIGPIPE,在 pipefail 下整个管道返回 141;使用 func || handler 还可能抑制函数内部 errexit,不能据此证明普通调用安全。新版用有限次随机选择,避免截断无限输入。
LC_ALL=C:设置字符集为 C locale(标准 ASCII 字符集),确保字符处理的行为在不同系统下保持一致tr -dc 'a-zA-Z0-9':从/dev/urandom读取随机字节,并删除所有不在a-zA-Z0-9范围内的字符
# 文件操作
# 文件备份
# 安全备份文件
backup_file() {
local file="$1" backup_dir="${2:-./backup}" target
[ -f "$file" ] || { printf '不是普通文件: %s\n' "$file" >&2; return 1; }
mkdir -p "$backup_dir" || return 1
target=$(mktemp "$backup_dir/${file##*/}.bak.XXXXXX") || return 1
if cp "$file" "$target"; then
printf '备份完成: %s\n' "$target"
else
rm -f "$target"
return 1
fi
}
# 使用示例
backup_file "/etc/nginx/nginx.conf" "/tmp/backup"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 批量文件重命名
# 批量重命名文件
batch_rename() {
python3 - "$@" <<'PYCODE'
import os, pathlib, sys
try:
if len(sys.argv) not in (3, 4, 5):
raise ValueError('用法: batch_rename <字面原文> <替换文本> [目录] [--apply]')
search, replacement = sys.argv[1:3]
directory = pathlib.Path(sys.argv[3] if len(sys.argv) >= 4 else '.')
apply = len(sys.argv) == 5 and sys.argv[4] == '--apply'
if len(sys.argv) == 5 and not apply:
raise ValueError('未知执行选项')
if not search or '/' in replacement or '\0' in replacement:
raise ValueError('原文不能为空,替换文本不能含路径分隔符或 NUL')
plan = []
for src in sorted(directory.iterdir()):
if src.is_symlink() or not src.is_file():
continue
name = src.name.replace(search, replacement)
if name == src.name:
continue
if name in ('', '.', '..'):
raise ValueError('非法目标名称')
dest = src.with_name(name)
plan.append((src, dest))
targets = [str(dest) for _, dest in plan]
if len(targets) != len(set(targets)):
raise ValueError('多个来源映射到同一目标')
for src, dest in plan:
if os.path.lexists(dest):
raise FileExistsError(f'目标已存在: {dest!s}')
for src, dest in plan:
print(f'{str(src)!r} -> {str(dest)!r}', flush=True)
if apply:
# 同目录普通文件:目标排他创建,遇并发冲突也不覆盖;失败立即停止。
os.link(src, dest)
os.unlink(src)
except (OSError, ValueError) as exc:
print(f'重命名失败: {exc}', file=sys.stderr)
sys.exit(1)
PYCODE
}
# 使用示例:将 .txt 改为 .bak
batch_rename ".txt" ".bak" "/path/to/files" # 预览全部映射并检查冲突
batch_rename ".txt" ".bak" "/path/to/files" --apply # 确认后执行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
这里的替换为字面量、全部出现位置,旧 ${basename//$pattern/$replacement} 则把 pattern 当 Shell 模式。只处理指定目录的普通文件(含隐藏文件、排除符号链接),不递归。为避免 mv 默认覆盖,使用同目录硬链接再删除原目录项;文件系统必须支持硬链接。批量操作不保证整体原子性:中途失败会留下已完成项;若 unlink 失败可能保留两个名称,需按计划核对后恢复。不要在其他进程同时修改来源时运行。
备份的随机后缀替代秒级时间戳,重复调用不会覆盖;mkdir、mktemp、cp 任一步失败均非零,不输出“备份完成”。只保证内容备份,不保证跨平台完整保留 ACL/扩展属性。
# 目录清理
# 清理空目录
cleanup_empty_dirs() {
local dir="${1:-.}"
find "$dir" -depth -mindepth 1 -type d -empty -delete || return 1
printf '%s\n' '空目录清理完成'
}
# 清理临时文件
cleanup_temp_files() {
local dir="${1:-.}"
find "$dir" -type f \( -name '*.tmp' -o -name '*.temp' -o -name '*~' \) -print
# 确认上面匹配列表后再执行;find -exec 保留空格、换行等文件名边界。
find "$dir" -type f \( -name '*.tmp' -o -name '*.temp' -o -name '*~' \) -exec rm -f {} +
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 系统信息获取
清理前单独执行同样的 find 条件并去掉 -delete/-exec rm ... 查看结果;调用上述函数即表示执行清理。空目录函数排除起始目录本身;临时文件函数只删普通文件,括号确保 -type f 约束全部名称条件。不要通过默认 xargs 或按换行 read 传递任意路径。
# 系统版本检测
# 检测操作系统类型
detect_os() {
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo "Linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS"
elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]]; then
echo "Windows"
else
echo "Unknown: $OSTYPE"
fi
}
# 获取系统详细信息
get_system_info() {
echo "=== 系统信息 ==="
echo "操作系统: $(detect_os)"
echo "内核版本: $(uname -r)"
echo "架构: $(uname -m)"
echo "主机名: $(hostname)"
echo "用户: $(whoami)"
echo "Shell: $SHELL"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 磁盘空间检查
# 检查磁盘使用情况
check_disk_usage() {
local threshold="${1:-80}" # 默认阈值 80%
local data
[[ "$threshold" =~ ^[0-9]+$ ]] && [ "$threshold" -le 100 ] || return 2
data=$(df -Pk) || return 1
printf '%s\n' "$data" | awk -v threshold="$threshold" '
NR>1 {
gsub(/%/, "", $5)
if ($5 > threshold) {
print "警告: " $1 " 使用率 " $5 "% 超过阈值 " threshold "%"
}
}'
}
# 查找大文件
find_large_files() {
local size="${1:-100M}" dir="${2:-.}"
# 原 find -size +100M 在 BSD/GNU 上后缀不同;统一换算为字节 c。
python3 - "$size" "$dir" <<'PYCODE'
import os, pathlib, re, sys
try:
match = re.fullmatch(r'([0-9]+)([ckMG]?)', sys.argv[1])
if not match:
raise ValueError('大小格式: 数字 + c/k/M/G;无单位按字节')
units = {'': 1, 'c': 1, 'k': 1024, 'M': 1024**2, 'G': 1024**3}
threshold = int(match[1]) * units[match[2]]
root = pathlib.Path(sys.argv[2])
if not root.is_dir():
raise ValueError('目录不存在')
results = []
def walk_error(exc):
raise exc
for parent, _, names in os.walk(root, onerror=walk_error):
for name in names:
path = pathlib.Path(parent) / name
if path.is_symlink() or not path.is_file():
continue
size = path.stat().st_size
if size > threshold:
results.append((size, str(path)))
for size, path in sorted(results, reverse=True):
print(f'{size} bytes\t{path!r}')
except (OSError, ValueError) as exc:
print(exc, file=sys.stderr)
sys.exit(1)
PYCODE
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 网络和进程管理
# 端口检查
# 检查端口是否被占用
check_port() {
local port="$1" protocol="${2:-tcp}"
[[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ] || return 2
command -v lsof >/dev/null 2>&1 || { printf '%s\n' '需要 lsof' >&2; return 2; }
case "$protocol" in
tcp|TCP) lsof -nP -iTCP:"$port" -sTCP:LISTEN ;;
udp|UDP) lsof -nP -iUDP:"$port" ;;
*) printf '%s\n' 'protocol 仅支持 tcp/udp' >&2; return 2 ;;
esac
}
# 检查端口并显示进程信息
check_port_with_process() {
# lsof 自身显示 COMMAND/PID,保留多进程结果,不把多个 PID 作为单个 ps 参数。
check_port "$1" "${2:-tcp}"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TCP 查监听套接字,UDP 没有 LISTEN 状态。lsof 返回 1 可能表示未匹配,也可能有访问限制,应结合 stderr 和权限判断,不能据此断言系统端口空闲。Linux 可另用 ss,macOS 的 netstat 不能套用 Linux -tuln 参数;protocol 必须参与筛选。
# 进程管理
# 查找并终止进程
kill_process_by_name() {
local process_name="$1" signal="${2:-TERM}" pids pid status
if pids=$(pgrep -x "$process_name"); then :; else
status=$?
[ "$status" -eq 1 ] && { printf '%s\n' '未找到匹配进程' >&2; return 1; }
return "$status"
fi
while IFS= read -r pid; do
kill -s "$signal" "$pid" || return 1
printf '已向 PID %s 发送 %s\n' "$pid" "$signal"
done <<< "$pids"
}
# 监控进程资源使用
monitor_process() {
local process_name="$1" interval="${2:-5}" pids pid status
[[ "$interval" =~ ^[0-9]+$ ]] && [ "$interval" -gt 0 ] || return 2
while true; do
if pids=$(pgrep -x "$process_name"); then
printf '=== %s ===\n' "$(date '+%Y-%m-%d %H:%M:%S')"
while IFS= read -r pid; do
ps -p "$pid" -o pid,ppid,%cpu,%mem,comm || return 1
done <<< "$pids"
else
status=$?
[ "$status" -eq 1 ] || return "$status"
printf '进程 %s 未运行\n' "$process_name"
fi
sleep "$interval" || return 1
done
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 实用工具函数
pgrep 的参数仍是正则,-x 要求整个进程名匹配;先用同一 pgrep -x 核对目标,再调用终止函数。不要用于精确身份校验,进程可能退出、PID 可能复用;monitor 用 Ctrl-C 停止。
# 日志记录
# 日志记录函数
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >&2
}
# 使用示例
log "INFO" "开始处理文件"
log "ERROR" "文件不存在"
log "WARN" "磁盘空间不足"
2
3
4
5
6
7
8
9
10
11
12
13
# 颜色输出
# 颜色输出函数
color_echo() {
local color="$1"
local message="$2"
case "$color" in
red) printf '\033[31m%s\033[0m\n' "$message" ;;
green) printf '\033[32m%s\033[0m\n' "$message" ;;
yellow) printf '\033[33m%s\033[0m\n' "$message" ;;
blue) printf '\033[34m%s\033[0m\n' "$message" ;;
purple) printf '\033[35m%s\033[0m\n' "$message" ;;
cyan) printf '\033[36m%s\033[0m\n' "$message" ;;
*) printf '%s\n' "$message" ;;
esac
}
# 使用示例
color_echo "green" "成功完成"
color_echo "red" "发生错误"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 进度条显示
# 简单进度条
show_progress() {
local current="$1"
local total="$2"
local width="${3:-50}"
[[ "$current" =~ ^[0-9]+$ && "$total" =~ ^[0-9]+$ && "$width" =~ ^[0-9]+$ ]] || return 2
# 10# 避免 08 等被当作八进制;限制规模避免算术溢出。
[ "${#current}" -le 6 ] && [ "${#total}" -le 6 ] && [ "${#width}" -le 3 ] || return 2
current=$((10#$current)); total=$((10#$total)); width=$((10#$width))
[ "$total" -gt 0 ] && [ "$width" -gt 0 ] && [ "$current" -le "$total" ] || return 2
local percentage=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
printf "\r["
printf "%*s" "$filled" | tr ' ' '='
printf "%*s" "$empty" | tr ' ' ' '
printf "] %d%%" "$percentage"
}
# 使用示例
for i in {1..100}; do
show_progress "$i" 100
sleep 0.1
done
echo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 数据处理
# CSV 文件处理
# 读取 CSV 文件
read_csv() {
python3 - "$1" "${2:-,}" <<'PYCODE'
import csv, sys
try:
with open(sys.argv[1], newline='', encoding='utf-8') as stream:
for fields in csv.reader(stream, delimiter=sys.argv[2], strict=True):
print(f'字段数量: {len(fields)}')
for index, value in enumerate(fields, 1):
print(f'字段 {index}: {value}')
print('---')
except (OSError, csv.Error, TypeError, ValueError) as exc:
print(exc, file=sys.stderr)
sys.exit(1)
PYCODE
}
# 提取 CSV 特定列
extract_csv_column() {
python3 - "$1" "$2" "${3:-,}" <<'PYCODE'
import csv, sys
try:
column = int(sys.argv[2])
if column < 1:
raise ValueError('列号从 1 开始')
with open(sys.argv[1], newline='', encoding='utf-8') as stream:
writer = csv.writer(sys.stdout, lineterminator='\n')
for fields in csv.reader(stream, delimiter=sys.argv[3], strict=True):
if column > len(fields):
raise ValueError('记录缺少指定列')
writer.writerow([fields[column - 1]])
except (OSError, csv.Error, TypeError, ValueError) as exc:
print(exc, file=sys.stderr)
sys.exit(1)
PYCODE
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
输入默认 UTF-8、分隔符为单个字符。提取列输出为单列 CSV,含逗号/换行时仍会正确加引号。旧 IFS="," read -r -a fields 与 cut -d, -fN 只适用于不含引号、转义和字段内分隔符的简单文本;"a,b",c 是两个 CSV 字段,不能按逗号直接拆成三个。完整规则见 Python csv 官方文档 (opens new window)。
# 文本搜索和替换
# 批量文本替换
batch_replace() {
python3 - "$@" <<'PYCODE'
import os, pathlib, shutil, sys, tempfile
try:
if len(sys.argv) not in (4, 5, 6):
raise ValueError('用法: batch_replace <原文> <替换> <文件glob> [目录] [--apply]')
search, replacement, pattern = sys.argv[1:4]
directory = pathlib.Path(sys.argv[4] if len(sys.argv) >= 5 else '.')
apply = len(sys.argv) == 6 and sys.argv[5] == '--apply'
if not search or (len(sys.argv) == 6 and not apply):
raise ValueError('原文不能为空,执行选项只能是 --apply')
if not directory.is_dir():
raise ValueError('目录不存在')
old, new = search.encode('utf-8'), replacement.encode('utf-8')
plan = []
def walk_error(exc):
raise exc
import fnmatch
for parent, _, names in os.walk(directory, onerror=walk_error):
for name in sorted(names):
path = pathlib.Path(parent) / name
if not fnmatch.fnmatchcase(name, pattern) or path.is_symlink() or not path.is_file():
continue
data = path.read_bytes()
if old not in data:
continue
backup = path.with_name(path.name + '.bak')
if os.path.lexists(backup):
raise FileExistsError(f'备份已存在: {backup}')
plan.append((path, backup))
for path, backup in plan:
print(f'替换: {str(path)!r}; 备份: {str(backup)!r}', flush=True)
if not apply:
continue
# 独占创建备份,不覆盖旧备份;备份成功后才替换源文件。
with path.open('rb') as source, backup.open('xb') as dest:
shutil.copyfileobj(source, dest)
fd, staged = tempfile.mkstemp(prefix='.replace-', dir=path.parent)
try:
with os.fdopen(fd, 'wb') as stream:
stream.write(backup.read_bytes().replace(old, new))
shutil.copymode(path, staged)
os.replace(staged, path)
finally:
if os.path.exists(staged):
os.unlink(staged)
except (OSError, ValueError) as exc:
print(f'替换失败: {exc}', file=sys.stderr)
sys.exit(1)
PYCODE
}
# 使用示例
batch_replace "old_text" "new_text" "*.txt" "/path/to/files" # 仅预览
batch_replace "old_text" "new_text" "*.txt" "/path/to/files" --apply
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
搜索与替换参数是 UTF-8 编码的字面量(点号、斜杠、&、反斜杠不作正则或 sed 替换解释);文件模式是只匹配 basename 的 glob,递归子目录且跳过符号链接。旧 sed "s/$search/$replace/g" 会引入正则、替换转义和分隔符冲突,正则处理应另用 sed 指南。
执行前检查所有匹配文件的备份冲突,每个改动保留 .bak。单文件通过同目录临时文件替换,整批不保证原子性;中途失败已完成项仍保留,失败备份可能不完整,必须检查后再恢复。替换会改变 inode,只保留普通权限位,不保留硬链接关系、ACL、扩展属性等元数据;不要对并发修改文件或依赖这些属性的配置直接套用。
# 最佳实践
# 脚本头部模板
#!/usr/bin/env bash
# 脚本描述
# 作者: Your Name
# 日期: $(date +%Y-%m-%d)
set -euo pipefail # 严格模式
# 按需局部设置 IFS,变量始终正确引用
# 脚本配置
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) || exit 1
readonly SCRIPT_DIR
readonly SCRIPT_NAME="$(basename "$0")"
2
3
4
5
6
7
8
9
10
11
12
# 错误处理模板
# 错误处理函数
error_exit() {
echo "错误: $1" >&2
exit 1
}
# 检查命令是否存在
check_command() {
if ! command -v "$1" >/dev/null 2>&1; then
error_exit "命令 '$1' 未找到,请先安装"
fi
}
# 检查文件是否存在
check_file() {
if [ ! -f "$1" ]; then
error_exit "文件 '$1' 不存在"
fi
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用说明
- 复制代码片段:根据需要复制相应的函数到你的脚本中
- 修改参数:根据实际情况调整函数参数和默认值
- 测试验证:在测试环境中验证代码片段的正确性
- 错误处理:添加适当的错误处理和日志记录
# 注意事项
- 所有函数都使用
local声明局部变量 - 理解
set -euo pipefail的上下文边界,关键写入显式检查状态 - 错误信息输出到
stderr(>&2) - 函数参数使用默认值语法
${param:-default} - 使用
command -v检查命令是否存在
通过这个代码片段集合,你可以快速构建高效的 Shell 脚本,提高日常开发效率! 🚀