首页
/ SerenityOS 的 rev 命令完全指南:逐行反转文本的实用工具与源码实现解析

SerenityOS 的 rev 命令完全指南:逐行反转文本的实用工具与源码实现解析

2026-09-09 11:33:48作者:傅爽业Veleda

导读

rev 是 SerenityOS 自带的一个小而精的命令行工具:它将输入文件逐行读取,并把每一行的字符顺序反转后输出到标准输出。无论是想倒序查看文件内容、配合 ls 反序显示文件名,还是在管道中做文本预处理,rev 都是一个即用即走的实用利器。本文以官方手册页 rev.md 为主体,结合其真实源码 rev.cpp 深入讲解它的全部参数、三种典型用法,以及背后的实现原理,读完即可在 SerenityOS 上熟练使用并理解其工作机制。

命令概览:名称与语法

rev 的全称是 reverse lines,功能即"反转行"。其命令语法非常简洁:

$ rev [file...]
  • rev逐行读取指定的一个或多个文件,将每一行内的字符顺序反转,然后打印到标准输出
  • 如果不指定任何文件,rev 会从标准输入读取;
  • 如果参数中出现了特殊的文件名 -rev 也会从标准输入读取数据。

这种"无参读 stdin、- 读 stdin、其他参数读文件"的约定与 cattac 等标准 Unix 工具保持一致,便于在管道和重定向场景中灵活组合。

命令行参数

参数 含义
file 需要反转输出的文件路径,可传多个;- 表示标准输入,省略时默认读标准输入

注意:官方手册仅提供 file 这一位置参数,rev 没有额外的选项开关(如行号、长度限制等),保持了一以贯之的"单一职责、极简设计"风格。

三种典型用法示例

手册页给出了三个覆盖文件参数、标准输入与混合用法的完整示例,逐一演练如下。

1. 反转多个文件的每一行

准备两个测试文件 foobar,先看看原始内容,再交给 rev 处理:

$ cat foo bar
foo 1
foo 2
bar 1
bar 2
$ rev foo bar
1 oof
2 oof
1 rab
2 rab

可以看到,rev 按参数顺序依次处理两个文件,每一行的字符都被完全颠倒——foo 1 变成 1 oof,且行与行之间的顺序保持不变,只反转行内字符。

2. 与管道结合:反转文件名列表

rev 与管道天然契合,最常见的场景之一是把 ls 输出的文件名逐行反转:

$ ls
foo
bar
$ ls | rev
oof
rab

由于没有传入文件参数,rev 从标准输入读取 ls 的输出,每行(每个文件名)被独立反转。这种能力常被用于制造"镜像效果"的目录清单,或作为复杂文本处理流水线中的预处理环节。

3. 文件与标准输入混合使用

rev 允许把普通文件名与 - 混在同一个命令行中,- 出现的位置即标准输入内容插入的位置。下面的命令先反转文件 foo 的内容,再反转 ls 的输出:

$ cat foo
foo 1
foo 2
$ ls
foo
bar
$ ls | rev foo -
1 oof
2 oof
oof
rab

输出顺序完全遵循命令行参数的排列:先是 foo 文件的两行反转结果,随后是标准输入(ls 的输出)逐行反转的结果。该示例还说明 - 可以出现在参数列表的任意位置,而不必局限于开头或结尾。

源码实现深度解析

手册之外的实现细节,可以从 rev.cpp 一探究竟。整个程序只有 78 行,却完整展示了 SerenityOS 用户态工具的典型工程范式。

1. 启动与权限收缩

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    TRY(Core::System::pledge("stdio rpath"sv));

程序首先通过 pledge 声明自身所需的最小权限集合 stdio rpath(标准输入输出 + 读取文件路径)。pledge 是 SerenityOS 特有的安全机制,进程此后只能执行已承诺的系统调用。而在完成文件打开、即将进入循环读写的阶段,代码再次调用 TRY(Core::System::pledge("stdio"sv)),进一步放弃 rpath 权限,把进程的攻击面压缩到最小——先开文件、再收权限,这是 SerenityOS 用户态工具的经典安全模式。

2. 参数解析与输入流构建

Vector<StringView> paths;
Core::ArgsParser args_parser;

args_parser.set_general_help("Concatente files to stdout with each line in reverse.");
args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No);
args_parser.parse(arguments);

工具使用 LibCore::ArgsParser 声明式地定义参数:一个名为 path、可选的任意数量位置参数。随后构建 Vector<FILE*> 流列表:

  • 若指定了文件参数,则逐个打开:遇到 "-" 时追加 stdin,否则用 fopen(path, "r") 打开普通文件;若打开失败,通过 warnln 输出错误信息(Failed to open {}: {})并跳过该文件继续处理后续参数;
  • 若未指定任何参数,则默认只追加 stdin

因此手册中"无参读 stdin、- 读 stdin"的行为在源码层面得到了精确落实。

3. 资源清理的 RAII 保障

程序使用 ScopeGuard 保证缓冲区与文件流在退出时一定被释放:

char* buffer = nullptr;
ScopeGuard guard = [&] {
    free(buffer);
    for (auto* stream : streams) {
        // If the user passes '-' as an argument multiple times, ...
        if (fclose(stream) && errno != 0) {
            perror("fclose");
        }
    }
};

源码注释特别点明了一个边界情况:如果用户在命令行中多次传入 -,则 stdin 会被多次 fclose,导致失败但 errno 可能为 0,因此代码先检查 errno 再决定是否报错,避免重复关闭造成无意义的报错信息。这一细节体现了 SerenityOS 对边界条件的严谨处理。

4. 逐行读取与逐字节反转

核心循环使用 POSIX 的 getline 动态读取每一行:

for (auto* stream : streams) {
    for (;;) {
        size_t n = 0;
        errno = 0;
        ssize_t buflen = getline(&buffer, &n, stream);
        if (buflen == -1) {
            if (errno != 0) {
                perror("getline");
                return 1;
            }
            break;
        }
        outln("{}", ByteString { buffer, Chomp }.reverse());
    }
}

读取到行后,用 ByteString { buffer, Chomp } 构造字符串。其中 Chomp(即"咬掉"换行符)来自 StringImpl.h 中的枚举 ShouldChomp,含义是在构造字符串时去掉行尾的换行符,从而保证反转后输出时由 outln 统一补上换行,不会产生空行或双换行。

反转动作本身由 ByteString::reverse() 完成,实现在 ByteString.cpp

ByteString ByteString::reverse() const
{
    StringBuilder reversed_string(length());
    for (size_t i = length(); i-- > 0;) {
        reversed_string.append(characters()[i]);
    }
    return reversed_string.to_byte_string();
}

该实现从字符串末尾向前逐字符(字节)追加到一个预分配了 length() 容量的 StringBuilder 中,避免多次扩容,最后一次性产出反转后的新字符串。从源码结构可以看出,反转是"按字节"进行的:对于纯 ASCII 文本(如本例中的 foo 1)结果完全正确;而对于 UTF-8 等多字节编码的非 ASCII 字符,字节会被拆散反转,得到的将是字节序颠倒的内容而非字符级反转。因此在 SerenityOS 上使用 rev 处理中文等多字节文本时需要注意这一行为前提。

5. 返回码约定

程序在正常读完所有输入后返回 0;若 getline 读取失败且 errno 被置位(如磁盘读取错误),则输出 perror 信息并返回 1,与 Unix 工具"成功为 0、失败非 0"的约定一致。

构建与集成位置

rev 与 SerenityOS 其他基础工具一起编译,其源文件被登记在 Userland/Utilities/CMakeLists.txtrev.cpp),随系统镜像构建后安装到系统 PATH 中,用户即可直接以 rev 命令调用。若想在当前源码树中查看或验证其行为,可对照手册页 rev.md 与源码 rev.cpp 同步研读。

小结

rev 虽小,却是一个理解 SerenityOS 用户态工具设计理念的绝佳切片:极简的 POSIX 风格接口(无参/- 读 stdin、文件参数依序处理)、pledge 权限收缩的纵深防御、ArgsParser 声明式参数解析、ScopeGuard 的确定性资源清理,以及 ByteString::reverse() 中"预分配 + 倒序追加"的高效字符串处理。掌握它,你既能用 rev 快速完成行内反转的文本任务,也能举一反三地读懂 SerenityOS 中大量同构工具的实现脉络。

热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
603
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23