linux下的diff和patch使用

diff命令

diff命令时unix中用来比较文件差异的工具,也通常用于代码管理中。

diff命令显示结果有三种格式:

  • 正常格式(normal diff) diff f1 f2
  • 上下文格式(context diff) diff -c f1 f2
  • 合并格式(unified diff) diff -u f1 f2
  • side-by-side格式 diff -y f1 f2

比较文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost b]# cat -n f1
1 0
2 3
3 1
4 a
5 b
6 c
7 d
8 e
9 f
[root@localhost b]# cat -n f2
1 3
2 a
3 b
4 aaa
5 f
6 bbb
7 ccc
8 ddd
[root@localhost b]#

正常格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost b]# diff f1 f2
1d0 # 变动位置说明, f1文件的第一行被删除(d,delete)
< 0 # `<`行 表示f1文件变动行的内容
3d1
< 1
6,8c4 # f1文件的第6行到第8行内容被修改(c, change)成f2文件的第四行内容
< c
< d
< e
---
> aaa # `>`行 表示f2文件变动行的内容
9a6,8 # f1文件第9行后面被增加(a, add)了f2文件的第6行到第8行的内容
> bbb
> ccc
> ddd
[root@localhost b]#

上下文格式

上个世纪80年代初,加州大学伯克利分校推出BSD版本的Unix时,觉得diff的显示结果太简单,最好加入上下文,便于了解发生的变动。因此,推出了上下文格式的diff
可以通过 diff -C Numdiff --context[=Num]的方式指定显示上下文的行数,默认为3行(前三行加后三行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost b]# diff f1 f2 -c
*** f1 2020-05-22 00:21:43.668716845 +0800 # *** 表示变动前文件
--- f2 2020-05-22 00:32:51.747625563 +0800 # --- 表示变动后文件
*************** # 15个星号,表示内容分割
*** 1,9 **** # 显示变动前的文件(***)的第一行到第九行
- 0 # - 表示改行被删除
3
- 1
a
b
! c # ! 表示内容被修改
! d
! e
f
--- 1,8 ---- # 显示变动后的文件(---)的第一行到第八行
3
a
b
! aaa # 对应变动前的修改部分
f
+ bbb # + 表示新增内容
+ ccc
+ ddd
[root@localhost b]#

合并格式

如果两个文件相似度很高,那么上下文格式的diff,将显示大量重复的内容,很浪费空间。1990年,GNU diff率先推出了”合并格式”的diff,将f1和f2的上下文合并在一起显示
可以通过 diff -U Numdiff --unified[=Num]的方式指定显示上下文的行数,默认为3行(前三行加后三行)

git的diff格式就是采用的合并格式的变体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost b]# diff f1 f2 -u
--- f1 2020-05-22 00:21:43.668716845 +0800 # --- 表示变动前的文件
+++ f2 2020-05-22 00:32:51.747625563 +0800 # +++ 表示变动后的文件
@@ -1,9 +1,8 @@ # 变动的位置用两个@作为起首和结束
# f1文件从第一行开始连续9行的内容变成了f2文件的第一行开始连续8行内容
-0 # - 表示 f1 删除的行
3
-1
a
b
-c
-d
-e
+aaa # + 表示 f2 新增的行
f
+bbb
+ccc
+ddd
[root@localhost b]#

side-by-side格式

这种格式下,会将两个文件的内容分成两列对比展示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost b]# diff -y -W 20 f1 f2
0 < # < 表示只在f1文件中存在
3 3 # 表示两个文件内容相同
1 <
a a
b b
c | aaa # 两个文件中的内容有差异
d <
e <
f f
> bbb # > 表示只在f2文件中存在
> ccc
> ddd
[root@localhost b]#

命令格式说明

diff [选项] [文件1或目录1] [文件2或目录2](四种组合方式)

比较两个文件时,如果其中一个文件为 - 符号,表示从标准输入读取,以 ctrl+d 结束输入
如果输入的参数一个时文件,一个是目录时,会在该目录下查找同名文件进行比较,如果文件不存,则提示失败
如果输入的参数是两个目录时,也是只比较目录下的同名文件,默认是只比较目录下的文件,不会递归比较子目录
diff命令返回0 表示无差异,返回1 表示有差异, 返回2 表示命令出错(如文件不存在等)

常用的选项

选项 说明
--normal 正常格式进行展示,如果不指定显示格式,默认为正常格式
-c-C NUM--context=NUM 按照上下文格式进行展示
-u-U NUM--unified=NUM 按照合并格式进行展示
-y--side-by-side 按照side-by-side格式进行展示
-W NUM--width=NUm 指定-y模式下显示的总列数,默认为130
--suppress-common-lines 指定-y模式下,不显示相同内容的行
-r--recursive 递归比较子目录
-x--exclude=PAT 排除与PAT(pattern样式)匹配的文件
-X--exculde-from=FILE 排除与FILE中样式匹配的文件
-i--ignore-case 忽略大小写的区别
-b--ignore-space-change 忽略空格的差异
-w--ignore-all-space 忽略所有的空白差异
-B--ignore-blank-line 忽略空白行差异
-I RE--ignore-matching-lines=RE 忽略所有匹配RE(regexp正则表达式)的行的更改
-Z--ignore-trailing-space 忽略行尾空格
-E--ignore-tab-expansion 忽略tab扩展差异
--ignore-file-name-case 比较时忽略文件大小写
--no-ignore-file-name-case 比较时不忽略文件大小写
--ignore-file-name-case 比较时忽略文件大小写
-N--new-file 比较目录时,如果f3只在a目录中存在,默认会输出Only in a: f3,如果配置了该参数后,diff命令会将f3文件与一个空白文件进行对比
-p--show-c-function 如果比较的文件时c语言源文件,显示差异所在的函数名
-P--unidirectional-new-file -N类似,只有当第二个目录包含了第一个目录没有的文件时,才会将这个文件与空白文件做对比

patch命令

执行patch命令时,patch文件中定义的修改后的目录不能存在

patch命令说明

patch命令的使用有两种格式

  • 如果针对单文件打补丁: patch [option] [originalfile [patchfile]]
  • 同时常用更多的方式为: patch -pnum < patchfile

常用的命令选项
| 名称 | 说明 |
| ———————- | ———————————————————————————————————————————————— |
| --dry-run | 只是打印应用补丁后的结果,并不会实际修改文件 |
| -R--reverse | 该命令执行时会假设补丁文件是通过交换ori和mod参数,实际上可以对打过补丁后的文件做回滚操作 |
| -b--backup | 对每一个修改的文件做备份,会生成xxx.orig备份文件 |
| -pNum--strip=NUM | 设置执行命令时会去除patchfile中的几层路径名。0表示不删除,使用patchfile中的全路径,如果未指定该参数或NUM设置有问题,会提示找不到需要打补丁的文件 |

diff + patch的补丁应用

如果是对目录制作补丁文件,目录应该使用相对路径,不要使用绝对路径
两个目录的结构要一样

根据修改后目录的内容不同,生成补丁的方式也会不一样

  • 如果mod的目录中只包含了对ori目录中某些文件的修改或新增了某些文件

    diff -urP src_ori_dir src_mod_dir > src.patch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    [root@localhost test_diff_patch]# tree ./src_ori
    ./src_ori # ori目录中有两个文件 123 和 abc,文件中的内容与文件名相同
    ├── 123
    └── abc

    0 directories, 2 files
    [root@localhost test_diff_patch]# cat ./src_ori/*
    123
    abc
    ###
    # patch中修改了abc文件的内容为bbb,并新增了文件ccc
    ###
    [root@localhost test_diff_patch]# cat src.patch
    Only in src_ori: 123
    diff -ruP src_ori/abc src_mod/abc
    --- src_ori/abc 2020-05-23 14:25:13.895198462 +0800
    +++ src_mod/abc 2020-05-23 14:04:06.095735637 +0800
    @@ -1 +1 @@
    -abc
    +bbb
    diff -ruP src_ori/ccc src_mod/ccc
    --- src_ori/ccc 1970-01-01 08:00:00.000000000 +0800
    +++ src_mod/ccc 2020-05-23 14:25:41.160887860 +0800
    @@ -0,0 +1 @@
    +ccc
    [root@localhost test_diff_patch]# patch -p0 < src.patch
    patching file src_ori/abc
    patching file src_ori/ccc
    [root@localhost test_diff_patch]# tree ./src_ori
    ./src_ori # 执行了patch命令后,ori中多了ccc文件,abc的内容也变成了bbb
    ├── 123
    ├── abc
    └── ccc

    0 directories, 3 files
    [root@localhost test_diff_patch]# cat ./src_ori/*
    123
    bbb
    ccc
    [root@localhost test_diff_patch]#
  • 只要mod的目录中有对ori目录文件做删除,那么mod目录中必须包含未删除ori中的其他所有文件

    diff -urN src_ori_dir src_mod_dir > src.patch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    # ori中文件内容与文件名相同
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ ll ./*/*
    -rwxrwxrwx 1 baoze baoze 7 Jun 2 18:55 ./mod/aaa*
    -rwxrwxrwx 1 baoze baoze 4 Jun 2 18:54 ./ori/aaa*
    -rwxrwxrwx 1 baoze baoze 4 Jun 2 18:54 ./ori/bbb*

    ./ori/inner:
    total 0
    drwxrwxrwx 1 baoze baoze 512 Jun 2 18:54 ./
    drwxrwxrwx 1 baoze baoze 512 Jun 2 18:54 ../
    -rwxrwxrwx 1 baoze baoze 4 Jun 2 18:54 ccc*
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$
    ## mod中内容修改了aaa文件,删除了bbb和ccc文件
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ diff -ruN ori mod > mod.patch
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ cat mod.patch
    diff -ruN ori/aaa mod/aaa
    --- ori/aaa 2020-06-02 18:54:37.447786500 +0800
    +++ mod/aaa 2020-06-02 18:55:36.400920500 +0800
    @@ -1 +1 @@
    -aaa
    +newaaa
    diff -ruN ori/bbb mod/bbb
    --- ori/bbb 2020-06-02 18:54:42.786351600 +0800
    +++ mod/bbb 1970-01-01 08:00:00.000000000 +0800
    @@ -1 +0,0 @@
    -bbb
    diff -ruN ori/inner/ccc mod/inner/ccc
    --- ori/inner/ccc 2020-06-02 18:54:29.759650700 +0800
    +++ mod/inner/ccc 1970-01-01 08:00:00.000000000 +0800
    @@ -1 +0,0 @@
    -ccc
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$
    # 对ori进行打补丁
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ patch -p0 < mod.patch
    patching file ori/aaa
    patching file ori/bbb
    patching file ori/inner/ccc
    # 打补丁后ori目录下只有aaa文件,内容也有修改
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ ll ori/
    total 0
    drwxrwxrwx 1 baoze baoze 512 Jun 2 19:08 ./
    drwxrwxrwx 1 baoze baoze 512 Jun 2 19:06 ../
    -rwxrwxrwx 1 baoze baoze 7 Jun 2 19:08 aaa*
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$ cat ori/aaa
    newaaa
    baoze@DESKTOP-EV6CLJE:/mnt/d/workspace/patch_test$