GetAttributes在子线程中使用错误的工作目录
我使用File::Find
来遍历目录树和Win32::File
的GetAttributes
函数来查看在其中找到的文件的属性。这工作在一个单线程程序。GetAttributes在子线程中使用错误的工作目录
然后我把目录遍历到一个单独的线程,它停止工作。 GetAttributes
在“系统找不到指定的文件”的每个文件上都失败,作为$^E
中的错误消息。
我将问题追溯到File::Find
使用chdir
这一事实,并且显然GetAttributes
未使用当前目录。我可以通过传递一个绝对路径来解决这个问题,但是然后我可以运行到路径长度限制,并且在脚本运行的地方肯定会出现长路径,所以我真的需要利用chdir
和相对路径。
为了说明问题,这里是它会在当前目录中的文件的脚本,在子目录另一个文件,CHDIR对子目录,并查找文件3种方式:system("dir")
,open
和GetAttributes
。
当脚本不带参数运行,dir
显示子目录,open
发现在子目录中的文件,并GetAttributes
成功返回其属性。当与--thread
一起运行时,所有测试均以子线程完成,并且dir
和open
仍然有效,但GetAttributes
失败。然后它调用GetAttributes
对原始目录(我们已经chdir'ed了)的文件,它找到了一个!不知怎的,GetAttributes
正在使用进程的原始工作目录 - 或者可能是主线程的工作目录 - 与所有其他文件操作不同。
我该如何解决这个问题?我可以保证主线程不会做任何chdir'ing,如果这很重要。
use strict;
use warnings;
use threads;
use Data::Dumper;
use Win32::File qw/GetAttributes/;
sub doit
{
chdir("testdir") or die "chdir: $!\n";
system "dir";
my $attribs;
open F, '<', "file.txt" or die "open: $!\n";
print "open succeeded. File contents:\n-------\n", <F>, "\n--------\n";
close F;
my $x = GetAttributes("file.txt", $attribs);
print Dumper [$x, $attribs, $!, $^E];
if(!$x) {
# If we didn't find the file we were supposed to find, how about the
# bad one?
$x = GetAttributes("badfile.txt", $attribs);
if($x) {
print "GetAttributes found the bad file!\n";
if(open F, '<', "badfile.txt") {
print "opened the bad file\n";
close F;
} else {
print "But open didn't open it. Error: $! ($^E)\n";
}
}
}
}
# Setup
-d "testdir" or mkdir "testdir" or die "mkdir testdir: $!\n";
if(!-f "badfile.txt") {
open F, '>', "badfile.txt" or die "create badfile.txt: $!\n";
print F "bad\n";
close F;
}
if(!-f "testdir/file.txt") {
open F, '>', "testdir/file.txt" or die "create testdir/file.txt: $!\n";
print F "hello\n";
close F;
}
# Option 1: do it in the main thread - works fine
if(!(@ARGV && $ARGV[0] eq '--thread')) {
doit();
}
# Option 2: do it in a secondary thread - GetAttributes fails
if(@ARGV && $ARGV[0] eq '--thread') {
my $thr = threads->create(\&doit);
$thr->join();
}
最后,我想通了,是用perl保持某种只适用于perl的内置运营商二次CWD,而GetAttributes
是使用原生的CWD。我不知道它为什么这样做,或者为什么它只发生在辅助线程中;我最好的猜测是,perl试图模拟每个进程的一个cwd的unix规则,并且因为模块不兼容而失败。
不管是什么原因,有可能迫使本土CWD是一样的Perl的CWD,每当你即将做Win32::*
操作,这样来解决它:
use Cwd;
use Win32::FindFile qw/SetCurrentDirectory/;
...
SetCurrentDirectory(getcwd());
按理说File::Find
应在Win32上运行时执行此操作。
当然这只会让“路径名太长”的问题变得更糟,因为现在您访问的每个目录都将成为绝对路径的目标,即SetCurrentDirectory
;尝试通过一系列较小的SetCurrentDirectory
调用来解决此问题,并且您必须找出一种方法来找回来自哪里,当您甚至没有fchdir
时很难。