一个有趣的现象
最近在调试一个问题时,发现了一个有趣的现象:touch
一个存在的文件,文件的mtime
发生了更新,文件所在目录的mtime
不会更新;而使用vim
编辑这个文件后再保存,文件和文件所在目录的mtime
都会被更新。为什么会这样呢?本文将解释一下这种现象的成因。
背景知识
使用ls -al
命令查看文件或目录时,在倒数第二列我们可以看到一个时间,它是文件最近的修改时间modify time(mtime
)。而使用stat
查看文件信息时,除了可以看到有mtime
,还有access time(atime
)、change time(ctime
)和birth time(btime
/crtime
):
$ ls -al
total 8
drwxr-xr-x 2 imred imred 4096 Jul 17 22:19 .
drwxr-xr-x 3 imred imred 4096 Jul 17 22:19 ..
-rw-r--r-- 1 imred imred 0 Jul 17 22:19 abc.txt$ stat abc.txtFile: abc.txtSize: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 820h/2080d Inode: 5682 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ imred) Gid: ( 1000/ imred)
Access: 2024-07-17 22:19:20.927884988 +0800
Modify: 2024-07-17 22:19:20.927884988 +0800
Change: 2024-07-17 22:19:20.927884988 +0800Birth: 2024-07-17 22:19:20.927884988 +0800
使用man 7 inode
可以得到关于这些时间的详细定义:
Last access timestamp (atime)stat.st_atime; statx.stx_atimeThis is the file's last access timestamp. It is changed by file accesses, for example, by execve(2), mknod(2), pipe(2), utime(2), and read(2) (of more than zero bytes). Other interfaces, such as mmap(2), may or may not update the atime timestampSome filesystem types allow mounting in such a way that file and/or directory accesses do not cause an update of the atime timestamp. (See noatime, nodiratime, and relatime in mount(8), and related information in mount(2).) In addition, the atime timestamp is not updated if a file is opened with the O_NOATIME flag; see open(2).File creation (birth) timestamp (btime)(not returned in the stat structure); statx.stx_btimeThe file's creation timestamp. This is set on file creation and not changed subsequently.The btime timestamp was not historically present on UNIX systems and is not currently supported by most Linux filesystems.Last modification timestamp (mtime)stat.st_mtime; statx.stx_mtimeThis is the file's last modification timestamp. It is changed by file modifications, for example, by mknod(2), truncate(2), utime(2), and write(2) (of more than zero bytes). Moreover, the mtime timestamp of a directory is changed by the creation or deletion of files in that directory. The mtime timestamp is not changed for changes in owner, group, hard link count, or mode.Last status change timestamp (ctime)stat.st_ctime; statx.stx_ctimeThis is the file's last status change timestamp. It is changed by writing or by setting inode information (i.e., owner, group, link count, mode, etc.).
我们在意的是mtime
,对于文件来说,它在文件内容被修改时会得到更新,而目录的mtime
更新则发生在当目录中的文件创建或删除时。当文件的权限信息等元信息被修改时,mtime
并不会更新,而是会更新ctime
。
成因探讨
在test
目录下有一个名为abc.txt
的文件,我们先看一下其信息:
$ stat . | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 4700 Links: 2
Modify: 2024-07-17 22:58:14.387883267 +0800Birth: 2024-07-17 22:19:06.287885009 +0800$ stat abc.txt | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 5682 Links: 1
Modify: 2024-07-17 22:19:20.927884988 +0800Birth: 2024-07-17 22:19:20.927884988 +0800
test
的mtime
为22:58:14
,abc.txt
的mtime
为22:19:20
。
使用touch
更新abc.txt
的mtime
后,再次查看:
$ touch abc.txt$ stat . | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 4700 Links: 2
Modify: 2024-07-17 22:58:14.387883267 +0800Birth: 2024-07-17 22:19:06.287885009 +0800$ stat abc.txt | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 5682 Links: 1
Modify: 2024-07-17 23:06:50.407883335 +0800Birth: 2024-07-17 22:19:20.927884988 +0800
test
的mtime
仍然为22:58:14
,abc.txt
的mtime
则被更新为23:06:50
。
使用vim
编辑abc.txt
后再次查看:
$ vim abc.txt$ stat . | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 4700 Links: 2
Modify: 2024-07-17 23:09:48.437882556 +0800Birth: 2024-07-17 22:19:06.287885009 +0800$ stat abc.txt | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 5732 Links: 1
Modify: 2024-07-17 23:09:48.417882556 +0800Birth: 2024-07-17 23:09:48.417882556 +0800
test
和abc.txt
的mtime
都被更新为23:09:48
。
为什么使用touch
和使用vim
会有这种差异呢?难道是因为touch
并没有真的改变文件内容导致的?那我们使用echo
命令来编辑abc.txt
试试:
$ echo xxx > abc.txt$ stat . | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 4700 Links: 2
Modify: 2024-07-17 23:09:48.437882556 +0800Birth: 2024-07-17 22:19:06.287885009 +0800$ stat abc.txt | grep -E "Modify|Birth|Inode"
Device: 820h/2080d Inode: 5732 Links: 1
Modify: 2024-07-17 23:19:02.617882317 +0800Birth: 2024-07-17 23:09:48.417882556 +0800
和touch
一样,只有abc.txt
的mtime
更新了,所以文件内容是否发生了实质性修改这个因素并不会影响结果,那vim
编辑文件时和echo
编辑文件时到底有什么差异?
如果你观察够仔细,会发现我在过滤stat
输出时,把Inode
和btime
也打印出来了。对比一下前后几次stat
命令的输出,我们会看到,在使用vim
编辑abc.tx
t后,其Inode
从5682
变成了5732
,btime
从22:19:20
变成了23:09:48
。换言之,使用vim
编辑后的文件已经不是原来那个文件了。那么期间在test
目录下必定创建了新的abc.txt
,因此test
目录的mtime
更新也就是理所应当的了。
那么使用vim
编辑一个文件时,在文件系统中究竟发生了什么呢?我们可以使用iwatch
命令来研究这个问题。使用iwatch
监听test
目录,同时使用vim
修改abc.txt
,查看iwatch
的输出:
$ iwatch . -e all_events
[17/Jul/2024 23:31:58] IN_CREATE ./.abc.txt.swp
[17/Jul/2024 23:31:58] IN_CREATE ./.abc.txt.swx
[17/Jul/2024 23:31:58] IN_CLOSE_WRITE ./.abc.txt.swx
[17/Jul/2024 23:31:58] IN_DELETE ./.abc.txt.swx
[17/Jul/2024 23:31:58] * ./.abc.txt.swx is deleted
[17/Jul/2024 23:31:58] IN_CLOSE_WRITE ./.abc.txt.swp
[17/Jul/2024 23:31:58] * ./.abc.txt.swp is closed
[17/Jul/2024 23:31:58] IN_DELETE ./.abc.txt.swp
[17/Jul/2024 23:31:58] * ./.abc.txt.swp is deleted
[17/Jul/2024 23:31:58] IN_CREATE ./.abc.txt.swp
[17/Jul/2024 23:32:02] IN_CREATE ./4913
[17/Jul/2024 23:32:02] IN_CLOSE_WRITE ./4913
[17/Jul/2024 23:32:02] IN_DELETE ./4913
[17/Jul/2024 23:32:02] * ./4913 is deleted
[17/Jul/2024 23:32:02] IN_MOVED_FROM ./abc.txt
[17/Jul/2024 23:32:02] IN_MOVED_TO ./abc.txt~
[17/Jul/2024 23:32:02] * ./abc.txt is moved to ./abc.txt~
[17/Jul/2024 23:32:02] IN_CREATE ./abc.txt
[17/Jul/2024 23:32:02] IN_CLOSE_WRITE ./abc.txt
[17/Jul/2024 23:32:02] * ./abc.txt is closed
[17/Jul/2024 23:32:02] IN_DELETE ./abc.txt~
[17/Jul/2024 23:32:02] * ./abc.txt~ is deleted
[17/Jul/2024 23:32:02] IN_CLOSE_WRITE ./.abc.txt.swp
[17/Jul/2024 23:32:02] IN_DELETE ./.abc.txt.swp
[17/Jul/2024 23:32:02] * ./.abc.txt.swp is deleted
你会发现,vim
编辑文件时绝非打开、读取、修改、写入这样的流程,而是会创建多个临时文件。原始的abc.txt
文件会被重命名为abc.txt~
,然后创建新的abc.txt
,写入修改后的内容。如果整个过程中没有发生错误,所有的临时文件最终都会删除,只剩下一个abc.txt
。因为涉及到在目录中创建和删除文件的操作,mtime
被更新也就理所当然了,这也就解释了文章开头描述的有趣现象。
补充信息
.abc.txt.swp
有什么作用?
主要有两个作用,一个是用于存储那些尚未写入磁盘的变更,防止vim
崩溃后丢失数据;另一个是为了避免多个vim
实例编辑同一个文件,当你使用多个vim
实例编辑同一个文件时,你会得到类似这样的警告:
E325: ATTENTION
Found a swap file by the name ".abc.txt.swp"
...
参考链接:https://vi.stackexchange.com/questions/177/what-is-the-purpose-of-swap-files
abc.txt~
有什么作用?
这个文件是用来备份原始文件数据的,如果vim
直接编辑原始文件,编辑过程中又发生了错误,没有这个备份文件的话,原始文件数据可能就丢失了。
参考链接:https://stackoverflow.com/questions/607435/why-does-vim-save-files-with-a-extension