Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также саму программу AutoHotkey и проблемы её использования)

Проект перевода документации AutoHotkey: перечень переведённых статей и статей в работе.

Особенности работы с переменными окружения в скриптах AutoHotkey

Автор статьи - Androgen Belkin


Мощный и относительно простой скриптовый язык AutoHotkey, конечно имеет "встроенные" средства по работе с переменными окружения. Однако иногда этих средств недостаточно, а использование уже имеющихся – требует знания нюансов. Вот мы здесь и выясним, чего "недостаточно" и что за "нюансы".


Для справки:

Переменные окружения (или их еще называют "переменные средЫ" или, по-английски, environment variables) это переменные, используемые операционной системой и разными программами для задания и нахождения путей к каким-либо папкам или файлам. Например, как какая-нибудь программа узнает, где можно хранить временные файлы? Очень просто - она запрашивает у винды переменную %TEMP% (или переменную %TMP%) и получает в ответ путь к этой папке (или папкам).

Переменные окружения бывают:


Так какие здесь могут быть затруднения? А вот какие: автор AutoHotkey, начиная с v1.0.43.08, ввел новую директиву #NoEnv. Эта директива запрещает использование в скриптах переменных окружения. Сделано это для увеличения производительности скриптов. Т.е. всякий раз, когда в скрипте встречается переменная, содержимое которой не задано, скрипт не пойдет искать это содержимое в переменных окружения (и не будет тратить на это время и ресурсы). Иными словами, если вы напишете, например, MyTempDir = %TEMP%, то переменная MyTempDir так и останется пустой.

Сейчас эта директива ещё опциональна, т.е. вы можете указать её и "насладиться" её последствиями, или твёрдой рукой выкорчевать эту "вредоносную" директиву из своих скриптов и убедиться, что никакого такого "увеличения производительности" на глаз так и не видно. Однако автор AutoHotkey обещает, что со временем эта директива будет используемой по умолчанию, и сделать ей DELETE уже не удастся. Поэтому предлагаю сделать вид, что эта директива уже безвозвратно внедрена в наши скрипты, и подумать, как теперь жить :)

Надо сказать, что Крис (он же автор AutoHotkey), чтобы мы не очень страдали и возмущались, предлагает замену некоторым распространенным переменным окружения. Выглядит это просто: писать теперь нужно не Temp, а A_Temp; не WinDir, а A_WinDir. Список таких замен постепенно расширяется, но очевидно, что все возможные переменные окружения учесть не получится, и потому я предлагаю способы работы с оставшейся "неучтенкой". Кроме того, даже при работе со стандартными переменными окружения есть "сюрпризы".


Итак, начнем с простого. Допустим, нам нужно использовать в скрипте переменную окружения ALLUSERSPROFILE. В этой переменной содержится путь к папке профилей всех пользователей компьютера.

Если мы напишем так:

MsgBox, AllUsersProfile = "%AllUsersProfile%" ; показываем результат

...то увидим, чему равно значение этой переменной окружения.

Но если напишем:

#NoEnv ; запрещаем имена переменных как у переменных окружения
MsgBox, AllUsersProfile = "%AllUsersProfile%" ; показываем результат

...то никакого значения мы не увидим. Сначала мы должны "вручную" получить значение этой переменной. Делается это так:

#NoEnv ; запрещаем имена переменных как у переменных окружения
EnvGet, AllUsersProfile, AllUsersProfile ; получаем значение переменной окружения
MsgBox, AllUsersProfile = "%AllUsersProfile%" ; показываем результат

Тут, как видим, все просто. Двигаем дальше. При скриптовых автоматизациях одно из самых распространенных действий - это анализ всяких конфигурационных файлов (ini, cfg, inf и даже bar, log, txt и т.д.), независимо от того, создали их вы, или они были созданы без вашего участия. Так вот, предположим, у нас есть ini-файл, где прописаны пути через переменные окружения (сохраните этот код в файле c:\Test.ini):

[PATH]
cmd1=%SystemRoot%\system32\calc.exe

И теперь попробуем запустить калькулятор, прочитав путь к нему из этого файла (обратите внимание, мы пока не используем директиву #NoEnv):

IniRead, MyCmd1, c:\Test.ini, PATH, cmd1 ; читаем путь из ini-файла
Run, %MyCmd1% ; запускаем прогу

Не получится. Скрипт выдаст ошибку и пояснит, что не может запустить эту программу. Просто он не понимает, что такое %SystemRoot%. Объясним ему:

IniRead, MyCmd1, c:\Test.ini, PATH, cmd1 ; читаем путь из ini-файла
Transform, MyCmd1, Deref, %MyCmd1% ; разворачиваем все встреченные в строке переменные - в их значения
Run, %MyCmd1% ; запускаем прогу

В таком виде калькулятор запускается. Ура? Не спешим радоваться. Теперь добавляем директиву #NoEnv:

#NoEnv ; запрещаем имена переменных как у переменных окружения
IniRead, MyCmd1, c:\Test.ini, PATH, cmd1 ; читаем путь из ini-файла
Transform, MyCmd1, Deref, %MyCmd1% ; разворачиваем все встреченные в строке переменные - в их значения
Run, %MyCmd1% ; запускаем прогу

...и видим ту же ошибку, что была и до ввода команды Transform. М-м-да. А если посмотреть на это окно с сообщением об ошибке повнимательнее, то видно следующее: "Action: <\system32\calc.exe>". Т.е. перед "\system32" - пусто. Вот это и есть тот "сюрприз", о котором я говорил.

На англ. форуме AHK я нашел, что некто majkinetor тоже бьется с этой проблемой. (Если интересно, вот ссылки: http://www.autohotkey.com/forum/topic10440.html и http://www.autohotkey.com/forum/viewtopic.php?t=10451). Люди там попадаются грамотные - решили проблему. Результатом их групповых усилий стала функция по разворачиванию переменных окружения в их содержание. Вот эта функция (слегка мной подредактированная в соответствии с документацией по WinAPI), и её применение для нашего случая:

#NoEnv ; запрещаем имена переменных как у переменных окружения
IniRead, MyCmd1, c:\Test.ini, PATH, cmd1 ; читаем путь из ini-файла
MyCmd1 := ExpandEnvVars( MyCmd1 ) ; вызываем функцию по разворачиванию переменных окружения в их содержание
Run, %MyCmd1% ; запускаем прогу

ExpandEnvVars( ppath ) ; функция по разворачиванию переменных окружения в их содержание
{
    VarSetCapacity( Dest, 2000 ) ; обеспечиваем достаточную вместимость переменной
    DllCall( "ExpandEnvironmentStrings", Str, ppath, Str, Dest, Int, 1998 ) ; получаем содержание п. окруж-я
    Return, Dest ; возвращаем содержание переменной окружения
}

Оформите эту функцию как отдельный скрипт и подключайте директивой #Include ко всем скриптам, в которых предполагаете работать с переменными окружения. И если вы не забудете её вызвать, чтобы развернуть попавшиеся переменные окружения, то упомянутые "сюрпризы" станут обходить вас стороной.

Ладно. С этим, надеюсь, все понятно. А что, если нам для чего-нибудь понадобится список ВСЕХ переменных окружения? (Чуть позже я скажу, для чего такой список может быть полезен). Оказалось, что и для этих целей в AutoHotkey не предусмотрено простого способа. Но и эту задачу решили доблестные AutoHotkey-щики.

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

;*************************************************************************************
; AutoHotkey Version:   1.0.44.08+
; Автор:                Androgen Belkin
; Имя скрипта:          Environment variables.ahk (v.1.0)
;*************************************************************************************
; Получение списка переменных окружения и вывод их в окне, откуда их можно копировать.
;*************************************************************************************
/*
WinAPI функция GetEnvironmentStrings возвращает указатель на адрес блока памяти, содержащий п.окружения для
текущего процесса. Переменные окружения содержатся в блоке в виде null-terminated string
(символьной строки, заканчивающейся терминальным символом '0' - знак конца строки в C/C++).
Var1=Value1\0
Var2=Value2\0
...
VarN=ValueN\0\0
Использующиеся в скрипте WinAPI функции lstrlen и lstrcpy правильно работают только с такими строками.
*/

#NoTrayIcon ; не отображать иконку скрипта в трее
#NoEnv      ; запрещаем имена переменных как у переменных окружения (повышаем производительность скрипта)
; получаем указатель на адрес блока памяти, содержащий пер.окружения
Block_Pointer := DllCall( "GetEnvironmentStrings" )
String_Pointer := Block_Pointer ; начинаем читать строки с адреса начала блока
Loop ; пере'Loop'ачиваем весь нужный блок памяти
{
    String_Length := DllCall( "lstrlen", UInt, String_Pointer ) ; получаем количество символов в строке
    If String_Length = 0 ; если переменные закончились
        Break ; конец цикла
    ; задаем нужную вместимость переменной (+ символ конца строки)
    VarSetCapacity( Cur_String, String_Length + 1, 0 )
    ; копируем строку из указанного адреса памяти в переменную
    DllCall( "lstrcpy", Str, Cur_String, UInt, String_Pointer )
    String_Pointer := String_Pointer + String_Length + 1 ; готовимся к переходу к следующей строке
    If Cur_String = =S:=S:\ ; иногда, неизвестно откуда, появляется эта загадочная переменная...
        Continue ; продолжаем цикл (не будем вносить ее в список)
    If NOT Env_List ; если список переменных окружения еще пуст
        Env_List = %Cur_String% ; начинаем список
    Else
        Env_List = %Env_List%`n%Cur_String% ; добавляем строку в список
}
DllCall( "FreeEnvironmentStrings", UInt, Block_Pointer ) ; "отпускаем" вызванный блок памяти
; Список готов и содержится в Env_List.
; Дальше его уже можно использовать. Тогда остальную часть можно просто удалить.

Gui, +LastFound ; делаем окно GUI последним найденным
Gui, Add, Text, Center, Список переменных окружения: ; добавляем заголовок списка
Gui, Add, Edit, -VScroll -WantReturn ReadOnly, %Env_List% ; поместить список в GUI
Gui, Add, Button, Default w75 h23, &OK ; добавляем кнопку "ОК" с заданными размерами
Gui, Show, Hide ; отобразить окно, но в скрытом виде
WinGetPos,,, Gui_Width ; получить ширину окна
GuiControl, Move, Button1, % "x" Gui_Width/2 - 75/2 ; поместить кнопку "ОК" по центру ширины
GuiControl, Move, Static1, % "w" Gui_Width - 20 ; поместить заголовок списка по центру ширины
Gui, Show,, Environment variables -- by Androgen© ; показываем окно (ранее скрытое)
ControlSend, Edit1, ^{HOME} ; снимаем выделение с текста в списке
Return ; конец секции автовыполнения, ждем действий юзера

GuiClose: ; при нажатии кнопки закрытия окна
GuiEscape: ; при нажатии кнопки ESC
ButtonOK: ; при нажатии кнопки "ОК"
ExitApp ; закончить скрипт

Так как же можно использовать этот список? Ну, например, можно проанализировать его на предмет наличия в нем нужных переменных, чтобы скрипт не спотыкался, если их нет или они заданы не так, как вы рассчитывали. Или чтобы создать отчет при наладке системы или администрировании. Или можно запустить этот скрипт из какой-нибудь проги и посмотреть, какие переменные окружения добавляет сама эта прога. Например, если запустить скрипт из Total Commander'а, то можно увидеть его переменные COMMANDER_PATH и COMMANDER_INI (мы их упоминали). Ну и т.д. и т.п. Наконец, скрипт можно использовать как пример использования непростой команды DllCall :)

В общем, средства для работы с переменными окружения в AutoHotkey вряд ли можно назвать дружественными. А жаль. Будем надеяться, что когда-нибудь и эта часть инструментов AutoHotkey'я будет столь же простой, как и многие другие его части.

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.