Что нового

[Файловая система] Поиск файлов включая подкаталоги с режимами и уровнем вложенности

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
Наиболее часто используемый мной скрипт, выложенный NIKZZZZ здесь. Решил изучить его для возможности манипулировать им.
Для начала вот
Код:
Global $Stack[50]
Global $Stack1[50]

$Text = ""
FileFindNextFirst("c:\windows")
While 1
  $tempname = FileFindNext()
  If $tempname = "" Then ExitLoop
  $Text &= $tempname & @CRLF
WEnd

MsgBox(4096, '', $Text)

Func FileFindNextFirst($FindCat)
  $Stack[0] = 1
  $Stack1[1] = $FindCat
  $Stack[$Stack[0]] = FileFindFirstFile($Stack1[$Stack[0]] & "\*.*")
  Return $Stack[$Stack[0]]
EndFunc   ;==>FileFindNextFirst

Func FileFindNext()
  While 1
    $file = FileFindNextFile($Stack[$Stack[0]])
    If @error Then
      FileClose($Stack[$Stack[0]])
      If $Stack[0] = 1 Then
        Return ""
      Else
        $Stack[0] -= 1
        ContinueLoop
      EndIf
    Else
      If StringInStr(FileGetAttrib($Stack1[$Stack[0]] & "\" & $file), "D") > 0 Then
        $Stack[0] += 1
        $Stack1[$Stack[0]] = $Stack1[$Stack[0] - 1] & "\" & $file
        $Stack[$Stack[0]] = FileFindFirstFile($Stack1[$Stack[0]] & "\*.*")
        ContinueLoop
      Else
        Return $Stack1[$Stack[0]] & "\" & $file
      EndIf
    EndIf
  WEnd
EndFunc   ;==>FileFindNext

Этот же скрипт но с подробным описанием для изучения
Код:
#include <Array.au3> ; временно для просмотра массивов
HotKeySet('{ESC}', "_Quit") ; выход по ESC
; изменил имена переменных стек1 и стек2, чтоб не запутаться в них.
Global $llllllllllll[50] ; массив будет содержать уровни вложенности, нулевой элемент будет содержать текущую вложенность
Global $sssss[50] ; массив будет содержать пути в соответствии с уровнем вложенности

$Text = ""
_FileFindNextFirst("C:\WINDOWS") ; Вызов функции с передачей в тело функции каталога
While 1
	$tempname = _FileFindNext()
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

MsgBox(4096, '', $Text)

Func _FileFindNextFirst($KorenF) ; получаем каталог в переменную $KorenF. Этот вызов выполняется единожды перед циклом While-WEnd
	$llllllllllll[0] = 1 ; устанавливаем элемент массива равным 1
	$sssss[1] = $KorenF ; устанавливаем элемент массива равным путю к каталогу

	$llllllllllll[1] = FileFindFirstFile($KorenF & "\*.*")
	;$llllllllllll[$llllllllllll[0]] = FileFindFirstFile($sssss[$llllllllllll[0]] & "\*.*") ; упростили эту запись, так как в этой же функции определены значения переменных
	_ArrayDisplay( $llllllllllll, "Смотрим первый llllllllllll" ) ; временно для просмотра массивов
	_ArrayDisplay( $sssss, "Смотрим первый sssss" )
	Return $llllllllllll[1] ; возвращаем первые элементы массива 1 и 1, что значит найдено
	;так как переменные глобавльные, то $sssss[1] содержит корневой каталог поиска.
EndFunc   ;==>_FileFindNextFirst

Func _FileFindNext() ; вызывается постоянно в цикле, пока не выполнится поиск всех файлов
	While 1
		$file = FileFindNextFile($llllllllllll[$llllllllllll[0]]) ; поиск следующего файла $llllllllllll[1]
		If @error Then ; если в этом каталоге файлы закончились, то -------------------
			FileClose($llllllllllll[$llllllllllll[0]]) ; закрываем закрываем поиск
			If $llllllllllll[0] = 1 Then ; если находимся в корневом каталоге, то
				Return "" ; выход из функции нисчем (означает что весь поиск закончен)
			Else ; если находимся НЕ в корневом каталоге, то
				$llllllllllll[0] -= 1 ; поднимаемся на уровень выше приближаясь к корневому каталогу
				ContinueLoop ; и снова выполняем поиск
			EndIf
		Else ; если в этом каталоге файлы НЕ закончились, то -------------------
			If StringInStr(FileGetAttrib($sssss[$llllllllllll[0]] & "\" & $file), "D") > 0 Then ; если элемент является не файлом, а каталогом, то
				$llllllllllll[0] += 1 ; увеличиваем первый элемент на 1 (указывая этим уровень вложенности каталога)
				$sssss[$llllllllllll[0]] = $sssss[$llllllllllll[0] - 1] & "\" & $file ; записываем во второй массив с уровнем вложенности предыдущий путь выше уровнем + имя каталога
				$llllllllllll[$llllllllllll[0]] = FileFindFirstFile($sssss[$llllllllllll[0]] & "\*.*") ; выполняем поиск файла в подкаталоге и записываем в массив с соответствующем уровнем вложенности
				_ArrayDisplay( $llllllllllll, "Смотрим llllllllllll" ) ; временно для просмотра массивов
				_ArrayDisplay( $sssss, "Смотрим sssss" )
				ContinueLoop ; снова выполняем цикл поиска файла
			Else ; если элемент является файлом, а НЕ каталогом, то
				Return $sssss[$llllllllllll[0]] & "\" & $file ; возвращаем из функции путь к очередному файлу
			EndIf
		EndIf
	WEnd
EndFunc   ;==>_FileFindNext

Func _Quit()
    Exit
EndFunc

А теперь модернизированный вариант этого скрипта
Код:
#include <Array.au3> ; временно для просмотра массивов
HotKeySet('{ESC}', "_Quit") ; выход по ESC
; изменил имена переменных стек1 и стек2, чтоб не запутаться в них.
Global $llllllllllll[50] ; массив будет содержать уровни вложенности, нулевой элемент будет содержать текущую вложенность
Global $sssss[50] ; массив будет содержать пути в соответствии с уровнем вложенности

$Text = ""
_FileFindNextFirst("C:\WINDOWS") ; Вызов функции с передачей в тело функции каталога
While 1
	$tempname = _FileFindNext('log',0,2)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

MsgBox(4096, '', $Text)

Func _FileFindNextFirst($KorenF) ; получаем каталог в переменную $KorenF. Этот вызов выполняется единожды перед циклом While-WEnd
	$llllllllllll[0] = 1 ; устанавливаем элемент массива равным 1
	$sssss[1] = $KorenF ; устанавливаем элемент массива равным путю к каталогу

	$llllllllllll[1] = FileFindFirstFile($KorenF & "\*.*")
	;$llllllllllll[$llllllllllll[0]] = FileFindFirstFile($sssss[$llllllllllll[0]] & "\*.*") ; упростили эту запись, так как в этой же функции определены значения переменных
	;_ArrayDisplay( $llllllllllll, "Смотрим первый llllllllllll" ) ; временно для просмотра массивов
	;_ArrayDisplay( $sssss, "Смотрим первый sssss" )
	Return $llllllllllll[1] ; возвращаем первые элементы массива 1 и 1, что значит найдено
	;так как переменные глобавльные, то $sssss[1] содержит корневой каталог поиска.
EndFunc   ;==>_FileFindNextFirst


;$mode=0 - возвращать файлы
;$mode=1 - возвращать типы файлы
;$mode=2 - возвращать каталоги
;$Level=  от 1 до 49 по размерности массива и этого достаточно.
;$type = 'log' - позволяет задать функцию без параметров (по умолчанию поиск всех файлов).
Func _FileFindNext($type = 'log', $mode = 0, $Level=49) ; вызывается постоянно в цикле, пока не выполнится поиск всех файлов
	While 1
		$file = FileFindNextFile($llllllllllll[$llllllllllll[0]]) ; поиск следующего файла $llllllllllll[1]
		If @error Then ; если в этом каталоге файлы закончились, то -------------------
			FileClose($llllllllllll[$llllllllllll[0]]) ; закрываем закрываем поиск
			If $llllllllllll[0] = 1 Then ; если находимся в корневом каталоге, то
				Return "" ; выход из функции нисчем (означает что весь поиск закончен)
			Else ; если находимся НЕ в корневом каталоге, то
				$llllllllllll[0] -= 1 ; поднимаемся на уровень выше приближаясь к корневому каталогу
				ContinueLoop ; и снова выполняем поиск
			EndIf
		Else ; если в этом каталоге файлы НЕ закончились, то -------------------
			If StringInStr(FileGetAttrib($sssss[$llllllllllll[0]] & "\" & $file), "D") > 0 Then ; если элемент является не файлом, а каталогом, то
				If $llllllllllll[0] = $Level Then ContinueLoop ; проверка уровня вложенности
				$llllllllllll[0] += 1 ; увеличиваем первый элемент на 1 (указывая этим уровень вложенности каталога)
				$sssss[$llllllllllll[0]] = $sssss[$llllllllllll[0] - 1] & "\" & $file ; записываем во второй массив с уровнем вложенности предыдущий путь выше уровнем + имя каталога
				$llllllllllll[$llllllllllll[0]] = FileFindFirstFile($sssss[$llllllllllll[0]] & "\*.*") ; выполняем поиск файла в подкаталоге и записываем в массив с соответствующем уровнем вложенности
				;_ArrayDisplay( $llllllllllll, "Смотрим llllllllllll" ) ; временно для просмотра массивов
				;_ArrayDisplay( $sssss, "Смотрим sssss" )
				If $mode=2 Then ; если режим поиска каталогов ($mode=2), то
					Return $sssss[$llllllllllll[0]] ; возвращаем каталог
				Else
					ContinueLoop ; снова выполняем цикл поиска файла
				EndIf
			Else ; если элемент является файлом, а НЕ каталогом, то
				If $mode=2 Then ContinueLoop ; если режим поиска каталогов ($mode=2), то повторяем цикл
				If $mode=1 Then ; если режим поиска файлов по типу ($mode=1), то
					If StringRight($sssss[$llllllllllll[0]] & "\" & $file, 4)<>'.'&$type Then ; если расширение файла НЕ соответствует
						ContinueLoop ; выполняем цикл сначала
					Else; если расширение файла соответствует
						Return $sssss[$llllllllllll[0]] & "\" & $file ; возвращаем из функции путь к очередному файлу с соответствующи расширением
					EndIf
				Else ; если режим поиска не $mode=1 или $mode=2, то
					Return $sssss[$llllllllllll[0]] & "\" & $file ; возвращаем из функции путь к очередному файлу с любым расширением
				EndIf
			EndIf
		EndIf
	WEnd
EndFunc   ;==>_FileFindNext

Func _Quit()
    Exit
EndFunc

Добавлены режимы
$mode=0 - возвратить все файлы
$mode=1 - возвратить определённый тип файла, например log или au3 и т.д.
$mode=2 - возвратить все каталоги
$Level= от 1 до 49 по размерности массива и этого достаточно. Для файлов корневого каталога уровень вложенности равен 1, а для папок 2.

Чтобы получить содержимое каталога без подкаталогов, достаточно выполнить два цикла:
Код:
_FileFindNextFirst("C:\WINDOWS") ; Вызов функции с передачей в тело функции каталога
While 1
	$tempname = _FileFindNext('',2,2)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd
_FileFindNextFirst("C:\WINDOWS") ; Вызов функции с передачей в тело функции каталога
While 1
	$tempname = _FileFindNext('',0,1)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd


Код:
Global $Stack[50], $Stack1[50]

$Text = ""
FileFindNextFirst("C:\WINDOWS")
While 1
	$tempname = FileFindNext('',2,2)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

FileFindNextFirst("C:\WINDOWS")
While 1
	$tempname = FileFindNext('',0,1)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

MsgBox(4096, '', $Text)

Func FileFindNextFirst($FindCat)
	$Stack[0] = 1
	$Stack1[1] = $FindCat
	$Stack[1] = FileFindFirstFile($FindCat & "\*.*")
	Return $Stack[1]
EndFunc   ;==>FileFindNextFirst

;$mode=0 - файлы
;$mode=1 - типы файлов
;$mode=2 - каталоги
;$Level=  от 1 до 49
Func FileFindNext($type = 'log', $mode = 0, $Level=49)
	While 1
		$file = FileFindNextFile($Stack[$Stack[0]])
		If @error Then
			FileClose($Stack[$Stack[0]])
			If $Stack[0] = 1 Then
				Return ""
			Else
				$Stack[0] -= 1
				ContinueLoop
			EndIf
		Else
			If StringInStr(FileGetAttrib($Stack1[$Stack[0]] & "\" & $file), "D") > 0 Then
				If $Stack[0] = $Level Then ContinueLoop
				$Stack[0] += 1
				$Stack1[$Stack[0]] = $Stack1[$Stack[0] - 1] & "\" & $file
				$Stack[$Stack[0]] = FileFindFirstFile($Stack1[$Stack[0]] & "\*.*")
				If $mode=2 Then
					Return $Stack1[$Stack[0]]
				Else
					ContinueLoop
				EndIf
			Else
				If $mode=2 Then ContinueLoop
				If $mode=1 Then
					If StringRight($Stack1[$Stack[0]] & "\" & $file, 4)<>'.'&$type Then
						ContinueLoop
					Else
						Return $Stack1[$Stack[0]] & "\" & $file
					EndIf
				Else
					Return $Stack1[$Stack[0]] & "\" & $file
				EndIf
			EndIf
		EndIf
	WEnd
EndFunc   ;==>FileFindNext

Этот скрипт работает быстрее в отличии от скриптов, отправляющих весь список файлов в массив.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
AZJIO [?]
Этот скрипт работает быстрее в отличии от скриптов, отправляющих весь список файлов в массив
Самый быстрый вариант это с использованием консольной команды Dir. Подробнее это обсуждалось тут, самый оптимальный вариант на мой взгляд:

Код:
Func _FileSearch($sPath, $sFileMask)
	Local $sOut, $aOut, $hDir
	
	$sOut = StringToBinary("0" & @CRLF, 2)
	$hDir = Run(@ComSpec & ' /U /C DIR "' & $sPath & '\' & $sFileMask & '" /S /B /A-D', @SystemDir, @SW_HIDE, 6)
	
	While 1
		$sOut &= StdoutRead($hDir, False, True)
		If @error Then ExitLoop
	Wend
	
	$aOut = StringRegExp(BinaryToString($sOut, 2), "[^\r\n]+", 3)
	If @error Then Return SetError(1)
	
	$aOut[0] = UBound($aOut)-1
	Return $aOut
EndFunc


а так неплохо, за исключением непонятных имён у переменных ;).
 
Автор
A

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
CreatoR [?]
а так неплохо, за исключением непонятных имён у переменных
Вот вернул имена оригинала удалив всё лишнее, чтобы не запутывать. Готовый пример и рабочий скрипт.

Код:
Global $Stack[50], $Stack1[50]

$Text = ""
FileFindNextFirst("C:\WINDOWS")
While 1
	$tempname = FileFindNext('',2,2)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

FileFindNextFirst("C:\WINDOWS")
While 1
	$tempname = FileFindNext('',0,1)
	If $tempname = "" Then ExitLoop
	$Text &= $tempname & @CRLF
WEnd

MsgBox(4096, '', $Text)

Func FileFindNextFirst($FindCat)
	$Stack[0] = 1
	$Stack1[1] = $FindCat
	$Stack[1] = FileFindFirstFile($FindCat & "\*.*")
	Return $Stack[1]
EndFunc   ;==>FileFindNextFirst

;$mode=0 - файлы
;$mode=1 - типы файлов
;$mode=2 - каталоги
;$Level=  от 1 до 49
Func FileFindNext($type = 'log', $mode = 0, $Level=49)
	While 1
		$file = FileFindNextFile($Stack[$Stack[0]])
		If @error Then
			FileClose($Stack[$Stack[0]])
			If $Stack[0] = 1 Then
				Return ""
			Else
				$Stack[0] -= 1
				ContinueLoop
			EndIf
		Else
			If StringInStr(FileGetAttrib($Stack1[$Stack[0]] & "\" & $file), "D") > 0 Then
				If $Stack[0] = $Level Then ContinueLoop
				$Stack[0] += 1
				$Stack1[$Stack[0]] = $Stack1[$Stack[0] - 1] & "\" & $file
				$Stack[$Stack[0]] = FileFindFirstFile($Stack1[$Stack[0]] & "\*.*")
				If $mode=2 Then
					Return $Stack1[$Stack[0]]
				Else
					ContinueLoop
				EndIf
			Else
				If $mode=2 Then ContinueLoop
				If $mode=1 Then
					If StringRight($Stack1[$Stack[0]] & "\" & $file, 4)<>'.'&$type Then
						ContinueLoop
					Else
						Return $Stack1[$Stack[0]] & "\" & $file
					EndIf
				Else
					Return $Stack1[$Stack[0]] & "\" & $file
				EndIf
			EndIf
		EndIf
	WEnd
EndFunc   ;==>FileFindNext



Добавлено:
Сообщение автоматически объединено:

CreatoR
Поставил на скрипты таймер времени
Код:
$Text = ""
$timer = TimerInit() ; засекаем время
$aReturn = _FileSearch('C:\WINDOWS', '*.*')
For $i = 1 to $aReturn[0]
	$Text &= $aReturn[$i] & @CRLF
Next
$timer11=TimerDiff($timer)
MsgBox(0, '', $timer11)

Два раза запустил оба скрипта с отключенным антивирусником, результат консольный проигрывает 1200 против 900 с небольшим разбросом. В обоих случаях в конце результаты отправлялись в текстовый файл, количество найденных файлов было одинаково = 12222
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
AZJIO, для подобных задач обычно используют рекурсию.

*click*
 
Автор
A

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
Yashied
Почему то CreatoR старается избавиться от рекурсии. Хотя по мне, если своё дело делает стабильно, то важен результат. Проверил скрипт с рекурсией, таймер выдал 1800, при тех же 12222 файлах.

Кстати, сравнил время консольного метода по типам файлов, здесь он обыгрывает модернизированный скрипт NIKZZZZ, примерно 700 к 800.

Вот он тестовый с рекурсией...
Код:
#Include <File.au3>
Global $Text = ""
$timer = TimerInit() ; засекаем время
_FindFiles('C:\WINDOWS', '*.*')
$timer11=TimerDiff($timer)
MsgBox(0, '', $timer11)
MsgBox(4096, '', $Text)
$file = FileOpen(@ScriptDir&'\file.txt' ,2)
FileWrite($file, $text)
FileClose($file)
Exit

Func _FindFiles($sRoot, $sFile)

    Local $FileList

    $FileList = _FileListToArray($sRoot, $sFile, 1)
    If Not @error Then
        For $i = 1 To $FileList[0]
			$Text &= $sRoot & '\' & $FileList[$i] & @CRLF
        Next
    EndIf
    $FileList = _FileListToArray($sRoot, '*', 2)
    If Not @error Then
        For $i = 1 To $FileList[0]
            _FindFiles($sRoot & '\' & $FileList[$i], $sFile)
        Next
    EndIf
EndFunc   ;==>_FindFiles
 

Vice

Новичок
Сообщения
3
Репутация
0
А нет ли подобной функции только чтобы искало файлы определенного размера (диапазон размеров) и не просто в каталогах, но и в архивах, которые есть в каталогах?
 

Andrey_A

Продвинутый
Сообщения
325
Репутация
68
Самый быстрый вариант это с использованием консольной команды Dir. Подробнее это обсуждалось тут, самый оптимальный вариант на мой взгляд:

Скрипт №2 работает c рекурсией - нужен параметр получать файлы и папки без рекурсии
 

Andrey_A

Продвинутый
Сообщения
325
Репутация
68
AZJIO, спасибо, буду разбираться
 
Верх