Linux使用cut命令提取文本列的方法详解
作者:知远漫谈
引言
在日常的 Linux 系统管理和数据处理工作中,我们经常需要从结构化的文本文件中提取特定字段。比如日志分析、CSV 数据清洗、系统用户信息筛选等场景。这时,cut 命令就成为了一把锋利的小刀,能够精准“切割”出我们需要的那一列或几列内容。
本文将带你全面掌握 cut 命令的使用方法,涵盖其基本语法、高级技巧、常见误区,并通过 Java 代码示例进行功能对比,帮助你理解不同工具在文本列提取任务中的优劣与适用场景。文章最后还将提供性能对比图表和实际应用场景建议,让你不仅会用,更能用得高效、用得聪明!
什么是 cut 命令?
cut 是一个标准的 Unix/Linux 工具,属于 GNU coreutils 包的一部分。它的核心作用是从每一行中“剪切”出指定的部分——可以是字节、字符或字段(列)。它轻量、快速、无依赖,在脚本自动化中广泛使用。
提示:cut 默认按行处理输入,每行独立操作,非常适合结构化文本(如 CSV、TSV、/etc/passwd 等)。
基本语法结构
cut [选项] [文件...]
常用选项:
-b, --bytes=LIST:按字节位置提取-c, --characters=LIST:按字符位置提取-f, --fields=LIST:按字段(列)提取,默认以制表符\t分隔-d, --delimiter=DELIM:自定义字段分隔符--output-delimiter=STRING:设置输出时字段之间的分隔符-s, --only-delimited:仅输出包含分隔符的行(用于过滤无效行)
其中最常用的是 -f 和 -d 的组合。
按字段提取:-f 与 -d 的黄金搭档
示例1:提取 /etc/passwd 中的用户名和 shell
/etc/passwd 文件格式为冒号分隔的七列:
用户名:密码占位符:UID:GID:描述:家目录:登录Shell
我们想提取第一列(用户名)和第七列(Shell):
cut -d ':' -f 1,7 /etc/passwd
输出示例:
root:/bin/bash daemon:/usr/sbin/nologin bin:/usr/sbin/nologin sys:/usr/sbin/nologin ...
注意:-d 后面紧跟分隔符,必须是单个字符(不能是字符串),如需多字符分隔,请结合 awk 或其他工具。
示例2:提取 CSV 文件中的姓名和邮箱
假设有一个 users.csv:
ID,Name,Email,Department 1,Alice,alice@example.com,Engineering 2,Bob,bob@example.com,Sales 3,Carol,carol@example.com,Marketing
我们想提取 Name 和 Email(第2、3列):
cut -d ',' -f 2,3 users.csv
输出:
Name,Email Alice,alice@example.com Bob,bob@example.com Carol,carol@example.com
字段范围与复杂选择
cut 支持灵活的字段选择语法:
N:第 N 列N-M:从第 N 到第 M 列(闭区间)N-:从第 N 列到行尾-M:从开头到第 M 列N,M,K:多个不连续列
示例3:提取第2列到最后一列
cut -d ',' -f 2- users.csv
输出:
Name,Email,Department Alice,alice@example.com,Engineering Bob,bob@example.com,Sales Carol,carol@example.com,Marketing
示例4:提取第1、3、5列(跳过中间)
cut -d ',' -f 1,3,5 data.txt
注意:如果某行列数不足,缺失列会被忽略,不会报错。这既是优点(容错),也是缺点(可能漏数据)。
按字符/字节提取:-c 与 -b
有时我们面对的是固定宽度的文本(如旧式日志、Fortran 输出等),这时可以按字符位置提取。
示例5:提取每行前10个字符
cut -c 1-10 logfile.txt
示例6:提取第5到第15字节(注意:对多字节字符如中文可能截断!)
cut -b 5-15 unicode.txt
重要区别:
-c按“字符”计数,适合 UTF-8 等多字节编码-b按“字节”计数,可能破坏多字节字符结构
推荐在处理 Unicode 文本时优先使用 -c
过滤无效行:-s 选项
当使用 -f 时,如果某行不含分隔符,则整行原样输出(除非使用 -s)。
示例7:只输出含逗号的行
cut -d ',' -f 2 -s messy.csv
适用于清理格式混乱的数据文件。
自定义输出分隔符:–output-delimiter
默认情况下,cut 输出字段仍使用原分隔符。你可以用 --output-delimiter 替换它。
示例8:用竖线代替逗号输出
cut -d ',' -f 2,3 --output-delimiter=' | ' users.csv
输出:
Name | Email Alice | alice@example.com Bob | bob@example.com Carol | carol@example.com
非常实用在生成报表或导入其他系统时调整格式。
常见陷阱与注意事项
1. 分隔符只能是单字符
# ❌ 错误:cut 不支持多字符分隔符 cut -d '::' -f 2 file.txt # ✅ 正确:先用 sed/awk 替换,再 cut sed 's/::/:/g' file.txt | cut -d ':' -f 2
2. 字段编号从1开始
# ❌ 错误 cut -f 0,1 file.txt # ✅ 正确 cut -f 1,2 file.txt
3. 空字段处理
如果某列为空,cut 仍会保留其位置:
a,,c,d
执行 cut -d ',' -f 2 将输出空行(不是跳过)。
4. 不支持正则表达式
cut 是纯文本定位工具,无法像 awk 那样基于模式匹配列。
实战演练:日志字段提取
假设你有如下 Nginx 访问日志(简化版):
192.168.1.1 - - [10/Oct/2023:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 612 192.168.1.2 - - [10/Oct/2023:13:55:40 +0000] "POST /login HTTP/1.1" 302 0
目标:提取 IP、请求方法、状态码
由于空格不是可靠分隔符(URL 中可能含空格),更推荐用 awk,但若格式固定,也可尝试:
cut -d ' ' -f 1,6,9 access.log
输出:
192.168.1.1 "GET 200 192.168.1.2 "POST 302
不够理想?那就该换 awk 了:
awk '{print $1, $6, $9}' access.log
结论:cut 适合结构清晰、分隔符明确的文本;复杂结构请用 awk 或 perl。
性能表现如何?—— 与 awk/sed 对比
虽然 cut 功能有限,但它在简单任务中速度极快,因为其实现非常轻量。
我们做一个简单测试(百万行数据):
# 生成测试数据
seq 1 1000000 | awk '{print $1","$1*2","$1*3","$1*4}' > test.csv
# 测试 cut
time cut -d ',' -f 2,4 test.csv > /dev/null
# 测试 awk
time awk -F',' '{print $2,$4}' OFS=',' test.csv > /dev/null
通常 cut 会比 awk 快 2~5 倍,尤其在 SSD 上差异更明显。
Java 实现对比:自己写“cut”
既然 cut 如此常用,我们能否用 Java 实现类似功能?当然可以!下面是一个简化版的 Java Cut 工具类。
import java.io.*;
import java.util.*;
import java.nio.file.*;
public class JavaCut {
public static void main(String[] args) throws IOException {
if (args.length < 3) {
System.err.println("Usage: java JavaCut <delimiter> <fields> <file>");
System.err.println("Example: java JavaCut , 1,3 data.csv");
return;
}
String delimiter = args[0];
String fieldSpec = args[1];
String filename = args[2];
List<Integer> fields = parseFieldSpec(fieldSpec);
processFile(filename, delimiter, fields);
}
private static List<Integer> parseFieldSpec(String spec) {
List<Integer> fields = new ArrayList<>();
for (String part : spec.split(",")) {
if (part.contains("-")) {
String[] range = part.split("-");
int start = Integer.parseInt(range[0]);
int end = range.length > 1 ? Integer.parseInt(range[1]) : Integer.MAX_VALUE;
for (int i = start; i <= end && i <= 1000; i++) { // 防止无限循环
fields.add(i);
}
} else {
fields.add(Integer.parseInt(part));
}
}
return fields;
}
private static void processFile(String filename, String delimiter, List<Integer> fields) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
String line;
while ((line = reader.readLine()) != null) {
String[] tokens = line.split(delimiter, -1); // 保留尾部空字段
List<String> selected = new ArrayList<>();
for (int index : fields) {
if (index > 0 && index <= tokens.length) {
selected.add(tokens[index - 1]); // Java 数组从0开始
}
}
System.out.println(String.join(delimiter, selected));
}
}
}
}编译运行:
javac JavaCut.java java JavaCut , 2,4 test.csv
这个 Java 版本支持:
- 自定义分隔符
- 字段范围(如
2-4) - 多字段选择(如
1,3,5) - 保留空字段
功能对比:Linux cut vs Java Cut
| 功能 | Linux cut | Java Cut 实现 |
|---|---|---|
| 按字段提取 | ✅ | ✅ |
| 按字符/字节提取 | ✅ | ❌(可扩展) |
| 自定义输出分隔符 | ✅ | ✅ |
| 仅输出含分隔符的行 | ✅ (-s) | ❌ |
| 多字符分隔符 | ❌ | ✅(split 支持) |
| 处理超大文件(流式) | ✅ | ✅ |
| 跨平台 | ❌(需安装) | ✅ |
| Unicode 安全 | ✅ (-c) | ✅ |
选择建议:
- 简单、快速、脚本中 → 用
cut - 需要复杂逻辑、跨平台、集成到应用 → 用 Java 实现
- 多字符分隔、正则匹配 → 用
awk
性能对比图表(百万行数据)
下面我们用 Mermaid 图表展示在不同数据规模下,cut 与 Java 实现的耗时对比。

从图中可见,cut 在各数据规模下均显著快于 Java 实现,尤其在百万行级别差距达 3 倍。这是因为:
cut是 C 编写,直接系统调用,无 JVM 启动开销- Java 需要对象创建、GC、UTF 解码等额外成本
cut内部优化极致,逐字符处理无缓冲复制
高级技巧:cut 与其他命令组合
cut 很少单独使用,常作为管道中的一环。
技巧1:排序去重后提取
cat data.csv | sort | uniq | cut -d ',' -f 1
技巧2:结合 grep 过滤后提取
grep "ERROR" app.log | cut -d ' ' -f 1,4
技巧3:统计某列唯一值数量
cut -d ',' -f 3 users.csv | sort | uniq -c | sort -nr
输出示例:
45 Engineering 32 Sales 18 Marketing
技巧4:提取列后重新组合成新格式
cut -d ',' -f 1,3 users.csv | sed 's/,/ -> /'
输出:
1 -> alice@example.com 2 -> bob@example.com 3 -> carol@example.com
实际应用场景推荐
场景1:系统监控脚本
#!/bin/bash # 监控高负载进程,提取 PID 和 COMMAND ps aux --sort=-%cpu | head -10 | tail -n +2 | cut -c 10-15,68-
场景2:API 日志分析
# 提取访问最频繁的 endpoint cut -d ' ' -f 7 access.log | sort | uniq -c | sort -nr | head -5
场景3:数据库导出数据清洗
# 从 mysqldump 中提取表名 grep "^CREATE TABLE" dump.sql | cut -d '`' -f 2
Java 增强版:支持更多特性
前面的 Java Cut 是基础版,下面我们实现一个增强版本,支持:
-s类似行为(跳过无分隔符行)- 多字符分隔符(使用正则)
- 输出分隔符自定义
- 字符位置提取(模拟
-c)
import java.io.*;
import java.util.*;
import java.nio.file.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class JavaCutPro {
private static class Options {
String delimiter = "\\s+"; // 默认空白分隔
String outputDelimiter = "\t";
List<Integer> fields = new ArrayList<>();
boolean onlyDelimited = false;
boolean byChar = false;
String charRange = "";
}
public static void main(String[] args) throws IOException {
Options opts = parseArgs(args);
if (opts == null) return;
Path file = Paths.get(args[args.length - 1]);
processFile(file, opts);
}
private static Options parseArgs(String[] args) {
if (args.length < 2) {
printUsage();
return null;
}
Options opts = new Options();
int i = 0;
while (i < args.length - 1) {
switch (args[i]) {
case "-d":
opts.delimiter = args[++i];
break;
case "-f":
opts.fields = parseFieldSpec(args[++i]);
break;
case "--output-delimiter":
opts.outputDelimiter = args[++i];
break;
case "-s":
opts.onlyDelimited = true;
break;
case "-c":
opts.byChar = true;
opts.charRange = args[++i];
break;
default:
System.err.println("Unknown option: " + args[i]);
return null;
}
i++;
}
if (opts.fields.isEmpty() && !opts.byChar) {
System.err.println("Must specify -f or -c");
return null;
}
return opts;
}
private static void processFile(Path file, Options opts) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(file)) {
String line;
while ((line = reader.readLine()) != null) {
if (opts.byChar) {
processByChar(line, opts.charRange);
} else {
processByField(line, opts);
}
}
}
}
private static void processByField(String line, Options opts) {
String[] tokens = line.split(opts.delimiter, -1);
// 如果启用 -s 且没有分隔符(即只有一列),跳过
if (opts.onlyDelimited && tokens.length <= 1) {
return;
}
List<String> selected = new ArrayList<>();
for (int index : opts.fields) {
if (index > 0 && index <= tokens.length) {
selected.add(tokens[index - 1]);
}
}
if (!selected.isEmpty()) {
System.out.println(String.join(opts.outputDelimiter, selected));
}
}
private static void processByChar(String line, String rangeSpec) {
List<int[]> ranges = parseCharRanges(rangeSpec);
StringBuilder sb = new StringBuilder();
for (int[] range : ranges) {
int start = range[0] - 1; // 转为0基
int end = range[1];
if (start < line.length()) {
int actualEnd = Math.min(end, line.length());
sb.append(line.substring(start, actualEnd)).append(" ");
}
}
if (sb.length() > 0) {
System.out.println(sb.toString().trim());
}
}
private static List<int[]> parseCharRanges(String spec) {
List<int[]> ranges = new ArrayList<>();
for (String part : spec.split(",")) {
if (part.contains("-")) {
String[] ends = part.split("-");
int start = Integer.parseInt(ends[0]);
int end = ends.length > 1 ? Integer.parseInt(ends[1]) : start;
ranges.add(new int[]{start, end});
} else {
int pos = Integer.parseInt(part);
ranges.add(new int[]{pos, pos});
}
}
return ranges;
}
private static List<Integer> parseFieldSpec(String spec) {
List<Integer> fields = new ArrayList<>();
for (String part : spec.split(",")) {
if (part.contains("-")) {
String[] range = part.split("-");
int start = Integer.parseInt(range[0]);
int end = range.length > 1 ? Integer.parseInt(range[1]) : Integer.MAX_VALUE;
for (int i = start; i <= end && i <= 1000; i++) {
fields.add(i);
}
} else {
fields.add(Integer.parseInt(part));
}
}
return fields;
}
private static void printUsage() {
System.err.println("Usage: java JavaCutPro [OPTIONS] FILE");
System.err.println("Options:");
System.err.println(" -d DELIM Field delimiter (regex)");
System.err.println(" -f FIELDS Comma-separated field numbers (e.g., 1,3-5)");
System.err.println(" -c RANGES Character ranges (e.g., 1-10,15-20)");
System.err.println(" --output-delimiter OUTPUT_DELIM");
System.err.println(" -s Suppress lines with no delimiter");
}
}这个增强版几乎可以替代日常使用的 cut,并额外支持:
- 正则分隔符(如
\s+匹配任意空白) - 字符位置提取
- 更灵活的范围语法
测试你的理解:小测验
试着预测以下命令的输出:
Q1: echo "apple,banana,cherry,date" | cut -d ',' -f 2-
Q2: echo "hello world" | cut -c 3-7
Q3: printf "a:b:c\nx:y\np\n" | cut -d ':' -f 2 -s
A1: banana,cherry,date
A2: llo w
A3: 只输出 b 和 y(第三行被 -s 过滤)
总结与最佳实践
cut 是 Linux 文本处理生态中不可或缺的基础工具。虽然功能简单,但在合适场景下效率极高。以下是使用建议:
何时用 cut:
- 分隔符明确且为单字符
- 只需提取固定列,无复杂条件
- 追求极致性能(尤其大文件)
- 在 Shell 脚本中作为管道组件
何时不用 cut:
- 分隔符是字符串或正则表达式
- 需要条件判断、计算、格式转换
- 处理嵌套结构或非结构化文本
- 需要跨平台一致性(Windows 无原生 cut)
最佳实践:
- 始终明确分隔符,避免默认
\t导致错误 - 使用
-s清理脏数据 - 用
--output-delimiter控制输出格式 - 复杂任务交给
awk—— “瑞士军刀”更适合多功能需求 - 百万行以上数据优先考虑
cut而非脚本语言实现
结语
掌握 cut 命令,就像程序员掌握了数组索引——看似基础,却是构建复杂数据流水线的基石。配合 grep、sort、uniq、awk 等工具,你可以在命令行完成绝大多数数据预处理任务,无需启动笨重的 IDE 或数据库。
希望本文不仅教会你 cut 的用法,更启发你思考:在正确的场景选择正确的工具,才是高效工作的本质。
以上就是Linux使用cut命令提取文本列的方法详解的详细内容,更多关于Linux cut提取文本列的资料请关注脚本之家其它相关文章!
