使用扫描目录时readdir,您是否可以安全地重命名文件而不必担心输入无限递归?例如:
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;
Run Code Online (Sandbox Code Playgroud)
因此,重命名,例如后file到prefix_file,readdir不会发现prefix_file在一个后面的迭代while循环(然后再重命名为prefix_prefix_file等等?也许明显,它不会做,但因为我无法找到我的文档中提到的无论如何我会问这个问题.
底层系统调用是POSIX readdir(),规范说:
如果在最近一次调用之后从目录中删除或添加了文件,
opendir()或者rewinddir()是否readdir()未指定后续调用以返回该文件的条目.
它只是意味着您可能会或可能不会看到文件.您可能会发现特定平台确实指定了发生的情况,但它可能无法移植到其他系统.
rename但是,既不添加也不删除任何目录条目.它只编辑了一个.
我回答了:
It(
rename())更改目录中的条目; 会发生什么取决于[文件系统]的实现方式.如果您将文件名更改a为humongous-long-name-that-is-too-boring-to-be-believable,则该条目将很可能在磁盘上的目录中移动,从而导致未指定的行为[如主要答案中所述].......是否......rename()实际上是否会扫描一次扫描readdir()取决于系统(操作系统和文件系统),这就是我所声称的.
经过进一步的讨论,我创建了一个关于一个特定系统能够和确实发生了什么的例子.我用过的步骤:
readdir.c并make.files.sh进入目录.readdir从源创建程序readdir.c(make readdir例如,使用).
struct dirent包含成员d_namlen不是POSIX强制要求的.a../readdir.提示您时返回.您应该看到与此类似的输出,但inode编号将不同. $ ./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
$
Run Code Online (Sandbox Code Playgroud)
sh make.files.sh.这将创建文件moderately-long-file-name.000.. moderately-long-file-name.999../readdir一次.不要回来了.mv a zzz-let-sleeping-file-renames-lie-unperturbedreaddir. $ ./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
$
Run Code Online (Sandbox Code Playgroud)
这是我在Mac OS X 10.11.6 El Capitan上使用默认的HFS +文件系统.当目录很小(没有中等长度的文件名)时,重命名的文件没有显示出来.创建额外文件以使目录大小约为34 KiB时,重命名的文件确实显示出来.
这表明在某些文件系统(特别是Apple的HFS +)上,在某些情况下,readdir()目录的扫描会受到文件重命名操作的影响.如果你想编写和使用rename命令而不是使用mv,那么就这样 - 当我尝试时,它对结果没有任何影响.
在其他文件系统或其他操作系统上,YMMV.但是,这足以证明在某些系统上,在readdir()扫描进行过程中重命名文件
最终可能会在输出中出现两次相同的"文件".
make.files.sh#!/bin/sh
for file in $(seq -f 'moderately-long-file-name.%03.0f' 0 999)
do > "$file"
done
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)