Как безопасно отобразить переменную FOR %%~p, за которой следует строковый литерал
У меня есть переменная %%p
создан из for /f
Когда я пытаюсь использовать его с некоторыми дополнительными ссылками, такими как: %%~dp
а затем написать текст, после чего он получает доступ к другой переменной
set var="%%~dpabc.txt"
Кодовые выходы
%%~dpa instead of %%~dp
3 ответа
Таким образом, вы должны использовать FOR /F с несколькими токенами, как
for /f "tokens=1-16" %%a in (file) do echo %%~dpabc.txt
Или ваш код мог бы иметь вложенные циклы FOR. Что-то вроде
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dpabc.txt
)
)
Или даже что-то вроде
for %%a in (something) do call :sub
exit /b
:sub
for %%p in (somethingelse) do echo %%~dpabc.txt
exit /b
Все три приведенных выше примера кода выведут диск и путь %%~dpa
сопровождаемый "bc.txt". Согласно документации, переменные FOR являются глобальными, поэтому предложение DO цикла FOR подпрограммы имеет доступ к обоим %%a
а также %%p
,
Aschipfl хорошо документирует правила разбора модификаторов и переменных букв.
Всякий раз, когда вы используете переменную FOR перед строковым литералом, вы должны быть чрезвычайно осторожны, чтобы строковый литерал не мог интерпретироваться как часть раскрытия переменной FOR. Как видно из вашего примера, это может быть сложно. Сделайте буквальную динамику, и проблема еще хуже.
set /p "myFile=Enter a file name: "
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp%myFile%
)
)
Если пользователь вводит "abc.txt", то мы вернулись к тому, с чего начали. Но, глядя на код, не очевидно, что у вас есть потенциальная проблема.
Как говорят Герхард и Мофи, вы в безопасности, если используете символ, который нельзя интерпретировать как модификатор. Но это не всегда легко, особенно если вы используете FOR /F, возвращающий несколько токенов.
Есть решения!
1) Остановите разбор переменной FOR с помощью !!
и задержка расширения
Если вы посмотрите на правила того, как cmd.exe анализирует сценарии, вы увидите, что переменные FOR раскрываются в фазе 4 до того, как в фазе 5 происходит отложенное расширение. Это дает возможность использовать !!
как жесткая остановка для расширения FOR при условии, что включено отложенное расширение.
setlocal enableDelayedExpansion
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp!!abc.txt
)
)
%%~dp
расширяется должным образом в фазе 4, а затем в фазе 5 !!
расширяется до нуля, давая желаемый результат с буквой диска, за которой следует "abc.txt".
Но это не решает все ситуации. Это возможно для !
для использования в качестве переменной FOR, но этого должно быть легко избежать, кроме как в экстремальных ситуациях.
Больше беспокойства вызывает тот факт, что отложенное расширение должно быть включено. Это не проблема здесь, но если переменная FOR раскрывается в строку, содержащую !
тогда этот символ будет проанализирован отложенным расширением, и результаты, скорее всего, будут испорчены.
Итак !!
Хак с отложенным расширением безопасен для использования, только если вы знаете, что значение переменной FOR не содержит !
,
2) Используйте промежуточные переменные среды
Единственный простой надежный метод, позволяющий избежать проблем во всех ситуациях, - это передать значение переменной FOR в промежуточную переменную среды, а затем переключить отложенное расширение и работать со всей требуемой строкой.
for %%a in (something) do (
for %%p in (somethingelse) do (
set "drive=%%~dp"
setlocal enableDelayedExpansion
echo !drive!abc.txt
endlocal
)
)
3) Используйте символы Юникода через переменные окружения
Существует комплексное пуленепробиваемое решение, но для того, чтобы понять, как оно работает, требуется немало справочной информации.
Командный процессор cmd.exe представляет все строки внутренне как Unicode, как и переменные среды. Можно использовать любую кодовую точку Unicode, кроме 0x00. Это также относится к переменным символам FOR. Последовательность символов переменной FOR основана на числовом значении кодовой точки Unicode.
Но код cmd.exe, либо из пакетного сценария, либо введенный в командной строке, ограничен символами, поддерживаемыми активной кодовой страницей. Это может показаться тупиком - что хорошего в символах Юникода, если вы не можете получить к ним доступ с помощью своего кода?
Ну, есть простое, хотя и не интуитивное решение: cmd.exe может работать с предопределенными значениями переменных среды, которые содержат значения Unicode за пределами активной кодовой страницы!
Все модификаторы переменных FOR являются символами ASCII, которые находятся в первых 128 кодовых точках Юникода. Поэтому, если вы определите переменные с именами от $1 до $n, которые будут содержать непрерывный диапазон символов Юникода, начиная, скажем, с точки кода 256 (0x100), то вы гарантированно никогда не перепутаете вашу переменную FOR с модификатором.
Таким образом, если $1 содержит кодовую точку 0x100, то вы бы ссылались на переменную FOR как %%%$1%
, И вы можете свободно использовать модификаторы, такие как `%%~dp%$1%.
Эта стратегия имеет дополнительное преимущество в том, что относительно просто отслеживать переменные FOR при разборе диапазона токенов с чем-то вроде "tokens=1-30", поскольку имена переменных по своей сути последовательны. Последовательность символов активной кодовой страницы обычно не совпадает с последовательностью кодовых точек Unicode, что затрудняет доступ ко всем 30 токенам, если вы не используете взлом переменной Unicode.
Теперь определение переменных $n с помощью кодовых точек Unicode не является простой задачей разработки. К счастью, это уже сделано:-) Ниже приведен код, который демонстрирует, как определять и использовать переменные $n.
@echo off
setlocal disableDelayedExpansion
call :defineForChars 1
for /f "tokens=1-16" %%%$1% in (file) do echo %%~d%$16%abc.txt
exit /b
:defineForChars Count
::
:: Defines variables to be used as FOR /F tokens, from $1 to $n, where n = Count*256
:: Also defines $max = Count*256.
:: No other variables are defined or tampered with.
::
:: Once defined, the variables are very useful for parsing lines with many tokens, as
:: the values are guaranteed to be contiguous within the FOR /F mapping scheme.
::
:: For example, you can use $1 as a FOR variable by using %%%$1%.
::
:: FOR /F "TOKENS=1-31" %%%$1% IN (....) DO ...
::
:: %%%$1% = token 1, %%%$2% = token 2, ... %%%$31% = token 31
::
:: This routine never uses SETLOCAL, and works regardless whether delayed expansion
:: is enabled or disabled.
::
:: Three temporary files are created and deleted in the %TEMP% folder, and the active
:: code page is temporarily set to 65001, and then restored to the starting value
:: before returning. Once defined, the $n variables can be used with any code page.
::
for /f "tokens=2 delims=:." %%P in ('chcp') do call :DefineForCharsInternal %1
exit /b
:defineForCharsInternal
set /a $max=%1*256
>"%temp%\forVariables.%~1.hex.txt" (
echo FF FE
for %%H in (
"0 1 2 3 4 5 6 7 8 9 A B C D E F"
) do for /l %%N in (1 1 %~1) do for %%A in (%%~H) do for %%B in (%%~H) do (
echo %%A%%B 0%%N 0D 00 0A 00
)
)
>nul certutil.exe -decodehex -f "%temp%\forVariables.%~1.hex.txt" "%temp%\forVariables.%~1.utf-16le.bom.txt"
>nul chcp 65001
>"%temp%\forVariables.%~1.utf8.txt" type "%temp%\forVariables.%~1.utf-16le.bom.txt"
<"%temp%\forVariables.%~1.utf8.txt" (for /l %%N in (1 1 %$max%) do set /p "$%%N=")
for %%. in (dummy) do >nul chcp %%P
del "%temp%\forVariables.%~1.*.txt"
exit /b
:defineForChars
рутина была разработана в DosTips как часть более масштабной групповой работы по легкому доступу ко многим токенам с помощью оператора FOR /F.
:defineForChars
рутина и варианты представлены в следующих сообщениях в этой теме:
Такое поведение вызвано каким-то жадным характером разбора for
ссылки на переменные и его ~
-modifiers. В основном это следует этим правилам, учитывая предыдущие %
/%%
Знаки уже были обнаружены:
- проверить, если следующий символ
~
; если да, то:- взять как можно больше из следующих символов в регистре без учета регистра
fdpnxsatz
(даже несколько раз каждый), которые предшествуют символу, который определяетfor
ссылка на переменную или$
-знак; если такой$
-sign встречается, то:- сканировать для
:
; если найден, то:- если после персонажа
:
используйте это какfor
ссылаться на переменную и расширять, как ожидается, если она не определена, то не расширять; - если
:
последний символ,cmd.exe
потерпит крах!
- если после персонажа
- еще (нет
:
найдено) не расширяйте ничего;
- сканировать для
- иначе (если нет
$
знак встречается) развернитеfor
переменная с использованием всех модификаторов;
- взять как можно больше из следующих символов в регистре без учета регистра
- иначе (если нет
~
найден) использовать следующий символ какfor
ссылка на переменную и раскрытие, если таковое не определено, или даже после него нет символа, не расширяется;
Как уже объяснялось в правилах синтаксического анализа метапеременных ,
~
-обнаружение модификатора происходит жадным образом. Но вы можете прекратить синтаксический анализ с помощью другой метапеременной, которая в конечном итоге сведется к нулю, или с помощью
~$
-modifier, предложенный user463115 в комментарии , который даже не требует другой мета-переменной, поэтому можно использовать любую существующую:
rem // Using `%%~#` will expand to an empty string (note that `#` is not a valid `~`-modifier):
for %%# in ("") do (
rem // Establish a `for`-loop that defines meta-variables `%%a` to `%%p`:
for /F "tokens=1-16" %%a in ("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16") do (
rem /* Since `d`, `p` and `a` are all valid `~`-modifiers and a `for` meta-variable
rem `%%b` exists while `b` is not a valid `~`-modifier, `%%~dpab` is expanded: */
echo(%%~dpabc.txt
rem /* `for` meta-variable parsing is stopped after `%%~dp`, because the following `%`
rem is not a valid `~`-modifier, and neither exists a `for` meta-variable named `%`;
rem `%%~#` is expanded to an empty sting then (parsing surely stops at `#`): */
echo(%%~dp%%~#abc.txt
rem /* The following does not even require a particular `for` meta-variable like `%%#`,
rem it just uses the existing one `%%p` with the `~$`-modifier that specifies an
rem environment variable; since there is no variable name in between `$` and `:`,
rem there is certainly no such variable (since they must have names), hence `$~%:p`
rem expands to an empty string; note that `~$` is always the very last modifier: */
echo(%%~dp%%~$:pabc.txt
)
)
Обратите внимание, что этот подход не работает, если
for
мета-переменная с именем
%
(что не совсем обычно, но возможно).