有没有一种方法可以批量指定多行字符串,类似于unix shell中的heredoc.类似的东西:
cat <out.txt bla bla .. EOF
我们的想法是从模板文件创建自定义文件.
不是我所知道的.
我最了解的是
> out.txt ( @echo.bla @echo.bla ... )
(@
防止命令shell本身打印它正在运行的命令,并echo.
允许您使用空格开始一行.)
这是另一种方法.
@echo off :: ###################################################### :: ## Heredoc syntax: ## :: ## call :heredoc uniqueIDX [>outfile] && goto label ## :: ## contents ## :: ## contents ## :: ## contents ## :: ## etc. ## :: ## :label ## :: ## ## :: ## Notes: ## :: ## Variables to be evaluated within the heredoc ## :: ## should be called in the delayed expansion style ## :: ## (!var! rather than %var%, for instance). ## :: ## ## :: ## Literal exclamation marks (!) and carats (^) ## :: ## must be escaped with a carat (^). ## :: ###################################################### :-------------------------------------------- : calling heredoc with results sent to stdout :-------------------------------------------- call :heredoc stickman && goto next1 \o/ | This is the "stickman" heredoc, echoed to stdout. / \ :next1 :----------------------------------------------------------------- : calling heredoc containing vars with results sent to a text file :----------------------------------------------------------------- set bodyText=Hello world! set lipsum=Lorem ipsum dolor sit amet, consectetur adipiscing elit. call :heredoc html >out.txt && goto next2!bodyText!
!lipsum!
Thus endeth the heredoc. :) :next2 echo; echo Does the redirect to a file work? Press any key to type out.txt and find out. echo; pause>NUL type out.txt del out.txt :: End of main script goto :EOF :: ######################################## :: ## Here's the heredoc processing code ## :: ######################################## :heredocsetlocal enabledelayedexpansion set go= for /f "delims=" %%A in ('findstr /n "^" "%~f0"') do ( set "line=%%A" && set "line=!line:*:=!" if defined go (if #!line:~1!==#!go::=! (goto :EOF) else echo(!line!) if "!line:~0,13!"=="call :heredoc" ( for /f "tokens=3 delims=>^ " %%i in ("!line!") do ( if #%%i==#%1 ( for /f "tokens=2 delims=&" %%I in ("!line!") do ( for /f "tokens=2" %%x in ("%%I") do set "go=%%x" ) ) ) ) ) goto :EOF
示例输出:
C:\Users\oithelp\Desktop>heredoc \o/ | This is the "stickman" heredoc, echoed to stdout. / \ Does the redirect to a file work? Press any key to type out.txt and find out.Hello world!
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Thus endeth the heredoc. :)
是的,很有可能.^是文字转义字符,只需将它放在换行符之前.在这个例子中,我还添加了额外的换行符,以便在文件中正确打印:
@echo off echo foo ^ this is ^ a multiline ^ echo > out.txt
输出:
E:\>type out.txt foo this is a multiline echo E:\>
@echo off for /f "delims=:" %%a in ( 'findstr -n "^___" %0') do set "Line=%%a" (for /f "skip=%Line% tokens=* eol=_" %%a in ( 'type %0') do echo(%%a) > out.html :: out.html pause goto: EOF ___DATA___ title>
随着在DosTips,西伯利亚人发布的演示错误GOTO语句的惊人表现的形式(goto) 2>nul
.然后Aacini和jeb记录了一些关于奇怪行为的其他有趣发现.它基本上表现得像一个EXIT /B
,除了它允许CALLed例程中的连接命令在父调用者的上下文中执行.
这是一个简短的脚本,演示了大多数要点:
@echo off setlocal enableDelayedExpansion set "var=Parent Value" ( call :test echo This and the following line are not executed exit /b ) :break echo How did I get here^^!^^!^^!^^! exit /b :test setlocal disableDelayedExpansion set "var=Child Value" (goto) 2>nul & echo var=!var! & goto :break echo This line is not executed :break echo This line is not executed
- 输出 -
var=Parent Value How did I get here!!!!
这种惊人的行为使我能够编写一个优雅的批量模拟这里的文档,其中包含许多可用于unix的选项.我将PrintHere.bat实现为一个独立的实用程序,应该放在PATH中列出的文件夹中.然后,任何批处理脚本都可以轻松调用该实用程序以获取此处的doc功能.
以下是一般用法语法:
call PrintHere :Label Here doc text goes here :Label
怎么可能实现这个?...我的PrintHere实用程序使用了(GOTO) 2>nul
两次技巧.
我第一次使用(GOTO) 2>nul
返回调用者所以我可以获得调用脚本的完整路径,以便PrintHere知道要读取的文件.然后我再次打印PrintHere!
我第二次使用(GOTO) 2>nul
返回调用者和GOTO终止标签,以便不执行此处的doc文本.
注意 - 下面的脚本在标签的定义中包含一个制表符(0x09),位于标签的正下方:start
.某些浏览器可能难以显示和复制选项卡.作为替代方案,您可以从我的保管箱下载PrintHere.bat.txt,只需将其重命名为PrintHere.bat即可.
我最初在DosTips上发布了PrintHere.bat,您可以在其中跟踪未来的发展.
PrintHere.bat
@echo off & setlocal disableDelayedExpansion & goto :start ::PrintHere.bat version 1.1 by Dave Benham ::: :::call PrintHere [/E] [/- "TrimList"] :Label ["%~f0"] :::call PrintHere [/E] [/- "TrimList"] :Label "%~f0" | someCommand & goto :Label :::PrintHere /? :::PrintHere /V ::: ::: PrintHere.bat provides functionality similar to the unix here doc feature. ::: It prints all content between the CALL PrintHere :Label line and the ::: terminating :Label. The :Label must be a valid label supported by GOTO, with ::: the additional constraint that it not contain *. Lines are printed verbatim, ::: with the following exceptions and limitations: ::: ::: - Lines are lmited to 1021 bytes long ::: - Trailing control characters are stripped from each line ::: ::: The code should look something like the following: ::: ::: call PrintHere :Label ::: Spacing and blank lines are preserved ::: ::: Special characters like & < > | ^ ! % are printed normally ::: :Label ::: ::: If the /E option is used, then variables between exclamation points are ::: expanded, and ! and ^ literals must be escaped as ^! and ^^. The limitations ::: are different when /E is used: ::: ::: - Lines are limited to ~8191 bytes long ::: - All characters are preserved, except !variables! are expanded and ^! and ::: ^^ are transformed into ! and ^ ::: ::: Here is an example using /E: ::: ::: call PrintHere /E :SubstituteExample ::: Hello !username!^! ::: :SubstituteExample ::: ::: If the /- "TrimList" option is used, then leading "TrimList" characters ::: are trimmed from the output. The trim characters are case sensitive, and ::: cannot include a quote. If "TrimList" includes a space, then it must ::: be the last character in the list. ::: ::: Multiple PrintHere blocks may be defined within one script, but each ::: :Label must be unique within the file. ::: ::: PrintHere must not be used within a parenthesized code block. ::: ::: Scripts that use PrintHere must use \r\n for line termination, and all lines ::: output by PrintHere will be terminated by \r\n. ::: ::: All redirection associated with a PrintHere must appear at the end of the ::: command. Also, the CALL can include path information: ::: ::: call "c:\utilities\PrintHere.bat" :MyBlock>test.txt ::: This line is written to test.txt ::: :MyBlock ::: ::: PrintHere may be used with a pipe, but only on the left side, and only ::: if the source script is included as a 2nd argument, and the right side must ::: explicitly and unconditionally GOTO the terminating :Label. ::: ::: call PrintHere :PipedBlock "%~f0" | more & goto :PipedBlock ::: text goes here ::: :PipedBlock ::: ::: Commands concatenated after PrintHere are ignored. For example: ::: ::: call PrintHere :ignoreConcatenatedCommands & echo This ECHO is ignored ::: text goes here ::: :ignoreConcatenatedCommands ::: ::: PrintHere uses FINDSTR to locate the text block by looking for the ::: CALL PRINTHERE :LABEL line. The search string length is severely limited ::: on XP. To minimize the risk of PrintHere failure when running on XP, it is ::: recommended that PrintHere.bat be placed in a folder included within PATH ::: so that the utility can be called without path information. ::: ::: PrintHere /? prints out this documentation. ::: ::: PrintHere /V prints out the version information ::: ::: PrintHere.bat was written by Dave Benham. Devlopment history may be traced at: ::: http://www.dostips.com/forum/viewtopic.php?f=3&t=6537 ::: :start set "tab= " NOTE: This value must be a single tab (0x09), not one or more spaces set "sp=[ %tab%=,;]" set "sp+=%sp%%sp%*" set "opt=" set "/E=" set "/-=" :getOptions if "%~1" equ "" call :exitErr Invalid call to PrintHere - Missing :Label argument if "%~1" equ "/?" ( for /f "tokens=* delims=:" %%L in ('findstr "^:::" "%~f0"') do echo(%%L exit /b 0 ) if /i "%~1" equ "/V" ( for /f "tokens=* delims=:" %%L in ('findstr /rc:"^::PrintHere\.bat version" "%~f0"') do echo(%%L exit /b 0 ) if /i %1 equ /E ( set "/E=1" set "opt=%sp+%.*" shift /1 goto :getOptions ) if /i %1 equ /- ( set "/-=%~2" set "opt=%sp+%.*" shift /1 shift /1 goto :getOptions ) echo %1|findstr "^:[^:]" >nul || call :exitErr Invalid PrintHere :Label if "%~2" equ "" ( (goto) 2>nul setlocal enableDelayedExpansion if "!!" equ "" ( endlocal call %0 %* "%%~f0" ) else ( >&2 echo ERROR: PrintHere must be used within a batch script. (call) ) ) set ^"call=%0^" set ^"label=%1^" set "src=%~2" setlocal enableDelayedExpansion set "call=!call:\=[\\]!" set "label=!label:\=[\\]!" for %%C in (. [ $ ^^ ^") do ( set "call=!call:%%C=\%%C!" set "label=!label:%%C=\%%C!" ) set "search=!sp!*call!sp+!!call!!opt!!sp+!!label!" set "cnt=" for /f "delims=:" %%N in ('findstr /brinc:"!search!$" /c:"!search![<>|&!sp:~1!" "!src!"') do if not defined skip set "skip=%%N" if not defined skip call :exitErr Unable to locate CALL PrintHere %1 for /f "delims=:" %%N in ('findstr /brinc:"!sp!*!label!$" /c:"!sp!*!label!!sp!" "!src!"') do if %%N gtr %skip% if not defined cnt set /a cnt=%%N-skip-1 if not defined cnt call :exitErr PrintHere end label %1 not found if defined /E ( for /f "skip=%skip% delims=" %%L in ('findstr /n "^^" "!src!"') do ( if !cnt! leq 0 goto :break set "ln=%%L" if not defined /- (echo(!ln:*:=!) else for /f "tokens=1* delims=%/-%" %%A in (^""%/-%!ln:*:=!") do ( setlocal disableDelayedExpansion echo(%%B endlocal ) set /a cnt-=1 ) ) else ( for /l %%N in (1 1 %skip%) do set /p "ln=" for /l %%N in (1 1 %cnt%) do ( set "ln=" set /p "ln=" if not defined /- (echo(!ln!) else for /f "tokens=1* delims=%/-%" %%A in (^""%/-%!ln!") do ( setlocal disableDelayedExpansion echo(%%B endlocal ) ) ) <"!src!" :break (goto) 2>nul & goto %~1 :exitErr >&2 echo ERROR: %* (goto) 2>nul & exit /b 1
完整文档嵌入在脚本中.以下是一些使用示例:
逐字输出
@echo off call PrintHere :verbatim Hello !username!^! It is !time! on !date!. :verbatim
- 输出 -
Hello !username!^! It is !time! on !date!.
展开变量(不需要启用延迟扩展)
@echo off call PrintHere /E :Expand Hello !username!^! It is !time! on !date!. :Expand
--OUTPUT--
Hello Dave! It is 20:08:15.35 on Fri 07/03/2015.
展开变量并修剪前导空格
@echo off call PrintHere /E /- " " :Expand Hello !username!^! It is !time! on !date!. :Expand
--OUTPUT--
Hello Dave! It is 20:10:46.09 on Fri 07/03/2015.
输出可以重定向到文件
@echo off call PrintHere :label >helloWorld.bat @echo Hello world! :label
输出不能重定向为输入,但可以通过管道输出!不幸的是,语法不是那么优雅,因为管道的两端都在新的CMD.EXE进程中执行,因此(GOTO) 2>nul
返回子cmd进程,而不是主脚本.
@echo off call PrintHere :label "%~f0" | findstr "^" & goto :label Text content goes here :label
使用带参数的宏允许以更简单的方式编写"heredoc":
@echo off rem Definition of heredoc macro setlocal DisableDelayedExpansion set LF=^ ::Above 2 blank lines are required - do not remove set ^"\n=^^^%LF%%LF%^%LF%%LF%^^" set heredoc=for %%n in (1 2) do if %%n==2 (%\n% for /F "tokens=1,2" %%a in ("!argv!") do (%\n% if "%%b" equ "" (call :heredoc %%a) else call :heredoc %%a^>%%b%\n% endlocal ^& goto %%a%\n% )%\n% ) else setlocal EnableDelayedExpansion ^& set argv= rem Heredoc syntax: rem rem %%heredoc%% :uniqueLabel [outfile] rem contents rem contents rem ... rem :uniqueLabel rem rem Same notes of rojo's answer apply rem Example borrowed from rojo's answer: set bodyText=Hello world! set lipsum=Lorem ipsum dolor sit amet, consectetur adipiscing elit. %heredoc% :endHtml out.txt!bodyText!
!lipsum!
:endHtml echo File created: type out.txt del out.txt goto :EOF rem Definition of heredoc subroutine :heredoc label set "skip=" for /F "delims=:" %%a in ('findstr /N "%1" "%~F0"') do ( if not defined skip (set skip=%%a) else set /A lines=%%a-skip-1 ) for /F "skip=%skip% delims=" %%a in ('findstr /N "^" "%~F0"') do ( set "line=%%a" echo(!line:*:=! set /A lines-=1 if !lines! == 0 exit /B ) exit /B