Что нового

Получить точный путь с учётом регистра

Andrey_A

Продвинутый
Сообщения
319
Репутация
68
Есть реальные пути, но они искажены символами верхнего и нижнего регистра.
Есть функция, которая позволяет получать точный путь учитывая регистр:
Код:
$sFile='D:\CollectionFiles2\ПрИвЕт\Name.txt' ; реальный путь учитывая регистр
$sFile='d:\collectionfiles2\привет\name.txt' ; искажённый
; $sPath=_Get_Info_Path($sFile,180) ; для Windows 7
$sPath=_Get_Info_Path($sFile,194) ; для Windows 10
MsgBox(4096,'Переменная $sPath',$sPath)

$sFile='C:\Windows\System32\explorer.exe' ; реальный путь учитывая регистр
$sFile='C:\WINDOWS\SYSTEM32\EXPLORER.EXE' ; искажённый
; $sPath=_Get_Info_Path($sFile,180) ; для Windows 7
$sPath=_Get_Info_Path($sFile,194) ; для Windows 10
MsgBox(4096,'Переменная $sPath',$sPath)

$sFile='C:\Windows\System32\DriverStore\' ; реальный путь учитывая регистр
$sFile='c:\windows\SYSTEM32\driverstore\' ; искажённый
; $sPath=_Get_Info_Path($sFile,180) ; для Windows 7
$sPath=_Get_Info_Path($sFile,194) ; для Windows 10
MsgBox(4096,'Переменная $sPath',$sPath)

Func _Get_Info_Path($sFile,$iIndex)
  $oApp=ObjCreate('Shell.Application')
  If IsObj($oApp)Then
    $aPath=StringRegExp($sFile,'(.*\\)(.+\\?)',3)
    $oObject=$oApp.NameSpace($aPath[0])
    If IsObj($oObject)Then
      $oName=$oObject.ParseName($aPath[1])
      If IsObj($oName)Then Return $oObject.GetDetailsOf($oName,$iIndex)
    EndIf
  EndIf
  Return $sFile
EndFunc

Минусы этого метода:
1. Он очень медленный, если проверять тысячи файлов
2. Номер информации может меняться на разных системах, к примеру на 7-ке - 180, на Win10 - 194, возможно какие-то ещё есть ...
3. Нашёл недавно ещё один минус - не для любых файлов это работает, к примеру, не для всех файлов DLL возвращается нужная информация. Но это можно поправить: получить ИМЯ через нулевой индекс и направить родительский путь обратно в функцию.

Есть ли способ сделать подобное на API функциях, чтобы было более стабильно?

В системе где-то это есть - если вызвать диалог "Свойства" для любого файла/каталога, то там будет путь и имя в правильном регистре.
Сообщение автоматически объединено:

Есть более простой метод:
Код:
Func __Get_Info_Path($sFile)
  $aPath=StringRegExp($sFile,'(.*\\)([^\\]+)(\\?)',3)
  $oApp=ObjCreate('Shell.Application')
  Return $oApp.NameSpace($aPath[0]).ParseName($aPath[1]).Path
EndFunc


Но он выдаёт ошибку при нахождении файла в скрытых/системных папках:
Код:
$sFile='C:\WINDOWS\Fonts\arial.ttf'
$sFile='C:\Windows\Downloaded Program Files\desktop.ini'


Такие пути не получить, у меня WIN10
Конечно можно поставить проверки и ошибок не будет, но , возможно найдётся метод без Shell.Application...
 
Последнее редактирование:

Alecsis

Осваивающий
Сообщения
98
Репутация
41
Такие пути не получить, у меня WIN10
Таки да, их можно получить. «Вот-с по-нашему, по-неучёному»© :drinks:
Код:
Local $sFile1='C:\WINDOWS\FoNTs\arIAl.ttf'
ConsoleWrite('File name "As Is" = ' & FileGetLongName($sFile1) & @CRLF)
ConsoleWrite('Windows version   = ' & @OSVersion & @CRLF)
ConsoleWrite('AutoIt version    = ' & @AutoItVersion & @CRLF)

Протокол запуска:
>"C:\DevTools\AutoIt3\SciTE\..\AutoIt3.exe"       /ErrorStdOut "D:\Alecsis\Prog\AutoIt\_Debug\ttGetLFN.au3"   
File name "As Is" = C:\Windows\Fonts\arial.ttf
Windows version   = WIN_10
AutoIt version    = 3.3.16.1
>Exit code: 0    Time: 0.07041
 
Последнее редактирование:
Автор
A

Andrey_A

Продвинутый
Сообщения
319
Репутация
68
Таки да, их можно получить
Таки нет - вы на других путях пробовали? Чем длиннее путь , тем FileGetLongName дальше от истины...
Код:
$sFile='E:\TC IMAGE\Utilities\Scripting\AutoIt\Include\WordConstants.au3' ; реальный путь учитывая регистр
$sFile='e:\tc image\utilities\scripting\autoit\include\wordconstants.au3' ; искажённый
$Res=FileGetLongName($sFile)
MsgBox(4096,'Переменная $Res',$Res)
; FileGetLongName - возвращает путь 'e:\TC IMAGE\utilities\scripting\AutoIt\Include\wordconstants.au3'

Тот же самый результат выдаёт $aRes=DllCall('kernel32.dll','int','GetLongPathName','str',$sFile,'str','','int',260)

А теперь фокус-покус: тоже самое, но в верхнем регистре, и что нам выдаёт FileGetLongName?
Код:
$sFile='E:\TC IMAGE\Utilities\Scripting\AutoIt\Include\WordConstants.au3' ; реальный путь учитывая регистр
$sFile='E:\TC IMAGE\UTILITIES\SCRIPTING\AUTOIT\INCLUDE\WORDCONSTANTS.AU3' ; искажённый в верхнем регистре
$Res=FileGetLongName($sFile)
MsgBox(4096,'Переменная $Res',$Res)
; FileGetLongName - возвращае путь 'E:\TC IMAGE\UTILITIES\SCRIPTING\AutoIt\Include\WORDCONSTANTS.AU3'


И где тут таки да? Узнаёт из всего пути "\AutoIt\Include\" и всё.
 
Последнее редактирование:

Norm

Продвинутый
Сообщения
269
Репутация
70
Если файл существует, то можно вернуть его путь через FileFindNextFile()
На скорость не проверял, но в качестве альтернативы (без Shell.Application) может вполне сгодиться.
Код:
If @OSArch = "X64" Then DllCall("kernel32.dll", "int", "Wow64DisableWow64FsRedirection", "int", 1)
Local $sFilePath = "C:\Program Files (x86)\AutoIt3\Extras\AutoUpdateIt\AutoSQLiteUpdateIt.au3"
$sFilePath = StringUpper($sFilePath) ; или StringLower()

Local $EchtPfad, $hSearch = FileFindFirstFile($sFilePath)

While 1
    $sString = FileFindNextFile($hSearch)
    If @error Then
        ExitLoop
    Else
        FileClose($hSearch)
        $EchtPfad = "\"& $sString & $EchtPfad
        $sFilePath = StringReplace($sFilePath, "\"& $sString, "")
        If StringRegExp($sFilePath, ":$") Then
            $EchtPfad = StringUpper($sFilePath) & $EchtPfad
            ExitLoop
        Else
            $hSearch = FileFindFirstFile($sFilePath)
            If $hSearch = -1 Then
                ExitLoop
            EndIf
        EndIf
    EndIf
WEnd

MsgBox(0, FileExists($EchtPfad), $EchtPfad)
 
Последнее редактирование:

Oki

Продвинутый
Сообщения
452
Репутация
62

Norm

Эта очевидная идея сразу отметается именно из-за претензий на скорость, высказанных в стартовом посте. Более того, есть даже более жадные функции, коллекционирующие сразу всё содержимое папок, но с тем же недостатком.
 
Автор
A

Andrey_A

Продвинутый
Сообщения
319
Репутация
68
может вполне сгодиться
Да, вы правы, я тоже к этому же на днях и пришёл - у меня получилось так:
Код:
$sFile='C:\WINDOWS\SYSTEM32\DRIVERSTORE\'
$s=_Get_Path_Registr($sFile)
MsgBox(4096,'Переменная $s',$s)

Func _Get_Path_Registr($sFile)
  If 0=FileExists($sFile)Then Return ''
  Local $R=StringSplit($sFile,'\'),$sLine=StringIsLower(StringLeft(@ScriptDir,1))? StringLower($R[1]):StringUpper($R[1])
  For $i=2 To $R[0]
    Local $h=FileFindFirstFile($sLine&'\'&$R[$i]),$fLine=FileFindNextFile($h),$0=FileClose($h)
    $sLine&='\'&$fLine
  Next
  Return $sLine
EndFunc

Тестировал на скорость - это лучше чем Shell.Application
Почему скорость выше, потому что в цикле не ни одной проверки, кроме того Shell.Application на некоторых системных папках засыпает чуть-ли не на секунду, да и кучу проверок надо делать постоянно создавая новые объекты... и не факт, что Shell выдаст то что нужно.
Конечно, если найдётся другой способ, будет хорошо, но я тестируя сотни вариантов, остановился на этом.
 
Последнее редактирование:

Norm

Продвинутый
Сообщения
269
Репутация
70
Разумо ли обьявлять переменные в цыкле?
 
Автор
A

Andrey_A

Продвинутый
Сообщения
319
Репутация
68
Разумо ли обьявлять переменные в цыкле?
Это вы сами (или каждый для себя определит).
ИМХО, я пишу код без пробелов и сокращая код - можно все переменные в начале функции обозначить в Local, а в цикле написать Dim для перечисления тех или иных действий, чтобы не раздувать код вертикально...
Много раз тестировал - на скорость это не влияет - координально на скорость влияет любые условия if и др. нежели Local или Dim внутри цикла
 
Последнее редактирование:

Norm

Продвинутый
Сообщения
269
Репутация
70
Гдето читал об это, но теперь уже не найду, где рекомендуют избегать объявления внутри цыкла.
А от себя добавить, что условия If без EndIf также влияет на скорость (без EndIf медленнее).
 
Верх