使用 readdir 时重命名文件是否安全? [英] Is it safe to rename files while using readdir?
问题描述
在使用 readdir
扫描目录时,您可以安全地重命名文件而不必担心进入无限递归?例如:
While scanning a directory with readdir
, can you safely rename files without worrying about entering an infinite recursion? For example:
use v5.12; # make readdir set $_ in while loops
use strict;
use warnings;
use File::Spec;
my $dir = 'tdir';
opendir ( my $dh, $dir ) or die "Could not open dir '$dir': $!";
while (readdir $dh) {
next if /^\.\.?\z/;
my $filename = File::Spec->catfile( $dir, $_ );
if ( -f $filename) {
my $newname = File::Spec->catfile( $dir, "prefix_$_" );
rename ($filename, $newname) or warn $!;
}
}
closedir $dh;
因此,例如将 file
重命名为 prefix_file
后,readdir
在以后的迭代中将找不到 prefix_file
while
循环(然后再次将其重命名为 prefix_prefix_file
等等?可能很明显它不会这样做,但是由于我在文档中找不到它无论如何都会问这个问题.
So after renaming for example file
to prefix_file
, readdir
will not find prefix_file
in a later iteration of the while
loop (and then rename it again to prefix_prefix_file
and so on? Probably obvious that it will not do this, but since I could not find it mentioned in the documentation I'll ask the question anyway.
推荐答案
答案
底层系统调用是POSIX的readdir()
,并且规范说:
Answer
The underlying system call is POSIX's readdir()
, and the specification says:
如果在最近一次调用 opendir()
或 rewinddir()
之后从目录中删除或添加文件,是否是对 的后续调用readdir()
返回该文件的条目未指定.
If a file is removed from or added to the directory after the most recent call to
opendir()
orrewinddir()
, whether a subsequent call toreaddir()
returns an entry for that file is unspecified.
这只是意味着您可能会或可能不会看到这些文件.您可能会发现特定平台确实指定了发生的情况,但它可能无法移植到其他系统.
It simply means that you might or might not get to see the files. You might find that a particular platform does specify what happens, but it probably isn't portable to other systems.
rename
既不添加也不删除任何目录条目.它只是编辑一个.
rename
neither adds nor removes any directory entries, though. It just edits one.
我回复了:
它(rename()
)改变了目录中的一个条目;会发生什么取决于 [文件系统] 的实现方式.如果您将文件名从 a
更改为 humongous-long-name-that-is-too-boring-to-be-believable
,则条目很有可能将在磁盘上的目录中移动,导致未指定的行为 [如主要答案中所述].... rename()
是否真的搞砸了使用 readdir()
的扫描取决于系统(操作系统和文件系统),这就是我所声称的.
It (
rename()
) changes an entry in the directory; what happens depends on how [the file system] is implemented. If you change the file name froma
tohumongous-long-name-that-is-too-boring-to-be-believable
, there's a decent chance that the entry will move in the directory on disk, leading to unspecified behaviour [as noted in the main answer]. … Whether …rename()
actually screws up a scan withreaddir()
depends on the system (operating system and file system), which is all that I claimed.
经过进一步讨论,我创建了这个示例,说明在一个特定系统上可以发生和确实发生的事情.我使用的步骤:
After further discussion, I created this example of what can and does happen on one specific system. I used the steps:
- 创建一个目录 - 其名称无关紧要.
- 进入那个目录.
- 将
readdir.c
和make.files.sh
复制到目录中. - 从源
readdir.c
创建程序readdir
(例如,使用make readdir
).- 代码假设
struct dirent
包含成员d_namlen
,这不是 POSIX 强制要求的. - 没有它也是可行的(但需要做一些小改动).
- Create a directory — its name does not matter.
- Change into that directory.
- Copy
readdir.c
andmake.files.sh
into the directory. - Create program
readdir
from sourcereaddir.c
(usemake readdir
, for example).- The code assumes that the
struct dirent
includes memberd_namlen
which is not mandated by POSIX. - It would be feasible to do without it (but minor changes would be needed).
$ ./readdir 44249044: ( 1) . 42588881: ( 2) .. 44260959: ( 10) .gitignore 44398380: ( 1) a Found entry 'a' - hit return to continue: Continuing... 44398371: ( 10) make.files 44398280: ( 13) make.files.sh 44398338: ( 8) makefile 44398351: ( 7) readdir 44260963: ( 9) readdir.c 44398352: ( 12) readdir.dSYM 44260960: ( 9) README.md 44398364: ( 6) rename 44260964: ( 8) rename.c 44398365: ( 11) rename.dSYM $
- 运行
sh make.files.sh
.这将创建文件moderately-long-file-name.000
..moderately-long-file-name.999
. - 再次运行
./readdir
.不要按回车键. - 切换到不同的终端窗口.
- 将目录更改为正在运行测试的目录.
- 运行:
mv a zzz-let-sleeping-file-renames-lie-unperturbed
- 切换回运行
readdir
的终端窗口. - 点击返回.您可能会看到类似于以下内容的输出:
- Run
sh make.files.sh
. This will create filesmoderately-long-file-name.000
..moderately-long-file-name.999
. - Run
./readdir
again. Do not hit return yet. - Switch to a different terminal window.
- Change directory to the one where the test is being run.
- Run:
mv a zzz-let-sleeping-file-renames-lie-unperturbed
- Switch back to the terminal window running
readdir
. - Hit return. You will probably see output similar to:
$ ./readdir 44249044: ( 1) . 42588881: ( 2) .. 44260959: ( 10) .gitignore 44398380: ( 1) a Found entry 'a' - hit return to continue: Continuing... 44398371: ( 10) make.files 44398280: ( 13) make.files.sh 44398338: ( 8) makefile 44431473: ( 29) moderately-long-file-name.000 44431474: ( 29) moderately-long-file-name.001 44431475: ( 29) moderately-long-file-name.002 ... 44432470: ( 29) moderately-long-file-name.997 44432471: ( 29) moderately-long-file-name.998 44432472: ( 29) moderately-long-file-name.999 44398351: ( 7) readdir 44260963: ( 9) readdir.c 44398352: ( 12) readdir.dSYM 44260960: ( 9) README.md 44398364: ( 6) rename 44260964: ( 8) rename.c 44398365: ( 11) rename.dSYM 44398380: ( 45) zzz-let-sleeping-file-renames-lie-unperturbed $
这是我在 Mac OS X 10.11.6 El Capitan 和默认 HFS+ 上得到的文件系统.当目录很小(没有中等长度的文件名),则重命名的文件没有出现.当额外创建的文件使目录大小约为 34 KiB,然后重命名的文件确实出现了.
This is what I got on Mac OS X 10.11.6 El Capitan with the default HFS+ file system. When the directory was small (without the moderately long file names), then the renamed file did not show up. When the extra files were created so that the directory size was around 34 KiB, then the renamed file did show up.
这表明在某些文件系统上(特别是 Apple 的 HFS+)在某些情况下,目录的
readdir()
扫描是受文件重命名操作的影响.如果您想编写和使用一个rename
命令而不是使用mv
,所以就这样 —当我尝试时这对结果没有影响.This demonstrates that on some file systems (specifically, Apple's HFS+) and under some circumstances, the
readdir()
scan of a directory is affected by a rename operation on a file. If you wish to write and use arename
command instead of usingmv
, so be it — when I tried, it made no difference to the result.在其他文件系统或其他操作系统上,YMMV.然而,这足以证明在某些系统上,重命名文件而
readdir()
正在进行扫描可能会出现相同的文件"在输出中两次.On other file systems or other operating systems, YMMV. However, this suffices to demonstrate that on some systems, renaming a file while a
readdir()
scan is in progress can end up with the same 'file' appearing twice in the output.#!/bin/sh for file in $(seq -f 'moderately-long-file-name.%03.0f' 0 999) do > "$file" done
readdir.c
/* SO 3901-5527 - attempt to demonstrate renaming moving entries */ #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static const char *stop_after = "a"; static void process_directory(const char *dirname) { DIR *dp = opendir(dirname); if (dp == 0) fprintf(stderr, "Failed to open directory %s\n", dirname); else { struct dirent *entry; while ((entry = readdir(dp)) != 0) { /* Ignore current and parent directory */ printf("%8d: (%3d) %s\n", (int)entry->d_ino, entry->d_namlen, entry->d_name); if (strcmp(entry->d_name, stop_after) == 0) { printf("Found entry '%s' - hit return to continue: ", stop_after); fflush(stdout); char *buffer = 0; size_t buflen = 0; getline(&buffer, &buflen, stdin); free(buffer); printf("Continuing...\n"); } } closedir(dp); } } int main(int argc, char **argv) { int opt; while ((opt = getopt(argc, argv, "s:")) != -1) { switch (opt) { case 's': stop_after = optarg; break;; default: fprintf(stderr, "%s: Unrecognized option '-%c'\n", argv[0], optopt); fprintf(stderr, "Usage: %s [-s stop_after] [directory ...]\n", argv[0]); return(EXIT_FAILURE); } } if (optind == argc) process_directory("."); else { for (int i = optind; i < argc; i++) process_directory(argv[i]); } return(0); }
这篇关于使用 readdir 时重命名文件是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- The code assumes that the
- 代码假设