运维生存指南:Linux文本处理四剑客实战心法

47 阅读4分钟

Linux四剑客心法

前言:为什么要学这些“老古董”?

这不是一篇让你死记硬背参数的手册,这是一份帮你摆脱 GUI 依赖,在黑漆漆的终端里也能运指如飞的生存指南

在 Linux 也就是服务器的世界里,我们没有 Excel,没有 VS Code。当你面对一个 2GB 的日志文件,老板问你“昨天那个 Bug 到底触发了几次?”时,熟练掌握四剑客,你能 3 秒出结果;否则,你只能把文件下载到本地把自己电脑卡死

我们将通过这四个工具,解决开发运维最真实的痛点:

命令核心特点典型应用场景
find唯一直接操作文件系统 inode在系统中查找文件,结合-exec批量处理
grep速度快,擅长正则搜索从海量日志中查找报错信息、关键字
sed流式非交互编辑修改配置文件、文本内容的增删改查
awk编程语言,擅长列处理格式化报表、统计计算、逻辑判断

基本功:避开那些 Shell 的坑

引号

使用四剑客命令时(特别是写在脚本中),除非涉及 Shell 变量扩展(需要$VAR),否则请**一律使用单引号''**包裹处理逻辑

说人话就是:别给自己找麻烦,**永远优先用单引号 **。只有明确知道需要 Shell 帮你把变量换成值的时候,再用双引号

引号含义
单引号所见即所得,单引号里面内容原封不动输出,不进行任何解析
双引号弱引用,允许进行变量替换转义处理 ,但不会执行命令替换,不解析{}和通配符
反引号优先执行,先执行反引号里面的命令,会进行命令替换

重定向

Unix/Linux中,进程启动时自带三个通道:stdin(0)stdout(1)stderr(2)

符号描述典型用法
>1>标准输出覆盖echo "ok" > status.txt
>>1>>标准输出追加echo "log entry" >> app.log
2>标准错误覆盖ls non_exist_file 2> error.log
2>>标准错误追加ls non_exist_file 2>> error.log
2>&1错误重定向到标准输出command > all.log 2>&1
&>混合输出(Bash特有)command &> all.log(等同于上一行)
<标准输入重定向xargs < file_list.txt
<<Here Document用于脚本中内联多行输入

& 的作用就是“引用文件描述符”,而不是当作普通文件名

常见使用

# 丢弃 stdout(只看报错)
ping example.com > /dev/null

# 丢弃 stderr(屏蔽报错)
rm invalid_file 2> /dev/null

# 丢弃所有输出(世界清静了)
command > /dev/null 2>&1

# 既输出到屏幕,又写入文件
command 2>&1 | tee -a file.log

通配符

用于给 Linux 中大部分命令使用,用于批量找文件名(找文件)

符号含义示例
*匹配任意长度字符*.log(所有 log 文件)
?匹配任意一个字符file?.txt(匹配 file1.txt)
{}输出序列,通常与echo、touch、mkdir配合使用mkdir /data/{log,conf}(创建两个目录)
[]同正则用法,匹配括号内的任一字符file[123].txt
[!][^]取反
# 输出等宽的数字序列 01 02 03 10   001 002 ... 100
echo {01..10}
echo {01..100}

# 输出1 3 5 7 9
echo {1..10..2}

# A AC AB
echo A{,C,B}

# 找出/bin目录下面命令,命令仅有2个字符组成
ls -l /bin/??

Xargs 的前世今生

你是否疑惑过:**为什么要用 xargs?**想象一下管道|是一根水管,流过来的是“水”(文本流)。但是 rm、mv 这些命令,它们不喝水,它们要的是“瓶装水”(一个个参数)

Xargs 就是那个把水装进瓶子里的适配器

  • ❌ 错误:find . -name "*.log" | rm(rm 不读水管里的字)
  • ✅ 正确:find . -name "*.log" | xargs rm(xargs 把字喂到 rm 的参数位)
[输入源] | xargs [选项] [目标命令] [目标命令的选项]
常用选项含义
-n<数字>指定每次传递的参数个数
-I 占位符定义占位符(通常用{}),用于需要将参数插入命令中间的场景
-0配合 find -print0 使用,处理文件名中的空格或特殊字符
-p交互式确认每次操作(输入y才会执行)
-t打印要执行的命令(命令仍执行
-d<定界符>

实战案例

# 查找所有.txt文件并删除
find . -name '*.txt' | xargs rm

# 将找到的.log文件移动到/backup目录
find . -name '*.log' | xargs -I {} mv {} /backup

# 批量重命名文件
find . -name '*.jpg' | xargs -I {} mv {} {}.bak

# 在所有.py文件中搜索"import numpy"
find . -name '*.py' | xargs grep 'import numpy'

# 批量修改权限
find /data -type f | xargs chmod 644

# 处理文件名中的空格或特殊字符
# 文件名中可以包含空格、换行、制表符甚至 \n,而传统用换行分隔的方式会被这些特殊字符“欺骗”,导致文件名被错误拆分
# -print0:不会在每个文件名后加换行符(\n),而是用 空字符(null character, \0) 分隔每个文件名
# xargs -0:告诉 xargs 输入项是以空字符 \0 分隔的
find . -name '*.mp3' -print0 | xargs -0 rm

# 批量打包
find /etc/ -type f -name '*.conf' | xargs tar zcf /backup/etc-conf-v2.tar.gz

# 输出 name name name name
echo 'nameXnameXnameXname' | xargs -dX

Find

find是四剑客中唯一直接扫描文件系统 inode的重型武器。它不看内容,只看“户口本”(文件名、时间、权限、大小)

find [路径] [条件] [动作]
维度选项作用
名称-name指定文件名,默认精确匹配(加上*为模糊匹配)
-iname文件名不区分大小写
类型-type [类型]指定文件类型(f、d、l、s-套接字、b-块设备、c-字符设备)
大小-size [+/-]N[单位]指定文件大小范围,+10M -100k -10G
深度-maxdepth指定查找文件深度
时间-mtime、-atime、-ctime指定文件时间
权限-perm [权限模式]644 (精确匹配)、/u=s (包含 SUID)

尽量不要直接在/目录执行无条件的find,这会把磁盘 I/O 打满。一定要用-maxdepth或指定具体子目录!

实战案例

  • 按文件名、文件类型、文件大小、用户/组、搜索深度
# 文件名
find /path -name 'file[0-9].txt'       # 查找file1.txt, file2.txt等

# 文件类型
find /path -type f -name '*.log'       # 查找/var/log下所有.log文件

# 文件大小
find /path -size +5M -size -15M        # 查找大于5M小于15M的文件

# 用户/组
find /path -user root                  # 查找属于root用户的文件
find /path -group www-data             # 查找属于www-data组的文件

# 搜索深度
find /path -maxdepth 2 -name '*.conf'  # 仅搜索两层目录
find /path -mindepth 3 -name '*.log'   # 从第三层目录开始搜索
  • 按权限
# 查找权限为644的文件
find /path -perm 644

# 查找SUID权限的文件
find /path -perm /u=s

# 查找组可写的文件
find /path -perm /g=w
  • 按时间
# 找出 7 天前修改过的日志
find /var/log -name '*.log' -mtime +7

# 找出 1 小时内被访问过的配置文件
find /etc -name '*.conf' -atime -60

# 找出最近 24 小时内权限被修改的文件(安全审计!)
find / -ctime -1440 -type f
  • 组合条件
# 同时满足名称和类型条件
find /path -name '*.log' -type f

# 查找jpg或png文件
find /path \( -name '*.jpg' -o -name '*.png' \)

# 排除.tmp文件
find /path ! -name '*.tmp'
  • 与xargs连用(见上文)

  • 与-exec连用:star::star::star::star::star:

# 对每个匹配的文件,单独调用一次命令
find [路径] [条件] -exec [命令] {} \;

# 合并所有匹配的文件路径,一次性执行
find [路径] [条件] -exec [命令] {} +
# 批量压缩文件,相当于执行 gzip file1.log file2.log file3.log
find /data -name "*.log" -exec gzip {} +
# 批量打包文件
find /data -name "*.log" -type f -exec tar -cvf test.tar.gz {} +

# 相当于对每个.tmp文件执行 rm file1.tmp; rm file2.tmp
find /tmp -name "*.tmp" -exec rm {} \;

# 删除7天前的日志文件
find /var/log -name "*.log" -mtime +7 -exec rm {} \;

# 将找到的.jpg文件备份到/backup
find . -name "*.jpg" -exec cp {} /backup \;

# 对每个找到的.csv文件运行分析脚本
find /reports -name "sales_*.csv" -exec ./analyze.sh {} \;

# 先压缩文件,再删除原文件
find . -name "*.txt" -exec gzip {} \; -exec rm {} \;

# 默认已正确处理空格/特殊字符(无需像xargs那样用-print0)
find . -name "*.doc" -exec mv {} ~/Documents \;

# 删除3.10之前的以.dbf结尾的文件
find ./ -name "*.dbf" ! -newermt "2025-03-10" -exec rm {} \;
特性xargsfind -exec
执行效率高(批量传递参数)低(逐行执行)
参数长度限制自动分割参数受系统参数长度限制
特殊字符处理需配合 -print0-0原生支持
灵活性可结合其他命令(如 grep仅限 find 找到的文件

Grep

grep(Global Regular Expression Print)是一个强大的文本搜索命令,支持使用正则表达式在文件中查找匹配的行,并将其输出

grep是查看日志、代码搜索最高频的工具,是它的代名词

grep [option] pattern file
常用参数含义
-A n / -B n / -C n显示匹配行后n行 / 前n行 / 前后各n
-i忽略大小写
-v反选,显示不匹配的行
-n显示匹配行的行号
-l只列出匹配内容的文件名,不看具体内容
-c仅输出匹配的行数,不显示内容
-w精确匹配整个单词(避免匹配到包含该子串的词)
-o仅输出匹配的字符串本身(每行一个匹配项)
-r/-R递归搜索目录(-R会跟随符号链接)
-E支持拓展正则,等同于egrep
-P开启 Perl 兼容正则(支持更高级正则,如零宽断言)
-q静默模式,不输出任何信息
-e <范本样式>指定字符串作为查找文件内容的范本样式
--color高亮显示匹配内容(现代系统通常默认开启)

实战案例

# 在 去掉注释行和空行
grep -Ev "^$|^#" /etc/nginx/nginx.conf

# 或者使用拓展正则
egrep -v '^$|^#' /etc/ssh/sshd_config

# 查找 500 错误及其前 2 行和后 3 行上下文
grep "500 Internal Server Error" -B 2 -A 3 access.log

# 统计 IP 地址出现的次数(提取 + 排序 + 统计)
grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log | sort | uniq -c | sort -nr

# 检测程序是否在运行 (排除 grep 进程自身)
ps aux | grep nginx | grep -v grep

# -r: 递归查找目录 (程序员找代码神器)
grep -r "TODO" ./src

Sed

sed(Stream Editor)是流水线式的非交互编辑器,其工作模式如下:

  • Read:读一行到内存(模式空间)
  • Execute:在内存里动刀子(改)
  • Print:改完扔回流水线(输出)
  • Repeat:清空模式空间,读下一行
sed [选项] '[地址范围]操作' 文件

注意:Sed 默认不修改原文件,只有加上-i才会修改,最好是先预览确认没问题后再加上-i修改原文件

匹配范围(模式)说明
空地址全文处理
单地址指定文件某一行
/pattern/被模式匹配到的每一行
范围区间10,20十到二十行
10,+5十向下五行
1,$一到末尾行
/pattern1/,/pattern2/限定范围区间
步长1~2:表示奇数行,2~2:表示偶数行
常用选项说明
-n只输出符合要求行,与p组合使用(默认全输出)
-r开启扩展正则
-i直接修改文件内容(危险操作)只有使用该选项才会修改原文件
-i.bak在将处理的结果写入文件之前备份一份
-e<script>以选项中指定的script来处理输入的文本文件
-f<script>以选项中指定的script文件来处理输入的文本文件
内置命令字符(动作)说明
a(append)在指定行下面添加一/多行文本
i(insert)在指定行上面添加一/多行文本
c(change)替换指定整行
w(write)将找到的行保存到新的文件中
d(delete)删除匹配行
p(print)打印匹配行的内容(与-n连用)
s/正则/替换内容/g匹配正则内容,然后替换,g为全局匹配。替换时可以加以下命令,实现大小写转换
\l:把下个字符转换成小写
\L:把replacement字母转换成小写,直到\U或\E出现
\u:把下个字符转换成大写
\U:把replacement字母转换成大写,直到\L或\E出现
\E:停止以\L或\U开始的大小写转换

实战案例

sed '模式 动作' 文件
cat > test.txt <<EOF
1. Linux is kernel.
2. Python is language.
3. Database is storage.
4. Nginx is web server.
EOF
# 打印第2行
sed -n '2p' test.txt

# 打印2到3行
sed -n '2,3p' test.txt

# 打印最后一行
sed -n '$p' test.txt

# 打印包含 "Linux" 的行
sed -n '/Linux/p' test.txt

# 范围匹配:从包含 Python 的行开始,到包含 Nginx 的行结束
sed -n '/Python/,/Nginx/p' test.txt
# 删除空行
sed '/^$/d' test.txt

# 删除第3行
sed '3d' test.txt

# 删除包含 "Database" 的行
sed '/Database/d' test.txt

替换语法,支持很多分隔符:@、#、/

s/正则/替换内容/g
# 将 is 替换为 WAS
sed 's/is/WAS/g' test.txt

# 分隔符冲突时,可用 # 或 @ 替代 /
sed 's#/bin/bash#/sbin/nologin#g' /etc/passwd

# 将 "hello world" 换成 "world hello"
echo "hello world" | sed -r 's/(\w+) (\w+)/\2 \1/'
# 在第2行后面添加一行
sed '2a New Line Here' test.txt

# 在最后一行添加
sed '$a The End.' test.txt

# 2~4行数据写到new.txt
sed '2,4w new.txt' test.txt

awk

awk是一种处理文本文件的编程语言,文件的每行数据都被称为记录,每条记录被分成若干字段,每次从文件中读取一条记录

awk由一系列条件动作组成,可以包含多个动作,用;分隔。若没指定条件则匹配所有数据,没指定动作则默认为print,awk默认支持拓展正则表达式

awk [options] 'BEGIN{...} Pattern{...} END{...}' 文件
  • BEGIN {}:在读第一行之前执行(打印表头、定义变量)
  • Pattern {Action}:每一行都执行一次。主处理逻辑
  • END {}:全部行读完后执行(输出统计结果)
graph LR
    A["BEGIN{ ... }"] -->|1.预处理: 设分隔符/打印表头| B("Loop: 读一行,切一刀")
    B -->|2.主逻辑: 计算/判断/过滤| B
    B -->|3.读完所有行| C["END{ ... }"]
    C -->|4.最终汇总: 输出统计总数| End

平时我们写的 awk '{print $1}',其实就是中间的 Loop 部分。BEGIN 和 END 是可选的

选项说明
-F [分隔符]指定分隔符,可以为字符串或正则表达式(默认为连续的空格或制表符)
-v var=value赋值一个用户定义变量,将外部变量传递给awk
-f scriptfile从脚本文件中读取awk
内置变量描述
FILENAME当前输入文档的名称
FNR当前输入文档的当前行号,尤其当有多个输入文档时有用
NR(Number of Record)当前行号
$0当前行全部数据
$n当前行第n个字段内容(不要用""包裹)
NF(Number of Fields)当前记录(行)的字段个数,即列数
FS(Field Separator)字段分隔符,默认为空格或Tab制表符
OFS输出字段分隔符,默认为空格
ORS输出记录分隔符,默认为\n
RS输入记录分隔符,默认为\n
  • 取数据
# 按列输出
df -h | awk '{print $2}'		    # 逐行打印第2列
df -h | awk '{print NR}'		    # 输出行号
df -h | awk '{print NF}'		    # 输出每行数据的列数
df -h | awk '{print $NF}' 	       # 打印每行数据的最后一列
df -h | awk '{print $(NF-1)}'      # 打印每行倒数第二列
df -h | awk '{print $0}'	        # 打印每行全部内容
df -h | awk '{print $1 "\t" $2}' | column -t  # 使用制表符,需要添加双引号

# 按行输出
df -h | awk 'NR>=2 && NR<=5'
df -h | awk 'NR==2'
  • 做统计

配合 sort 和 uniq,这是面试必考题

# 统计 Nginx 日志中访问最多的 5 个 IP
# $1 是 IP 列
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -5
  • 逻辑判断
# 过滤第四列以0或1开头行,输出第1,3列
awk -F ':' '$4 ~ /^[01]/ {print $1,$3}' /etc/passwd

# 取第三列大于100的行
awk -F':' '$3>=100{print $1,$3,$NF}' /etc/passwd | column -t
  • 做计算
# 统计某个目录下所有文件的总大小
# ls -l 输出:-rw-r--r-- 1 root root  1024  Jan 1 filename
ls -l | awk '
{ sum += $5 } 
END { print "Total: " sum/1024 " KB" }'
  • 模糊过滤
# 过滤出包含root或nobody的行
awk '/root|nobody/' /etc/passwd

# 从包含root的行到包含nobody的行
awk '/root/,/nobody/' /etc/passwd
  • 自定义变量
awk -v x="bob" -v y=10 '{print x,y}' /tmp/hosts

# 直接调用系统变量
awk -v shell=$SHELL '{print shell}' /tmp/hosts
awk '{print "'$SHELL'"}' /tmp/hosts

结语

不要试图一次性背下所有的参数,不需要也没必要

  • 找文件,用 find
  • 搜内容,用 grep
  • 改文件,用 sed
  • 做统计,用 awk