如果打开的文件被移动或删除
1. 概述
在本教程中,我们将了解当我们删除、移动或替换具有打开文件句柄的文件时系统的行为。 首先,我们将简要讨论文件和 inode。然后,我们将浏览不同的场景,看看每个场景会发生什么。
2. 了解文件和索引节点
当我们在 Linux 文件系统上工作时,它使用 inode 来存储有关文件的信息。
当我们列出文件夹中的文件时,我们会看到指向 inode 的链接。一个 inode 可以有多个链接,这些链接可以是符号链接或硬链接 。
我们可以在文件上使用stat 并查看它指的是哪个 inode:
$ touch inode_example
$ stat inode_example
File: inode_example
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd03h/64771d Inode: 20448632 Links: 1
这个文件指的是inode 20448632,stat也说它有一个链接。让我们添加一个新的硬链接并在inode_example上重新运行stat:
$ ln inode_example hardlink_inode_example
$ stat inode_example
File: inode_example
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd03h/64771d Inode: 20448632 Links: 2
我们可以看到Links值增加了 1。现在,我们也可以在hardlink_inode_example上使用stat:
$ stat hardlink_inode_example
File: hardlink_inode_example
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd03h/64771d Inode: 20448632 Links: 2
请注意,硬链接也指向相同的 inode 20448632。hardlink_inode_example和inode_example这两个文件都是指向同一个 inode 的硬链接。
与硬链接不同,符号链接有自己的 inode 编号。
考虑到所有这些,我们可以将文件名视为 inode 的别名,并且我们可以将多个文件链接到同一个 inode。
3. 删除文件
当我们打开一个文件时,我们会得到一个指向它的文件描述符,也称为文件句柄。我们可以使用已删除文件的打开文件句柄。我们可以像文件存在一样对其进行读写。文件名在文件系统中不可见,但我们的文件句柄将指向仍然存在的 inode。
让我们编写一个名为remove_opened_file.sh的脚本来测试这个想法:
#!/bin/bash
FILE="/tmp/remove_example"
( sleep 1
echo "Before rm." >&4
rm "$FILE"
if [ ! -e "$FILE" ]; then
echo "The file $FILE was removed."
fi
echo "After rm." >&4
) 4>"$FILE" &
( sleep 2
echo "This is the content in file handle 4:"
cat <&4
) 4<"$FILE"
在这个脚本中,我们启动了两个子shell ,我们使用重定向 在两个子 shell中创建和打开*/tmp/remove_example作为文件句柄4*。当子shell终止时,系统将关闭文件句柄。
在第一个子shell中,我们首先等待一秒钟。我们这样做是为了确保第二个子 shell 已经启动并打开了文件。然后,我们向文件描述符4写入两行,一行在删除文件之前,另一行在之后。我们还在写最后一行之前test 该文件不存在。我们这样做只是为了清楚。
第二个子shell开始等待两秒钟,所以它保持文件打开,而第一个子shell写入并删除它。之后,它打印文件句柄4的内容。
让我们运行它,看看会发生什么:
$ ./remove_opened_file.sh
The file /tmp/remove_example was removed.
This is the content in file handle 4:
Before rm.
After rm.
如我们所见,即使文件不存在,第二个子shell也会打印两行。
这种行为是因为当我们删除一个文件时,我们实际上是从 inode 取消链接文件名。inode 仍然存在,因此如果我们保持文件打开,我们可以对其进行读写。
系统会在以下情况下删除 inode:
- 没有更多指向它的硬链接
- 没有打开的文件句柄
这还有另一个效果:如果我们删除了一个文件,但仍有指向它的打开文件句柄,则可用磁盘空间量不会改变。
4. 移动文件
系统可以通过两种方式移动文件:
- 重命名文件
- 将其内容复制到目标,然后删除源
根据情况,系统使用一种方法或另一种方法。例如,当我们将文件移动到不同的文件系统时,系统必须将文件复制到目标。另一方面,系统在同一文件系统上工作时可以简单地重命名文件。
4.1.文件被重命名
如果文件被重命名,这不会影响打开的文件句柄。我们仍然可以写入和读取它。由于inode是一样的,所以我们在文件移动后写入的内容会在目标文件中。
让我们编写move_opened_file.sh来移动打开的文件,看看会发生什么:
#!/bin/bash
FILE=/tmp/move_example
FILE_NEW=/tmp/move_example.new
( echo "Before mv." >&4
mv "$FILE" "$FILE_NEW";
if [ ! -e "$FILE" -a -e "$FILE_NEW" ]; then
echo "The file $FILE was moved to $FILE_NEW."
fi
echo "After mv." >&4
echo "This is the content in $FILE_NEW:"
cat "$FILE_NEW"
) 4>"$FILE"
在这个脚本中,我们使用了一个子 shell 和重定向,就像我们在上一节中所做的那样。我们在运行mv 命令之前和之后写入打开的文件句柄。最后,我们使用cat 打印新文件的内容。
我们可以注意到source 和 origin 在同一个文件夹*/tmp*中,因此系统可以简单地重命名文件。让我们看看结果:
The file /tmp/move_example was moved to /tmp/move_example.new.
This is the content in /tmp/move_example.new:
Before mv.
After mv.
正如我们从输出中看到的那样,新文件有两行,即使我们将这两行都写入了旧文件。
4.2. 文件被复制
当系统将文件复制到目标时,行为是不同的。在这种情况下,复制后写入源文件的内容不会在目标文件中。此外,一旦我们关闭文件句柄,我们将丢失这些新内容。这是因为目标文件将是一个新的 inode。我们打开的文件句柄将指向旧的 inode,一旦文件被复制,它将被删除。
现在,让我们在move_opened_file.sh脚本中更改$FILE_NEW,因此目标位于不同的文件系统中。我们可以将其更改为$FILE_NEW=~/move_by_copy_example.new,因为通常*/tmp和/home*位于不同的文件系统中。让我们看看结果:
The file /tmp/move_example was moved to /home/blogdemo/move_by_copy_example.new.
This is the content in /home/blogdemo/move_by_copy_example.new:
Before mv.
正如我们所料,新文件只有 mv 之前的那一行。我们仍然可以使用文件句柄,它将表现为已删除的文件。
5. 替换文件
当文件被替换时,情况类似于删除文件。如果我们有一个打开的文件句柄并且我们用另一个文件替换它,系统会删除原始文件。这意味着只要我们保持文件句柄打开,我们仍然可以使用该文件。
我们可以编写一个脚本来测试这种行为。我们称之为replace_opened_file.sh:
#!/bin/bash
FILE=/tmp/replace_example
FILE_OLD=/tmp/replace_example.old
echo "This is the old file." > $FILE_OLD
( sleep 1
echo "Before mv." >&4
mv "$FILE_OLD" "$FILE"
echo "After mv." >&4
) 4>"$FILE" &
( sleep 2
echo "This is the content in file handle 4:"
cat <&4
echo "This is the content in $FILE:"
cat $FILE
) 4<"$FILE"
在此脚本中,我们将首先创建一个旧文件。然后我们运行两个打开一个新文件的子 shell,就像我们在测试删除文件时所做的那样。第一个子 shell 将用旧文件替换新文件。最后,我们打印新文件的内容。
让我们运行replace_opened_file.sh:
$ ./replace_opened_file.sh
This is the content in file handle 4:
Before mv.
After mv.
This is the content in /tmp/replace_example:
This is the old file.
正如我们所看到的,只要我们打开文件,我们就可以对其进行写入和读取。但是,我们可以通过最后一行*cat “$FILE”*看到,当我们重新打开文件时,它具有旧文件的内容。