1. 引言:被忽视的命令行符号
在 Linux 命令行中,有一些符号看起来“不像命令”,却扮演着极其关键的角色。它们不是可执行文件,也不是内置命令,而是 Shell 和命令行工具共同约定的特殊标记。
本文聚焦其中最核心的两个:
| 符号 | 名称 | 核心功能 |
|---|---|---|
- |
单横线(标准输入/输出占位符) | 表示“数据从这里流进来”或“数据从这里流出去” |
-- |
双横线(选项终止符) | 表示“选项到此结束,之后都是普通参数” |
这些符号的巧妙之处在于——它们不需要被“实现”,只需要被“遵守”。任何遵循 POSIX 标准的命令行工具,都会默认支持这些约定。
2. -:标准输入/输出的“通配符”
2.1 核心理念:位置即含义
在命令行中,- 不是一个“选项”,而是一个位置占位符。当工具在预期“文件名”的位置看到 - 时,它会理解为:
- 如果是从文件读取数据 → 从标准输入(stdin)读取
- 如果是向文件写入数据 → 向标准输出(stdout)写入
graph LR
A["命令参数位置"] --> B{"参数是 - ?"}
B -->|"读取操作"| C["从 stdin 读取"]
B -->|"写入操作"| D["向 stdout 写入"]
B -->|"其他操作"| E["字面文件名 '-'"]
2.2 典型使用场景
场景一:压缩/解压时的流式处理
# 将 tar 输出重定向到 gzip(而非写入文件)
tar -cf - ./directory | gzip > archive.tar.gz
# ↑ 这里 - 表示“输出到 stdout”
# 从标准输入读取,解压到当前目录
gzip -dc archive.tar.gz | tar -xf -
# ↑ 这里 - 表示“从 stdin 读取”
场景二:用 diff 比较文件与标准输入
# 比较文件与管道输出
echo "new content" | diff file.txt -
# 将 file.txt 与 stdin 的内容进行比较
# 比较命令输出与文件
find . -type f | sort | diff sorted_files.txt -
场景三:替换标准文件操作
# 将 echo 的输出写入文件(- 通常代表 stdout,但这里 cat 会自动处理)
echo "hello" | cat - > output.txt
# 等同于:echo "hello" > output.txt
# 合并文件与标准输入
cat file.txt - <<< "additional line"
# 先输出 file.txt,再输出 "additional line"
场景四:程序间管道连接
# 第一步:生成数据
generate_data > data.txt
# 可以替换为:
generate_data | process_data | tee output.txt
# 不需要中间文件,纯流式处理
2.3 - 的变体
| 写法 | 含义 | 示例 |
|---|---|---|
- |
标准输入/输出 | cat - |
/dev/stdin |
标准输入设备文件 | cat /dev/stdin |
/dev/stdout |
标准输出设备文件 | echo "hi" > /dev/stdout |
/dev/fd/0 |
文件描述符 0 | cat /dev/fd/0 |
在某些需要文件路径的场景(如 docker run -v),/dev/stdin 比 - 更可靠。
3. --:选项边界的“界碑”
3.1 问题起源:当文件名以 - 开头
普通用户几乎不会创建以 - 开头的文件,但在以下场景中非常常见:
- 从不可信来源下载的文件(攻击者可能命名为
-rf) - 程序生成的临时文件(如 Git 的
.git/refs/heads/-d) - 测试数据中的特殊命名
# 创建一个名为 -f 的文件
touch -- -f
# 或
touch ./-f
# 尝试读取它
cat -f
# 错误:-f 被解析为 cat 的选项(期待后续参数)
3.2 解决方案:-- 终止选项解析
-- 告诉命令行解析器:“后面的所有内容,无论是否以 - 开头,都视为普通参数,不要再作为选项处理”。
# 正确删除 -f 文件
rm -- -f
# 正确查看 -f 文件内容
cat -- -f
# 正确查找名为 -name 的文件
grep -- "-pattern" file.txt
3.3 -- 在其他场景的应用
场景一:避免参数冲突:
# grep 搜索 " -v" 字符串(而非使用 -v 选项)
grep -- " -v" file.txt
场景二:明确参数边界(脚本安全):
#!/bin/bash
# 使用 -- 保护用户输入
rm -- "$user_input" # 即使 user_input = "-rf /",也会被当作文件名处理
场景三:传递给子命令(常见于包装脚本):
# 将所有参数传递给 ssh
ssh user@host -- "$@"
4. 易混淆概念的辨析
4.1 - 不是重定向
# 重定向:改变数据流向
command > file # stdout → file
command < file # stdin ← file
# - 占位符:告诉工具从 stdin 读取
command - # 等价于 command /dev/stdin
重定向是 Shell 的语法,- 是工具的约定。
4.2 - 与 -- 的不同层次
| 特性 | - (stdin/stdout) |
-- (选项终止) |
|---|---|---|
| 作用对象 | 数据内容 | 命令行参数解析 |
| 处理层级 | 应用程序内部 | 命令行解析器(Shell / getopt) |
| 实现方式 | 工具自身需实现 | getopt() 库原生支持 |
| 典型配合 | 管道链 | 参数边界保护 |
4.3 - 在非 GNU 工具中的差异
并非所有工具都支持 - 作为 stdin/stdout 的占位符。例如 cp:
cp - file.txt
# 错误:cp 不将 - 识别为标准输入
此时应使用 /dev/stdin:
cat file.txt | cp /dev/stdin copy.txt # 部分系统支持
5. 实战组合:- 与 -- 的协同
5.1 从压缩包中提取特定文件并处理
tar -czf - -- ./src | ssh user@host "tar -xzf - -C /dest"
# ↑ -- 确保 ./src 不被解析为选项 ↑ - 从 stdin 读取
5.2 安全处理用户输入的文件名
#!/bin/bash
# 假设用户可能输入以 - 开头的文件名
while IFS= read -r file; do
# -- 确保 file 中的 - 不会被当作选项
# - 作为 cat 的输入占位符
cat -- "$file" | process_command -
done < filelist.txt
6. 总结:小符号,大作用
| 符号 | 一句话总结 | 记忆口诀 |
|---|---|---|
- |
“我不在乎文件名,给我数据流就行” | 用 - 接水管 |
-- |
“后面的都别解析了,全是普通参数” | 用 -- 划界限 |
这两个符号的设计体现了 Unix 哲学的精髓:简单的约定,通用的理解。
-是工具作者需要“主动支持”的功能——写工具时,检查参数是否为-,如果是则切换到 stdin/stdout--是getopt()类库“免费提供”的能力——任何使用标准选项解析的命令行工具都自动支持
理解它们,意味着:
- 在管道链中可以写出更简洁的命令(
tar -cf -) - 在脚本中可以安全处理任意文件名(
rm -- "$var") - 在编写工具时,可以遵循这些“隐形约定”,让自己写的命令更符合 Unix 生态的预期
原文 https://blog.csdn.net/2301_79518550/article/details/148503080