19

私は、Windowsで恐ろしく長いパスを作成するビルドプロセスの一部を持っています。それは私のせいではありません。いくつかのディレクトリの深さであり、どのディレクトリ名も異常に長くはありません。それらはちょうど長く、それを超えるのに十分な数ですMAX_PATH(260文字)。これらの名前にはASCII以外のものは使用していません。

大きな問題は、ターゲット中にModule :: Buildの内部で爆発が発生するdistことですが、同じディレクトリを作成するため、ビルドシステムは重要ではないと思います。

これらの長すぎるディレクトリの1つを作成すると、File::Path失敗します。

 use File::Path qw( make_path );

 make_path( 'C:\\.....' ); # fails if path is over 260 chars

同様に、絶対パスが通過すると、各ディレクトリレベルを手動で構築することは失敗しますMAX_PATH

これは新しいことではなく、Perlのせいでもありません。マイクロソフトは、ファイル、パス、名前空間の命名にそれを文書化しています。彼らの修正は\\?\、Unicodeファイル名APIにアクセスするためにパスの前にを追加することを提案しています。ただし、それでも失敗するため、Perlスクリプトの完全な修正ではないようです。

 use File::Path qw( make_path );

 make_path( '\\\\?\\C:\\.....' );  # still fails if path is over MAX_PATH, works otherwise

これはmake_path、引数を引き離してから、一度に1レベルずつディレクトリを通過するため、\\?\内にある最上位レベルにのみ適用されるためである可能性がありますMAX_PATH

私はActiveStateにバグレポートを掘り起こし、Unicodeファイル名を取得するために修正する必要がある他の何かがあることを示唆しています。JanDuboisは、Re:Windows 2K/XPの「長い」ファイル名についてもう少し詳しく説明しています。それが適用されるかどうかわからない(そして非常に古い)。perlrunは、これがスイッチの仕事であると述べていますが-C、明らかにその部分は放棄されました。perl RTキューには、より最近のバグ60888があります。Win32:ファイル名で完全なUnicodeをサポートします(ワイドシステムコールを使用)

宮川氏は、長いパスについて特に言及せずに、 Unicodeファイル名の問題Win32API::Fileについて言及しています。ただし、Win32API :: File CPANフォーラムのエントリは、恐れのみを示しているようです。これは怒りにつながり、憎しみにつながります。Perlmonksの投稿に例があります。WindowsでUnicode(UTF16-LE)ファイル名のファイルを統計する方法は?。それWin32::CreateDirectoryが答えのようです。次にWindowsマシンの隣に着いたときに、それを試してみます。

次に、ロングパスパスを作成できると仮定します。今、私はそれを処理するために、Module :: Build、そして多分他のことを教えなければなりません。それが缶に言うことをするならば、それはmonkeypatchesですぐに簡単かもしれませんWin32::GetANSIPathName()

4

5 に答える 5

10

Windowsには、文字列を処理する必要のある関数ごとに2つの個別のシステムコールがあります。ANSI別名アクティブコードページをエンコーディングとして使用する「A」呼び出し(例:cp1252)と、UTF-16leを使用する「W」呼び出しです。Perlは「A」呼び出しを使用しますが、\\?\「W」呼び出しでのみ機能します。

以下のスクリプトに示すように、Win32 :: APIを使用して「W」呼び出しにアクセスできますが、Win32 :: LongPathは「W」呼び出しを使用するだけでなく、自動的に追加します\\?\

Win32 :: APIをCreateDirectoryW使用して呼び出し、長いパス(\\?\-prefixed path)を使用する例:

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Encode qw( encode );
use Symbol;

use Win32;

use Win32API::File qw(
    CreateFileW OsFHandleOpen
    FILE_GENERIC_READ FILE_GENERIC_WRITE
    OPEN_EXISTING CREATE_ALWAYS FILE_SHARE_READ
);

use Win32::API;
use File::Spec::Functions qw(catfile);

Win32::API->Import(
    Kernel32 => qq{BOOL CreateDirectoryW(LPWSTR lpPathNameW, VOID *p)}
);

my %modes = (
    '<' => {
        access => FILE_GENERIC_READ,
        create => OPEN_EXISTING,
        mode   => 'r',
    },
    '>' => {
        access => FILE_GENERIC_WRITE,
        create => CREATE_ALWAYS,
        mode   => 'w',
    },
    # and the rest ...
);

use ex::override open => sub(*;$@) {
    $_[0] = gensym;

    my %mode = %{ $modes{$_[1]} };

    my $os_fh = CreateFileW(
        encode('UCS-2le', "$_[2]\0"),
        $mode{access},
        FILE_SHARE_READ,
        [],
        $mode{create},
        0,
        [],
    ) or do {$! = $^E; return };

    OsFHandleOpen($_[0], $os_fh, $mode{mode}) or return;
    return 1;
};

my $path = '\\\\?\\' . Win32::GetLongPathName($ENV{TEMP});
my @comps = ('0123456789') x 30;

my $dir = mk_long_dir($path, \@comps);
my $file = 'test.txt';
my $str = "This is a test\n";

write_test_file($dir, $file, $str);

$str eq read_test_file($dir, $file) or die "Read failure\n";

sub write_test_file {
    my ($dir, $file, $str) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '>', $path
        or croak "Cannot open '$path':$!";

    print $fh $str or die "Cannot print: $!";
    close $fh or die "Cannot close: $!";
    return;
}

sub read_test_file {
    my ($dir, $file) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '<', $path
        or croak "Cannot open '$path': $!";

    my $contents = do { local $/; <$fh> };
    close $fh or die "Cannot close: $!";
    return $contents;
}

sub mk_long_dir {
    my ($path, $comps) = @_;

    for my $comp ( @$comps ) {
        $path = catfile $path, $comp;
        my $ucs_path = encode('UCS-2le', "$path\0");
        CreateDirectoryW($ucs_path, undef)
            or croak "Failed to create directory: '$path': $^E";
    }
    return $path;
}
于 2009-11-12T13:37:34.243 に答える
6

次のコードは、実際には非常に深い(260文字を超える)ディレクトリ構造を作成します。少なくとも私のマシンでは:

use Win32::API;

$cd = Win32::API->new('kernel32', 'CreateDirectoryW', 'PP', 'N');

$dir = '\\\\?\\c:\\!experiments';

$res = 1;

do
{
    print 'path length: ' . length($dir) . "\n";
    $dirname = pack('S*', unpack('C*', "$dir\0"));  #dirty way to produce UTF-16LE string

    $res = $cd->Call($dirname, 0);
    print "$res\n";

    $dir .= '\\abcde';

} while ( $res );
于 2009-11-12T15:09:52.000 に答える
2

これはあなたの特定の問題の解決策ではないことを理解しています。ただし、非常に長いパスをドライブ文字にマップできると、問題を回避できるため、非常に長いパス名を処理するのに役立ちます。 Windows固有のコードとドキュメント。

これを行う方法を理解するために私が費やしたすべての努力にもかかわらず、私はどういうわけかを使用することをお勧めしSUBSTます。Win32::FileOpはとを提供SubstUnsubstます。次に、最上位の作業ディレクトリを未使用のドライブ文字(を使用して見つけることができますSubsted)にマップできます。私はチェックしZて逆方向に作業を開始します。

または、シェルアウトして、substパラメータなしでユーティリティを呼び出して現在の置換のリストを取得し、そこにないものを選択することもできます。

ビルドプロセス中に置換が変更される可能性があるため、これは完全に安全ではありません。

于 2009-11-12T15:39:37.757 に答える
2

これは本当にコメントであるはずですが、コメントにコードを投稿することはほとんど役に立ちません。

UNCパスも機能しません。

C:\>ネットシェア
perlbuild e:\ home \ src
#!/usr/bin/perl

use strict;
use warnings;

use File::Path qw(make_path);
use File::Slurp;
use Path::Class;

my $top = dir('//Computer/perlbuild');
my @comps = ('0123456789') x 30;

my $path = dir($top, @comps);

make_path $path, { verbose => 1 };

my $file = file($path, 'test.txt');

write_file "$file" => 'This is a test';

print read_file "$file";

結果:

mkdir \\ Computer \ perlbuild \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456
789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789
\ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 01
23456789 \ 0123456789:そのようなファイルまたはディレクトリはありません。ファイル名または拡張子も
 C:\ Temp\k.plの15行目で長い
于 2009-11-13T16:49:28.130 に答える
-1

私は3つの考えを持っていましたが、それらはすべて一種のハックです。

  1. いくつかの短いディレクトリ名(C:\ data_directory \ a \ b \ c \ d \ 4 \ 5 \ 6 \ ...)から始めて、ディレクトリの名前を変更します(もちろん、最も深いディレクトリから始めます)。

  2. 適度に長いパスへのWindowsショートカットを作成し、そこからファイルとサブディレクトリを作成しますか?(または、Cygwinをインストールしてシンボリックリンクを使用しますか?)

  3. 短い名前のディレクトリに目的のファイルを作成し、zip / tarして、長い名前のディレクトリに解凍します。または、zip / tarファイルを「手動で」作成し、目的の場所に解凍します。

于 2009-11-12T14:46:22.043 に答える