Как безопасно отобразить переменную FOR %% ~ p, за которой следует строковый литерал - PullRequest
1 голос
/ 21 мая 2019

У меня есть переменная %%p, созданная из команды for /f Когда я пытаюсь использовать его с некоторыми дополнительными ссылками, такими как: %%~dp, а затем пишу текст, он обращается к другой переменной

set var="%%~dpabc.txt"

Кодовые выходы

%%~dpa instead of %%~dp

Ответы [ 2 ]

4 голосов
/ 22 мая 2019

Таким образом, вы должны использовать 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
  )
)

На этапе 4 %%~dp должным образом расширяется, а на этапе 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 может работать с предопределенными значениями переменных среды, которые содержат значения Юникода вне активной кодовой страницы!

Все модификаторы переменных 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 представлены в следующих сообщениях в этой теме:

3 голосов
/ 21 мая 2019

Такое поведение вызвано жадным характером анализа for ссылок на переменные и их ~ -модификаторов.В основном это следует этим правилам, учитывая, что предыдущие % / %% -символы уже были обнаружены:

  • проверить, является ли следующий символ ~;если да, то:
    • принимает как можно больше из следующих символов в наборе fdpnxsatz без учета регистра (даже несколько раз каждый), предшествующих символу, который определяет ссылку на переменную for или$ -знак;если встречается такой $ -символ, то:
      • сканирует для :;если найдено, то:
        • , если после : есть символ, используйте его как ссылку на переменную for и разверните, как ожидается, если только он не определен, не расширяйте;
        • если : является последним символом, cmd.exe вылетит!
      • иначе (не найдено :) ничего не расширяйте;
    • else (если знак $ не обнаружен) разверните переменную for, используя все модификаторы;
  • else (если ~ не найдено) используйте следующий символ в качестве ссылки на переменную for и разверните, если это не определено или даже после символа нет, то не расширяйте;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...