Принцип составления однострочных команд для метода Shell

Dragokas

Angry & Scary Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
7,814
Реакции
6,593
Статья от 17.01.2013
Автор: Dragokas

Принцип составления однострочных команд Batch


Однострочные команды Batch используются там, где нет возможности написать команды
в несколько строк, в случаях когда они объединены логикой и не могут выполнятся в разрыве друг от друга в разных сессиях интерпретатора.

Область применения:
1) Языки программирования высокого уровня
2) Реестр Windows (reg-файл)

Пример логики связи:
1) Команды "удалить файлы a и b"
CMD/BATCH:
del a
del b
можно выполнить отдельно:
VB.NET / VBA:
shell "cmd /c del a"
shell "cmd /c del b"
2) Команды "вывести на экран сумму 1 + 1"
CMD/BATCH:
Set /A n= 1 + 1
Echo %n%
разрывать нельзя, так как значение переменной "n" будет утеряно.

Чтобы составить сложный набор однострочных команд для выполнения в методе (процедуре, объекте) Shell другого ЯП
нужно сначала добиться успешной отработки Вашей конструкции в среде командного интерпретатора.

Открываем консоль - Win + R (выполнить), CMD {Enter}
Формируем однострочные команды, подставляя первой командой запуск самого EXE-файла интерпретатора:
CMD/BATCH:
CMD.exe /C Echo Привет
Если нужно выполнить несколько команд, строку команд обрамляем кавычками (согласно синтаксиса ключей CMD /?)
а сами команды разделяются знаком "амперсанд" (&)
CMD/BATCH:
::если файл "а" существует, скопировать его под именем "b". Удалить файл "c".
cmd /c "if exist a copy a b& del c"
Все кавычки, которые будут заключены внутрь считаются как единая строка и не бьются на отдельные токены. Об этом беспокоится не нужно.
CMD/BATCH:
::Например, копирование файла с пробелами в пути и/или имени:
cmd /c "copy "c:\my folder\a.txt" "c:\my folder\b.txt""

Если участвует цикл FOR, в отличие от текста bat-файла, здесь спецсимволы не обрабатываются, поэтому % в цикле удваивать не нужно.
Если команду нужно указать вне цикла, тогда весь блок цикла помещается в скобки.
CMD/BATCH:
::Подсчет кол-ва файлов *.txt
cmd /c "(for %a in (*.txt) do set /A n+=1)& Echo !n!"
Если нужно выполнить несколько команд в цикле, обрамляем их скобками после ключевого слова do:
CMD/BATCH:
cmd /c "for %a in (*.txt) do (set /A n+=1& Echo !n!)"
В однострочной команде переменная сразу не изменяет значения. Получить его можно, влючив "отложенное расширение переменных", а переменную окружив знаками (!):
CMD/BATCH:
cmd /c "SetLocal EnableDelayedExpansion& Set /A n= 1 + 1& Echo !n!"
чтобы не писать такую длинную команду добавьте к CMD ключ /V:ON обязательно перед ключем /C. Все, что после него считается выполняемыми командами.
CMD/BATCH:
cmd /v:on /c "Set /A n= 1 + 1& Echo !n!"
.

Построение кавычек внутри переменных других ЯП

Кавычка (") - является специальным (служебным) символом для языка Visual Basic,
и если в реестре, или в языках C++, C# ... для задания кавычки (") как значение переменной нужно указать экран в виде бекслеша (\)
[csharp=-1]st = "\""[/csharp]
в VB кавычка удваивается.

Есть 2 способа присвоения значений с кавычкой переменной:
1) Удвоение кавычки
VB.NET / VBA:
Shell = "cmd /c ""copy ""c:\my folder\a.txt"" ""c:\my folder\b.txt"""""

2) Через функцию Chr(34), которая возвращает символ под номером 34 ASCII-таблицы кодов (").
В VB результат выполнения функции и строковой тип данных может конкатенироваться знаком амперсанд (&).
VB.NET / VBA:
Shell = "cmd /c " & Chr(34) & "copy " & Chr(34) & "c:\my folder\a.txt" & Chr(34) & " " & Chr(34) & "c:\my folder\b.txt" & string(2, Chr(34))
Но я предпочитаю вариант № 1.

Общий принцип разбора чужих запросов с кавычками таков:

Вариант 1) Автоматически.
Поставьте Stop после команды присвоения переменной сложного запроса.
выполните ?variable {ENTER} в окне Immediate (Вид - окно неотложного (View - Immediate (Ctrl+G)))
Immediate_Answer.png

Вариант 2) Вручную.
Кавычка в CMD - это двойная кавычка здесь в переменной ""), т.е. если видите запись вида:
VB.NET / VBA:
param = """ """
Чтобы "расшифровать", отбрасываете визуально левую и правую кавычку, остальные парные кавычки меняете на одиночные и получаете то, что увидит командная строка:
CMD/BATCH:
" "

Также в реестре, C++, C# ... спецсимволом является сам знак бекслеш (\), который разделяет папки, путь к файлу.
Здесь приняты 2 способа:
1) Удвоение бекслеша (\\)
cs_compile.reg написал(а):
[HKEY_CLASSES_ROOT\VisualStudio.cs.11.0\shell\compile\command]
@="\"c:\\Program Files (x86)\\hidcon.exe\" \"c:\\Program Files (x86)\\ModsCompiler.bat\" \"%1\""
2) Замена бекслеша (\) на обычный слеш (/)
cs_compile.reg написал(а):
@="cmd /c \"\"c:/Program Files (x86)/ModsCompiler.bat\" \"%1\"\""



Альтернативой для ЯП при использовании Shell является выгрузка кода в несколько строк во внешний файл. Запуск его отдельно.
При этом такой код может быть заранее:
1) записан в переменную по указанным выше правилам.
Код от INV.DS

VB.NET / VBA:
'Запись:
'Для записи текста служит функция Print(и Write)
Open "c:\1.txt" For Append As #1 'Открываем файл для добавления записи, с номером канала Print #1, "Твой ТЕКСТ"' Записываем в файл 1.txt текст
Close #1 'Закрываем файл
'Есть еще функция для определения свободного канала - FreeFile. Например:
f = FreeFile 'Возвращает номер свободного канала
Open "c:\1.txt" For Append As f 'Открываем файл для добавления записи
Print #f, "Твой ТЕКСТ"'Записываем в файл 1.txt текст
Close #f 'Закрываем файл
'Чтение:
'Считать данные из файла сложнее чем записать. Есть два способа чтения данных из файла. Первый:
f = FreeFile
Open "c:\1.txt" For Input As f' Открываем файл 1.txt для чтения
Text1.Text = Input(LOF(f), f) 'Считываем текст из открытого файла в текстовое поле(Оператор LOF(Len Of File) определяет длину файла)
Close f
'Второй:
Dim txt as String
Open "c:\1.txt" For Input As #1' Открываем файл 1.txt для чтения
Do While Not EOF(1) ' Функция EOF(End Of File) проверяет, достигнут ли конец файла Line Input #1, txt ' Читаем строку данных Text1.Text = txt
Loop
Close #1
2) подключен к проекту в виде файла Custom Resource
Руководство от Alex77755

32.jpg

33.jpg

34.jpg

35.jpg

36.jpg
3) Присоединён в конец уже скомпилированного EXE-файла в открытом виде
Код VB6 от Catstail

Вот комментированный код запуска файла, хранящегося в теле другого файла:

VB.NET / VBA:
Private Sub Command1_Click()
  '::: Определяем домашнюю директорию
  HomeDir$ = App.Path
  '::: Берем свободный номер файла
  fi% = FreeFile
  '::: Открываем сами себя на чтение в двоичном доступе
  Open HomeDir$ + "\" + App.EXEName + ".exe" For Binary Access Read As #fi%
  '::: Определяем собственную длину
  LF& = LOF(fi%)
  '::: Позиционируем файловый указатель на начало последних
  '::: 4-x байтов EXE
  Seek #fi%, LF& - 3
  '::: Читаем длину тела запускаемого файла
  Get #fi%, , Offset&
  '::: Позиционируем файловый указатель на начало
  '::: тела запускаемого файла
  Seek #fi%, LF& - Offset& - 3
  '::: Создаем буфер для тела запускаемого файла
  Buf$ = Space$(Offset&)
  '::: Читаем тело
  Get #fi%, , Buf$
  '::: Закрываем входной файл
  Close #fi%
  '::: Берем свободный номер файла
  fo% = FreeFile
  '::: Открываем в тек. директории файл с каким-либо именем
  Open HomeDir$ + "\notepad.sss" For Binary Access Write As #fo%
  '::: Сбрасываем в файл содержимое
  Put #fo%, , Buf$
  '::: Закрываем
  Close #fo%
  '::: Скрываем форму
  Me.Hide
  '::: Запускаем... и ждем завершения
  Processn.ExecPrg HomeDir$ + "\notepad.sss", "", RC%, vbNormalFocus
  '::: Дождались - покажем форму
  Me.Show
  '::: Удалим запускаемый файл
  Kill HomeDir$ + "\notepad.sss"
  '::: Покажем код завершения
  MsgBox "RC=" + CStr(RC%)
End Sub

А вот комментированный код exe-мэйкера:

VB.NET / VBA:
Sub Main()
  '::: Домашняя директория
  HomeDir$ = App.Path
  '::: Командная строка
  CMD$ = Command$
  '::: положение пробела в ком. строке
  k% = InStr(CMD$, " ")
  '::: нет пробела - ошибка
  If k% = 0 Then
  MsgBox "Запуск: maker Главный Запускаемый"
  Exit Sub
  End If
  '::: первый параметр - имя главного файла
  master$ = Left$(CMD$, k% - 1)
  '::: второй параметр - имя запускаемого из
  slave$ = Mid$(CMD$, k% + 1)
  '::: Свободный номер файла
  fi% = FreeFile
  '::: Открываем главный (для чтения и записи!)
  Open HomeDir$ + "\" + master$ For Binary Access Read Write As #fi%
  '::: Получаем длину
  LFM& = LOF(fi%)
  '::: Свободный номер файла
  ffi% = FreeFile
  '::: открываем подчиненный (только для чтения)
  Open HomeDir$ + "\" + slave$ For Binary Access Read As #ffi%
  '::: Получаем длину
  LFS& = LOF(ffi%)
  '::: создаем буфер для запускаемого файла
  BufS$ = Space$(LFS&)
  '::: Читаем весь запускаемый файл в буфер
  Get #ffi%, , BufS$
  '::: Позиционируем главный файл в конец
  Seek #fi%, LFM& + 1
  '::: Выводим буфер
  Put #fi%, , BufS$
  '::: выводим длину (чтобы главный мог узнать, сколько байтов от конца нужно взять)
  Put #fi%, , LFS&
  '::: Все закрываем
  Close
  '::: OK!
  MsgBox "OK!"
End Sub
Некоторые примеры:
1) Рекурсивный подсчет кол-ва файлов, заданных маской
VB.NET / VBA:
Sub CMD_Seek_Recursive()
Dim RetCode, Query As String, StartFolder As String, SeekFile As String, Flags As String
Dim isRecursive As Boolean
Dim inclSystem As Boolean
Dim inclHidden As Boolean
Dim inclReadOnly As Boolean

'Папка, начиная с которой ведется поиск
StartFolder = "l:\bash\test"
'Имя искомого файла (допускается подстановочный знак *)
SeekFile = "*.txt"

'Options
'Включить рекурсию
isRecursive = True
'Включить в поиск системные, скрытые файлы, только для чтения
inclSystem = True
inclHidden = True
inclReadOnly = True

Flags = IIf(isRecursive, "/s", "") + "/a:-D" + _
  IIf(inclSystem, "", "-S") + IIf(inclHidden, "", "-H") + IIf(inclReadOnly, "", "-R")

Query = "cmd.exe /Q /V:on /C ""(for /f ""delims="" %x in ('dir /b " & Flags & " " & _
  """" & StartFolder & "\" & SeekFile & """') do (set /a n+=1))& Exit !n!"""

RetCode = CreateObject("WScript.Shell").Run(Query, 0, True)

Debug.Print "Количество найденных файлов = " & RetCode
End Sub

И чуть по-меньше буков:

VB.NET / VBA:
Sub CheckCount()
Debug.Print Get_Count_Files("l:\bash\test", "*.txt")
End Sub

'Выдает кол-во файлов SeekFile в папке StartFolder рекурсивно без учета скрытых файлов
Function Get_Count_Files(StartFolder As String, SeekFile As String) As Long
Get_Count_Files = CreateObject("WScript.Shell").Run("cmd /V:on /C ""(for /R """ & _
  StartFolder & """ %x in (""" & SeekFile & """) do set /a n+=1)& Exit !n!""", 0, True)
End Function
2) Еще в VB можно получить код возврата, сгенерированного командами CMD или вручную через Exit
Метод от Казанский

Получение ErrorLevel из команды CMD в переменную VBS-скрипта
(на примере команды сравнения файлов)

VB.NET / VBA:
Function FileCompare(path1, path2)
  'возвращает результат двоичного сравнения двух файлов
  'с помощью системной утилиты fc.exe. Возвращаемое значение:
  '0 - файлы одинаковы;
  '1 - файлы различаются;
  '2 - файл не найден
FileCompare = CreateObject("wscript.shell").Run( _
  "cmd /c fc /b """ & path1 & """ """ & path2 & """", 0, True)
End Function

Комментарий Dragokas:
Добавлю от себя: чтобы симитировать свой произвольный код возврата, достаточно указать команду Exit <code>
Например,
VB.NET / VBA:
'Двоичный сдвиг 0110(6) -> 0011(3)
FileCompare = CreateObject("wscript.shell").Run( _
  "cmd /v:on /c set /A x=6"">>""1& exit !x!", 0, True)
 
Последнее редактирование:
выполните ?variable {ENTER} в окне Immediate (Вид - окно неотложного (View - Immediate (Ctrl+G)))
Увидим результат (свернуть)
Посмотреть вложение 225792
Картинка потерялась))
несколько команд, строку команд обрамляем кавычками
А аргументы типа %1 ?
 
В языке VB, VBScript %1 передаются без удвоения знака процента.
Знак % - является особым спецсимволом только в пакетных файлах.
Например, из-под командной строки CMD его удваивать тоже не нужно.

Например, получить перечень имен файлов в текущей папке через цикл будет:
  • из пакетного файла .bat/.cmd:
Код:
for %%a in (*) do @echo %%a
  • из CMD.exe:
Код:
for %a in (*) do @echo %a
 
Назад
Сверху Снизу