1

Тема: WSH: Работа с командной строкой

Возникла необходимость работы с CMD. Глянул в хелп Микрософта "Windows Script Host", там приведен такой пример (ключевое слово strCommand, Example 2):

Dim oShell
Set oShell = WScript.CreateObject ("WSCript.shell")
oShell.run "cmd /K CD C:\ & Dir"
Set oShell = Nothing

Пример работает, но есть проблема - результатом работы скрипта является не список файлов и папок диска С (как подразумевается в коде скрипта), а той директории, откуда запущен скрипт.
Подскажите пожалуйста, где "собака порылась"?

2

Re: WSH: Работа с командной строкой

VBScript: запуск консольного приложения с чтением его вывода
Наверное, просто добавить второй амперсанд.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

3

Re: WSH: Работа с командной строкой

Хмм… У меня и приведённый код работает корректно.

4

Re: WSH: Работа с командной строкой

Уточнение: если скрипт запускать из любой папки диска С - то отрабатывает корректно. Если скрипт запускается с другого диска, то "результатом работы скрипта является не список файлов и папок диска С (как подразумевается в коде скрипта), а той директории, откуда запущен скрипт".

5

Re: WSH: Работа с командной строкой

Всё правильно, так и должно быть. Команда «cd <Диск:\Путь>» меняет текущую папку на указанном диске, но не меняет сам текущий диск. Используйте либо «dir c:\», либо «cd /d c:\». Посмотрите также в сторону пары «pushd/popd».

P.S. Будьте осторожны с использованием UNC-путей.

6

Re: WSH: Работа с командной строкой

alexii, Ваше "используйте ... «dir c:\»" - помогло.
Строку кода:

oShell.run "cmd /K CD C:\ & Dir"

изменил на:

oShell.run "cmd /K CD & dir C:\"

Спасибо!

7

Re: WSH: Работа с командной строкой

The gray Cardinal, спасибо за интересный пример "VBScript: запуск консольного приложения с чтением его вывода". Возьму на вооружение.
Обратил внимание, что при старте скрипта мелькает окно консоли. Как это окно "загасить"?

8

Re: WSH: Работа с командной строкой

Надо было написать подробнее:

…либо «cd /d c:\ & dir».

9

Re: WSH: Работа с командной строкой

Вы справку читаете?

oShell.run "cmd /K dir C:\", 0

10

Re: WSH: Работа с командной строкой

Есть ли возможность получать стандартный вывод консоли непосредственно в процессе работы консольного приложения? Обычно он получается только после завершения работы приложения, а работа может занять длительное время... нужен хоть какой-то промежуточный лог.

11 (изменено: jite, 2011-06-13 01:15:21)

Re: WSH: Работа с командной строкой

mbf пишет:

Есть ли возможность получать стандартный вывод консоли непосредственно в процессе работы консольного приложения? Обычно он получается только после завершения работы приложения, а работа может занять длительное время... нужен хоть какой-то промежуточный лог.

Конечно есть:

var oSh = WScript.CreateObject("WScript.Shell");
var oEx = oSh.Exec("cmd /k");
var oIn = oEx.StdIn;
var oOut = oEx.StdOut;
var n = 0;
oIn.WriteLine(sDOS2Win("cd /d c:\\windows", true)); //sDOS2Win() нужна при наличии в команде символов кириллицы
oIn.WriteLine(sDOS2Win("dir /s *.ini", true));
oIn.WriteLine("exit");
while (!oEx.StdOut.AtEndOfStream) {
  WScript.Echo(sDOS2Win(oOut.ReadLine()));
  n++;
}
WScript.Echo("Строк прочтено: " + n);

/** Возвращает текст sText преобразованный из кодировки cp866 (DOS) в windows-1251. 
 * Или наоборот - из 1251 в DOS - если bInsideOut true.
 */
function sDOS2Win(sText, bInsideOut) {
  var aCharsets = ["windows-1251", "cp866"];
  sText += "";
  bInsideOut = bInsideOut ? 1 : 0;
  with (new ActiveXObject("ADODB.Stream")) { //http://www.w3schools.com/ado/ado_ref_stream.asp
    type = 2; //Binary 1, Text 2 (default) 
    mode = 3; //Permissions have not been set 0,  Read-only 1,  Write-only 2,  Read-write 3,  
    //Prevent other read 4,  Prevent other write 8,  Prevent other open 12,  Allow others all 16
    charset = aCharsets[bInsideOut];
    open();
    writeText(sText);
    position = 0;
    charset = aCharsets[1 - bInsideOut];
    return readText();
  }
}

12

Re: WSH: Работа с командной строкой

После некоторых раздумий решил не открывать новой темы, а использовать эту для своеобразного подведения итогов. Название подходящее.


/*--- Процесс чтения вывода приложения, запущенного из скрипта методом Exec() ---
 * 1) После запуска приложения, его StdOut.AtEndOfStream => false и переходит в true только после закрытия приложения
 * 2) Команда чтения (ReadLine, Read и ReadAll) работает так: ждет сколь угодно долго появления в буфере StdOut либо 
 * строки/символов вывода, либо положительного StdOut.AtEndOfStream (т.е. завершения работы приложения). В последнем 
 * случае возвращает финальную NewLine (\r\n) или - в случае с ReadLine(),- съедает ее, возвращая пустую строку (потому 
 * что всегда ест переносы). Т.е. даже если приложение ничего не выводит, с него прочтется хотя бы пустая строка
 * 3) Контролировать чтение потока из внешнего приложения - через while (!oEx.StdOut.AtEndOfStream)
 * 4) При больших объемах вывода за одну строку (более 4 KB) надо контролировать oSh.Exec -> while (oEx.Status == 0), 
 *    внутри которого уже будет if (oEx.StdOut.AtEndOfStream) break;
 *    Если же следовать только стандартной проверке StdOut.AtEndOfStream, то при объеме вывода приложения более 4KB на 
 *    строку, чтение этого самого вывода просто вешается
 *    4.1) Следует учитывать, что при контроле через oEx.Status буфер oEx.StdOut к моменту завершения внешнего 
 *         приложения вполне может НЕ быть прочтен полностью, поэтому его следует дочитывать добавля ниже цикл из п.3 
 * 5) Если запущенное приложение и не думает завершаться (cmd /k), а мы с него читаем, то мы зависаем вместе с этим 
 * приложением навечно, что весьма печально.
*/

Примечания:
по п. 4 см. тему JScript: Проблема с WshScriptExec.Exec
по п. 4.1 Эффект можно наблюдать, если в примере, описанном постом выше, "while (!oEx.StdOut.AtEndOfStream)" заменить на "while (oEx.Status == 0)"

Вопросы:

1) Прошу уточнить, если что не так в данных рассуждениях.

2) П. 5. Что делать?
Например вот в этой ситуации, как заставить скрипт самостоятельно завершаться/прерываться?

var oSh = WScript.CreateObject("WScript.Shell");
var oEx = oSh.Exec("cmd /k");
var oOut = oEx.StdOut;
while (!oEx.StdOut.AtEndOfStream) {
  WScript.Echo(oOut.ReadLine());
}

13

Re: WSH: Работа с командной строкой

А пока никто не знает, что делать с п. 5, разобрался, наконец, с вопросом выполнения из-под JScript команд, да и прочих приложений в консоли cmd.
Ну это мне сейчас все очевидно: win-> dos -> запись в StdIn (dos) -> выполнение (dos) -> чтение StdOut (dos) -> win, а до этого тема порядком достала непонятками с корректным преобразованием кодировок.
Оформил в виде 2 функций (если надо больше модульности, легко встроить одну в другую):

/** Выполняет последовательно указанные аргументами команды в консоли cmd и возвращает вывод их выполнения одной строкой. 
 * Ввод-вывод в родной для WSH кодировке Win-1251, выполнение - в родной для консоли 866.
 */
function sExecDosCommand(sCommand, sAnotherCommand) {
  if (arguments.length == 0) return "";
  var oSh = WScript.CreateObject("WScript.Shell");
  var oEx, oIn, oOut, sRes;
  try {
    oEx = oSh.Exec("cmd /k");
    oIn = oEx.StdIn;
    oOut = oEx.StdOut;
    for ( var i = 0; i < arguments.length; i++)
      oIn.WriteLine(sDOS2Win(arguments[i] + "", true));
    oIn.WriteLine("exit");
    sRes = sDOS2Win(oOut.ReadAll());
  }
  catch(e) {
    return("Err. " + e.number + ": " + e.message);
  }
  //Минус внутренняя exit и последний перенос строки
  return sRes.substring(0, sRes.lastIndexOf("\r\n", sRes.length - 3) - 2); 
}

/** Возвращает текст sText преобразованный из кодировки cp866 (DOS) в windows-1251. 
 * Или наоборот - из 1251 в DOS - если флаг bInsideOut равен true.
 */
function sDOS2Win(sText, bInsideOut) {
  var aCharsets = ["windows-1251", "cp866"];
  sText += "";
  bInsideOut = bInsideOut ? 1 : 0;
  with (new ActiveXObject("ADODB.Stream")) { //http://www.w3schools.com/ado/ado_ref_stream.asp
    type = 2; //Binary 1, Text 2 (default) 
    mode = 3; //Permissions have not been set 0,  Read-only 1,  Write-only 2,  Read-write 3,  
    //Prevent other read 4,  Prevent other write 8,  Prevent other open 12,  Allow others all 16
    charset = aCharsets[bInsideOut];
    open();
    writeText(sText);
    position = 0;
    charset = aCharsets[1 - bInsideOut];
    return readText();
  }
}

Пример использования:

WScript.Echo("***" + sExecDosCommand("cd /d c:\\windows", "dir *.ini", "chcp", "echo Из командной строки") + "***");
WScript.Echo("WScript.Echo ФЫВА!");

Кириллица корректно обрабатывается как на этапе воода данных, так и на выводе.

Результат выполнения:

***D:\00\_0_assets\_files-args-run>cd /d c:\windows

C:\WINDOWS>dir *.ini
 Том в устройстве C имеет метку bar_sys
 Серийный номер тома: 682B-5C99

 Содержимое папки C:\WINDOWS

07.05.2009  14:45                35 A5W.INI
8< пропущено >8
11.02.2011  16:24               323 win.ini
              22 файлов          9 492 байт
               0 папок   4 058 451 968 байт свободно

C:\WINDOWS>chcp
Текущая кодовая страница: 866

C:\WINDOWS>echo Из командной строки
Из командной строки***
WScript.Echo ФЫВА!

Кстати, я так и не смог понять, как работает механизм преобразования кодировок ADODB.Stream: почему надо вначале писать в поток в той кодировке, которую хотим получить, а затем читать в той, из которой преобразуем. И не один я задавался этим вопросом, и кто-то даже кажется постиг причины...
Но главное, работу функций проверил и перепроверил, ничего не напутано, они делают то, что должны делать, и называются правильно.