《UNIX/Linux系统管理技术手册-读书笔记-第七章

脚本编程与shell

1. 脚本化的哲学

原则 说明 能做什么
编写微型脚本(scriptlet) 不为大型任务才写脚本,日常小痛点也用脚本解决 积累个人工具库(~/bin),提升日常效率
学好少数核心工具 shell、文本编辑器、脚本语言应全面掌握 核心工具熟练度直接影响整体效率
全面自动化 不限于shell,办公软件、数据库、GUI应用都可脚本化 将重复操作自动化,降低出错率
不要过早优化 脚本优先考虑程序员效率和代码清晰,而非计算效率 快速交付可用脚本,避免过度工程化
拣选正确的脚本语言 sh(轻量/可移植)、Python(通用首选)、Ruby(Web/DSL) 根据任务选语言,避免用错工具

语言选择建议

  • 新项目首选Python 3(通用性最强、生态最广)
  • 简单命令序列用Bash/sh(系统自带,可移植)
  • 避免在新工作中使用Perl(已老态),PHP/JavaScript不适合系统管理

2. shell基础(Bash/sh)

2.1 命令行编辑

快捷键 功能
Ctrl+A / Ctrl+E 行首/行尾
Ctrl+P / Ctrl+N 上一条/下一条命令
Ctrl+R 反向搜索历史
set -o vi 启用vi模式(按Esc进入命令模式)

2.2 管道与重定向

符号 功能 示例
< 标准输入重定向 sort < /etc/passwd
> 标准输出重定向(覆盖) grep bash /etc/passwd > bash_users
>> 标准输出重定向(追加) echo "new" >> file
2> 标准错误重定向 find / -name core 2> /dev/null
>& 合并STDOUT和STDERR cmd >& /tmp/out
| 管道(连接命令) ps -ef | grep httpd
&& 前命令成功则执行后命令 mkdir foo && cd foo
|| 前命令失败则执行后命令 cd foo || echo "No such dir"
; 顺序执行(无关成败) mkdir foo; cd foo; touch a

文件描述符:STDIN=0,STDOUT=1,STDERR=2。

2.3 变量与引用

用法 示例 说明
赋值(无空格) etcdir=/etc 等号两边不能有空格
引用($前缀) echo $etcdir
花括号分隔 echo "Saved ${rev}th" 防止变量名与后续字符混淆
双引号 echo "I speak $mylang" 允许变量扩展
单引号 echo 'I speak $mylang' 禁止变量扩展
反引号(命令替换) echo "Lines: \wc -l < /etc/passwd`"` 执行命令并替换输出(推荐用$(...)替代)

2.4 常用过滤命令

命令 功能 常用选项
cut 提取列/字段 -d: -f7(分隔符:第7字段)
sort 排序 -n数值,-h人类可读,-k指定键,-u去重
uniq 去重(需先排序) -c计数,-d仅重复行
wc 统计行/词/字符 -l仅行数,-w仅词数
tee 分流输出 find ... | tee /dev/tty | wc -l
head / tail 头/尾几行 -n 20tail -f实时监视日志
grep 文本搜索 -i忽略大小写,-v反向匹配,-l仅文件名

3. sh脚本编程

3.1 脚本基本结构

1
2
3
4
#!/bin/sh          # shebang,指定解释器
# 注释
echo "Hello"
exit 0             # 退出码:0成功,非0失败

执行方式

  • chmod +x script && ./script
  • sh script
  • source script(在当前shell中执行,用于设置环境变量)

3.2 输入与输出

1
2
3
4
5
6
7
echo -n "Enter name: "      # -n 不换行
read user_name              # 读取输入

printf "\t aa\tbb\tcc\n"    # 格式输出(更可控)

# 错误信息输出到STDERR
echo "Error" 1>&2

3.3 命令行参数

变量 含义
$0 脚本名
$1, $2, ... 第1、2…个参数
$# 参数个数(不含$0)
$@ / $* 所有参数(作为列表/字符串)
$? 上一条命令的退出码

函数定义

1
2
3
4
5
6
7
8
9
show_usage() {
    echo "Usage: $0 src dest" 1>&2
    exit 1
}

# 调用
if [ $# -ne 2 ]; then
    show_usage
fi

3.4 控制流程

if语句

1
2
3
4
5
6
7
if [ condition ]; then
    commands
elif [ condition ]; then
    commands
else
    commands
fi

数值比较(使用-eq-ne-lt-le-gt-ge):

1
2
3
if [ $count -eq 5 ]; then
    echo "Count is 5"
fi

字符串比较(使用=!=-z为空、-n非空):

1
2
if [ "$name" = "root" ]; then ...
if [ -z "$var" ]; then echo "var is empty"; fi

文件测试

1
2
3
4
5
6
7
[ -f file ]   # 普通文件
[ -d dir ]    # 目录
[ -r file ]   # 可读
[ -w file ]   # 可写
[ -x file ]   # 可执行
[ -s file ]   # 非空
[ file1 -nt file2 ]  # file1比file2新

case语句

1
2
3
4
5
case $level in
    0) echo "Error" ;;
    1) echo "Warning" ;;
    *) echo "Unknown" ;;
esac

for循环

1
2
3
for file in *.log; do
    echo "Processing $file"
done

while循环(读文件常用):

1
2
3
while read line; do
    echo $line
done < /etc/passwd

算术运算(Bash):

1
2
3
a=1; b=2
c=$((a + b))      # 数值计算,结果为3
d="$a + $b"       # 字符串拼接,结果为"1 + 2"

3.5 文件名中的空格处理

1
2
3
4
5
6
7
# 使用find -print0 + xargs -0
find /home -type f -size +1M -print0 | xargs -0 ls -l # 以空字节`\0`作为分隔符

# 引号包裹
less "My spacey file"
# 或反斜线转义
less My\ spacey\ file

4. 正则表达式(核心元字符)

符号 含义 示例
. 任意单个字符 a.c匹配"abc"
[chars] 字符集合 [aeiou]匹配任意元音
[^chars] 排除字符集合 [^0-9]匹配非数字
^ 行首 ^root匹配以root开头的行
$ 行尾 bash$匹配以bash结尾的行
\w 单词字符 [A-Za-z0-9_]
\s 空白字符 空格、Tab等
\d 数字 [0-9]
| (walrus|egg man)
(expr) 分组+捕获 (\d{5})捕获5位数字
? 0次或1次 colou?r匹配"color"/“colour”
* 0次或多次 a*b匹配"b",“ab”,“aab”…
+ 1次或多次 a+b匹配"ab",“aab”…
{n} 精确n次 \d{5}匹配5位数字
{min,max} min~max次 \d{3,5}匹配3~5位数字

贪婪 vs 惰性

  • .* 贪婪:尽可能多匹配(可能造成灾难性回溯)
  • .*? 惰性:尽可能少匹配
  • 最佳实践:能用具体字符类(如[^>]*)就不用.*

5. Python编程(系统管理员首选)

5.1 Python 2 vs Python 3

  • 新项目直接用Python 3/usr/bin/python3
  • 现有代码沿用其原版本
  • 大多数系统默认python指向Python 2,但趋势是Python 3

5.2 快速入门

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/usr/bin/python3
print("Hello, world!")

# 变量、列表、字典、元组
name = "Gwen"
rating = 10
chars = ['SpongeBob', 'Patrick', 'Squidward']
elements = ('lithium', 'carbon', 'boron')
ordinal = {1: 'first', 2: 'second', 3: 'third'}

# 字符串格式化
print("name:\t%s\nrating:\t%d" % (name, rating))

# 文件读写
f = open('/etc/passwd', 'r')
print(f.readline(), end="")
f.close()

5.3 缩进规则(核心特性)

  • 缩进级别决定代码块,不用花括号
  • 绝不要混用制表符和空格(建议4空格缩进)
  • 行尾冒号表示后续缩进块开始

5.4 常用语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# if-elif-else
if a == "1":
    print("one")
elif a == "2":
    print("two")
else:
    print("other")

# for循环
for i in range(1, 10):
    print(i, end=" ")

# 遍历字典
for dwarf, color in suits.items():
    print(f"{dwarf}: {color}")

# 函数定义
def show_usage(message, code=1):
    print(message)
    sys.exit(code)

5.5 正则表达式(re模块)

1
2
3
4
5
import re
pattern = re.compile(r"(\d{5})")
result = pattern.search("Zip: 12345")
if result:
    print(result.group(1))   # 12345

6. Ruby编程

6.1 快速对比

特性 Python Ruby
代码块界定 缩进 do...end{...}
字符串插值 f"{var}"(3.6+)或 %s "#{var}"
一切皆对象 ✅(更纯粹)
方法括号 必须 可选
DSL能力 一般 强(如Chef/Puppet)
Web框架 Django/Flask Rails

6.2 快速入门

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env ruby
print "Hello, world!\n"

name = 'Gwen'
chars = ['SpongeBob', 'Patrick', 'Squidward']
elements = {3=>'lithium', 7=>'carbon', 5=>'boron'}

# 变量插值
puts "Name: #{name}"

# 代码块(迭代)
elements.each do |key, value|
    puts "Atomic number #{key} is #{value}"
end

6.3 命令行单行模式

1
2
3
4
5
6
7
8
# 替换文本(类似sed)
ruby -pe 'gsub!(/old/, "new")' file

# 处理passwd
ruby -F: -ane 'print $F[0], ":", $F[-1]' /etc/passwd

# 就地编辑(带备份)
ruby -i.bak -pe 'gsub!(/foo/, "bar")' *.txt

7. Python/Ruby环境管理

7.1 包管理

语言 包管理器 安装 搜索 导出依赖
Python pip pip install package pip search keyword pip freeze > req.txt
Ruby gem gem install package gem search regex gem list

7.2 Python虚拟环境(virtualenv)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 创建虚拟环境
virtualenv myproject

# 激活
source myproject/bin/activate

# 安装包(在虚拟环境内)
pip install requests

# 退出
deactivate

7.3 Ruby环境管理(Bundler + RVM)

Bundler(项目级依赖):

1
2
3
4
5
6
7
8
9
# 创建Gemfile,定义依赖
source 'https://rubygems.org'
gem 'rails', '~> 5.0'

# 安装依赖
bundle install

# 在项目环境中执行命令
bundle exec rails server

RVM(多Ruby版本管理):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 安装RVM(需检查官网最新方法)
curl -sSL https://get.rvm.io | sudo bash -s stable

# 安装特定Ruby版本
rvm install ruby-2.3.0

# 创建gemset环境
rvm use ruby-2.3.0@myapp --create --default

# 在指定环境运行命令
rvm in /path/to/project do rake test

8. Git版本控制

8.1 核心命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 配置
git config --global user.name "Your Name"
git config --global user.email "[email protected]"

# 初始化仓库
git init

# 添加文件到暂存区
git add file
git add .          # 添加所有(排除.gitignore)

# 提交
git commit -m "message"

# 查看状态
git status

# 查看差异
git diff file

# 查看历史
git log --oneline

# 忽略文件(.gitignore)
echo "tmpfile" >> .gitignore

8.2 关键概念

概念 说明
.git 仓库元数据目录(不要删
索引(stage) 待提交文件的暂存区
提交(commit) 仓库中的一个版本快照
master 默认主分支
HEAD 当前分支的最新提交

8.3 Git社会化编程(GitHub/GitLab)

概念 说明
fork 仓库的个人副本
pull request 请求将分支变更合并到主仓库
master分支 通常为稳定主线
GitFlow 广泛使用的分支管理模型

安全警告

  • Git不跟踪文件所有权和权限(仅可执行位)
  • 绝不要把密码/密钥提交到Git仓库
  • 适合跟踪:脚本、配置文件模板、文档、代码

9. 本章核心命令速查表

命令/概念 用途
chmod +x script 使脚本可执行
./script / sh script 执行脚本
source script 在当前shell中执行(环境变量)
cut -d: -f7 /etc/passwd 提取passwd第7字段(shell)
sort -nk3 -t: /etc/group 按GID排序
uniq -c 统计重复行
grep -i pattern file 搜索(忽略大小写)
tail -f /var/log/syslog 实时监视日志
find . -print0 | xargs -0 处理含空格的文件名
python3 script.py 执行Python3脚本
ruby script.rb 执行Ruby脚本
pip install virtualenv 安装Python虚拟环境
virtualenv venv && source venv/bin/activate 创建并激活Python虚拟环境
gem install bundler 安装Ruby依赖管理器
bundle install 安装项目依赖
git init / add / commit Git基础命令链
git status / diff / log 查看仓库状态

DBA视角补充

  • 脚本能力是DBA进阶运维的关键:备份脚本、巡检脚本、数据迁移脚本都离不开shell/Python。
  • 环境管理尤为重要:同一台服务器可能有多个数据库版本(如MySQL 5.7和8.0)及其依赖的Python库,用virtualenv隔离各工具环境,避免冲突。
  • Git管理SQL脚本:所有DDL、存储过程、迁移脚本都应纳入Git版本控制,配合CI/CD实现自动化数据库变更。
  • 正则表达式在日志分析中高频使用:如从慢查询日志提取SQL,从错误日志提取特定错误码。
  • 避免在生产库上直接测试脚本:先在测试环境验证,再用cron/systemd timer调度,逐步推进到生产。