有交互CLI——用户流程第一次变成循环
无交互CLI的用户流程是一条直线:输入→输出→结束。有交互CLI打破了这个模式——程序启动后不退出,用户可以反复输入命令,每次输入都得到输出,直到用户主动说「退出」。用户流程从直线变成了循环,这一个变化带来了全新的设计问题。
一、从直线到循环 无交互CLI:
1 2 3 $ grep "hello" file.txt hello world $ ← 程序已退出
有交互CLI:
1 2 3 4 5 6 7 8 9 $ tool > scan file.bin found at offset 42 > status running, 3 threads > scan another.bin not found > exit $ ← 用户主动退出
区别:
1 2 3 4 5 6 7 无交互CLI: 启动 → 执行 → 输出 → 退出 (直线) 有交互CLI: 启动 → [输入 → 执行 → 输出 → 再输入 → 执行 → 输出 → ...] → 退出 (循环)
循环的引入让一切都变了。
二、从直线到循环带来的会话 有交互 CLI 有了「会话」的概念——程序从启动到退出之间的整段时间是一个会话。
1 2 3 4 5 6 7 8 9 会话开始:tool 启动 ↓ 用户输入命令 1 → 执行 → 输出 ↓ 用户输入命令 2 → 执行 → 输出 ↓ ... ↓ 用户输入 exit → 会话结束
这是有交互CLI的第一次质变:程序不再执行完就退出,而是持续运行等待下一次输入 (会话状态的具体形态与演化见 06,第五节——状态流)。
三、命令循环:用户流程的核心结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 flowchart TD A["等待用户输入"] --> B["解析命令"] B --> C{"是 exit?"} C -->|"是"| D["退出程序"] C -->|"否"| E["执行命令"] E --> F["输出结果"] F --> G{"执行成功?"} G -->|"是"| A G -->|"否"| H["输出错误信息"] H --> A style A fill:#4a9eff,color:#fff style D fill:#f44,color:#fff style E fill:#ff9f4a,color:#fff
有交互CLI的用户流程是一个命令循环 :
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌─────────────────────────────┐ │ 等待用户输入 │ ← 循环起点 │ ↓ │ │ 解析命令 │ │ ↓ │ │ 执行命令 │ │ ↓ │ │ 输出结果 │ │ ↓ │ │ 是 exit? ──是──→ 退出 │ │ │ 否 │ │ └──→ 回到等待用户输入 │ ← 循环终点 └─────────────────────────────┘
这个循环的代码形态:
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { char input[256 ]; while (1 ) { printf ("> " ); fgets(input, sizeof (input), stdin ); if (strcmp (input, "exit\n" ) == 0 ) break ; execute(input); } return 0 ; }
命令循环是有交互CLI的心脏。 所有后续讨论都围绕这个循环展开。
四、用户流程的完整路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 用户流程(完整): 1. 打开终端 2. 输入启动命令(如 tool) 3. 看到提示符 ">" ← 程序在等待 4. 输入命令 5. 看到输出 6. 看到提示符 ">" ← 程序在等待 7. 输入下一个命令 8. 看到输出 9. ...(重复 6-8) 10. 输入 exit 11. 看到程序退出 12. 终端提示符重新出现
和无交互CLI的区别:
1 2 无交互CLI:输入 → 等待 → 输出 → 结束 有交互CLI:输入 → 等待 → 输出 → 提示符 → 输入 → ... → exit → 结束
关键区别:程序不会自动退出,用户掌握退出权。 这意味着:
1 2 3 程序的生命周期 = 用户的会话时间 程序的状态在整个会话期间持续存在 用户可以随时决定「再做一件事」或「结束了」
五、命令解析:用户输入变成程序动作 无交互CLI的参数解析是一次性的——main(argc, argv) 解析一次就结束了。有交互CLI的参数解析是循环的 ——每次用户输入都要解析。
1 2 3 4 5 6 用户输入 "scan file.bin --verbose" ↓ 解析 Command: scan Args: {path: "file.bin", verbose: true} ↓ 执行 scan_file("file.bin", true)
命令解析器的结构:
1 2 3 4 5 6 7 8 9 输入字符串 ↓ 分词 [命令名, 参数1, 参数2, ...] ↓ 查找命令表 找到对应的处理函数 ↓ 调用 执行命令 ↓ 返回 回到命令循环
命令表 是命令解析器的核心数据结构:
1 2 3 4 5 命令表: "scan" → handle_scan() "status" → handle_status() "help" → handle_help() "exit" → handle_exit()
如果用户输入了未知命令:
1 2 3 > foobar unknown command: foobar >
命令解析是有交互CLI的第二心脏。 第一是命令循环,第二是命令解析。
六、输出模式的变化 无交互CLI的输出是一次性的 ——程序输出所有结果后就退出了。有交互CLI的输出是分段的 ——每次命令执行后输出一部分,然后回到等待状态。
1 2 3 4 5 6 7 8 无交互CLI的输出: 全部结果一次性输出 用户看到的是完整输出 有交互CLI的输出: 每次命令输出一部分 用户看到的是增量输出 旧输出可能滚出屏幕
这带来了新的设计问题:
1 2 3 4 1. 输出格式化:如何让每次输出清晰可读? 2. 输出缓冲:是否需要在命令之间清屏? 3. 错误输出:错误信息和正常输出如何区分? 4. 大量输出:如果一次命令产生 1000 行输出怎么办?
七、错误流 / 控制流 / 反馈流的变化 三种流在有交互CLI都发生了第一次质变,跨形态完整展开见 06(控制流第二节、错误流第四节、反馈流第六节):
1 2 3 4 5 6 7 8 错误流:出错 → 输出错误 → 继续运行(无交互:出错 → 退出) 错误不再导致程序退出,程序必须能从错误中恢复、继续处理下一个命令。 控制流:命令循环掌握程序骨架,但「执行什么命令」由用户输入决定 用户是控制流的源头——用户驱动的循环。 反馈流:程序出错 → 用户看到错误 → 输入修正命令 → 同进程继续(无交互:跨进程重来) 反馈流第一次真正实现——用户成为了反馈回路的一部分。
八、退出:用户流程的终点 无交互CLI的退出:程序执行完毕自动退出。
有交互CLI的退出:用户主动决定退出 。
有交互CLI的生命周期由用户控制——用户决定什么时候开始,什么时候结束。 无交互CLI的生命周期由程序本身决定——执行完毕就结束(退出流的完整演化见 06 第七节)。
九、有交互CLI的用户流程总结 1 2 3 4 5 6 7 8 9 10 11 有交互CLI的用户流程: 启动 → 提示符 → [输入 → 解析 → 执行 → 输出 → 提示符] × N → exit → 结束 特征(用户流视角): ┌─────────────────────────────────────────┐ │ 命令循环 程序持续运行 │ │ 命令解析 用户输入变成程序动作 │ │ 增量输出 每次命令输出一部分 │ │ 用户控制退出 用户决定什么时候结束 │ └─────────────────────────────────────────┘
有交互CLI相比无交互CLI的核心变化:
维度
无交互CLI
有交互CLI
用户流程
直线
循环
状态
无
有会话状态
退出
自动
用户主动
错误
终止
恢复继续
反馈
跨进程重新启动
同进程内修正
控制流
程序自己控制
用户驱动循环
(状态/错误/反馈/控制/退出的跨形态演化合并见 06)
从无交互到有交互,一个交互让程序从「一次性工具」变成了「持续服务的会话」。
但有交互CLI仍然是**——纯文本——用户只能输入文字,看不到界面。当程序需要让用户「选择」「浏览」「拖拽」时,纯文本就力不从心了。下一篇看TUI——在文本终端上实现界面交互。