Сценарии Perl для Windows

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

В данной статье я хочу рассказать о нескольких утилитах из разработанного мной набора программных инструментов. Эти средства позволяют выполнять поиск файла в каталогах, определяемых переменной среды PATH, рассчитывать используемый деревом каталогов объем диска и количество файлов, удалять файлы по определенному критерию (например, по времени создания или размеру), а также запрашивать и изменять системные параметры. Конечно, ничего особо выдающегося или специфического в этих программах нет, и тем не менее они часто используются моими коллегами в работе, поскольку с подобными задачами в тот или иной момент неизбежно сталкивается большинство администраторов.

Поиск через переменную PATH

Когда вы вводите в командной строке имя запускаемой программы и нажимаете клавишу ENTER, операционная система сначала пытается найти эту программу в текущем каталоге (разумеется, если не указан полный путь в строке запуска). Если программа не была обнаружена в текущем каталоге, система начинает последовательно проверять все каталоги, пути к которым заданы в переменной среды PATH, придерживаясь того порядка, в котором они перечислены в данной переменной.

Иногда бывает, что в переменной PATH хранятся описания путей к утратившим актуальность программам (скажем, к устаревшим версиям файлов .exe или .dll). В частности, это может иметь место при установке приложения, которое в ходе развертывания модифицирует переменную PATH либо устанавливает альтернативные версии файлов .exe или .dll. В результате могут возникнуть проблемы, поскольку возможна ситуация, когда операционная система будет находить и использовать вместо нужных файлов их устаревшие версии. Одним из наиболее типичных примеров подобной коллизии является тот случай, когда программа сообщает о некорректной версии файла среды исполнения Microsoft Visual Basic (vbrun.dll) или языка Cи (msvcrt.dll).

Поиск всех копий «проблемных» файлов — задача трудная, поскольку обычно они рассеяны по многим каталогам. Приведенный в листинге 1 сценарий FindInPath.pl выполняет поиск заданного файла в каталогах, описываемых переменной PATH, и сообщает о местоположении найденных файлов. В строке запуска сценария после его названия нужно просто указать имена интересующих файлов, как показано ниже:

perl findinpath.pl vbrun300.dll
 msvcrt.dll

В результате запуска сценария с этими параметрами будут возвращены пути, содержащиеся в переменной PATH, ко всем обнаруженным на данной системе версиям файлов vbrun300.dll и msvcrt.dll. Сценарий поддерживает ввод групповых символов DOS «*» и «?», поэтому для выполнения быстрого поиска всех исполняемых библиотек Visual C++ (VC++) можно использовать следующую команду запуска:

perl findinpath.pl msvc*.dll

Сценарий FindInPath.pl базируется на функции glob языка Perl. Она позволяет задействовать групповые символы для классификации файлов и каталогов. Например, если требуется получить список всех файлов, имеющих расширение .dll из каталога windowssystem32, можно воспользоваться следующей инструкцией языка Perl:

@Files = glob( ?C:windowssystem32*.dll? );

Функция glob работает подобно команде DIR в DOS, но, в отличие от DIR, не может обрабатывать пробелы, имеющиеся в середине описания пути, поэтому при запуске показанной ниже команды будет возвращен пустой массив:

@Files = glob( ?C:program files*.*? );

Для решения проблемы, связанной с пробелами, существует модуль File::DosGlob (входит в комплект ActiveState Perl), однако и этот модуль не безупречен. В данном случае первая трудность заключается в том, что этот модуль распознает в качестве разделителя в описании пути только символ, используемый в UNIX (т. е. прямой слэш (/), в отличие от используемого в DOS и Windows обратного слэша ()). Соответственно, путь вида C:windowssystem32*.dll данным модулем обрабатываться не будет, вместо этого путь должен указываться в виде C:/windows/system32/*.dll. Обойти данную проблему не так сложно, и тем не менее это досадно, особенно учитывая, что встроенная функция glob языка Perl поддерживает обработку символа обратного слэша.

Вторая проблема состоит в том, что хотя модуль File::DosGlob и обрабатывает пробелы в середине описания пути, но делает это не самым удобным способом. Для работы модуля требуется, чтобы каталог или маска файла были заключены в кавычки (C:/»program files»/*.*) либо чтобы всем пробелам предшествовал символ обратного слэша (C:/program files/*.*).

Отсечение ветвей дерева каталогов

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

Обычно администраторы включают в пользовательские сценарии регистрации (logon script) процедуры удаления всех файлов из некоторых заданных каталогов (в частности, из пользовательского каталога временных файлов). Но при этом можно задавать критерии фильтрации только по именам или типам файлов. Например, командный файл может содержать следующую строку:

del %temp%*.*

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

Возможности создания таких критериев при удалении файлов реализованы в сценарии CleanOldFiles.pl. Здесь в качестве условия можно задавать количество месяцев, недель, дней, часов, минут или секунд либо указывать конкретные время и дату. Например, если запустить данный сценарий показанной ниже командой:

perl cleanoldfiles.pl
 C:windowssystem32*.dll
 -date 2001-07-01

то будут отображены все файлы с расширением .dll, содержащиеся в каталоге windowssystem32, имеющие дату создания более раннюю, чем 1 июля 2001 года.

В показанном ниже примере с помощью команды:

perl cleanoldfiles.pl -r C:	emp*.tmp
 C:	emp*.txt -mon 3 -d 22
 -delete

из каталога C:TEMP будут удалены все файлы с расширениями .txt и .tmp, возраст которых превышает 3 месяца и 22 дня. Если указан ключ -r, то будут удалены файлы и из подкаталогов. Для исключения возможности случайного удаления файлов используется дополнительный ключ -delete. Если он не указан, то сценарий просто отображает файлы, соответствующие заданным критериям.

Кроме того, с помощью ключей -minsize и -maxsize можно указывать соответственно минимальный и максимальный размеры файлов. При использовании этих ключей сценарий будет считать подлежащими удалению (или отображению) те файлы, размер которых больше или равен либо меньше или равен значению, заданному с помощью указанных ключей. При установке значений этих ключей можно указать размер файла в байтах (например, 1024) либо использовать суффикс (например, 27.5M). При помощи суффикса можно задавать размер файла в килобайтах (K), мегабайтах (M), гигабайтах (G) или в терабайтах (T). Если никакой из перечисленных суффиксов не используется, тогда считается, что размер задан в байтах.

На листинге 2 приведен сценарий CleanOldFiles.pl. Во фрагменте, обозначенном меткой A, выполняется преобразование переданной в качестве параметра даты в массив @Date. Строка даты должна иметь формат ГГГГ-ММ-ДД или ГГГГ-ММ-ДД Ч:М:С. Другими словами, в строке данных год должен задаваться в виде четырех цифр (например, 2006), месяц — двумя цифрами (например, 02), день — также двумя цифрами (например, 23). Если вместе с датой указывается время, то задание времени допускает большую гибкость, поскольку часы, минуты и секунды могут устанавливаться с помощью как одной, так и двух десятичных цифр. Например, ввод показанной ниже команды в командной строке:

perl cleanoldfiles.pl
 C:windowssystem32*.dll
 -date 2001-07-01 1:02:03

считается вполне корректным. После обработки строки ввода сценарий вызывает функцию timelocal() (экспортируемую из модуля Time::Local). В результате получается значение времени и даты в виде, сходном с тем, в котором возвращает результаты функция time() (а именно количество секунд, прошедших с полуночи 1 января 1970 года).

Если строка даты не задана, вызывается код, обозначенный меткой B (листинг 1). В отличие от фрагмента с меткой A, здесь вычисляется интервал времени относительно момента запуска сценария с использованием тех временных параметров (минуты, часы, дни), которые были заданы. Если никакие временные параметры указаны не были, тогда временной интервал считается равным 0 секунд, в результате чего сценарий будет рассматривать все файлы, созданные до настоящего момента.

Фрагмент, имеющий метку C, выполняет обработку параметров каждого из путей, переданных при запуске сценария. Каждый из указанных путей разделяется на каталог и маску файла. Затем оба компонента обрабатываются функцией ProcessMask(), в результате чего, с учетом имени каталога и файловой маски, строится список соответствующих файлов (см. фрагмент с меткой D на листинге 2). Например, если была задана маска *.dll, то будут обработаны все файлы, имеющие расширение .dll. Когда в начале сценария осуществляется экспорт модуля File::DosGlob, при этом также экспортируется и функция glob, входящая в состав данного модуля. Поэтому, хотя во фрагменте сценария с меткой D вызывается функция glob(), фактически здесь выполняется вызов File::DosGlob::glob().

Основная работа сценария начинается с фрагмента, обозначенного в листинге 2 меткой E. Здесь производится сбор статистики по каждому из файлов. Если размер файла больше или равен минимально допустимому, но меньше или равен максимально допустимому значению, и при этом последнее обращение (но не время создания) к данному файлу удовлетворяет установленным временным ограничениям, файл будет отображен. В том случае если при запуске сценария был указан ключ -delete, этот файл будет удален.

Размер дерева каталогов

Администратору часто требуется выяснить, насколько сильно разрослось дерево каталогов. Это может быть актуально при удалении файлов и определении доступного пространства в каком-либо каталоге. Файл DirTreeSize.pl представляет собой простой, но весьма полезный сценарий, выполняющий сканирование всех файлов и подкаталогов и отображающий суммарное количество файлов и объем, занимаемый данным деревом каталогов. Пользоваться данным сценарием несложно. Скажем, для того чтобы просмотреть все файлы и подкаталоги каталога C:TEMP, следует указать в строке запуска после имени файла сценария имя соответствующего каталога:

perl dirtreesize.pl C:	emp

Путь к каталогу будет передан в качестве параметра функции ProcessDir(). Функция перебирает каждый файл и подкаталог, при этом для каждого файла вызывается функция ProcessFile(), а для каждого подкаталога рекурсивно вызывается функция ProcessDir(). Фрагмент сценария DirTreeSize.pl показан в листинге 3. Здесь код, обозначенный меткой A, вызывает для подкаталога функцию ProcessDir(), причем, прежде чем начать обработку подкаталога, производится увеличение содержимого счетчика каталогов и обновление отображаемых данных.

Для определения размера файла и обновления содержимого счетчика количества файлов используется функция stat() языка Perl (см. фрагмент с меткой B листинга 3). Затем, путем вызова функции UpdateDisplay(), сценарий отображает обновленное содержимое счетчиков каталогов и файлов, а также данные об используемом всеми этими файлами дисковом пространстве, что показано в листинге 3 во фрагменте с меткой C. В конце предложения print используется символ , что эквивалентно символу возврата каретки без перехода на следующую строку, поэтому выполняется обновление содержимого текущей строки. Таким образом, экран все время обновляется, но данные при этом отображаются не более чем в одной строке.

Получение информации о системных параметрах и их изменение

Предоставляемая операционными системами Windows функциональность настолько обширна, что программное управление даже простейшими ее параметрами может показаться сложной задачей. Рассмотрим, например, замену фонового рисунка на рабочем столе компьютера. Вручную это сделать довольно просто: достаточно щелкнуть правой кнопкой мыши на рабочем столе, выбрать из контекстного меню пункт «Свойства» (Properties), в появившемся окне выбрать закладку «Рабочий стол» (Desktop) — или закладку «Фон»(Background) для систем с Windows 2000 и Windows NT — и указать соответствующий файл рисунка. Также можно выполнить все эти операции через панель управления (Control Panel). Но как реализовать ту же самую процедуру с помощью сценария? Если знать, какую функцию следует использовать для этого, процесс становится достаточно прозрачным.

С помощью сценария SystemInfo.pl можно модифицировать или запрашивать состояние специфических параметров системы. В данном сценарии используется функция Win32 API SystemParametersInfo(). В принципе через эту функцию можно работать более чем со 150 системными параметрами, начиная от скрытых и кончая лежащими на поверхности. Ограниченная функциональность SystemParametersInfo(), реализованная в сценарии SystemInfo.pl, позволяет получать информацию и изменять настройку фонового рисунка рабочего стола, включать или отключать экранную заставку и задавать временные параметры ее включения, задавать скорость повторного нажатия клавиш на клавиатуре, а также включать или отключать возможность перетаскивания по экрану окна вместе с содержимым (full-window dragging) (альтернативный вариант — перетаскивание только границ окна — window-outline dragging). Более подробную информацию о построении запросов и установке системных параметров с помощью функции SystemParametersInfo() можно получить из статьи в Microsoft Developer Network (MSDN) «SystemParametersInfo» по адресу http://msdn.microsoft.com/library/default.asp?url=/ library/en-us/sysinfo/base/systemparametersinfo.asp.

Сценарий SystemInfo.pl требует для работы расширение Win32::API и модуль Win32::API::Prototype. Расширение Win32::API от Aldo Calpini позволяет выполнять из Perl прямой доступ к библиотекам DLL, поэтому из Perl-сценария можно вызывать любые функции DLL. Последнюю версию данного расширения можно загрузить по адресу: http://dada.perl.it. Модуль Win32::API::Prototype, упрощающий работу с расширением Win32::API, доступен для загрузки по следующему адресу: http://www.roth.net/perl.

В SystemInfo.pl используется ряд констант. Поддерживаемые константы описываются в упомянутой выше статье MSDN, а допустимые значения констант содержатся в заголовочном файле WinUser.h из комплекта Win32 Software Development Kit (SDK). В блоке BEGIN сценария определяется набор констант. Данный блок сразу создает группу констант. Я предпочел данный метод определению глобальных скалярных переменных, потому что определять каждую скалярную переменную неудобно. Гораздо проще работать со списком констант. Перед началом выполнения тела сценария интерпретатор Perl автоматически обрабатывает все блоки BEGIN. Таким образом, если блок BEGIN используется для определения констант, то они становятся доступны немедленно.

При создании констант сценарий включает безымянные (anonymous) процедуры (или блоки CODE) непосредственно в таблицу символов Perl. Это позволяет ему динамически создавать процедуры в ходе выполнения. Обратите внимание на то, что в блоке BEGIN имя назначаемой параметру процедуры полностью соответствует имени константы. Когда сценарий ссылается на определенную константу, фактически при этом вызывается соответствующая процедура, возвращающая значение этой константы. Данная технология может показаться чрезмерно усложненной, но именно так в Perl осуществляется работа с константами.

Остальная часть сценария достаточно очевидна. В зависимости от ключей, указанных при его запуске, ход сценария может развиваться по одному из путей. Если вместе с ключом задается параметр, сценарий считает, что необходимо изменить указанную настройку. Строка вида:

if( defined $Config{parameter} )

выполняет необходимую проверку для такой операции. Если в параметре специально указывается инструкция на изменение определенной настройки, тогда функция сценария Configure () задает ключ вызванного параметра в разделе % Config. В противном случае предполагается, что нужно просто запросить текущее значение данного параметра. Сценарий допускает использование следующих ключей:

  • Ключ -w служит для получения или изменения пути к файлу фонового рисунка рабочего стола. Данный ключ работает только на системах Windows 2003 Server, Windows XP и Windows 2000.
  • Ключ -s позволяет получать информацию о состоянии экранной заставки (включено/выключено) и изменять это состояние.
  • С помощью ключа -st можно получать информацию о заданной величине интервала времени простоя системы (в секундах) до включения экранной заставки, а также изменять это значение.
  • Ключ -k служит для запроса значения или изменения настройки скорости повторного нажатия клавиш на клавиатуре.

* Ключ -d используется для запроса текущего состояния (включено/отключено) функции перетаскивания по экрану окна вместе с содержимым (альтернативный вариант — перетаскивание только границ окна).

Если требуется выяснить, какой файл система использует в качестве фонового рисунка рабочего стола, нужно набрать в командной строке:

perl systeminfo.pl -w

Если необходимо изменить фоновый рисунок, следует указать сценарию путь к соответствующему файлу:

perl systeminfo.pl -w
"C:mypictures
eally_cool_
graphic.bmp"

Фоновый рисунок должен иметь формат .bmp. Если данная настройка изменяется через диалоговое окно свойств рабочего стола, то можно указывать другой тип графического файла (например, .jpg). Но система сначала преобразует и сохраняет рисунок в виде файла .bmp, а затем Windows устанавливает для фонового рисунка путь к соответствующему файлу .bmp.

Описанные программные средства могут стать для многих отправной точкой в мир Perl. Даже на платформах Win32, для которых технология сценариев является относительно новой, Perl затмевает другие языки программирования своими мощными возможностями, практичностью и функциональностью. Рассмотренные здесь сценарии представляют собой лишь малую часть содержимого моего «ящика с инструментами». Надеюсь, они пополнят и вашу коллекцию.

Дейв Рот - Автор нескольких Perl-расширений Win32, в том числе Win32::AdminMisc, Win32::ODBC, Win32::Daemon и Win32::Perms. rothd@roth.net