. Одним из "виновников" этой ситуации является FileSystemObject – корневой объект модели File System Object (FSO), который используется в сценариях VBScript для операций с файловой системой Windows. Объект FileSystemObject не является частью WSH, а входит в объектную модель Microsoft Scripting Runtime Library. FileSystemObject представляет собой весьма гибкую структуру, однако требует написания многих строк кода даже при реализации таких обычных действий как чтение, запись, добавление данных в файл или создание структуры каталогов.

Но так как почти все типичные действия, выполняемые с файлами и папками, обычно сводятся к нескольким стандартным процедурам, я реализовал на VBScript несколько функций, специально предназначенных для выполнения таких повторяющихся действий. Каждая из этих функций решает некоторую конкретную задачу, что позволяет вам при разработке своих сценариев фокусировать внимание на других, не повторяющихся операциях. Для этого нужно просто скопировать функцию в текст сценария VBScript и вызывать ее при необходимости выполнения соответствующих действий. К статье прилагаются две врезки – "Считывание из файла нескольких последних строк", где описана еще одна функция-расширение, и "Как самому создавать функции-расширения", где излагается принципиальный подход к разработке таких функций.

1. Считывание текстового файла

Функция ReadFile(ByVal FilePath), приведенная в Листинге 1, предназначена для считывания всего содержимого текстового файла. Функция ReadFile работает с текстовыми файлами формата Unicode и считывает их содержимое в некоторую переменную. В командной строке функциональным аналогом этой функции является команда Type.

Пример вызова функции ReadFile в сценарии показан ниже:

data = ReadFile _
  ("C:windowssetup.log")

В результате выполнения данной команды функция считывает содержимое файла C:windowssetup.log и сохраняет его в переменной data.

Если указанный файл не существует или является пустым, функция возвратит значение vbEmpty. Например, если после вызова функции ReadFile добавить в код сценария приведенную ниже строку:

If IsEmpty(data) _
    Then WScript.Quit

то в этом случае, если заданный файл не существует или пуст, выполнение сценария завершается.

Если в ваших текстовых файлах используются стандартные для Windows символы окончания строк, тогда можно добавить в сценарий код, перенаправляющий вывод функции ReadFile в строковый массив. Для этого просто дополните вызов функции ReadFile приведенным ниже кодом, разбивающим выводимые данные по окончаниям строк:

data = _
    Split(ReadFile("C:oot.ini") _
    vbNewLine)

Для того чтобы вывести на консоль полученный массив строк (в данном случае это будет содержимое файла boot.ini) включите в сценарий следующие строки:

data = ReadFile(C:oot.ini)
    For Each line In data
        WScript.Echo line
    Next
    For Each line In data
        WScript.Echo line
    Next

2. Запись данных в текстовый файл

В Листинге 2 показана функция WriteFile(ByVal FilePath, ByVal Text), с помощью которой можно записывать данные в текстовые файлы. Если заданный файл не существует, то он будет создан функцией в том формате (ANSI или Unicode), который по умолчанию используется в вашей системе для текстовых файлов.

Ниже приведен пример вызова функции WriteFile(ByVal FilePath, ByVal Text):

WriteFile("C:	mpscript.log", _
  "тест записи прошел успешно")

В результате выполнения данной команды в файл C: mpscript.log будет записана строка "тест записи прошел успешно".

Если запись в файл прошла успешно, тогда функция WriteFile возвращает значение True, в противном случае будет возвращено False. Таким образом, функция WriteFile позволяет легко обрабатывать в сценариях ошибки записи в файл. Например, для того чтобы вызвать функцию и выполнить проверку успешности завершения операции записи, можно использовать следующий синтаксис:

If Not WriteFile("C:	mpscript.log", _
  "тест записи прошел успешно") Then
  'Здесь размещается код процедуры обработки проблемной ситуации
End If

В случае возникновения ошибок функция WriteFile предоставляет некоторую обратную связь, в то время как обратная связь, предоставляемая объектом FileSystemObject, обычно ограничена. Помимо несуществующих родительских папок, наиболее часто возникающими ошибками в операциях с файлами являются заблокированные файлы и отсутствие прав доступа. Обе эти проблемы приводят к появлению ошибок Permission Denied (Отказано в доступе). Далее в статье будет рассмотрена функция MakeDirectory(ByVal Path), которая исключает проблемы, обусловленные несуществующими папками.

3. Добавление данных в файл

Показанная в Листинге 3 функция AppendFile(ByVal FilePath, ByVal Text) похожа на только что рассмотренную функцию WriteFile. Единственное существенное различие между ними состоит в том, что функция AppendFile открывает файл и дописывает данные в конец, но при этом не перезаписывает существующее содержимое файла. Если файл с заданным именем не существует, тогда функция AppendFile создает его. Также происходит и в командной строке при использовании в команде символа перенаправления вывода в виде двойной угловой скобки (>>). В этом случае при отсутствии нужного файла программа создаст его, вместо того, чтобы пытаться дописать текст в несуществующий файл.

При вызове функции AppendFile используются два аргумента: имя файла и текст, предназначенный для добавления в этот файл. Например, в результате выполнения приведенной ниже команды:

Call AppendFile ("C:users.txt","John.Doe")

в конец файла C:users.txt будет записана строка John.Doe. Здесь следует обратить внимание на то, что данная функция только дописывает текст в конец файла, но не делает перехода на следующую строку. Например, в результате выполнения показанного ниже фрагмента кода:

Call AppendFile("C:users.txt","John.Doe")
Call AppendFile("C:users.txt","Mike.Smith")

содержимое файла будет выглядеть следующим образом:

John.DoeMike.Smith

Поэтому если нужно, чтобы данные дописывались в файл отдельными строками, следует включать в команды вызова константу vbNewLine, подобно тому, как это сделано в приведенном ниже примере:

Call AppendFile("C:users.txt","John.Doe") & vbNewLine
Call AppendFile("C:users.txt","Mike.Smith") & vbNewLine

4. Считывание из файла нескольких первых строк

Функция, показанная в Листинге 4, была названа Head(ByVal FilePath, ByVal LineCount) по аналогии с UNIX-утилитой, выполняющей ту же задачу: чтение из файла нескольких первых строк. При вызове функции следует указать имя файла и необходимое количество строк, которые нужно прочитать. Например, если требуется прочитать первые 10 строк из файла C:windowsxpsp1hfm.log, тогда синтаксис вызова функции Head должен выглядеть следующим образом:

Call Head("C:windows","xpsp1hfm.log", 10)

5. Создание дерева каталогов за один шаг

В оболочке командной строки существует команда Mkdir (или Md), позволяющая сразу же создать систему вложенных друг в друга каталогов. Иначе говоря, если мы запустим в командной строке команду:

Md C:	mplogs20041

то Mkdir автоматически создаст каталоги C: mplogs и C: mplogs2004, если они не существуют. Однако, по совершенно непонятным причинам, в методе CreateFolder модели FSO Microsoft не реализовала поддержку создания вложенных папок, аналогичную той, которая есть в команде Mkdir: вместо этого метод CreateFolder в случае отсутствия заданного каталога просто возвращает сообщение об ошибке Path not found (Путь не найден).

Показанная в Листинге 5 функция MakeDirectory(ByVal Path) может при необходимости создавать полное дерево каталогов. Данная функция предполагает, что следует просто обеспечить наличие необходимых каталогов, при этом вам не нужно думать о проверке их существования, а также беспокоиться о том, сможет ли функция создать одну или более вложенных папок. Если папка уже существует или была успешно создана функцией MakeDirectory, функция возвращает значение True. Если же MakeDirectory не смогла создать необходимую структуру каталогов (что возможно, например, по причине недостатка прав доступа, а также если не найден диск или сетевой ресурс, где предполагается создавать папки), тогда она возвращает значение False.

При вызове функции MakeDirectory указывается единственный аргумент – путь к нужной папке. Например, если требуется создать папку C:usersjohn.doemail, независимо от того, существует ли каталог C:users, тогда просто включите в свой сценарий приведенную ниже команду:

rtn = MakeDirectory _
  ("C:usersjohn.doemail")

в результате чего функция MakeDirectory создаст за один шаг необходимую структуру каталогов.

Применение компонентов расширения упрощает процесс разработки сценариев

Итак, теперь в вашем распоряжении имеется пять удобных функций, применение которых позволит автоматизировать некоторые рутинные моменты работы с файлами и папками в сценариях VBScript. Их использование также поможет вам сэкономить время, избавив вас от необходимости повторного написания кода. Я надеюсь, что материал данной статьи послужит для многих читателей стимулом к созданию собственных расширений для VBscript!


Листинг 1. Функция ReadFile
Function ReadFile(ByVal FilePath)
Const ForReading = 1, TristateUseDefault = -2
Dim fso, file
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(FilePath) Then
Set file = fso.OpenTextFile(FilePath, ForReading, _
False, TristateUseDefault)
If file.AtEndOfStream = False Then ReadFile = file.ReadAll
file.Close
End If
End Function

Листинг 2. Функция WriteFile
Function WriteFile(ByVal FilePath, ByVal Text)
WriteFile = False
Const ForWriting = 2, TristateUseDefault = -2
Dim fso, file
Set fso = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
Set file = fso.OpenTextFile(FilePath, ForWriting, _
True, TristateUseDefault)
file.Write Text
file.Close
If Err.Number = 0 Then WriteFile = True
End Function

Листинг 3. Функция AppendFile
Function AppendFile(ByVal FilePath, ByVal Text)
AppendFile = False
Const ForAppending = 8, TristateUseDefault = -2
Dim fso, file
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FolderExists( fso.GetParentFolderName(FilePath) ) Then
Set file = fso.OpenTextFile(FilePath, ForAppending, _
True, TristateUseDefault)
file.Write text
file.Close
AppendFile = True
End If
End Function

Листинг 4. Функция Head
Function Head(ByVal FilePath, ByVal LineCount)
Dim fso, file
Const ForReading = 1, TristateUseDefault = -2
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(FilePath) Then
Set file = fso.OpenTextFile(FilePath, ForReading, False, _
TristateUseDefault)
Do While Not(file.AtEndofStream) and file.Line<= LineCount
Head = Head & file.Read(1)
Loop
End If
End Function

Листинг 5. Функция MakeDirectory
Function MakeDirectory(ByVal Path)
Dim fso, dir
Set fso = CreateObject("Scripting.FileSystemObject")
With fso
If .FolderExists( .GetDriveName(path)) Then
dir = Path
Dim rdirs(): ReDim rdirs(-1)
Do While Not .FolderExists(dir) And Not _
.GetDriveName(dir) = dir
ReDim Preserve rdirs(ubound(rdirs) + 1)
rdirs(ubound(rdirs)) = .GetFileName(dir)
dir = .GetParentFolderName(dir)
Loop
Do While UBound(rdirs) > -1
dir = .BuildPath(dir, rdirs(ubound(rdirs)) )
.CreateFolder dir
Redim Preserve rdirs(ubound(rdirs) - 1)
Loop
MakeDirectory = True
Else
MakeDirectory = False
End If
End With
End Function

Считывание из файла нескольких последних строк

Функция, показанная в Листинге А, была названа Tail (ByVal FilePath, ByVal LineCount) по аналогии с UNIX-утилитой, выполняющей аналогичную задачу: чтение из файла нескольких последних строк. Следует иметь в виду, что при работе с файлами большого размера использование функции Tail может привести к значительным затратам ресурсов памяти, поскольку в этом случае для нахождения нужных строк сценарий сначала должен считать все содержимое файла.

Call Tail("C:windowsxpsp1hfm.log", 10)
Листинг A. Функция Tail
Function Tail(ByVal FilePath, ByVal LineCount)
Dim fso, file, skip, i
Const ForReading = 1, TristateUseDefault = -2
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(FilePath) Then
Set file = fso.OpenTextFile(FilePath, ForReading, _
False, TristateUseDefault)
If Not file.AtEndofStream Then
file.Readall
skip = file.Line - LineCount: If skip < 0 then skip = 0
file.Close
Set file = fso.OpenTextFile(FilePath, ForReading, _
False, TristateUseDefault)
For i = 1 To skip: file.Skipline: Next
Tail = file.ReadAll()
file.Close
End If
End If
End Function

Как самому создавать функции-расширения

Одним из наиболее полезных навыков, которые следует приобрести при работе с VBScript, является умение писать функции, которые затем можно использовать в сценариях в качестве расширений (plug-in). Секрет удобства использования таких функций-расширений состоит в минимизации связей между самими этими функциями и сценарием, из которого они должны вызываться. Связь (coupling) является мерой взаимозависимости функции и сценария. Если эта связь минимальна, тогда мы можем рассматривать данную функцию как некий "черный ящик", который способен решать свойственные ему задачи, независимо от того, в каком именно сценарии он вызывается. Ниже приводится несколько простых практических рекомендаций, которых я придерживался при разработке функций, описанных в данной статье. Они могут оказаться полезными и для вас, когда вы займетесь созданием собственных функций, предназначенных для использования в разных сценариях:

  • Убедитесь, что в строке объявления функции вы передаете в нее в виде аргументов все необходимые параметры, при этом старайтесь сделать так, чтобы предаваемых параметров было как можно меньше. В этом случае одного взгляда на первую строку предложения Function будет достаточно для того, чтобы сразу понять, какие именно данные необходимо передать функции из сценария.
  • Для всех передаваемых в функцию аргументов используйте ключевое слово ByVal, как показано, например, в Листинге 1. В этом случае любые изменения значения аргумента в функции будут сделаны только локально внутри этой функции и не приведут к изменению значения соответствующего параметра в вызывающем ее сценарии. Хотя ни одна из рассмотренных в данной статье функций не производит каких-либо действий над переданными ей аргументами, однако в других случаях вам может потребоваться модифицировать их значения внутри созданных функций. В этой ситуации использование ключевого слова ByVal при передаче внешних значений в функцию позволит исключить возможность непредсказуемого влияния функции-расширения на работу основного сценария.
  • Если вы используете в функции какие-либо константы и переменные, не забудьте сначала объявить их в явном виде внутри функции. Данный подход поможет избежать случайного взаимного влияния переменных и их значений в функции и внешнем сценарии, а также позволит несколько ускорить выполнение сценария.

При соблюдении этих несложных правил вы легко сможете вновь использовать разработанный вами код в создаваемых сценариях.

Поделитесь материалом с коллегами и друзьями