Вот простой командный файл, который демонстрирует, как с задержкой расширения происходит сбой, если он находится в блоке, который передается по трубопроводу. (Ошибка заканчивается в конце script). Может ли кто-нибудь объяснить, почему это?
Ответ 1
Как показывает Аасини, кажется, что многие вещи терпят неудачу в pipeе.
echo hello | set /p var=
echo here | call :function
Но на самом деле это только проблема, чтобы понять, как работает pipe.
Каждая сторона канала запускает свой собственный cmd.exe в своем собственном асинхронном потоке.
Вот почему так много вещей, кажется, сломаны.
Но с этим знанием вы можете избежать этого и создавать новые эффекты
echo one | ( set /p varX= & set varX )
set var1=var2
set var2=content of two
echo one | ( echo %%%var1%%% )
echo three | echo MYCMDLINE %%cmdcmdline%%
echo four | (cmd /v:on /c echo 4: !var2!)
Обновление 2019-08-15:
Как выяснилось в , почему 'findstr' с расширением переменной в строке поиска возвращает неожиданные результаты при подключении к каналу?, cmd.exe используется только в том случае, если команда является внутренней для cmd.exe, если команда пакетный файл, или если команда заключена в блок в скобках. Внешние команды, не заключенные в скобки, запускаются в новом процессе без помощи cmd.exe.
ОБНОВЛЕНИЕ: углубленный анализ
Как показывает Дбенхем, обе стороны pipe эквивалентны фазам расширения.
Основные правила выглядят так:
Обычные фазы парсера выполняются
.. процентное расширение
.. обнаружение фазы специального символа/начала блока
.. отложенное расширение (но только если включено отложенное расширение И это не командный блок)
Запустите cmd.exe с помощью C:\Windows\system32\cmd.exe /S /D /c"<BATCH COMMAND>"
Эти расширения следуют правилам парсера cmd-строки, а не парсера batch-line.
.. процентное расширение
.. отложенное расширение (но только если включено отложенное расширение)
<BATCH COMMAND>
будет изменен, если он внутри блока скобок.
(
echo one %%cmdcmdline%%
echo two
) | more
Вызывается как C:\Windows\system32\cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo two )"
, все новые строки заменяются на оператор &
.
Почему фаза отложенного расширения зависит от круглых скобок?
Я полагаю, что он не может расширяться в фазе пакетного синтаксического анализа, поскольку блок может состоять из множества команд, и отложенное расширение вступает в силу при выполнении строки.
(
set var=one
echo !var!
set var=two
) | more
Очевидно, что !var!
нельзя оценить в контексте пакета, поскольку строки выполняются только в контексте строки cmd.
Но почему это может быть оценено в этом случае в контексте пакета?
echo !var! | more
По моему мнению, это "ошибка" или неосторожное поведение, но не первое
ОБНОВЛЕНИЕ: Добавление трюка НЧ
Как показывает dbenham, есть некоторые ограничения в поведении cmd, которое изменяет все переводы строки в &
.
(
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) | more
Это приводит к
C:\Windows\system32\cmd.exe /S /D /c" ( echo 7: part1 & rem This ...& echo part2 ) "
rem
будет отмечать полный хвост линии, поэтому даже закрывающая скобка отсутствует.
Но вы можете решить эту проблему, добавив свои собственные переводы!
set LF=^
REM The two empty lines above are required
(
echo 8: part1
rem This works as it splits the commands %%LF%% echo part2
) | more
Это приводит к C:\Windows\system32\cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem This works as it splits the commands %LF% echo part2 )"
И поскольку% lf% раскрывается при разборе скобок синтаксическим анализатором, результирующий код выглядит как
( echo 8: part1 & rem This works as it splits the commands
echo part2 )
Это поведение %LF%
всегда работает внутри круглых скобок, также в пакетном файле.
Но не на "обычных" строках, там один <linefeed>
остановит разбор этой строки.
ОБНОВЛЕНИЕ: асинхронно не полная правда
Я сказал, что оба потока асинхронны, обычно это так.
Но на самом деле левый поток может блокироваться, когда данные по каналу не используются правым потоком.
Кажется, в буфере "pipe" существует ограничение в ~ 1000 символов, затем поток блокируется до тех пор, пока данные не будут использованы.
@echo off
(
(
for /L %%a in ( 1,1,60 ) DO (
echo A long text can lock this thread
echo Thread1 ##### %%a > con
)
)
echo Thread1 ##### end > con
) | (
for /L %%n in ( 1,1,6) DO @(
ping -n 2 localhost > nul
echo Thread2 ..... %%n
set /p x=
)
)
Ответ 2
Я не был уверен, должен ли я отредактировать свой вопрос или разместить его в качестве ответа.
Я уже смутно знал, что труба выполняет как левую, так и правую сторону каждый в своем собственном сеансе CMD.EXE. Но ответы Аасини и Джеба заставили меня по-настоящему подумать и исследовать, что происходит с трубами. (Спасибо, что продемонстрировали, что происходит при подключении в SET/P!)
Я разработал этот исследовательский script - он помогает объяснить многое, но также демонстрирует какое-то странное и неожиданное поведение. Я выведу текст script, а затем вывод. Наконец, я дам некоторый анализ.
@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion
echo on
@echo NO PIPE - delayed expansion is ON
echo 1: %var1%, %var2%, !var1!, !var2!
(echo 2: %var1%, %var2%, !var1!, !var2!)
@echo(
@echo PIPE LEFT SIDE - Delayed expansion is ON
echo 1L: %%var1%%, %%var2%%, !var1!, !var2! | more
(echo 2L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(setlocal enableDelayedExpansion & echo 3L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(cmd /v:on /c echo 4L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 5L: %%var1%%, %%var2%%, !var1!, !var2! | more
@endlocal
@echo(
@echo Delayed expansion is now OFF
(cmd /v:on /c echo 6L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 7L: %%var1%%, %%var2%%, !var1!, !var2! | more
@setlocal enableDelayedExpansion
@echo(
@echo PIPE RIGHT SIDE - delayed expansion is ON
echo junk | echo 1R: %%var1%%, %%var2%%, !var1!, !var2!
echo junk | (echo 2R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (setlocal enableDelayedExpansion & echo 3R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (cmd /v:on /c echo 4R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 5R: %%var1%%, %%var2%%, !var1!, !var2!
@endlocal
@echo(
@echo Delayed expansion is now OFF
echo junk | (cmd /v:on /c echo 6R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 7R: %%var1%%, %%var2%%, !var1!, !var2!
Вот вывод
NO PIPE - delayed expansion is ON
C:\test>echo 1: value1, , !var1!, !var2!
1: value1, , value1,
C:\test>(echo 2: value1, , !var1!, !var2! )
2: value1, , value1,
PIPE LEFT SIDE - Delayed expansion is ON
C:\test>echo 1L: %var1%, %var2%, !var1!, !var2! | more
1L: value1, %var2%, value1,
C:\test>(echo 2L: %var1%, %var2%, !var1!, !var2! ) | more
2L: value1, %var2%, !var1!, !var2!
C:\test>(setlocal enableDelayedExpansion & echo 3L: %var1%, %var2%, !var1!, !var2! ) | more
3L: value1, %var2%, !var1!, !var2!
C:\test>(cmd /v:on /c echo 4L: %var1%, %var2%, !var1!, !var2! ) | more
4L: value1, %var2%, value1, !var2!
C:\test>cmd /v:on /c echo 5L: %var1%, %var2%, !var1!, !var2! | more
5L: value1, %var2%, value1,
Delayed expansion is now OFF
C:\test>(cmd /v:on /c echo 6L: %var1%, %var2%, !var1!, !var2! ) | more
6L: value1, %var2%, value1, !var2!
C:\test>cmd /v:on /c echo 7L: %var1%, %var2%, !var1!, !var2! | more
7L: value1, %var2%, value1, !var2!
PIPE RIGHT SIDE - delayed expansion is ON
C:\test>echo junk | echo 1R: %var1%, %var2%, !var1!, !var2!
1R: value1, %var2%, value1,
C:\test>echo junk | (echo 2R: %var1%, %var2%, !var1!, !var2! )
2R: value1, %var2%, !var1!, !var2!
C:\test>echo junk | (setlocal enableDelayedExpansion & echo 3R: %var1%, %var2%, !var1!, !var2! )
3R: value1, %var2%, !var1!, !var2!
C:\test>echo junk | (cmd /v:on /c echo 4R: %var1%, %var2%, !var1!, !var2! )
4R: value1, %var2%, value1, !var2!
C:\test>echo junk | cmd /v:on /c echo 5R: %var1%, %var2%, !var1!, !var2!
5R: value1, %var2%, value1,
Delayed expansion is now OFF
C:\test>echo junk | (cmd /v:on /c echo 6R: %var1%, %var2%, !var1!, !var2! )
6R: value1, %var2%, value1, !var2!
C:\test>echo junk | cmd /v:on /c echo 7R: %var1%, %var2%, !var1!, !var2!
7R: value1, %var2%, value1, !var2!
Я тестировал как левую, так и правую сторону трубы, чтобы продемонстрировать, что обработка симметрична с обеих сторон.
Тесты 1 и 2 демонстрируют, что круглые скобки не влияют на замедленное расширение при нормальных условиях партии.
Тесты 1L, 1R: Задержка расширения работает, как ожидалось. Var2 - undefined, поэтому% var2% и! Var2! вывод показывает, что команды выполняются в контексте командной строки, а не в контексте пакета. Другими словами, вместо анализа партитуры используются правила синтаксического анализа командной строки. (см. Как интерпретирует скрипт командной строки Windows (CMD.EXE)?) EDIT -! VAR2! расширяется в контексте родительского пакета
Тесты 2L, 2R: скобки отключают замедленное расширение! Очень странно и неожиданно. Изменить - jeb считает это ошибкой MS или ошибкой дизайна. Я согласен, не существует какой-либо разумной причины для непоследовательного поведения
Тесты 3L, 3R: setlocal EnableDelayedExpansion
не работают. Но это ожидается, потому что мы находимся в контексте командной строки. setlocal
работает только в пакетном контексте.
Тесты 4L, 4R: Отсроченное расширение изначально включено, но в скобках его отключить. CMD /V:ON
повторно активирует замедленное расширение, и все работает так, как ожидалось. У нас все еще есть контекст командной строки, и вывод будет таким, как ожидалось.
Тесты 5L, 5R: Почти то же, что и 4L, 4R, кроме задержанного расширения, уже включено, когда выполняется CMD /V:ON
. % var2% дает ожидаемый вывод контекста командной строки. Но! Var2! вывод пуст, который ожидается в пакетном контексте. Это еще одно очень странное и неожиданное поведение. Изменить - на самом деле это имеет смысл теперь, когда я знаю! Var2! расширяется в контексте родительского пакета
Тесты 6L, 6R, 7L, 7R:. Они аналогичны тестам 4L/R, 5L/R, за исключением того, что теперь отложенное расширение отключается. На этот раз все 4 сценария дают ожидаемый! Var2! пакетный вывод.
Если кто-то может дать логическое объяснение для результатов 2L, 2R и 5L, 5R, тогда я выберу это как ответ на свой первоначальный вопрос. В противном случае я, вероятно, соглашусь с этим сообщением в качестве ответа (на самом деле больше наблюдает за тем, что происходит, чем с ответом) Edit - jab прибил его!
Добавление: В ответ на комментарий jeb - это больше доказательств того, что команды в пакете в пакетном исполнении выполняются в контексте командной строки, а не в контексте пакета.
Эта партия script:
@echo on
call echo batch context %%%%
call echo cmd line context %%%% | more
дает этот результат:
C:\test>call echo batch context %%
batch context %
C:\test>call echo cmd line context %% | more
cmd line context %%
Заключительное дополнение
Я добавил несколько дополнительных тестов и результатов, которые показывают все результаты до сих пор. Я также демонстрирую, что расширение переменной переменной FOR происходит до обработки трубы. Наконец, я показываю некоторые интересные побочные эффекты обработки труб, когда многострочный блок сжимается в одну строку.
@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion
echo on
@echo(
@echo Delayed expansion is ON
echo 1: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^!, !var2!, ^^^!var2^^^!, %%cmdcmdline%% | more
(echo 2: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
for %%a in (Z) do (echo 3: %%a %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
(
echo 4: part1
set "var2=var2Value
set var2
echo "
set var2
)
(
echo 5: part1
set "var2=var2Value
set var2
echo "
set var2
echo --- begin cmdcmdline ---
echo %%cmdcmdline%%
echo --- end cmdcmdline ---
) | more
(
echo 6: part1
rem Only this line remarked
echo part2
)
(
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) | more
Вот вывод
Delayed expansion is ON
C:\test>echo 1: %, %var1%, %var2%, !var1!, ^!var1^!, !var2!, ^!var2^!, %cmdcmdline% | more
1: %, value1, %var2%, value1, !var1!, , !var2!, C:\Windows\system32\cmd.exe /S /D /c" echo 1: %, %var1%, %var2%, value1, !var1!, , !var2!, %cmdcmdline% "
C:\test>(echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
2: %, value1, %var2%, !var1!, !var1! !var2!, C:\Windows\system32\cmd.exe /S /D /c" ( echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"
C:\test>for %a in (Z) do (echo 3: %a %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
C:\test>(echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
3: Z %, value1, %var2%, !var1!, !var1! !var2!, C:\Windows\system32\cmd.exe /S /D /c" ( echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"
C:\test>(
echo 4: part1
set "var2=var2Value
set var2
echo "
set var2
)
4: part1
var2=var2Value
"
var2=var2Value
C:\test>(
echo 5: part1
set "var2=var2Value
set var2
echo "
set var2
echo --- begin cmdcmdline ---
echo %cmdcmdline%
echo --- end cmdcmdline ---
) | more
5: part1
var2=var2Value & set var2 & echo
--- begin cmdcmdline ---
C:\Windows\system32\cmd.exe /S /D /c" ( echo 5: part1 & set "var2=var2Value
var2=var2Value & set var2 & echo
" & set var2 & echo --- begin cmdcmdline --- & echo %cmdcmdline% & echo --- end cmdcmdline --- )"
--- end cmdcmdline ---
C:\test>(
echo 6: part1
rem Only this line remarked
echo part2
)
6: part1
part2
C:\test>(echo %cmdcmdline% & (
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) ) | more
Тесты 1: и 2: суммируйте все поведения, а трюк %% cmdcmdline %% действительно помогает продемонстрировать, что происходит.
Тест 3: демонстрирует, что расширение переменной FOR по-прежнему работает с блоком с каналами.
Тесты 4:/5: и 6:/7: показывают интересные побочные эффекты того, как трубы работают с многострочными блоками. Осторожно!
Я должен верить, что выяснение escape-последовательностей в сложных сценариях будет кошмаром.
Ответ 3
Смешная штука! Я не знаю ответа, я знаю, что операция с конвейером имеет последовательные сбои в пакете Windows, которые не должны присутствовать в исходной пачке MS-DOS (если такие функции могут выполняться в старой версии MS-DOS), поэтому я что ошибка была введена, когда были разработаны новые функции Windows Batch.
Вот несколько примеров:
echo Value to be assigned | set /p var=
Предыдущая строка НЕ присваивает значение переменной, поэтому мы должны ее исправить следующим образом:
echo Value to be assigned > temp.txt & set /p var=< temp.txt
Другой:
(
echo Value one
echo Value two
echo Value three
) | call :BatchSubroutine
Не работает. Исправьте это так:
(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt
Однако этот метод DO работает в определенных случаях; с DEBUG.COM например:
echo set tab=9> def_tab.bat
(
echo e108
echo 9
echo w
echo q
) | debug def_tab.bat
call def_tab
echo ONE%tab%TWO
Предыдущая программа показывает:
ONE TWO
В каких случаях работает, а какие нет? Только Бог (и Microsoft) может знать, но, похоже, это связано с новыми функциями пакетной обработки Windows: команда SET/P, замедленное расширение, блок кода в круглых скобках и т.д.
EDIT: асинхронные пакетные файлы
ПРИМЕЧАНИЕ. Я изменил этот раздел, чтобы исправить мою ошибку. См. Мой последний комментарий к jeb для деталей.
Как сказал Джеб, выполнение обеих сторон конвейера создает два асинхронных процесса, которые сделали возможным выполнение асинхронных потоков, даже если команда START
не используется.
Mainfile.bat:
@echo off
echo Main start. Enter lines, type end to exit
First | Second
echo Main end
First.bat:
@echo off
echo First start
:loop
set /P first=
echo First read: %first%
if /I not "%first%" == "end" goto loop
echo EOF
echo First end
Second.bat:
@echo off
echo Second start
:loop
set /P second=Enter line:
echo Second read: %second%
echo/
if not "%second%" == "EOF" goto loop
echo Second end
Мы можем использовать эту возможность для разработки программы, эквивалентной Expect application (аналогичным образом работает pexpect Phyton module), который мог бы управлять любой интерактивной программой таким образом:
Input | anyprogram | Output
Файл Output.bat достигнет части "Ожидание", проанализировав выход из программы, а Input.bat достигнет части "Sendline", предоставив вход для программы. Обратная связь с выходами на модули ввода будет достигнута посредством файла с требуемой информацией и простой системы семафора, контролируемой наличием/отсутствием одного или двух файлов флагов.