Linux小课堂: 数据处理核心命令之grep、sort、wc、uniq 与 cut 的深度解析
文本搜索利器:grep
命令的全面掌握
grep
是 Linux 系统中最强大且使用最频繁的文本过滤工具之一,其名称来源于 global search for a regular expression and print
(全局正则表达式匹配并打印)的缩写。它的核心功能是在文件中查找包含指定字符串或模式的行,并将这些行输出到终端。
1 ) 基本语法与用法
grep [选项] 搜索文本 文件路径
或
grep [option] "pattern" file
pattern
:要搜索的字符串或正则表达式file
:目标文件路径
例如,在 /etc/profile
配置文件中查找所有包含 "pass"
字符串的行:
grep pass /etc/profile
执行后,终端会列出所有包含 pass
的行,并以高亮颜色(通常是红色)标注关键词。
注意:若搜索文本中含有空格,必须使用双引号包裹,如:
grep "hello world" profile.txt
2 )关键参数详解
-i:忽略大小写(ignore case)
默认情况下,grep
区分大小写。添加 -i
参数可实现不区分大小写的搜索。
grep -i pass /etc/profile
此命令将匹配 pass
、Pass
、PASS
、PaSs
等多种形式。
-n:显示行号(number lines)
通过 -n
参数可以显示匹配行在文件中的行号,便于定位:
grep -n pass /etc/profile
输出示例:
11: PATH=$PATH:/usr/local/bin
38: export PASSWD=xxx
-v:反向筛选(invert match)
使用 -v
参数时,grep
将只输出不包含指定文本的行,相当于“排除”操作。
grep -v pass /etc/profile
该命令将显示所有不含 pass
的行,常用于日志清洗和异常排查。
-r:递归搜索目录(recursive)
当不确定目标文本位于哪个子文件中时,可使用 -r
参数对整个目录树进行递归搜索。
grep -r "helloworld" ./folder/
这将在 folder/
目录及其所有子目录中查找包含 helloworld
的文件。
提示:rgrep
命令等价于 grep -r
,但部分系统已弃用,建议直接使用 grep -r
。
3 )高级用法:结合正则表达式(Regular Expression)
为了支持复杂的模式匹配,需启用扩展正则表达式(Extended Regular Expressions),使用 -E
参数(或使用 egrep
命令):
grep -E 'pattern' file
或使用别名命令 egrep
,效果相同
常用正则符号表
符号 | 含义说明 |
---|---|
. | 匹配任意单个字符(换行符除外) |
^ | 匹配行首 |
$ | 匹配行尾 |
[abc] | 匹配方括号内的任意一个字符 |
[a-z] | 匹配小写字母范围 |
[A-Z] | 匹配大写字母范围 |
[0-9] | 匹配数字范围 |
? | 前一项出现 0 次或 1 次 |
* | 前一项出现 0 次或多次 |
+ | 前一项至少出现 1 次 |
` | ` |
( ) | 分组,定义优先级 |
实战示例
- 查找以
PATH
开头的行:
grep -E '^PATH' /etc/profile
- 匹配
PATH
或path
(即 P 大写或小写):
grep -E '^[Pp]ATH' /etc/profile
- 查找包含 0 到 4 之间任意数字的行:
grep -E '[0-4]' /etc/profile
- 查找包含任意字母(大小写均可)的行:
grep -E '[a-zA-Z]' /etc/profile
跨平台兼容性提示:尽管某些 Linux 发行版(如 CentOS)默认激活正则功能,但为确保脚本可移植性,始终推荐显式使用 -E
参数。
排序工具:sort
命令的灵活运用
sort
命令用于对文本行进行排序,底层基于高效的排序算法(如快速排序、归并排序),适用于结构化数据整理,默认按字典序升序排列
1 )基础排序操作
创建测试文件 name.txt
并填入若干英文名:
nano name.txt
输入内容如下:
Alice
Bob
Charlie
David
Eve
Frank
Grace
Henry
Ivy
执行基础排序:
sort name.txt
结果按字母顺序排列,不区分大小写影响相对位置。
2 ) 核心参数详解
-o:输出至新文件(output)
sort
默认仅在终端显示结果,不会修改原文件。使用 -o
可将排序结果保存到指定文件:
sort -o names_sorted.txt name.txt
原文件 names.txt
内容不变,排序结果存入 names_sorted.txt
-r:倒序排列(reverse)
实现降序排列,字母从 Z 到 A:
sort -r name.txt
-R:随机排序(random)
使用大写的 -R
参数可使每轮排序结果随机化,适合打乱顺序场景:
sort -R name.txt
多次运行将产生不同顺序,适用于抽奖、抽样等需求。
-n:数值排序(numeric sort)
默认排序将数字视为字符串处理(字典序),导致 100
排在 2
前面。使用 -n
实现真正的数值排序:
创建数字文件
nano numbers.txt
内容:
100
25
3
87
12 字符串排序(错误)
sort numbers.txt
输出:100, 12, 25, 3, 87 正确数值排序
sort -n numbers.txt
输出:3, 12, 25, 87, 100
统计分析:wc
命令精准计数
wc
(word count)是多功能统计工具,可用于计算行数、单词数、字符数及字节数
1 ) 基本输出格式
wc name.txt
输出格式为三列数字:
<lines> <words> <bytes> filename.txt
输出三个数值:行数 单词数 字节数 文件名
示例:
9 9 50 name.txt
解释:
- 第一个
9
:共 9 行(每行末尾\n
计为换行) - 第二个
9
:每行一个名字,共 9 个单词(由空格分隔的非空字符串) - 第三个
50
:总字节数 = 字符数 + 换行符占用字节
如:9 个名字共 41 字符 + 9 个\n
= 50 字节
2 ) 精细化统计参数
参数 | 功能 | 示例 |
---|---|---|
-l | 统计行数(lines) | wc -l name.txt → 9 |
-w | 统计单词数(words) | wc -w name.txt → 9 |
-c | 统计字节数(bytes) | wc -c name.txt → 50 |
-m | 统计字符数(characters) | wc -m name.txt → 50 (ASCII 下与 -c 相同) |
注:
-c
中的c
应理解为 “count of bytes”,或可能源自 “character” 或早期 Unix 命名习惯;虽非直观缩写,但在 POSIX 标准中固定存在-m
对 Unicode 多字节字符更准确,来自 “multibyte character”
去重处理:uniq
命令的局限与突破
uniq
用于删除相邻重复行,前提是数据已排序(否则无法识别非连续重复项),或将重复内容可视化
1 ) 基本用法
创建含重复内容的文件 repeat.txt
:
nano repeat.txt
内容:
Albert
France
France
France
Mater
Mater
Zoe
执行去重:
uniq repeat.txt
输出:
Albert
France
Mater
Zoe
重要限制:uniq
只能处理连续重复行,若未排序则无效。应配合 sort
先排序再去重:
sort repeat.txt | uniq
2 ) 扩展参数应用
-c:统计重复次数(count)
显示每行出现的频次:
uniq -c repeat.txt
输出:
1 Albert 3 France 2 Mater 1 Zoe
-d:仅显示重复行(duplicated)
只输出出现了两次以上的行:
uniq -d repeat.txt
输出:
France
Mater
便于快速定位高频项
-u 参数:仅显示唯一行(Unique Only)
uniq -u repeat.txt
输出:
Albert
Eve
持久化保存结果:
uniq repeat.txt unique.txt # 输出到文件
实用组合:统计并筛选高频词
sort log.txt | uniq -c | sort -nr | head -10
上述命令链实现日志中访问最多的前 10 条记录提取
字段剪切:cut
命令精确提取列数据
cut
用于从每行中截取特定位置的内容,适用于 CSV、日志等定长或分隔格式数据。
1 ) 按字符位置剪切(-c)
保留每行第 2 到第 4 个字符:
cut -c 2-4 name.txt
输入 Alice
→ 输出 lic
支持多种格式:
cut -c 1-3
:前 3 字符cut -c 5-
:从第 5 字符开始到结尾cut -c 2,5
:第 2 和第 5 字符
2 ) 按字段分隔符剪切(-f 与 -d)
对于 tab 或逗号分隔的数据,使用:
cut -d',' -f2 data.csv # 以逗号分隔,提取第2字段
示例 CSV 文件:
name,age,city
Alice,25,Beijing
Bob,30,Shanghai
cut -d',' -f1,3 data.csv
输出:
name,city
Alice,Beijing
Bob,Shanghai
模拟 Linux 文本处理管道示例
1 ) 方案1
以下是一个基于 NestJS 的服务模块,模拟 grep
、sort
、wc
、uniq
、cut
的核心逻辑,体现函数式编程思想与流式处理能力。
// text-processing.service.ts
import { Injectable } from '@nestjs/common';@Injectable()
export class TextProcessingService {/* grep 模拟:支持正则、忽略大小写、反向匹配*/grep(lines: string[],pattern: string,options: { ignoreCase?: boolean; invert?: boolean; useRegex?: boolean } = {},): string[] {const flags = options.ignoreCase ? 'gi' : 'g';const regex = options.useRegex? new RegExp(pattern, flags): new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);return lines.filter(line => {const matches = regex.test(line);return options.invert ? !matches : matches;});}/* sort 模拟:支持字母、数字、倒序、随机排序*/sort(lines: string[],options: { numeric?: boolean; reverse?: boolean; random?: boolean } = {},): string[] {let result = [...lines];if (options.random) {return result.sort(() => Math.random() - 0.5);}result.sort((a, b) => {if (options.numeric) {const numA = parseFloat(a) || 0;const numB = parseFloat(b) || 0;return numA - numB;}return a.localeCompare(b);});if (options.reverse) {result.reverse();}return result;}/* wc 模拟:统计行数、单词数、字节数*/wc(content: string): { lines: number; words: number; bytes: number; chars: number } {const lines = content.split('\n').length;const words = content.trim().split(/\s+/).filter(Boolean).length;const chars = content.length;const bytes = new Blob([content]).size;return { lines, words, bytes, chars };}/* uniq 模拟:去重、计数、仅重复项*/uniq(lines: string[],options: { count?: boolean; duplicatesOnly?: boolean } = {},): (string | { count: number; line: string })[] {const result: any[] = [];let currentLine = null;let count = 0;for (const line of lines) {if (line === currentLine) {count++;} else {if (currentLine !== null) {const item = options.count? { line: currentLine, count }: currentLine;if (!options.duplicatesOnly || count > 1) {result.push(item);}}currentLine = line;count = 1;}}// 最后一行if (currentLine !== null) {const item = options.count? { line: currentLine, count }: currentLine;if (!options.duplicatesOnly || count > 1) {result.push(item);}}return result;}/* cut 模拟:按字符或字段切割*/cutByChar(lines: string[], start: number, end?: number): string[] {return lines.map(line => {if (end) return line.slice(start - 1, end);return line.charAt(start - 1);});}cutByField(lines: string[], delimiter: string, fields: number[]): string[] {return lines.map(line => {const parts = line.split(delimiter);return fields.map(f => parts[f - 1] || '').join(delimiter);});}
}
控制器调用示例
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { TextProcessingService } from './text-processing.service';@Controller('linux')
export class AppController {constructor(private readonly tp: TextProcessingService) {}@Get('demo')demo() {const data = ['apple','Banana','apple','Cherry','banana','Apple'];const filtered = this.tp.grep(data, 'apple', { ignoreCase: true });const sorted = this.tp.sort(filtered, { reverse: true });const unique = this.tp.uniq(sorted, { count: true });return { original: data, filtered, sorted, unique };}
}
2 )方案2
模拟上述 Linux 命令行为,实现日志文件的读取、搜索、排序、统计与清洗功能。
// log-analyzer.service.ts
import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';@Injectable()
export class LogAnalyzerService {private readonly basePath = '/var/logs'; // 模拟日志目录 /* grep 模拟:搜索关键词(支持正则)*/grep(filePath: string, pattern: string, flags: 'i' | 'v' | '' = ''): string[] {const content = fs.readFileSync(path.join(this.basePath, filePath), 'utf-8');const lines = content.split('\n');const regexFlags = flags.includes('i') ? 'gi' : 'g';const regex = new RegExp(pattern, regexFlags);return lines.filter(line => {const hasMatch = regex.test(line);return flags.includes('v') ? !hasMatch : hasMatch;});}/* sort 模拟:排序(支持数值、倒序)*/sort(lines: string[], numeric = false, reverse = false): string[] {const sorted = [...lines].sort((a, b) => {if (numeric) {const numA = parseFloat(a) || 0;const numB = parseFloat(b) || 0;return numA - numB;}return a.localeCompare(b);});return reverse ? sorted.reverse() : sorted;}/* wc 模拟:统计行数、单词数、字节数*/wc(content: string): { lines: number; words: number; bytes: number } {const lines = content.split('\n').length;const words = content.trim().split(/\s+/).filter(Boolean).length;const bytes = Buffer.byteLength(content, 'utf-8');return { lines, words, bytes };}/* uniq 模拟:去重并统计*/uniq(lines: string[], options: { count?: boolean; duplicatesOnly?: boolean }): string[] | Record<string, number> {const result: Record<string, number> = {};lines.forEach(line => {result[line] = (result[line] || 0) + 1;});if (options.count) {return Object.entries(result).map(([line, count]) => `${count} ${line}`);}if (options.duplicatesOnly) {return Object.keys(result).filter(line => result[line] > 1);}return Object.keys(result);}/* cut 模拟:按字符范围截取*/cutByChar(lines: string[], start: number, end: number): string[] {return lines.map(line => line.substring(start - 1, end));}/* 完整流程演示:分析日志中的 IP 地址频率 */analyzeLogIps(logFile: string): Record<string, number> {const ips = this.grep(logFile, '\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b', '') // 提取IP.map(line => line.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/)?.[0]).filter(Boolean);const frequency: Record<string, number> = {};ips.forEach(ip => {frequency[ip] = (frequency[ip] || 0) + 1;});return frequency;}
}
控制器调用示例
// log.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { LogAnalyzerService } from './log-analyzer.service';@Controller('logs')
export class LogController {constructor(private readonly analyzer: LogAnalyzerService) {}@Get('search')search(@Query('file') file, @Query('pattern') pattern) {return this.analyzer.grep(file, pattern);}@Get('stats')stats(@Query('file') file) {const content = fs.readFileSync(`/var/logs/${file}`, 'utf-8');return this.analyzer.wc(content);}
}
3 )方案3
用于服务器端文本处理:
// text-processing.service.ts
import { Injectable } from '@nestjs/common';@Injectable()
export class TextProcessingService {/* 模拟 grep 功能:支持正则、忽略大小写、反向匹配 */grep(content: string[],pattern: string,options: { ignoreCase?: boolean; invert?: boolean; useRegex?: boolean } = {},): string[] {const flags = options.ignoreCase ? 'gi' : 'g';const regex = options.useRegex? new RegExp(pattern, flags): new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);return content.filter(line => {const matches = regex.test(line);return options.invert ? !matches : matches;});}/* 模拟 sort 功能:支持字母、数字、倒序排序*/sort(content: string[],options: { numeric?: boolean; reverse?: boolean } = {},): string[] {let sorted = [...content];sorted.sort((a, b) => {if (options.numeric) {const numA = parseFloat(a.trim()) || 0;const numB = parseFloat(b.trim()) || 0;return numA - numB;}return a.localeCompare(b);});if (options.reverse) sorted.reverse();return sorted;}/* 模拟 wc 功能*/wc(content: string[]): { lines: number; words: number; bytes: number } {const lines = content.length;const words = content.map(line => line.trim().split(/\s+/).filter(Boolean).length).reduce((a, b) => a + b, 0);const bytes = content.map(line => new Blob([line + '\n']).size).reduce((a, b) => a + b, 0);return { lines, words, bytes };}/* 模拟 uniq 功能*/uniq(content: string[], options: { count?: boolean; duplicatesOnly?: boolean } = {}): (string | { count: number; line: string })[] {const result: any[] = [];let currentLine = null;let count = 0;for (const line of content) {if (line === currentLine) {count++;} else {if (currentLine !== null) {const item = options.count ? { count, line: currentLine } : currentLine;if (!options.duplicatesOnly || count > 1) result.push(item);}currentLine = line;count = 1;}}if (currentLine !== null) {const item = options.count ? { count, line: currentLine } : currentLine;if (!options.duplicatesOnly || count > 1) result.push(item);}return result;}/* 模拟 cut -c 功能*/cutByChar(content: string[], start: number, end: number): string[] {return content.map(line => line.substring(start - 1, end));}
}
控制器调用示例:
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { TextProcessingService } from './text-processing.service';@Controller('text')
export class TextController {constructor(private readonly processor: TextProcessingService) {}@Get('demo')demo() {const lines = ['export PATH=/usr/bin','export pass=123456','USER=admin','PASSWORD=secret','PATH=/bin:/sbin'];const grepResult = this.processor.grep(lines, 'pass', { ignoreCase: true });const sorted = this.processor.sort(['3', '100', '25'], { numeric: true });const stats = this.processor.wc(lines);const cutResult = this.processor.cutByChar(['Alice', 'Bob'], 2, 4);return { grepResult, sorted, stats, cutResult };}
}
构建高效数据处理流水线
命令 | 核心用途 | 关键参数 | 典型应用场景 |
---|---|---|---|
grep | 文本搜索与过滤 | -i , -n , -v , -r , -E | 日志排查、配置检索、正则匹配 |
sort | 行排序 | -n , -r , -R , -o | 数据排序、去重预处理、随机抽样 |
wc | 统计分析 | -l , -w , -c , -m | 文件规模评估、自动化监控、审计、容量评估 |
uniq | 去重与频次分析 | -c , -d , -u | 日志聚合、访问统计、异常检测、清洗脏数据、频次统计 |
cut | 字段提取 | -c , -f , -d | 日志解析、报表生成、ETL 清洗、CSV 字段抽取 |
最佳实践建议:将多个命令通过管道符 |
连接,形成处理链:
cat access.log \| grep "404" \| cut -d' ' -f1 \| sort \| uniq -c \| sort -nr \| head -10
以上命令链实现了:提取所有 404 错误日志 → 获取 IP 地址 → 排序去重 → 统计频次 → 降序排列 → 显示 Top 10 攻击源 IP
或
所有命令均不对原始文件做修改,可通过重定向 >
或管道 |
构建复杂数据流水线:
grep "error" app.log | sort | uniq -c | sort -nr > summary.txt
或
grep "error" log.txt | sort | uniq -c | sort -nr | cut -f2-
这五个命令可通过管道无缝衔接,构成强大的文本处理流水线:
实现:找出错误日志 → 排序 → 统计频次 → 按频率降序 → 提取消息内容
最终结论:掌握 grep
、sort
、wc
、uniq
、cut
不仅是 Linux 基础技能,更是构建自动化运维、日志分析、大数据预处理系统的基石