join 命令详解与参数实测(Ubuntu 22.04)
GNU join 参数实测:排序前提、-1/-2/-j/-t/-o 输出格式、-a1/-a2/-e 外连接与 -v 反连接,Ubuntu 22.04(coreutils 8.32)全部命令回显实测。
join 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 GNU join 的每一个参数——按键列内连接与排序前置条件、
-1/-2显式键列、-tCSV 分隔符、-o输出列格式(含 auto)、-a1/-a2外连接、-e缺省填充、-j同键列号、-v只看未匹配行,以及键重复的组合行为,全部命令与回显已在 Ubuntu 22.04(GNU coreutils 8.32)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- join 版本:GNU coreutils 8.32(
join --version实测) - join 属于 coreutils 基础工具集,Ubuntu 22.04 默认预装,无需额外安装
- 演示目录:
/root/demo/join/ - 测试数据:
users.txt/depts.txt(按首列 ID 排序的员工与部门表)、users_unsorted.txt(乱序员工表)、emp.txt(键在第 2 列的工号表)、products.csv/stock.csv(逗号分隔的商品与库存表)、emp2.txt/word2.txt(键在第 2 列的对照表)
二、命令概览
2.1 语法与测试数据准备
join 的语法是 join [OPTION]... FILE1 FILE2:对两个文件中"连接字段相等"的每一对行,输出一行合并结果。默认连接字段是第一列、以空白分隔。硬性前提:两个文件必须已按连接字段排序(帮助原文:FILE1 and FILE2 must be sorted on the join fields),这是 join 能线性扫描两个文件的基础。
准备一套演示数据(均为已按键列排好的小表,另备一份乱序表用于报错演示):
mkdir -p /root/demo/join
cat > /root/demo/join/users.txt <<'EOF'
1 Alice
2 Bob
3 Carol
4 David
EOF
cat > /root/demo/join/depts.txt <<'EOF'
1 HR
2 Dev
3 Finance
6 Sales
EOF
cat > /root/demo/join/users_unsorted.txt <<'EOF'
3 Carol
1 Alice
4 David
2 Bob
EOF
cat > /root/demo/join/emp.txt <<'EOF'
E001 1
E002 2
E003 9
EOF
cat > /root/demo/join/products.csv <<'EOF'
1,apple,5
2,banana,3
3,cherry,8
EOF
cat > /root/demo/join/stock.csv <<'EOF'
1,100
2,200
4,50
EOF
cat > /root/demo/join/emp2.txt <<'EOF'
x 1
y 2
z 3
EOF
cat > /root/demo/join/word2.txt <<'EOF'
one 1
two 2
three 3
EOF
进入演示目录并确认核心两表就绪:
$ cd /root/demo/join
$ cat users.txt depts.txt
1 Alice
2 Bob
3 Carol
4 David
1 HR
2 Dev
3 Finance
6 Sales
users.txt 的键是 14(David 无部门),3 与 6(Sales 无员工)——两边各有对方没有的键,正好演示内连接与外连接的差异。depts.txt 的键是 1
2.2 --help 全量回显
join --help 输出全部参数与格式语法(实测原样保留,可作为参数速查):
Usage: join [OPTION]... FILE1 FILE2
For each pair of input lines with identical join fields, write a line to
standard output. The default join field is the first, delimited by blanks.
When FILE1 or FILE2 (not both) is -, read standard input.
-a FILENUM also print unpairable lines from file FILENUM, where
FILENUM is 1 or 2, corresponding to FILE1 or FILE2
-e EMPTY replace missing input fields with EMPTY
-i, --ignore-case ignore differences in case when comparing fields
-j FIELD equivalent to '-1 FIELD -2 FIELD'
-o FORMAT obey FORMAT while constructing output line
-t CHAR use CHAR as input and output field separator
-v FILENUM like -a FILENUM, but suppress joined output lines
-1 FIELD join on this FIELD of file 1
-2 FIELD join on this FIELD of file 2
--check-order check that the input is correctly sorted, even
if all input lines are pairable
--nocheck-order do not check that the input is correctly sorted
--header treat the first line in each file as field headers,
print them without trying to pair them
-z, --zero-terminated line delimiter is NUL, not newline
--help display this help and exit
--version output version information and exit
Unless -t CHAR is given, leading blanks separate fields and are ignored,
else fields are separated by CHAR. Any FIELD is a field number counted
from 1. FORMAT is one or more comma or blank separated specifications,
each being 'FILENUM.FIELD' or '0'. Default FORMAT outputs the join field,
the remaining fields from FILE1, the remaining fields from FILE2, all
separated by CHAR. If FORMAT is the keyword 'auto', then the first
line of each file determines the number of fields output for each line.
Important: FILE1 and FILE2 must be sorted on the join fields.
E.g., use "sort -k 1b,1" if 'join' has no options,
or use "join -t ''" if 'sort' has no options.
Note, comparisons honor the rules specified by 'LC_COLLATE'.
If the input is not sorted and some lines cannot be joined, a
warning message will be given.
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/join>
or available locally via: info '(coreutils) join invocation'
💡 说明:帮助原文给了两条工程要点——排序要求(
sort -k 1b,1与join默认空白分隔对齐)与输出格式语法(FILENUM.FIELD或0,逗号/空格分隔)。默认输出 = 键 + FILE1 剩余字段 + FILE2 剩余字段,第三节所有-o实测都围绕这条格式规则展开。
2.3 版本确认
$ join --version | head -1
join (GNU coreutils) 8.32
三、参数逐项实测
3.1 默认用法:按键列内连接
不带参数,两文件按第一列相等连接,只输出两边都有的键:
$ join users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
输出格式 = 键(1)+ FILE1 剩余字段(Alice)+ FILE2 剩余字段(HR)。键 4(David 在部门表里没有)与键 6(Sales 在员工表里没有)都被丢弃——这是标准的内连接语义。
💡 技术拓展:join 的连接语义与 SQL 一一对应,选参数前先对号入座——
SQL 语义 join 写法 输出范围 INNER JOIN 默认(本文 3.1) 仅两边都匹配的键 LEFT OUTER JOIN -a1(3.6)匹配行 + FILE1 未匹配行 RIGHT OUTER JOIN -a2(3.6)匹配行 + FILE2 未匹配行 FULL OUTER JOIN -a1 -a2(4.1)匹配行 + 两边全部未匹配行 仅看未匹配(反连接) -v 1/-v 2(4.2)只输出某文件未匹配的行 本文实测全部以上形态;缺字段可用
-e填充占位(3.7)。
3.2 键列未排序的报错与 sort 修复
把乱序的员工表直接 join,会被排序检查拦下:
$ cat users_unsorted.txt
3 Carol
1 Alice
4 David
2 Bob
$ join users_unsorted.txt depts.txt
join: users_unsorted.txt:2: is not sorted: 1 Alice
3 Carol Finance
join: input is not in sorted order
join 先按键 3 输出了 3 Carol Finance,读到键 1(小于前一个键 3)时判定乱序,先后给出 is not sorted 与 input is not in sorted order 两条信息并中止。用 sort -k 1,1 按第一列修复后再连:
$ sort -k 1,1 users_unsorted.txt > users_fixed.txt
$ cat users_fixed.txt
1 Alice
2 Bob
3 Carol
4 David
$ join users_fixed.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
修复后与 3.1 结果一致。"先排序、后连接"是使用 join 的固定前置动作。
💡 提示:排序规则由
LC_COLLATE决定(帮助原文),join 与 sort 必须在同一种排序规则下才有可比性;生产脚本建议显式统一LC_ALL=C后再排序再连接。
3.3 -1 与 -2:显式指定两文件的键列
-1 FIELD 指定 FILE1 的键列、-2 FIELD 指定 FILE2 的键列(列号从 1 起)。emp.txt 的键(部门 ID)在第 2 列,与 depts.txt 的第 1 列连接:
$ cat emp.txt
E001 1
E002 2
E003 9
$ join -1 2 -2 1 emp.txt depts.txt
1 E001 HR
2 E002 Dev
输出仍是"键 + FILE1 剩余字段(E001)+ FILE2 剩余字段(HR)"。键 9 在部门表无匹配,被丢弃。
💡 提示:两文件键列位置相同时可写
-j FIELD一步代替-1 FIELD -2 FIELD(帮助原文明确二者等价),见 3.8。
3.4 -t:指定分隔符(CSV 连接)
-t CHAR 同时指定输入与输出的字段分隔符。CSV 用 -t ,:
$ join -t , products.csv stock.csv
1,apple,5,100
2,banana,3,200
$ join -t , -o auto products.csv stock.csv
1,apple,5,100
2,banana,3,200
输出行同样以逗号分隔(1,apple,5,100 = 键 + 商品名 + 价格 + 库存)。键 3(cherry)与 4(库存 50)未互配,按内连接丢弃。-o auto 在这里与默认输出一致,它的用武之地见 4.4。
💡 提示:加
-t后字段解析变严格——不再忽略前导空白、TAB 与空格也不再等价;CSV 里字段内含逗号时,join 不做引号解析,需先保证字段干净。
3.5 -o:输出列格式
-o FORMAT 按指定列序输出,每个规格是 FILENUM.FIELD(0 代表键本身),逗号或空格分隔。只取键与部门名:
$ join -o 1.1,2.2 users.txt depts.txt
1 HR
2 Dev
3 Finance
不要键、只要名字与部门:
$ join -o '1.2 2.2' users.txt depts.txt
Alice HR
Bob Dev
Carol Finance
键 + 姓名 + 部门的完整三列(等价于默认格式,显式写出便于扩展):
$ join -o '1.1 1.2 2.2' users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
💡 提示:
-o的价值在外连接与缺省填充时才完全显现——它固定了每行的列数与列序,未匹配行的空位才有地方填-e的占位值(见 3.7)。
3.6 -a1 与 -a2:外连接
-a1 在内连接结果之外,追加 FILE1 中未匹配的行:
$ join -a1 users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
4 David
4 David 被保留(部门列为空)。-a2 对称地保留 FILE2 的未匹配行:
$ join -a2 users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
6 Sales
6 Sales 被保留(员工列为空)。未匹配行的空位默认直接省略,想填充占位符见 3.7。
💡 提示:
-a1/-a2对应 SQL 的 LEFT/RIGHT OUTER JOIN;两边都想保留用-a1 -a2(FULL OUTER JOIN),实测见 4.1;只想要未匹配行(反连接)用-v,见 4.2。
3.7 -e:缺省填充
-e EMPTY 把缺失字段替换为 EMPTY。它必须与 -o 搭配——-o 固定了列结构,空位才有确定位置可填:
$ join -a1 -e NULL -o '1.1 1.2 2.2' users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
4 David NULL
David 的部门列填上了 NULL。反向(FILE2 侧未匹配)同理:
$ join -a2 -e NULL -o '1.1 1.2 2.2' users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
NULL NULL Sales
Sales 行的键(1.1)与姓名(1.2)都缺,双双填 NULL。对照:不用 -o 时缺失字段直接省略、无占位可填:
$ join -a1 users.txt depts.txt | cat -A
1 Alice HR$
2 Bob Dev$
3 Carol Finance$
4 David$
cat -A 显示 4 David$ 行尾没有 NULL——这就是"-e 必须配 -o"的实证。
💡 提示:填充值常取
NULL、-、0等,按下游解析约定选择;4.4 的对账实例用0填库存更符合业务语义。
3.8 -j:两文件同一键列号
-j FIELD 等价于 -1 FIELD -2 FIELD(帮助原文),两文件用同一列号的字段连接。emp2.txt 与 word2.txt 的键都在第 2 列:
$ cat emp2.txt word2.txt
x 1
y 2
z 3
one 1
two 2
three 3
$ join -j 2 emp2.txt word2.txt
1 x one
2 y two
3 z three
输出 = 键(第 2 列的值 1/2/3)+ FILE1 剩余字段(x/y/z)+ FILE2 剩余字段(one/two/three)。
💡 提示:
-j 1等价于默认行为;键列不同时仍需-1/-2分别指定(见 3.3)。
四、实战示例
4.1 全外连接找两边差异
-a1 -a2 同时保留两文件的未匹配行,一眼看出"谁没部门、谁没员工":
$ join -a1 -a2 users.txt depts.txt
1 Alice HR
2 Bob Dev
3 Carol Finance
4 David
6 Sales
4 David(无部门)与 6 Sales(无员工)都出现在结果里,其余为正常匹配行。数据对账场景里这就是差异清单。
4.2 -v:只看未匹配行(反连接)
-v FILENUM 抑制匹配行、只输出指定文件的未匹配行——比 -a 更聚焦:
$ join -v 1 users.txt depts.txt
4 David
$ join -v 2 users.txt depts.txt
6 Sales
-v 1 回答"FILE1 里谁没匹配上",-v 2 反之。配合 -v 1 -v 2 可同时列出两边的孤儿记录。
4.3 成绩单关联:键重复时的组合行为
students.txt 键唯一,courses.txt 键可重复(一个学生多门课)。join 会为"每个键值对"输出一行:
$ cat students.txt courses.txt
1001 王小明
1002 李雷
1003 韩梅梅
1001 数学 92
1001 英语 88
1002 数学 85
1003 数学 90
1003 英语 95
$ join students.txt courses.txt
1001 王小明 数学 92
1001 王小明 英语 88
1002 李雷 数学 85
1003 韩梅梅 数学 90
1003 韩梅梅 英语 95
$ join -o '1.2 2.2 2.3' students.txt courses.txt
王小明 数学 92
王小明 英语 88
李雷 数学 85
韩梅梅 数学 90
韩梅梅 英语 95
$ rm -f students.txt courses.txt
键 1001 在两个文件中出现一次与两次,产出 1 × 2 = 2 行(数学 与 英语 各一行)——join 对重复键做两两组合,行数可能多于任一输入文件,统计行数时要注意。-o '1.2 2.2 2.3' 顺带演示了"去键列、只取需要的字段"。
💡 提示:本文示例键均保证已排序(1001、1001、1002、1003、1003 仍是非递减序列);重复键 + 乱序叠加会让 join 行为更难预测,务必先排序。
4.4 库存对账:-o auto 与 -a1、-e 组合
商品表左外连接库存表,缺货商品填 0——-o auto 让列结构由首行自动推导,免去手工写格式:
$ join -t , -a1 -e 0 -o auto products.csv stock.csv
1,apple,5,100
2,banana,3,200
3,cherry,8,0
cherry 在库存表没有记录,第 4 列被 -e 0 填成 0;-o auto 依据两文件首行各有多少字段自动输出"键 + FILE1 全部剩余字段 + FILE2 全部剩余字段",与手写 -o '0,1.2,1.3,2.2' 等效。
💡 提示:
-o auto的字段数来自首行(帮助原文),因此要求两文件的列数全程一致;列数不齐的脏数据请先清洗。
五、注意事项与常见问题
Q1:报 is not sorted 怎么处理?
两文件必须按键列排好序(帮助原文加粗强调)。用与 join 相同的排序规则先排序再连接:sort -k 1,1(见 3.2 修复实测);分隔符场景注意 -t 两侧保持一致。
Q2:-e 加了没生效?
-e 只在配合 -o 时生效:-o 固定列结构,缺失字段才有位置可填。3.7 的 cat -A 对照显示,无 -o 时未匹配行直接省略空字段。
Q3:键列有重复值会怎样? 两文件中的重复键会两两组合、各输出一行(4.3 实测:1 个学生 × 2 门课 = 2 行)。结果行数可能大于两个输入文件,做计数统计前先想清楚。
Q4:-j 与 -1/-2 什么关系?
-j FIELD 就是 -1 FIELD -2 FIELD 的缩写(帮助原文),两文件键列号相同时用它最省事;键列号不同分别写 -1/-2(3.3 实测)。
Q5:默认的分隔规则是什么?
无 -t 时以空白分隔且忽略前导空白(帮助原文);-t CHAR 后变为严格单字符分隔(如 CSV 的逗号)。两种模式的字段切分结果可能不同,切换时要重新核对键列号。
Q6:join 与 paste 怎么选? join 按键值关联、要求排序、支持内外连接,适合"数据库式"合并;paste 按行号并列、无排序要求、无条件对齐,适合"行序天然对应"的快速拼接。选型对比详见《paste 命令详解与参数实测(Ubuntu 22.04)》3.1。
Q7:CSV 文件第一行是表头怎么办?
帮助原文提供 --header:两文件首行按表头处理、直接输出不参与配对。本文演示数据为保持输出干净未带表头;带表头文件也可先 tail -n +2 去头再连接。
Q8:输出里键值排第一列吗?
是的。默认格式为"键 + FILE1 剩余字段 + FILE2 剩余字段"(帮助原文);要调整列序用 -o 显式指定(3.5 实测)。
六、参考链接
- GNU coreutils 官方手册(join 章节):https://www.gnu.org/software/coreutils/manual/html_node/join-invocation.html
- 在线文档:https://www.gnu.org/software/coreutils/join
- 本机帮助:
join --help、man join(Ubuntu 22.04 内置)