Linux小课堂: 输入重定向与管道操作详解

控制命令的数据来源

在 Linux Shell 环境中,命令的执行不仅依赖于参数传递,更可通过输入重定向显式指定其数据源

标准输入(stdin)默认来自键盘,但通过特定符号可将其切换为文件内容或交互式键盘输入流

核心要点: 输入重定向(</<<)控制命令输入源,管道(|)实现命令间数据流转

输入重定向

1 ) 单小于号 <:从文件读取输入

使用单个 < 符号可将文件内容作为命令的标准输入。例如:

bash 复制代码
cat < notes.csv 

该命令与 cat notes.csv 的输出结果相同,但执行机制存在本质差异:

  • cat notes.csvcat 命令接收文件路径作为参数,自行打开并读取文件;
  • cat < notes.csv:Shell 先打开 notes.csv 文件,将其内容注入标准输入流,并将此流传递给 cat 命令;cat 仅负责读取 stdin 并输出。

重点:< 实现了输入源的解耦,使命令无需直接操作文件描述符即可处理外部数据。

2 )双小于号 <<:从键盘输入(Here Document)

双小于号 << 启用"Here Document"语法,允许用户通过键盘逐行输入文本,直到遇到指定的结束标识符为止。典型用法如下:

bash 复制代码
sort -n << END 
12
9 
27
END 

上述命令启动 sort -n(数值排序),随后进入交互模式。用户每输入一行数字并回车,系统即缓存该行。当输入 END 并再次回车后,Shell 将所有输入内容整体作为 sort -n 的标准输入进行处理,最终输出排序结果:

ss 复制代码
9 
12
27

技术细节:

  • 结束字符串(如 END)可自定义,大小写敏感
  • 输入过程中不可编辑历史行,需确保一次性正确输入
  • 若结束符前有空白字符(如 << EOF 后接空格),则必须严格匹配

此机制广泛用于脚本内嵌配置、临时数据生成等场景

管道操作符 |

管道是 Linux 命令行最强大的特性之一,它通过 | 符号将一个命令的输出(stdout)直接作为下一个命令的输入(stdin),形成级联处理链条

作用:将前一个命令的输出作为后一个命令的输入,实现多命令级联

1 ) 管道基本原理

bash 复制代码
command1 | command2 | command3

数据流动过程如下:

  • command1 执行,输出结果不显示在终端
  • 输出流被实时传递给 command2 作为输入
  • command2 处理完毕后,其输出继续传给 command3
  • 最终结果由最后一个命令输出到终端或重定向目标

性能优势:管道采用流式处理,无需中间文件暂存,极大提升效率

2 ) 基础应用(按学生名排序)

bash 复制代码
cut -d ',' -f1 notes.csv | sort > sorted_names.txt
  • cut 提取 CSV 第一列(学生名)→ sort 按字典序排序 → 结果重定向至文件。

3 ) 目录大小排序(前10名)

bash 复制代码
du | sort -nr | head
  • du 统计目录大小 → sort -nr 按数值倒序 → head 取前10行。

4 ) 关键词文件查找(系统日志)

bash 复制代码
sudo grep -Ir '/var/log' | cut -d: -f1 | sort | uniq 
  • grep 递归搜索 /var/log 含关键词的行 → cut 提取文件名 → sort 排序 → uniq 去重。

5 )示例一:提取并排序学生姓名

假设 notes.csv 文件格式如下:

txt 复制代码
Alice,88,Good job 
Bob,76,Average
Charlie,95,Excellent

目标:提取第一列(姓名),按字母顺序排序

bash 复制代码
cut -d',' -f1 notes.csv | sort 

分解说明:

  • cut -d',' -f1 notes.csv:以逗号分隔,提取第1字段(姓名);
  • | sort:将 cut 输出的姓名列表进行字典序排序。

结果:

ss 复制代码
Alice 
Bob
Charlie

进一步扩展,将结果保存到文件:

bash 复制代码
cut -d',' -f1 notes.csv | sort > sorted_names.txt

6 ) 示例二:按目录大小排序并取前10项

统计当前目录下各子目录磁盘占用,并按大小降序排列,取最大10个:

bash 复制代码
du -h | sort -hr | head -n 10 

各组件功能:

  • du -h:递归计算每个子目录总大小,人类可读单位(KB/MB/GB);
  • sort -hr
    • -h 支持对 K, M, G 单位进行智能排序;
    • -r 逆序输出,大值优先;
  • head -n 10:仅显示前10行结果。

注意:若 du 遇到权限不足目录会产生 stderr 错误信息,可能干扰排序。建议过滤非关键错误:

bash 复制代码
du -h 2>/dev/null | sort -hr | head -n 10

其中 2>/dev/null 抑制错误输出

7 ) 示例三:查找含关键字的唯一文件名

搜索 /var/log 目录中所有包含 log 字样的文本文件,并提取唯一的文件路径名:

bash 复制代码
grep -ri "log" /var/log | cut -d':' -f1 | sort | uniq

步骤详解:

  1. grep -ri "log" /var/log
    • -r:递归遍历 /var/log 子目录;
    • -i:忽略大小写匹配;
    • 输出格式为 文件路径:匹配行内容
  2. cut -d':' -f1:以冒号分割,提取第一部分(即文件路径);
  3. sort:对文件路径进行排序,为去重准备;
  4. uniq:去除相邻重复路径,保留唯一项。

补充技巧:若需统计各文件出现次数,可用:

bash 复制代码
grep -ri "log" /var/log | cut -d':' -f1 | sort | uniq -c | sort -nr 
  • uniq -c:前置计数;
  • sort -nr:数值逆序排列,高频文件排前。

输入与输出重定向结合:构建完整 I/O 控制链

输入重定向可与输出重定向及错误流控制组合使用,实现复杂的数据流向管理。

案例:排序键盘输入并保存至文件

bash 复制代码
sort -n << END > sorted_numbers.txt 2>&1
12
7
35 
END
  • << END 提供标准输入;
  • > sorted_numbers.txt 将标准输出重定向至文件;
  • 2>&1 表示将标准错误流合并至标准输出流,确保异常信息也写入同一文件。

运行后,sorted_numbers.txt 内容为:

txt 复制代码
7
12 
35

关键点:2>&1 必须置于其他重定向之后,否则无法正确绑定当前输出目标。

键盘输入数值 → sort 排序 → 结果与错误信息写入文件

构建自动化日志分析工具案例

1 ) 方案1

NestJS 命令执行模拟代码

typescript 复制代码
import { Injectable } from '@nestjs/common';
import { exec } from 'child_process';
import { promisify } from 'util';
 
const execAsync = promisify(exec);
 
@Injectable()
export class CommandService {
  // 执行管道命令(目录大小排序)
  async sortDirectorySizes(): Promise<string> {
    const command = `du | sort -nr | head`;
    try {
      const { stdout } = await execAsync(command);
      return stdout;
    } catch (error) {
      throw new Error(`Command failed: ${error.stderr}`);
    }
  }
 
  // 输入重定向:从文件读取
  async readFromFile(filePath: string): Promise<string> {
    const command = `cat < ${filePath}`;
    const { stdout } = await execAsync(command);
    return stdout;
  }
 
  // 输入重定向:键盘输入模拟(TS实现)
  async keyboardInputSort(numbers: number[]): Promise<string> {
    const input = numbers.join('\n') + '\nEND';
    const command = `sort -n << END\n${input}\nEND`;
    const { stdout } = await execAsync(command);
    return stdout.trim();
  }
}

代码说明:

  • sortDirectorySizes:模拟 du | sort -nr | head,返回目录大小排序结果
  • readFromFile:使用 < 重定向读取文件内容
  • keyboardInputSort:动态生成 << 输入重定向命令,对数值数组排序

2 )方案2

模拟日志关键词提取与聚合逻辑

日志分析服务代码

typescript 复制代码
// log-analysis.service.ts
import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
 
interface LogMatch {
  filePath: string;
  lineNumber: number;
  content: string;
}
 
@Injectable()
export class LogAnalysisService {
  /
   * 模拟 grep -ri 功能:递归查找关键词 
   */
  async searchKeywordInLogs(rootDir: string, keyword: string): Promise<LogMatch[]> {
    const results: LogMatch[] = [];
 
    const scan = async (dir: string) => {
      const entries = await fs.promises.readdir(dir, { withFileTypes: true });
 
      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
 
        if (entry.isDirectory()) {
          if (!['.', '..'].includes(entry.name)) await scan(fullPath);
        } else if (entry.isFile() && this.isTextFile(entry.name)) {
          await this.searchInFile(fullPath, keyword, results);
        }
      }
    };
 
    await scan(rootDir);
    return results;
  }
 
  private isTextFile(filename: string): boolean {
    const ext = path.extname(filename).toLowerCase();
    return ['.log', '.txt', '.csv', ''].includes(ext); // 简化判断
  }
 
  private async searchInFile(
    filePath: string,
    keyword: string,
    results: LogMatch[],
  ) {
    try {
      const data = await fs.promises.readFile(filePath, 'utf-8');
      const lines = data.split(/\r?\n/);
      const lowerKeyword = keyword.toLowerCase();
 
      lines.forEach((line, index) => {
        if (line.toLowerCase().includes(lowerKeyword)) {
          results.push({
            filePath,
            lineNumber: index + 1,
            content: line.trim(),
          });
        }
      });
    } catch (err) {
      console.warn(`无法读取文件: ${filePath}`, err.message);
    }
  }
 
  /
   * 提取唯一文件路径(类比 cut | sort | uniq)
   */
  extractUniqueFilePaths(matches: LogMatch[]): string[] {
    return [...new Set(matches.map(m => m.filePath))]
      .sort();
  }
 
  /
   * 统计每个文件命中次数(类比 uniq -c)
   */
  countPerFile(matches: LogMatch[]): Array<{ filePath: string; count: number }> {
    const map = new Map<string, number>();
    matches.forEach(m => map.set(m.filePath, (map.get(m.filePath) || 0) + 1));
    return Array.from(map.entries())
      .map(([filePath, count]) => ({ filePath, count }))
      .sort((a, b) => b.count - a.count);
  }
}

控制器调用示例

typescript 复制代码
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { LogAnalysisService } from './log-analysis.service';
 
@Controller('logs')
export class AppController {
  constructor(private readonly logService: LogAnalysisService) {}
 
  @Get('search/:keyword')
  async search(@Param('keyword') keyword: string) {
    const matches = await this.logService.searchKeywordInLogs('/var/log', keyword);
    const uniqueFiles = this.logService.extractUniqueFilePaths(matches);
    const topFiles = this.logService.countPerFile(matches).slice(0, 10);
 
    return {
      totalMatches: matches.length,
      uniqueFilesCount: uniqueFiles.length,
      top10FilesByHits: topFiles,
      sampleMatches: matches.slice(0, 5),
    };
  }
}

对应 Shell 命令映射关系

Shell 命令 NestJS 实现
grep -ri "log" /var/log searchKeywordInLogs()
cut -d':' -f1 matches.map(m => m.filePath)
sort .sort()
uniq [...new Set(...)]
`uniq -c sort -nr`

设计思想:NestJS 服务封装了 Shell 管道的逻辑流本质------数据变换链(Data Transformation Pipeline),体现了函数式编程与 Unix 设计哲学的高度融合

技术细节总结

符号 作用 数据流向
< 文件→命令 文件内容作为命令输入
<< 键盘→命令 逐行输入至结束字符串
` ` 命令A→命令B
> / >> 命令→文件 输出覆盖/追加到文件

关键原则:

  1. 管道符 | 级联命令时,每个命令的输出必须为文本流
  2. 输入重定向(</<<)替代参数输入,直接提供数据源。
  3. 组合使用(如 << END > file)可实现 交互式输入+输出存储

核心要点凝练

特性 符号 作用 典型应用场景
输入重定向(文件) < 将文件内容作为命令输入 cat < file.txt
Here Document << 接收键盘输入直至结束符 脚本中嵌入多行配置
管道 ` ` 将前命令输出作为后命令输入
输出重定向 > / >> 控制命令输出位置 结果持久化
错误合并 2>&1 统一处理 stdout 与 stderr 日志记录完整性

终极原则:

一切皆流(Everything is a stream) ------ Linux 的强大源于对"流"的极致抽象。无论是文件、键盘、网络还是进程间通信,统一通过 stdin/stdout 接口进行交互,而重定向与管道正是操控这些数据流的核心手段

相关推荐
迎風吹頭髮3 小时前
Linux内核架构浅谈49-Linux per-CPU页面缓存:热页与冷页的管理与调度优化
linux·缓存·架构
jason.zeng@15022074 小时前
centos中安装redis
linux·redis·centos
w23617346014 小时前
Linux 服务器安全巡检与加固:从命令到实操(CentOS/Ubuntu 通用)
linux·服务器·安全·安全加固·安全巡检
TG_yunshuguoji4 小时前
阿里云云代理商:阿里云CDN刷新机制是什么?
服务器·阿里云·云计算
xiaogg36784 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
python百炼成钢5 小时前
3.Linux 网络相关
linux·运维·网络·stm32·单片机
Jtti5 小时前
香港硬防服务器防御DDOS攻击的优点
运维·服务器·ddos
lpfasd1237 小时前
第2部分:Netty核心架构与原理解析
运维·服务器·架构