Проблема с обратной косой чертой в путях (backslash)
Командная оболочка, входящая в состав UnxUtils, не всегда в состоянии
справиться с особенностями операционной системы Windows. Чаще всего придется
сталкиваться с привычкой оболочки обрабатывать эскейп-последовательности
там, где это не желательно (кстати, такая же проблема обнаружена и при
запуске команд под FAR-ом). Как известно, разделителем составных элементов
пути в ОС Windows является символ обратного слеша '\'. Бывают ситуации,
когда мы не можем работать с адаптированными для использовании в оболочке
путями (т.е. с теми, где бэкслеш заменен слешем). При этом оболочка
выполняет автоматическую обработку эскейп последовательностей.
Согласитесь, что совсем не к месту, когда, например, мы желаем получить
определенное значение реестра в переменной для последующей обработки, а
бэкслеши в этом значении интерпретируются как эскейп-последовательности.
Что бы убедиться в том, что я говорю правду, давайте напишем скрипт, который
продемонстрирует эту проблему. Итак, как мы мы будем присваивать
определенной переменной строковое значение пути, типичного для Windows
MYVAR='D:\server\mysql\bin;D:\cygwin\usr\local\bin'
echo $MYVAR
Запустив этот скрипт, мы получим результат
D:\server\mysquin;D:ygwin\usr\locain
Ужасно! Разве мы этого ожидали? В принципе, те, кто знаком с программированием
не удивятся такому результату, ведь они знают, что символ обратного слеша в
строках используется как идинтификатор так называемой эскейп-последовательности
- последовательности символов, которая описывает управляющий или непечатный
символ. Правильно будет переписать вышенаписанный код следующим образом
MYVAR='D:\\server\\mysql\\bin;D:\\cygwin\\usr\\local\\bin'
echo $MYVAR
Для того, что бы указать в коде программы символ обратного слеша
служит последовательность, состоящая из двух обратных слешей.
В результате выполнения этого скрипта мы увидим совершенно корректный
путь, тот самый, что и ожидали
D:\server\mysql\bin;D:\cygwin\usr\local\bin
Как видим, особых проблем с файловыми путями Windows при непосредственном
определении нет. Давайте попробуем получить какой-либо путь программным
способом. Пусть это будет эначение парамтра PATH в ключе реестра
HKCU\Environment. Для получения значения мы будем использовать утилиту
reg. Добавьте к значению этого параметра путь
"D:\server\mysql\bin;D:\cygwin\usr\local\bin;", что бы можно было
увидеть проблему. Если в реестре нет такого параметра, то добавьте новый
параметр типа REG_SZ.
Получать значение мы будем так
reg query HKCU\Environment /v PATH
Выполните эту команду из командной строки Windows и вы увидите содержание
запрошенного параметра
! REG.EXE VERSION 3.0
HKEY_CURRENT_USER\Environment
PATH REG_SZ D:\server\mysql\bin;D:\cygwin\usr\local\bin
Как видим, здесь все нормально. Но давайте попробуем выполнить эту команду
из скрипта командной оболочки, входящей в состав пакета UnxUtils, и присвоить
результат работы некоторой переменной MYVAR
MYVAR=`reg query "HKCU\Environment" /v PATH`
echo $MYVAR
Собственно, теперь мы можем наблюдать ту самую проблему несвоевременной
обработки
!REG.EXE VERSION 3.0
HKEY_CURRENT_USER\Environment
PATH REG_SZ D:\server\mysquin;D:ygwin\usr\locain
Еще один вариант такой "медвежьей" услуги
echo "$USERPROFILE\\putty.rnd"
В случае если %USERPROFILE% равно например
D:\Documents and Settings\root.EDEMNV.001, результатом
будет такая абракадабра
oot.EDEMNV.001\putty.rnds
А все потому, что последовательность \r совершенно справедливо
интерпретируется как возврат каретки. В этом случае все еще более усложняется,
т.к. USERPROFILE у нас предустановлено и любая попытка использовать значение
этой переменной приведет к автоматической интерполяции всех присутствующих
в переменной эскейп-последовательностей.
На самом деле, все так и должно быть. Решение этой проблемы - это приемлимая
плата за все те отсутствующие в Windows удобства, которые дает нам использование
типичной для UNIX командной оболочки. Дело в том, что любые данные, которые
попадают или выходят из окружения оболочки во время работы, интерпретируются
как текстовые данные. Следовательно, любые данные, которыми манипулирует
скрипт посредством переменных, обрабатываются на предмет
эскейп-последовательностей. Важно уяснить, что речь идет именно о переменных,
объявляемых внутри скрипта, а не о всех данных. Что бы понять разницу, в
противоположность всем вышеприведенным проблемным примерам достаточно рассмотреть
пример создания тарбола посредством команд tar | gzip. Данные от команды
tar направляются непосредственно на вход gzip, по этому рассматриваемая нами
проблема не актуальна для связки этих команд, даже в случае если эти команды
находятся в одном из проблемных скриптов.
Решение довольно простое, хотя и прибавляет немного кода. Нам нужна программа,
которая будет квотить возвращаемый результат. Назовем ее pquot. В принципе,
мы можем использовать конструкцию вида
MYVAR=`prog arg1 arg2 | pquot`
В этом случае pquot должна будет читать STDIN, квотить бэкслеши и выводить
результат на STDOUT. Но гораздо полезнее будет написать стартер, который будет
выполнять команднную строку. В этом случае мы сможем добраться до проблемных
перемнных окружения, правда, несколько необычным способом. Вызов команд
в случае использования стартера будет выглядеть так
MYVAR=`pquot prog arg1 arg2`
Я думаю смысл понятен: стартер выполняет роль промежуточного интерпретатора,
в качестве аргументов он получает командную строку, которую отправляет
на выполнение дефолтному командному интерпретатору, переопределяет и
читает поток вывода, эскейпит бэкслеши и в свою очередь записывает в собственный
STDOUT. Таким образом, в результате присвоения, MYVAR получит корректное значение,
которое можно будет использовать далее в программе.
Почему в предыдущем абзаце акцентировано внимание на командном интерпретаторе
по-умолчанию? Потому что благодаря этому, мы можем без проблем получать переменные
окружения, интерпретация которых может быть выполнена неверно из-за наличия
обратного слеша. Возвращаясь к примеру с %USERPROFILE%, получение абсолютного
пути будет выглядеть так
MYVAR=`pquot "echo %USERPROFILE%\putty.rnd"`
echo $MYVAR
В результате мы получим строку
D:\Documents and Settings\root.EDEMNV.001
Самый элементарный стартер на perl будет выглядеть так
#!/usr/bin/perl
exit(0) unless @ARGV;
open(PROG,"@ARGV |");
while($line = <PROG>){
$line =~ s#\\#\\\\#g;
print $line;
}
close(PROG);
К нему остается добавить лишь трансляцию кода завершения выполненной команды
и получится полноценный стартер. С другой стороны, не хотелось бы попадать в
зависимость от perl. Нет, мы его конечно любим и обязательно установим, но
подразумевается что скрипты командной оболочки полноценно работают
до установки какого-либо программного обеспечения, в том числе и perl.
Стартер должен входить в состав UnxUtils. Его доступность должна быть
гарантирована на самых начальных этапах работы нашей системы, коим и является
установка пакета UnxUtils.
Наверх