Linux使用cut命令提取文本列的方法詳解
引言
在日常的 Linux 系統(tǒng)管理和數(shù)據(jù)處理工作中,我們經(jīng)常需要從結(jié)構(gòu)化的文本文件中提取特定字段。比如日志分析、CSV 數(shù)據(jù)清洗、系統(tǒng)用戶信息篩選等場景。這時,cut 命令就成為了一把鋒利的小刀,能夠精準“切割”出我們需要的那一列或幾列內(nèi)容。
本文將帶你全面掌握 cut 命令的使用方法,涵蓋其基本語法、高級技巧、常見誤區(qū),并通過 Java 代碼示例進行功能對比,幫助你理解不同工具在文本列提取任務中的優(yōu)劣與適用場景。文章最后還將提供性能對比圖表和實際應用場景建議,讓你不僅會用,更能用得高效、用得聰明!
什么是 cut 命令?
cut 是一個標準的 Unix/Linux 工具,屬于 GNU coreutils 包的一部分。它的核心作用是從每一行中“剪切”出指定的部分——可以是字節(jié)、字符或字段(列)。它輕量、快速、無依賴,在腳本自動化中廣泛使用。
提示:cut 默認按行處理輸入,每行獨立操作,非常適合結(jié)構(gòu)化文本(如 CSV、TSV、/etc/passwd 等)。
基本語法結(jié)構(gòu)
cut [選項] [文件...]
常用選項:
-b, --bytes=LIST:按字節(jié)位置提取-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 后面緊跟分隔符,必須是單個字符(不能是字符串),如需多字符分隔,請結(jié)合 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 列(閉區(qū)間)N-:從第 N 列到行尾-M:從開頭到第 M 列N,M,K:多個不連續(xù)列
示例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
注意:如果某行列數(shù)不足,缺失列會被忽略,不會報錯。這既是優(yōu)點(容錯),也是缺點(可能漏數(shù)據(jù))。
按字符/字節(jié)提?。?c 與 -b
有時我們面對的是固定寬度的文本(如舊式日志、Fortran 輸出等),這時可以按字符位置提取。
示例5:提取每行前10個字符
cut -c 1-10 logfile.txt
示例6:提取第5到第15字節(jié)(注意:對多字節(jié)字符如中文可能截斷?。?/h3>
cut -b 5-15 unicode.txt
cut -b 5-15 unicode.txt
重要區(qū)別:
-c按“字符”計數(shù),適合 UTF-8 等多字節(jié)編碼-b按“字節(jié)”計數(shù),可能破壞多字節(jié)字符結(jié)構(gòu)
推薦在處理 Unicode 文本時優(yōu)先使用 -c
過濾無效行:-s 選項
當使用 -f 時,如果某行不含分隔符,則整行原樣輸出(除非使用 -s)。
示例7:只輸出含逗號的行
cut -d ',' -f 2 -s messy.csv
適用于清理格式混亂的數(shù)據(jù)文件。
自定義輸出分隔符:–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
非常實用在生成報表或?qū)肫渌到y(tǒng)時調(diào)整格式。
常見陷阱與注意事項
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
執(zhí)行 cut -d ',' -f 2 將輸出空行(不是跳過)。
4. 不支持正則表達式
cut 是純文本定位工具,無法像 awk 那樣基于模式匹配列。
實戰(zhàn)演練:日志字段提取
假設你有如下 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、請求方法、狀態(tài)碼
由于空格不是可靠分隔符(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
結(jié)論:cut 適合結(jié)構(gòu)清晰、分隔符明確的文本;復雜結(jié)構(gòu)請用 awk 或 perl。
性能表現(xiàn)如何?—— 與 awk/sed 對比
雖然 cut 功能有限,但它在簡單任務中速度極快,因為其實現(xiàn)非常輕量。
我們做一個簡單測試(百萬行數(shù)據(jù)):
# 生成測試數(shù)據(jù)
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 實現(xiàn)對比:自己寫“cut”
既然 cut 如此常用,我們能否用 Java 實現(xiàn)類似功能?當然可以!下面是一個簡化版的 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++) { // 防止無限循環(huán)
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 數(shù)組從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 實現(xiàn) |
|---|---|---|
| 按字段提取 | ? | ? |
| 按字符/字節(jié)提取 | ? | ?(可擴展) |
| 自定義輸出分隔符 | ? | ? |
| 僅輸出含分隔符的行 | ? (-s) | ? |
| 多字符分隔符 | ? | ?(split 支持) |
| 處理超大文件(流式) | ? | ? |
| 跨平臺 | ?(需安裝) | ? |
| Unicode 安全 | ? (-c) | ? |
選擇建議:
- 簡單、快速、腳本中 → 用
cut - 需要復雜邏輯、跨平臺、集成到應用 → 用 Java 實現(xiàn)
- 多字符分隔、正則匹配 → 用
awk
性能對比圖表(百萬行數(shù)據(jù))
下面我們用 Mermaid 圖表展示在不同數(shù)據(jù)規(guī)模下,cut 與 Java 實現(xiàn)的耗時對比。

從圖中可見,cut 在各數(shù)據(jù)規(guī)模下均顯著快于 Java 實現(xiàn),尤其在百萬行級別差距達 3 倍。這是因為:
cut是 C 編寫,直接系統(tǒng)調(diào)用,無 JVM 啟動開銷- Java 需要對象創(chuàng)建、GC、UTF 解碼等額外成本
cut內(nèi)部優(yōu)化極致,逐字符處理無緩沖復制
高級技巧:cut 與其他命令組合
cut 很少單獨使用,常作為管道中的一環(huán)。
技巧1:排序去重后提取
cat data.csv | sort | uniq | cut -d ',' -f 1
技巧2:結(jié)合 grep 過濾后提取
grep "ERROR" app.log | cut -d ' ' -f 1,4
技巧3:統(tǒng)計某列唯一值數(shù)量
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:系統(tǒng)監(jiān)控腳本
#!/bin/bash # 監(jiān)控高負載進程,提取 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:數(shù)據(jù)庫導出數(shù)據(jù)清洗
# 從 mysqldump 中提取表名 grep "^CREATE TABLE" dump.sql | cut -d '`' -f 2
Java 增強版:支持更多特性
前面的 Java Cut 是基礎版,下面我們實現(xiàn)一個增強版本,支持:
-s類似行為(跳過無分隔符行)- 多字符分隔符(使用正則)
- 輸出分隔符自定義
- 字符位置提?。M
-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; // 轉(zhuǎn)為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 過濾)
總結(jié)與最佳實踐
cut 是 Linux 文本處理生態(tài)中不可或缺的基礎工具。雖然功能簡單,但在合適場景下效率極高。以下是使用建議:
何時用 cut:
- 分隔符明確且為單字符
- 只需提取固定列,無復雜條件
- 追求極致性能(尤其大文件)
- 在 Shell 腳本中作為管道組件
何時不用 cut:
- 分隔符是字符串或正則表達式
- 需要條件判斷、計算、格式轉(zhuǎn)換
- 處理嵌套結(jié)構(gòu)或非結(jié)構(gòu)化文本
- 需要跨平臺一致性(Windows 無原生 cut)
最佳實踐:
- 始終明確分隔符,避免默認
\t導致錯誤 - 使用
-s清理臟數(shù)據(jù) - 用
--output-delimiter控制輸出格式 - 復雜任務交給
awk—— “瑞士軍刀”更適合多功能需求 - 百萬行以上數(shù)據(jù)優(yōu)先考慮
cut而非腳本語言實現(xiàn)
結(jié)語
掌握 cut 命令,就像程序員掌握了數(shù)組索引——看似基礎,卻是構(gòu)建復雜數(shù)據(jù)流水線的基石。配合 grep、sort、uniq、awk 等工具,你可以在命令行完成絕大多數(shù)數(shù)據(jù)預處理任務,無需啟動笨重的 IDE 或數(shù)據(jù)庫。
希望本文不僅教會你 cut 的用法,更啟發(fā)你思考:在正確的場景選擇正確的工具,才是高效工作的本質(zhì)。
以上就是Linux使用cut命令提取文本列的方法詳解的詳細內(nèi)容,更多關(guān)于Linux cut提取文本列的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
linux上配置jdk時,java命令提示沒有此文件或文件夾的解決方法
下面小編就為大家?guī)硪黄猯inux上配置jdk時,java命令提示沒有此文件或文件夾的解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
bash shell獲取當前腳本的絕對路徑(pwd/readlink)
有時候,我們需要知道當前執(zhí)行的輸出shell腳本的所在絕對路徑,本文主要介紹了bash shell獲取當前腳本的絕對路徑,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
Linux命令行和shell腳本編程寶典 Richard Blum
Linux命令行和shell腳本編程寶典,主要介紹了linux一些命令的使用2012-09-09

