使用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;
因此,在将file
重命名为prefix_file
之后,readdir
将在prefix_file
循环的后续迭代中找不到while
(然后将其重命名)再次到prefix_prefix_file
等等?可能很明显它不会这样做,但由于我在文档中找不到它,我还是会问这个问题。
答案 0 :(得分:7)
底层系统调用是POSIX' readdir()
,规范说:
如果在最近一次调用
opendir()
或rewinddir()
后从目录中删除或添加了文件,则后续调用readdir()
是否返回该文件的条目未指定
它只是意味着您可能会或可能不会看到文件。您可能会发现特定平台确实指定了发生的情况,但它可能无法移植到其他系统。
但是,
rename
既不添加也不删除任何目录条目。它只编辑了一个。
我回答:
它(
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
包含非POSIX强制要求的成员d_namlen
。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
$
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
。 $ ./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
$
这是我使用默认HFS +在Mac OS X 10.11.6 El Capitan上获得的 文件系统。当目录很小(没有适度长 文件名),然后重命名的文件没有显示出来。额外的时候 创建文件,使目录大小约为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
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);
}
答案 1 :(得分:1)
最简单的方法是首先chdir
进入你的目录,这样就不会出现构建路径的问题,然后在列表上下文中使用glob
,它将一次性返回所有名称混乱的可能性
看起来像这样(未经测试)
use strict;
use warnings 'all';
use autodie qw/ chdir rename /;
chdir 'tdir';
rename $_, "prefix_$_" for grep -f, glob '*';