Неочевидное поведение команд в командной строке.
Создав супер быстрый алгоритм BAT CMD, сумевший ушатать интерпретатор командной строки Windows — я столкнулся с неожиданно неочевидным поведением команд, о котором если и догадывался ранее, то — весьма туманно и неопределённо … Даже, не знаю — с чего начать ))) …
Установщик SET.
Давайте начнём с команды установщика SET … Почему — установщик ? …
Помогая выполнять арифметические операции SET /A B=2+5 эта команда принимает + как руководство к действию, сумматор, и вычисляя 2+5 — устанавливает переменной %B% значение =7 … Можно сказать, и — присваивает … Но …
присвоить — assign, appropriate … значить — пометить, заимствовать себе … в то время, как …
установить — install, place, mount, fix, specify, adjust, fit … что в переводе с английского — объединяет все действия к общей идее — установить нечто — на своё место ; приспособить — таким образом, чтобы со всех сторон, это выглядело — правильно … Поэтому, установщик — наиболее подходящее слово … Тем более, в других языках, оператор присваивания, это знак = равно …
Тем, более — SET — не только вычисляет … А, вот и — подходящий пример …
Установите переменной а значение & …
SET a=& … не работает … ECHO %a% = %a% …
Наверно, надо экранировать символ … Среди специальных символов, используемых в командной строке, можно выделить следующие : & | > < / ? * %
, которые используются для соединения команд, перенаправления ввода / вывода, поиска файлов по маске и т.д. … Амперсанд — входит в их число, и подлежит экранированию …
Что, чем и как экранировать ? … символ ^ экранирует практически всё, включая самого себя … Подробнее про экранирование спец символов, можно будет прочитать — ниже …
Значит нужно экранировать — так : ^& … Пробуем …
SET a=^& … не работает … ECHO %a% = Режим вывода команд на экран (ECHO) включен …
Ого, крепкий орешек ))) …
Начинаем искать специальные знания и натыкаемся на совет — использовать кавычки … Обычно, кавычки применяются, когда нужно игнорировать пробел в значении переменной, обычно воспринимаемый, как — делитель параметров … Также, кавычки — являются своеобразным экраном текстовых символов и строк … Что же — пробуем …
SET «a=&» … не работает … ECHO %a% = Режим вывода команд на экран (ECHO) включен … Тот же вид, только сбоку ))) … Ну, что же, поскольку мыслей больше нет — объединяем все известные способы — в одно целое …
SET «a=^&» … ура … заработало … ECHO %a% = & …
Посмотрите, вам не кажется эта запись кавычек — совершенно неочевидной … Наверное правильнее, было бы записать это выражение — так …
SET a=»^&» … Оказывается — нет … Эта запись — неправильная … Оказывается ещё и то, что команда SET — уже имеет неявные кавычки, которые гласят : всё, что находится внутри кавычек — принадлежит переменной a …
SET a=»^&» … ECHO %a% = «^&» … Явные кавычки и то, что экранировано внутри них — стало принадлежать неявным кавычкам и, соответственно — оператору / переменной а … Вот это наиболее яркий пример совершенно неочевидного поведения интерпретатора командной строки …
В случае математики, этот пример — не показывает такой нужной наглядности …
SET /A «a=2+5″ или SET /A a=»2+5» всё равно дают a = 7…
Теперь, посмотрим кавычки с пробелом в пути …
SET a=c:\my folder … ECHO %a% = c:\my folder … Пока вы оперируете переменной внутри интерпретатора командной строки — кавычки не имеют большого значения (до сложных случаев экранирования спецсимволов) … В 99% стандартных операций с текстом и числами, кавычки — не потребуются …
Создадим папку c:\my folder и внутри my folder.txt и выполнить команду …
DIR %a% … Содержимое папки c:\ — файл не найден … Содержимое папки c:\users\user — файл не найден …
DIR «c:\my folder» … Явным прямым указанием проверяем, есть ли такой каталог на диске, а в кавычках — потому-что есть пробелы в пути … Содержимое папки c:\my folder … файл my folder.txt … Всё прекрасно видится и читается … Изменяем команду установщика пути …
SET «a=c:\my folder» … ECHO %a% = c:\my folder … Эта команда — снова не подойдёт, так как не содержит кавычек, экранирующих пробелы в пути …
SET a=»c:\my folder» … Именно в этом случае, нужен — неправильный вариант команды, который фактически выглядит, как SET «a=»c:\my folder»» — внутри неявных кавычек SET …
ECHO %a% = «c:\my folder» … Проверяем значение переменной … Другое дело ))) …
DIR %a% … Проверяем DIR … Содержимое папки c:\my folder … файл my folder.txt … Всё работает … Когда вы используете пробелы или спец символы — в виде текста команды, в интерфейсе командной строки Windows — они подлежат экранировке, чтобы не быть интерпретированными — как управляющий операторы или конструкции синтаксиса …
Командная строка. Интерпретатор CMD. Интерфейс Windows.
Многие, наблюдая черный экран DOS в среде Windows — воспринимают его, как обычную программу CMD.EXE, считая этот способ взаимодействия с компьютером и ОС — страшным, сложным, непрактичным и устаревшим ))) … После много красочной Windows — очень некомфортно оказаться в среде, без графического интерфейса и визуальной обратной связи … На самом деле, это — одно из жесточайших и неочевидных заблуждений … Нужно явно понимать, что эти разные концепции командной строки, интерпретатор CMD и интерфейс Windows — тесно интегрированы одно в другое … Отсюда и проистекает понимание, того — как воспринимать спец символы — в зависимости от среды обработки …
Интерпретатор CMD командной строки … Если специальные символы или управляющие операторы используются для вычислений параметров и результатов команды — внутри самой себя — спецсимволы должны экранироваться, чтобы отменить их специальное действие и представить их в виде обычных тексто / графических символов …
Напечатать строку a > b … Это — не выражение, и не переменные — обычный текст …
ECHO a > b … не даст ничего — бред, с точки зрения интерпретатора …
«ECHO a > b» … заставить интерпретатор ответить явно, в чем бред ? ))) … не является внутренней или внешней командой, исполняемой программой или пакетным файлом …
ECHO «a > b» … напечатает «a > b» , так как кавычки — отменяют действие оператора перенаправления > и попадают в область действия неявных кавычек ECHO, поэтому — тоже будут отображены …
ECHO a ^> b … напечатает a > b … экранировать только действие оператора перенаправления (среди текстовых символов) … В ответ — ECHO напечатает обычные символы, как эхо в горах — что крикнешь, то и услышишь ))) …
Интерфейс Windows командной строки …
SET a=3
SET b=5
ECHO %a%>%b% … так как % попытался экранировать символ перенаправления > , а ECHO — не смогла сказать чего либо внятного — она отрапортовала, что : Режим вывода команд на экран (ECHO) включен … То есть — команда сообщила свой статус …
ECHO %a% > %b% … ничего, тишина ))) … Попробуем заставить CMD дать явный ответ …
«ECHO %a%>%b%» … «ECHO %a% > %b%» … echo 3>5 и echo 3 > 5 — действительно не являются никакими командами, а Эхо не может заместить или пере вычислить значение одним оператором — в другом …
ECHO %a%+=%b% … напечатает 3+=5 … это просто текст …
ECHO %a% … отобразит значение переменной a = 3 …
Однако, ECHO %a% > nul … не отобразит вывод Эхо, перенаправив его в специальный контроллер nul — это специальное устройство в операционной системе Windows, которое используется для отключения вывода текста в командной строке … нулевое устройство — есть, но не сохраняет вывод команд … что то вроде ассемблерной команды NOP — ничего не делать … Однако, ничего не делая, команда — тратит процессорное время, создаёт задержки, выравнивает блоки в памяти и вообще — является реальной командой с возможностью обращения к ней (программная заглушка) …
Однако, ECHO %a%>nul … Эхо снова сообщит свой статус, так как % снова попытается экранировать символ перенаправления и будет сбивать с толку интерпретатор данных и команд … Но, на самом деле, в обычном программном коде, подобная запись, без пробелов — практически не вызывает проблем, насколько я могу вспомнить … Какое значение имеет пробел в данных и командах BAT CMD ? …
Суть сравнения интерпретатора CMD и интерфейса Windows одного и того же выражения / окна командной строки — в том, что если неочевидно, к какому результату приведет действие той или иной команды, расположения спецсимволов и пробелов — лучше убедиться, в этом — заранее ))) …
Пробел в командах и данных BAT CMD.
Иногда, для красоты кода, в примерах программирования, команды, данные и спец символы управляющих конструкций — вводят через пробел …
SET a = 3 … результат ECHO %a% = 3 , но иногда может быть %a% = %a% , то есть — переменная не была пере / или — определена …
SET a = 3 ; SET a = 4 … результат ECHO %a% = 3 … Снова — бардак на раздаче … Эта ситуация может сложиться из-за неопределенного состояния регистров интерпретатора (предположение) после выполнения прошлых команд … Но, согласитесь, в этом — что-то есть и кроется какой-то нюанс ))) …
SET b=4 … ECHO %b% = 4 …
SET b=5 … ECHO %b% = 5 … Никаких проблем — не наблюдается … Сравните с предыдущим примером ECHO %a%>nul и ECHO %a% > nul …
Пробелы в командах BAT CMD имеют важное значение, поскольку они используются для разделения аргументов командной строки … В большинстве случаев пробелы между аргументами нужны, чтобы команда корректно выполнилась … Однако, есть некоторые случаи, когда в командах нужно использовать кавычки для защиты пробелов внутри аргументов …
Также пробелы — могут быть использованы в данных, передаваемых в команду … Если данные содержат пробелы, то их также нужно заключать в кавычки или экранировать символом ^ чтобы команда работала правильно …
Режим SetLocal EnableDelayedExpansion — может быть полезным и рекомендуется, так как позволяет использовать переменные — с пробелами внутри значений … В любом случае, использование пробелов в командах и данных в командной строке следует использовать — осторожно и — правильно экранировать символы, если это необходимо для корректной работы программного кода …
Программные файлы интерфейсы физического оборудования ПК.
В командах BAT CMD, кроме NUL — есть несколько других специальных файлов-устройств, которые могут использоваться в качестве контролов … Эти файлы-устройства предоставляют доступ к различным физическим устройствам компьютера через командную строку и могут быть использованы как входные и выходные интерфейсы для команд …
CON — консольный ввод / вывод, для ввода с клавиатуры и вывода на экран …
PRN — принтер, для вывода команд на принтер …
LPT1 (LPT2 , LPT3) — параллельные порты коммуникации ПК, в настоящее время устарели и могут быть задействованы через драйвера ОС адаптеров переходников USB — LPT …
COM1 (COM2 , [COM3 , COM4]) — последовательные порты ввода / вывода и обмена данными ПК c периферийными устройствами … В настоящее время — практически устарели, но ещё очень много софта и программ требует их для своей работы, и они могут быть задействованы через драйвера ОС широко распространенных версий адаптеров переходников USB — COM / RS-232 …
Различные наглядные примеры использования программных файлов физических устройств в командах BAT / CMD … COPY file.txt CON … DIR > LPT1 … ECHO %a% > COM1 … Однако — следует быть осторожными при использовании этих файлов-устройств, поскольку неверное использование может привести к непредсказуемым результатам, ошибкам устройства и прочим неожиданно / неприятным событиям …
Передача параметров между сценариями BAT CMD.
Для передачи параметров из одного .bat файла в другой, можно использовать оператор CALL … Эта команда вызывает один сценарий из другого, при этом некоторые данные можно передать, как входные параметры для вызываемого файла, до 9, через разделитель пробел …
CALL 2.bat aaa bbb ccc …
При запуске, BAT / CMD умеет принимать параметры %0 — %9, при этом %0 — уже занят текущим именем файла (текущим расположением пути запуска скрипта) …
CALL b.cmd … %0 = b.cmd ; остальные параметры — пустые (не определены) …
CALL b.cmd a b c d e f g h i j k l m n o … Пытаемся передать 15 параметров, и получаем … %0=b.cmd … %1=a … %2=b … %3=c … %4=d … %5=e … %6=f … %7=g … %8=h … %9=i … — это, то, что можно явно передать, как параметры … Но, если продолжить, то увидим : %10=a0 … … %11=a1 … %12=a2 … %13=a3 … %14=a4 … %15=a5 … Какие-то индексы неопределённых внутренних структур …
CALL b.cmd … Если же — вызвать сценарий скрипта — без параметров, но попытаться считать те же самые %0 — %15, то увидим — такую картину : b.cmd . . . . . . . . . 0 1 2 3 4 5 , где точки — это пустые, но осязаемые пробелы …
Модификатор %~1 используется для удаления любых кавычек, которые могут быть включены в параметр для защиты от пробелов … Нужные кавычки, также — передаются внутри кавычек, с последующим очищением внешних — модификатором %~ получения и преобразования входного параметра …
Упоминалось, что если параметр содержит знак = в начале имени — то от него невозможно отделаться никоим образом или способом … В моём случае на CMD Win7, это — не подтвердилось (=x обрезается до x ; кавычки — снимаются ) … Пример — ниже … Для удобства я располагаю его вертикально, чтобы было понятно, какой слева отправляемый параметр и справа — принимаемый …
%% … a.cmd … b.cmd
%0 … — … b.cmd
%1 … «a b» … «a b»
%2 … «c d» … «c d»
%3 … =e … e
%4 … «=f» … «=f»
%~5 … «=g» … =g
%6 … h … h
%7 … i … i
%8 … j … j
%9 … k … k
Несмотря на то, что внешне — всё работает исправно, сложные случаи параметров рекомендуется передавать — через внешние файлы …
Прочие особенности команд командной строки.
О сложности, неадекватности, неожиданности, непредсказуемости и неочевидности поведения команд командной строки, можно писать, если не вечно, то — много и долго ))) … Несмотря, на всю простоту и примитивность BAT CMD интерпретатора …
RANDOM.
Я не заметил никакой принципиальной разницы между %RANDOM% и !RANDOM!, хотя упоминается, что она — есть ))) …
SET.
Команда SET может неявно обрезать пробелы в начале и конце значения переменной, если ее определение задано с пробелами … Но я бы сильно не рекомендовал полагаться на неочевидность и непредсказуемость её поведения ))) …
SET a= 5 … ECHO %a% = пробел5 …
FOR.
В команде FOR имеется неявное поведение, когда переменная цикла $ может не подставляться, если она используется в блоке команд FOR сразу после DO — вернет только первый вариант %i, но не второй и третий … Не знаю, у меня — работает …
FOR %%I IN (1) DO ECHO %%I %%I %%I … ответ : 1 1 1 …
ECHO.
В команде ECHO есть неявный символ перевода строки, добавляющийся в конец строки — может создавать проблемы, если не учитывать его влияние …
ECHO /n aaa — запрет переноса строки — не работает, в моём случае … так и в справке по команде — нет этой опции ))) …
START.
Команда имеет неявное поведение, когда она используется для запуска графического интерфейса (GUI) … При запуске графического интерфейса с помощью команды START будет создан новый экземпляр командной строки, что может привести к непредвиденным результатам, если большинство команд ожидает выполнения — в той же командной строке …
DIR.
В случае с командой DIR, спецсимволы &, <, > или | — не требуют экранирования, так как они используются только для операций в командной строке, а не внутри команды DIR самой в себе …
Экранирование спецсимволов.
Действительно, при работе в языке BAT и CMD символы %, !, ^, «, &, ?, *, / — имеют особое значение … Для того чтобы использовать их в командной строке, необходимо использовать экранирование символов … Что, чем и как экранировать ? …
Символ ^ экранирует практически всё, включая самого себя … Символ ^ используется в CMD / BAT для экранирования символов …
Амперсанд &, оператор объединения команд в одной строке …
Угловые скобки < >, оператор перенаправления …
Вертикальная черта / штрих |, оператор конвейера …
Скобки ( ), символ группировки …
Звездочка *, шаблон подстановки имён файлов и папок …
Знак вопроса ?, маска для поиска файлов по шаблону имени …
Прямой слеш /, указатель опций команд …
Двойная кавычка «, указатель начала и конца строки … Кавычки — также пытаются, в меру сил — подавлять специальные операторы внутри обозначенной текстовой строки … Помните про её недостаточность, в случае с амперсандом SET «a=^&» … Также, подмечено, что если ECHO, вместо ответа — указывает свой статус (режим Эха включен), то заключение выражения в кавычки — лишь подтвердит, что указанной команды, исполняемого файла или сценария — не существует ))) … Плохая команда, можно сказать ))) …
Сами себя экранируют …
% экранирует %, и по мере возможности пытается экранировать некоторые управляющие символы, не разделённые пробелом …
! экранирует ! переменные в режиме SETLOCAL локального включения расширений функциональности команд интерпретатора CMD и разрешение отложенного раскрытия переменных … Отложенное (ленивое) раскрытие переменных EnableDelayedExpansion означает, что переменные будут раскрываться — не в момент выполнения команды, а в момент ее переопределения — в текущий момент времени…
И, уже известный ^ экранирует ^ …
Также отмечается, что если в переменную нужно внести значение с первым знаком равно =TestValue, то оно должно экранироваться нижним подчёркиванием, что вообще — совершенно неочевидно ))) … _=TestValue … По этой же причине — категорически не рекомендуется называть имена переменных — начиная с символа _ нижнего подчёркивания …
Раздел computer : список всех страниц …
Author: Daniel Colascione
Источник: Everyone quotes command line arguments the wrong way
История вопроса
Всем нам время от времени приходится передавать произвольные аргументы командной строки некоторой программе. Аргументы могут быть именами файлов, отладочными флагами, именами компьютеров или чем-то ещё: дело в том, что мы должны взять строку и убедиться, что некоторая программа получит именно эту строку в своём массиве argv независимо от содержимого строки. Задача сложнее, чем кажется.
Хорошо это или плохо1, но Windows знает лишь об одной командной строке для каждого процесса. Поскольку одна строка не очень полезна, библиотеки сговорились предоставлять иллюзию нескольких аргументов командной строки: перед созданием процесса программа компонует все аргументы в одну командную строку, и новый процесс перед вызовом функции main
, разбивает строку на аргументы и передаёт их как argv
. В принципе, каждая программа может обрабатывать командную строку по-своему, но большинство использует соглашение, которое понимают функция CommandLineToArgvW и библиотека Microsoft C. Это соглашение неплохое, потому что даёт способ кодирования любого аргумента командной строки как части командной строки без потери информации.
Проблема в том, что нет функции ArgvToCommandLineW. Как составить строку аргументов, понятную функции CommandLineToArgvW
?
Тестовая программа
Для разъяснения мы будем использовать маленькую программу, выводящую перечень аргументов:
#include <stdio.h>; int __cdecl wmain(int, wchar_t** argv) { for (int arg = 0; argv[arg]; ++arg) { wprintf (L"%d: [%s]\n", arg, argv[arg]); } }
Популярные решения: понятно, просто и неверно
Библиотека C runtime бесполезна
Нашим первым порывом должно быть намерение найти библиотечную функцию, которая решает проблему. Если бы мы провели такой поиск, то быстро нашли бы наборы функций C runtime для запуска дочерних процессов: _exec и _spawn. Эти функции кажутся ровно тем, что нам нужно: они берут произвольное количество аргументов командной строки и обещают запустить процесс. К сожалению, эти функции не обрабатывают аргументы и не обрамляют их кавычками: аргументы объединяются в одну строку через пробелы, и эта строка передаётся процессу, где она в конечном счёте интерпретируется функцией CommandLineToArgvW
. Такой подход работает лишь с аргументами, не содержащими пробелы.
Если мы запустим child.exe таким способом…
_spawnlp (_P_WAIT, L"child.exe", L"child.exe", L"argument 1", L"argument 2", NULL)
… child.exe получит пять аргументов командной строки:
0: [child.exe] 1: [argument] 2: [1] 3: [argument] 4: [2]
Это не то, что нужно!
Хотя функции запуска процессов C runtime кажутся тем, что нам требуется, на самом деле они бесполезны для решения проблемы с командной строкой.
Недостаточно просто добавить кавычки
Обнаружив проблемы предыдущего способа, можно предположить, что мы обратим внимание на совет в документации C runtime и заключим в двойные кавычки аргументы с пробелами. Это решение также неверное.
Вспомните2 о том, что соглашение CommandLineToArgV3 диктует заключение в двойные кавычки аргументов, содержащих пробелы. Предыдущий способ и обрамление аргументов кавычками дают хорошие результаты в простых случаях, и многие на этом останавливаются. Командная строка…
child.exe argument1 "argument 2" "\some\path with\spaces"
… интерпретируется корректно:
0: [child.exe] 1: [argument1] 2: [argument 2] 3: [\some\path with\spaces]
Пока всё хорошо: что будет с более сложными аргументами? Помните, что наше соглашение также требует предварять обратной косой чертой символ двойной кавычки внутри аргумента и ставить две обратных косых черты перед кавычкой, которая закрывает аргумент и не является частью аргумента4. Если мы придерживаемся нашего упрощённого подхода и передадим такую командную строку:
child.exe argument1 "she said, "you had me at hello"" "\some\path with\spaces"
… программа child увидит следующие аргументы:
0: [child.exe] 1: [argument1] 2: [she said, you] 3: [had] 4: [me] 5: [at] 6: [hello] 7: [\some\path with\spaces]
Такой результат нам тоже не годится! Проблема становится ещё более коварной при несбалансированном количестве кавычек:
child.exe argument1 "argument"2" argument3 argument4 0: [child.exe] 1: [argument1] 2: [argument2 argument3 argument4]
Аргументы, заканчивающиеся кавычками, также приводят к нежелательным интерпретациям:
child.exe "\some\directory with\spaces\" argument2 0: [child.exe] 1: [\some\directory with\spaces" argument2]
Многие популярные программы (в т.ч. командные оболочки, авторам которых следовало бы лучше изучить вопрос) используют этот простой подход. Разработчики проверяют лишь простые строки аргументов, оставляя пользователей в недоумении, когда командная строка оказывается испорченной.
Правильное решение
Мы убедились, что использовать кавычки с произвольным аргументом командной строки непросто, а неверная расстановка кавычек приводит к неуловимым и раздражающим проблемам. Нижеприведённая функция заключает аргумент в кавычки правильно; переведите её на свой язык программирования в соответствии с выбранным стилем кодирования.
void ArgvQuote ( const std::wstring& Argument, std::wstring& CommandLine, bool Force ) /*++ Описание: Эта функция добавляет заданный аргумент в командную строку так, что функция CommandLineToArgvW вернёт аргумент без изменений. Аргументы командной строки должны разделяться пробелами; эта функция не добавляет пробелы. Аргументы: Argument - Кодируемый аргумент. CommandLine - Командная строка, которая дополняется кодированным аргументом. Force - Определяет, надо ли заключать в кавычки аргумент, если в нём нет символов, которые надо заключить в кавычки. Возвращаемое значение: Нет. Окружение: Произвольное. --*/ { // // Без особого указания добавляем кавычки только при // необходимости --- во избежание проблем с программой, // которая неправильно обрабатывает кавычки // if (Force == false && Argument.empty () == false && Argument.find_first_of (L" \t\n\v\"") == Argument.npos) { CommandLine.append (Argument); } else { CommandLine.push_back (L'"'); for (auto It = Argument.begin () ; ; ++It) { unsigned NumberBackslashes = 0; while (It != Argument.end () && *It == L'\\') { ++It; ++NumberBackslashes; } if (It == Argument.end ()) { // // Экранируем все символы обратной косой черты, но // позволяем обработать как метасимвол завершающую двойную // кавычку,которую добавляем ниже. // CommandLine.append (NumberBackslashes * 2, L'\\'); break; } else if (*It == L'"') { // // Экранируем обратную косую черту и последующую // двойную кавычку. // CommandLine.append (NumberBackslashes * 2 + 1, L'\\'); CommandLine.push_back (*It); } else { // // Обычная обратная косая черта. // CommandLine.append (NumberBackslashes, L'\\'); CommandLine.push_back (*It); } } CommandLine.push_back (L'"'); } }
Чтобы сформировать командную строку для программы из произвольных аргументов, мы кодируем каждый аргумент (включая имя программы) при помощи вышеприведённой функции и добавляем после него пробел. После этого можно передать результирующую строку функции CreateProcess в параметре lpCommandLine
и быть уверенным, что дочерний процесс декодирует каждый аргумент в первоначальный вид.
cmd.exe
Можно сделать вывод, что мы закончили: ведь мы теперь понимаем, как отправить произвольные строки посредством CreateProcess
так, чтобы они остались неизменными — но жизнь сложнее. Часто мы не можем напрямую снабдить CreateProcess
своей командной строкой, а должны передать её дочернему процессу косвенным путём. Самый распространённый пример — путешествие в древний cmd.exe, который встречается, когда мы пользуемся функцией system, конструируем сценарий для последующего выполнения, пишем файл для nmake и занимаемся множеством других дел. Поскольку правила использования кавычек в CommandLineToArgvW
и cmd
отличаются, мы не можем передавать им одну и ту же командную строку и надеяться, что аргументы «выживут». Оттого что это различие малозаметное, легко забыть о нём и оставить скрытые ошибки в программах, взаимодействующих с cmd
.
cmd
по существу текстовый препроцессор: для заданной командной строки выполняет серию текстовых преобразований, затем передаёт преобразованную командную строку функции CreateProcess. Одни преобразования выполняют замену имён переменных окружения их значениями. У других преобразований, таких как перенаправление ввода-вывода, есть полезные побочные эффекты. Третьи, запускаемые операторами &, ||, &&, разделяют командную строку на части. Важно заметить, что cmd
не воспринимает аргументы командной строки сами по себе: для cmd
весь мир состоит из целых командных строк. Подобно почте, доставляющей открытки, cmd
не пытается понять, что такое он обрабатывает: он просто делает свою ограниченную работу (с помощью преообразований), затем передаёт результаты другой программе, которая выяснит значение окончательной командной строки.
Все преобразования cmd
запускаются метасимволами (, ), %, !, ^, ", <, >, &,
и |
. Особенно интересен символ "
: когда cmd
преобразует командную строку и видит "
, то копирует "
в новую командную строку, затем начинает копировать символы старой командной строки в новую без поиска метасимволов. Это копирование продолжается до тех пор, пока cmd
не достигнет конца командной строки, не запустит подстановку значения переменной или не увидит еще один символ "
. В последнем случае, cmd
копирует "
в новую командную строку и возобновляет обычную обработку. Это поведение почти, но не совсем такое же, как у функции CommandLineFromArgvW
4; разница в том, что cmd
не знает о последовательности \"
и начинает интерпретировать метасимволы раньше, чем ожидается. Должно быть очевидно, почему следующие команды дают подобный результат:
C:\> child "hello world" >\\.\nul C:\> child "hello"world" >\\.\nul 0: [child] 1: [helloworld >\\.\nul] C:\> child "hello\"world" >\\.\nul 0: [child] 1: [hello"world] 2: [>\\.\nul] C:\>
Если для сохранения аргументов мы полагаемся на поведение cmd
, кавычки приведут к неожиданному поведению. Если мы передаём недоверенные данные как параметры командной строки, ошибки, вызванные различиями в поведении, становятся проблемами безопасности:
C:\> child "malicious argument\" &whoami" 0: [child] 1: [malicious-argument"] ntdev\dancol
Здесь cmd
интерпретирует метасимвол &
как разделитель команд, потому что, с его точки зрения, символ &
находится за пределами кавычек. Команду whoami
, разумеется, можно заменить любым количеством вредных команд. Обратите внимание, что эта команда составлена правильно для использования с функцией CreateProcess
, а её передача cmd
вызывает проблему.
Лучшее использование кавычек
Метасимвол "
не может полностью защитить метасимволы в командной строке от непреднамеренной интерпретации, но с этой целью можно воспользоваться метасимволом ^
. Когда cmd
преобразует командную строку и видит ^
, он игнорирует символ ^
и копирует последующий символ в новую командную строку как есть. Вот почему ^
работает как символ продолжения строки: он диктует cmd
скопировать символ перевода строки как есть, а не рассматривать его как разделитель команд. Если мы предварим символом ^
каждый метасимвол в строке аргумента, cmd
преобразует её в ту строку, что и подразумевалось.
C:\> child ^"malicious argument\^"^&whoami^" 0: [child] 1: [malicious argument"&whoami]
Также важно экранировать посредством ^
символы "
, иначе cmd
скопирует символы ^
внутри пар "
, испортив строку аргумента:
C:\> child "malicious-argument\^"^&whoami" 0: [child] 1: [malicious-argument\^&whoami]
Поскольку обработка "
, выполняемая cmd
, бесполезна для наших целей, мы используем ^
, чтобы cmd
не пытался умничать с определением кавычек.
Выводы
В принципе, мы можем безопасно передавать программам произвольные аргументы командной строки, соблюдая базовые предосторожности.
Что делать:
- Всегда экранируйте все аргументы, чтобы они правильно декодировались функцией
CommandLineToArgvW
, возможно, посредством функцииArgvQuote
. - Если и только если командная строка будет интерпретироваться
cmd
, поставьте перед каждым метасимволом оболочки (или каждым символом) символ^
.
Что не делать:
- Просто заключать в кавычки каждый аргумент командной строки без дальнейшей обработки.
- Позволять cmd видеть неэкранированные символы
"
.
Примечания
1 Плохо.
2 Вы же прочитали текст по ссылкам?
3 Я знаю, что не читали.
4 Для понимания: CommandLineFromArgvW
ищет исключительно символы "
и \
, не заботясь о метасимволах cmd
.
В средах командной строки, таких как командная строка Windows и PowerShell, пробелы используются для разделения команд и аргументов, но имена файлов и папок также могут содержать пробелы. Чтобы указать путь к файлу имеющего символ пробела в названии или пути к файлу, вам нужно «экранировать» его.
Почему нужно избегать символ пробела?
«Пробел» в названии файла или папки, может препятствовать правильной обработке команды, экранирование пробела заставит оболочку рассматривать его как стандартный пробел, а не как специальный символ, разделяющий аргументы командной строки.
Например, предположим, что у вас есть текстовый файл, содержимое которого вы хотите просмотреть. Вы можете сделать это с помощью команды type. Предполагая, что текстовый файл находится по адресу C:\Папка\File.txt, следующая команда в командной строке покажет содержимое файла:
Отлично! А что, если у вас есть такой же файл по адресу C:\Новая папка\File.txt? Если вы попробуете выполнить приведенную ниже команду, это не сработает — пробелы в пути к файлу мешают правильно обработать команду.
type C:\Новая папка\File.txt
Командная строка считает, что вы пытаетесь найти файл с именем Новая, в результате вы получаете: «Ошибка во время обработки: C:\Новая.
Системе не удается найти указанный путь.». Тоже самое будет? если пробел есть в имени файла New File.txt
Три способа избежать ошибок из-за символа пробел в Windows 10
Есть три разных способа избежать проблем используя пробел в пути к файлу Windows:
- Заключив путь (или его части) в двойные кавычки (”).
- Добавляя символ вставки (^) перед каждым пробелом. (Это работает только в командной строке / CMD.)
- Добавляя знак ударения (`) перед каждым пробелом. (Это работает только в PowerShell.)
Мы покажем вам, как использовать каждый из перечисленных способов.
Заключите путь к файлу в кавычки («)
Стандартный способ убедиться, что Windows правильно обрабатывает путь к файлу, — заключить его в двойные кавычки ". Например, в нашем примере команды выше мы просто выполняем следующее:
type "C:\Новая папка\Test File.txt"
Вы можете заключить части пути в кавычки, если хотите. Например, предположим, что у вас есть файл с именем File.txt в этой папке. Вы можете запустить следующее:
type C:\"Новая папка"\File.txt
Однако в этом нет необходимости — в большинстве случаев вы можете просто заключить весь путь в кавычки.
Это решение работает как в традиционной среде командной строки (CMD), так и в Windows PowerShell.
Иногда: используйте символ каретки для правильной обработки пробелов (^)
В командной строке символ каретки ^ теоретически позволяет избежать пробелов. Просто добавьте его перед каждым пробелом в имени файла. (Вы найдете этот символ в числовом ряду на клавиатуре. Чтобы ввести символ каретки, нажмите Shift + 6.)
Вот проблема: хотя это должно работать, а иногда и работает, это работает не всегда. Командная строка обрабатывает этот символ странно.
Например, запустите следующую команду, но она не сработает:
type C:\Новая^ папка\Test^ File.txt
Ошибка экранирования пробела в командной строке
С другой стороны, если мы попытаемся открыть наш файл напрямую, введя его путь в командную строку, мы увидим, что символ каретки правильно экранирует пробелы:
C:\Новая^ папка\Test^ File.txt
Итак, когда это работает? Что ж, исходя из нашего исследования, похоже, что с некоторыми приложениями он работает, а с другими — нет. Это может варьироваться в зависимости от команды, которую вы используете. Командная строка обрабатывает этот символ странно. Если вам интересно, попробуйте с любой командой, которую вы используете, — она может работать, а может и не работать.
Мы рекомендуем использовать двойные кавычки в командной строке или переключиться на PowerShell и использовать способ, рассмотренный ниже.
PowerShell: используйте символ ударения (`)
PowerShell использует знак ударения ` в качестве символа-пробела. Просто добавьте его перед каждым пробелом в имени файла. (Вы найдете этот символ над клавишей Tab и под клавишей Esc на клавиатуре.)
type C:\Новая` папка\Test` File.txt
Каждый знак ударения сообщает PowerShell, что нужно избегать следующего символа.
Обратите внимание, что это работает только в среде PowerShell. В командной строке вам нужно будет использовать символ каретки.
Если вы знакомы с UNIX-подобными операционными системами, такими как Linux и macOS, вы, возможно, привыкли использовать символ обратной косой черты (\) перед пробелом, чтобы правильно обработать команду. Windows использует его для пути к файлам, поэтому он не работает — символы каретки (^) и ударения (`) это своего рода обратная косая черта Windows в зависимости от того, какую оболочку вы используете.
- SS64
- CMD
- How-to
How-to: Escape Characters, Delimiters and Quotes at the Windows command line.
Delimiters separate one parameter from the next — they split the command line up into words.
Parameters are most often separated by spaces, but any of the following are also valid delimiters:
Comma (,)
Semicolon (;)
Equals (=)
Space ( )
Tab ( )If you are passing a parameter to a batch file that contains any of these delimiter characters, it will split the parameter into two parameters unless you surround the whole thing with double quotes: «this is;one=param,»
Consecutive delimiters will be treated as one, even if they are different characters.
Notice that although / and — are commonly used to separate command options, they are absent from the list above. This is because batch file parameters are passed to CMD.exe which can accept its own parameters (which are invoked using / and — )
The default token delimiters in a FOR IN() clause are the same as on the command line but the delims= option is available to specify something different. When FOR /F is used to process a string, the default delimters are Space and TAB.
When using the TAB character as a delimiter be aware that many text editors will insert a TAB as a series of SPACEs.
If you use %* to refer to all parameters, the value returned will include the delimiters.
Escape Character
^ Escape character.Adding the escape character before a command symbol allows it to be treated as ordinary text. These characters which normally have a special meaning can be escaped and then treated like regular characters : & \ < > ^ |
Echo THIS ^& THAT
Echo Heading1 ^| heading2 ^| heading3
Echo The Escape character looks like this ^^
Escape CR/LF line endings.
The ^ escape character can be used to make long commands more readable by splitting them into multiple lines and escaping the Carriage Return + Line Feed (CR/LF) at the end of a line:
ROBOCOPY \\FileServ1\e$\users ^
\\FileServ2\e$\BackupUsers ^
/COPYALL /B /SEC /MIR ^
/R:0 /W:0 /LOG:MyLogfile.txt /NFL /NDL
- A stray space at the end of a line (after the ^) will break the command, this can be hard to spot unless you have a text editor that displays spaces and tab characters.
- If you want to comment something out with REM, then EVERY line needs to be prefixed with REM.
- If you use a double colon :: as a REM comment, that will still parse the caret at the end of a line, so in the example above changing the first line to :: ROBOCOPY… will comment out the whole multi-line command.
A ^ escape character at the end of a line can have an undesirable effect on comments, extending them to the next line.
To avoid this use REM or add a trailing space (or any other character) after the caret.:: This line is a comment ^
Echo and this line will not display.:: This line is a comment ^ ::
Echo and this line will display.
Escape and the pipeline
When piping or redirecting text, applying an escape character gets a little more complex, a pipe will start two new cmd.exe instances, each of these cmd processes are passed one side of the pipe and will parse their part once again (with the cmd-line parser, not with the batch-line-parser).
Knowing this you might try:
Echo THIS ^^& THAT | subroutine
So the new cmd instance would be passed THIS ^& THAT and everything would work? Not so fast, with two carets together, the first will escape the second, meaning that the & is no longer escaped at all.Echo This ^^^& That | subroutine
In this version, the first caret escapes the second caret ‘^’, the third caret escapes the ‘&’ so now the new cmd instance inherits ^&
(Many thanks to Jeb who explained this over in the forum better than I could.)When ECHOing a string with an odd number (or just an unknown number) of embedded quotes through the pipeline, the outer quotes should be escaped:
ECHO "abc"def" | FIND "def" ECHO ^"abc"def^" | FIND "def"
Escape Percents
The % character has a special meaning for command line parameters, variable names and FOR parameters.
To treat a percent as a regular character in a batch file, double it:%%
At the command line (not in a batch file) there are no parameters (%1, %2… ) so doubling a percent character is not needed and will not work as an escape.
Many characters such as \ = ( ) do not need to be escaped when they are used within a «quoted string» these are characters you might find in a filename/path. The % character is one exception to this rule, it is a valid NTFS filename character but also will be evaluated as a variable name even within a quoted string.
Escape Exclamation marks
When the shell is running in EnableDelayedExpansion mode the ! character is used to denote a variable and so must be escaped (twice) if you wish to treat it as a regular character:
^^!
Escape the Escape character
The escape character can be used to escape itself ^^ (so the first ^ will escape the second):
The characters in bold get escaped: ^& => &
^^ => ^
"^^" => "^^" ^^^& => ^&
^^^^ => ^^ ^^^^^& => ^^&This changes slightly if you are running with DelayedExpansion of variables: if any part of the command line includes an ‘!’ then CMD will escape a second time, so ^^^^ will become ^ [source] Quoted carets passed to CALL are a special case.
Special Cases using backslash escape
A small number of commands follow slightly different rules, FINDSTR, REG and RUNAS all use \ as an escape character instead of ^ (as also used by C, Python, SQL, bash and many other languages).
The \ escape can cause problems with quoted directory paths that contain a trailing backslash because the closing quote » at the end of the line will be escaped \».
To save a directory path with a trailing backslash (\) requires adding a second backslash to ‘escape the escape’
so for example instead of «C:\My Docs\» use «C:\My Docs\\»To be sure that a path includes a trailing backslash, you can test for it:
Set _prog=C:\Program Files\SS64 App
IF %_prog:~-1% NEQ \ (Set _prog=%_prog%\)
Echo «%_prog%»
Escape «Double Quotes»
If a single parameter contains spaces, you can pass it as one item by surrounding in «quotes» — this works well for long filenames.
In many places, e.g. the FIND comand, a « quote can be escaped by doubling it to «»
If a parameter is used to supply a filename like this:
MyBatch.cmd "C:\Program Files\My Data File.txt"The parameters will be:
%0 =MyBatch
%1 ="C:\Program Files\My Data File.txt"To launch a batch script with spaces in the script Path and other parameters, all requiring quotes:
CMD /k ""c:\batch files\test.cmd" "Parameter 1 with space" "Parameter2 with space""Double quotes in a command line with a backslash escape:
powershell.exe -command «&{$m = \»hello world\»;write-host $m}»
This can also be written with 3 quotes:
powershell.exe -command «&{$m = «««hello world»««;write-host $m}»
or more simply with powershell’s single quotes:
powershell.exe -command «&{$m = ‘hello world’;write-host $m}»
or for SQL:
sqlplus.exe user/\»«password with spaces»\»@service
Removing Quotes
Several methods for removing quotes are listed on the dequote page.
Working without Quotes
Without surrounding quotes, a long filename will be passed as %1 %2 %3…
MyBatch C:\Program Files\My Data File.txtTo refer to the pathname above use %* rather than %1 %2 %3 — the %* will cover all parameters — even if there are more than %9
You can apply Extended Filename syntax to %* by passing the value to a subroutine:
@ECHO OFF SET _params=%* :: pass params to a subroutine CALL :sub "%_params%" GOTO :eof :sub :: Now display just the filename (not path) ECHO %~n1
“All the best stories in the world are but one story in reality — the story of escape. It is the only thing which interests us all and at all times, how to escape” ~ A. C. Benson
Related commands
SETLOCAL EnableDelayedExpansion — More examples, particularly for HTML.
How-to: Long Filenames and NTFS — Valid characters in filenames.
FINDSTR Escapes and Length limits.
How-to: Parameters — Command Line Arguments %1 %~f1.
PowerShell equivalent: PowerShell Escape Character
In bash use \ to escape a line ending.
How does the Windows Command Interpreter (CMD.EXE) parse scripts? — StackOverflow.
Why is the DOS path character «\»? — Larry Osterman
Copyright © 1999-2025 SS64.com
Some rights reserved
Перейти к содержимому раздела
Серый форум
разработка скриптов
Вы не вошли. Пожалуйста, войдите или зарегистрируйтесь.
Страницы 1
Чтобы отправить ответ, вы должны войти или зарегистрироваться
1 2013-07-05 23:49:59 (изменено: BlackOverlord, 2013-07-05 23:51:09)
- BlackOverlord
- Гость
Тема: CMD/BAT: Как экранировать двойные кавычки?
Проблема такова: нужно запустить некий скрипт/программу и в качестве аргумента передать длинную строку, которая содержит любые символы (в том числе пробелы и двойные кавычки). Что делать с пробелами — понятно, но как быть с двойными кавычками? В линуксе они просто экранируются бэкслэшем, в винде — не работает, пробовал через ^ — не получается. Есть идеи?
2 Ответ от Rumata 2013-07-06 02:33:20
- Rumata
- Разработчик
- Неактивен
Re: CMD/BAT: Как экранировать двойные кавычки?
Удваивайте каждую внутреннюю кавычку.
( 2 * b ) || ! ( 2 * b )
3 Ответ от wisgest 2013-07-06 22:12:24
- wisgest
- Разработчик
- Неактивен
Re: CMD/BAT: Как экранировать двойные кавычки?
Насколько я знаю, единого правила на этот счёт нет. Может быть, кстати, и обратный слэш — для REG.EXE и удвоенные кавычки — для FIND.EXE.
При определённых условиях (кавычек в аргументе чётное число, между чётной и следующей за ней нечётной кавычкой нет пробелов; это позволяется вызываемым приложением) кавычки можно вообще не экранировать.
При передаче аргументов собственноручно разрабатываемым командным файлам хорошо воспользоваться советом Rumata — вышеуказанные условия соблюдаются автоматически, но при этом надо не забыть при разборе аргументов заменить удвоенные кавычки одним знаком.
Сообщения 3
Страницы 1
Чтобы отправить ответ, вы должны войти или зарегистрироваться