Windows 批处理的heredoc?

发布于 2024-07-24 13:15:24 字数 159 浏览 4 评论 0原文

有没有一种方法可以以类似于 unix shell 中的heredoc 的方式批量指定多行字符串。 类似于:

cat <<EOF > out.txt
bla
bla
..
EOF

这个想法是从模板文件创建自定义文件。

Is there a way of specifying multiline strings in batch in a way similar to heredoc in unix shells. Something similar to:

cat <<EOF > out.txt
bla
bla
..
EOF

The idea is to create a customized file from a template file..

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(20

天荒地未老 2024-07-31 13:15:25

OP想要的是非常具体的东西(创建带有输出的文本文件),并且接受的答案完美地做到了这一点,但是所提出的解决方案在特定上下文之外并不能很好地工作。 例如,如果我想将多行输入传递到命令中,则不能使用 ( echo ) 语法。 这就是对我有用的结果。

给定一个名为“echolines.pl”的 Perl 脚本,其中包含以下内容(以模拟“真实”程序):

use strict;
use warnings;

while (<>) {
        chomp;
        print qq(<$_>\n);
}

以及一个名为“testme.bat”的批处理文件,其中包含:

@echo off

set FOO=foo
set BAR=bar
set BAZ=baz

echo %FOO%^
&echo %BAR%^
&echo %BAZ%|perl echolines.pl

运行该脚本会产生预期的输出:

C:\>testme
<foo>
<bar>
<baz>

必须注意空格确保一切正常工作,任何地方都没有杂散空间。 具体来说:每个行尾应为脱字号 (^),后跟换行符,后续行必须立即以与号 (&) 开头,最后一行必须在要发送的最后一项之后立即开始管道。 如果不这样做将导致参数丢失或参数前后有额外的空格。

What the OP wanted was something very specific (creating a text file with the output) and the accepted answer does that perfectly, but the solution presented does not work well outside of that specific context. For example, if I want to pass multi-line input into a command, I can't use the ( echo ) syntax. Here's what wound up working for me.

Given a perl script named "echolines.pl" consisting of the following (to simulate a 'real' program):

use strict;
use warnings;

while (<>) {
        chomp;
        print qq(<$_>\n);
}

and a batch file named "testme.bat" containing:

@echo off

set FOO=foo
set BAR=bar
set BAZ=baz

echo %FOO%^
&echo %BAR%^
&echo %BAZ%|perl echolines.pl

running it produces the expected output:

C:\>testme
<foo>
<bar>
<baz>

Care with whitespace must be taken to ensure it all works correctly with no stray spaces anywhere. Specifically: each end-of-line should be a caret (^) followed by a newline, the subsequent lines must begin immediately with the ampersand (&) and the last line must have the pipe starting immediately after the last item to be sent. Failing to do this will result in missing parameters or extra whitespace before and after the parameters.

圈圈圆圆圈圈 2024-07-31 13:15:25

试试这个代码。 (底部的 JScript 代码将“out.html”写入磁盘)

@if(0)==(0) echo on
cscript.exe //nologo //E:JScript "%~f0" source1 out.html
start out.html
goto :EOF

[source1]
<!DOCTYPE html>
<html>
  <head>
   title></title>
  </head>
  <body>
    <svg width="900" height="600">
        <text x="230" 
              y="150"
              font-size="100"
              fill="blue"
              stroke="gray"
              stroke-width="1">
                  Hello World              
        </text>
    </svg>
  </body>
</html>
[/source1]

@end

if (WScript.Arguments.length != 2) WScript.Quit();
var tagName = WScript.Arguments(0);
var path    = WScript.Arguments(1);
var startTag = "[" + tagName + "]"
var endTag   = "[/" + tagName + "]"
var fso = new ActiveXObject("Scripting.FileSystemObject");
var file1 = fso.OpenTextFile(WScript.ScriptFullName);
var txt = "";
var found = false;
while (!file1.AtEndOfStream) {
  var line = file1.ReadLine();
  if (!found) {
    if (line.lastIndexOf(startTag, 0) === 0) found = true;
  } else {
    if (line.lastIndexOf(endTag, 0) === 0) break;
    txt += line + "\n";
  }
}
file1.Close();
var file2 = fso.CreateTextFile(path, true, false);
file2.Write(txt);
file2.Close();

Try this code. (JScript code at the bottom writes "out.html" to disk)

@if(0)==(0) echo on
cscript.exe //nologo //E:JScript "%~f0" source1 out.html
start out.html
goto :EOF

[source1]
<!DOCTYPE html>
<html>
  <head>
   title></title>
  </head>
  <body>
    <svg width="900" height="600">
        <text x="230" 
              y="150"
              font-size="100"
              fill="blue"
              stroke="gray"
              stroke-width="1">
                  Hello World              
        </text>
    </svg>
  </body>
</html>
[/source1]

@end

if (WScript.Arguments.length != 2) WScript.Quit();
var tagName = WScript.Arguments(0);
var path    = WScript.Arguments(1);
var startTag = "[" + tagName + "]"
var endTag   = "[/" + tagName + "]"
var fso = new ActiveXObject("Scripting.FileSystemObject");
var file1 = fso.OpenTextFile(WScript.ScriptFullName);
var txt = "";
var found = false;
while (!file1.AtEndOfStream) {
  var line = file1.ReadLine();
  if (!found) {
    if (line.lastIndexOf(startTag, 0) === 0) found = true;
  } else {
    if (line.lastIndexOf(endTag, 0) === 0) break;
    txt += line + "\n";
  }
}
file1.Close();
var file2 = fso.CreateTextFile(path, true, false);
file2.Write(txt);
file2.Close();
瞄了个咪的 2024-07-31 13:15:25

这更容易,并且非常类似于 cat << EOF> out.txt:

C:\>复制 con out.txt
这是我的第一行文字。
这是我的最后一行文字。
^Z
已复制 1 个文件。

输出如下所示:

C:\>type out.txt
这是我的第一行文字。
这是我的最后一行文本。

(复制 con + out.txt,键入您的输入,然后按 Ctrl-Z,然后复制文件)

COPY CON 表示“从控制台复制”(接受用户输入)

This is even easier, and closely resembles cat << EOF > out.txt:

C:\>copy con out.txt
This is my first line of text.
This is my last line of text.
^Z
1 file(s) copied.

Output looks like this:

C:\>type out.txt
This is my first line of text.
This is my last line of text.

(copy con + out.txt, type your input, followed by Ctrl-Z and file is copied)

COPY CON means "copy from the console" (accept user input)

此变体保存所有格式,

:heredoc
rem %1 - file
rem %2 - label
setlocal enableextensions enabledelayedexpansion
set script=%~1
rem to start ::::>label
rem to stop ::::<label
set "startLabel=::::>%~2"
set "stopLabel=::::<%~2"
rem start position
for /f "usebackq delims=: tokens=1" %%i in (`type "%script%" ^| findstr /b /n "%startLabel%"`) do @set "startPosition=%%i"
if not defined startPosition exit /b 1
rem stop position
for /f "usebackq delims=: tokens=1" %%i in (`type "%script%" ^| findstr /b /n "%stopLabel%"`) do @set "stopPosition=%%i"
rem errors
if not defined stopPosition exit /b 2
if %startPosition% equ %stopPosition% exit /b 3
if %startPosition% gtr %stopPosition% exit /b 4
rem lines count
set /a linesCount=stopPosition - startPosition
set /a linesCount=linesCount - 1
if %linesCount% equ 0 exit /b 5
rem unique files
call :uniqueFilename temp0 || exit /b 6
call :uniqueFilename temp1 || exit /b 6
call :uniqueFilename temp2 || exit /b 6
call :uniqueFilename temp3 || exit /b 6
call :uniqueFilename temp4 || exit /b 6
(
rem make empty file
type nul > "%temp0%"
rem another variant
rem >nul copy nul "%temp0%"
rem take part from start position
more +%startPosition% "%script%" > "%temp1%"
rem then compare with emty file printing only count lines
>"%temp2%" fc /t /lb%linesCount% "%temp0%" "%temp1%"
>"%temp3%"<"%temp2%" more +2
>"%temp4%"<"%temp3%" find /v "*****"
type "%temp4%"
rem variant below adds 2 empty lines at the end
rem fc "%temp0%" "%temp1%" /lb %linesCount% /t | find /v "*****" | more +2
)
set "errorCode=%errorlevel%"
rem cleaning
del /f /q "%temp0%" "%temp1%" "%temp2%" "%temp3%" "%temp4%" >nul 2>nul
endlocal & exit /b %errorCode%

:uniqueFilename
rem %1 - variable name
setlocal enableextensions enabledelayedexpansion
for /f "usebackq delims=, tokens=2" %%i in (`tasklist /fo csv /nh ^| find "tasklist"`) do set "n=%random%%%~i"
if not defined n exit /b 1
endlocal & set "%~1=%~dp0%n%.temp"
exit /b 0

将其用作

call :heredoc "%~0" bashrc
exit /b 0

::::>bashrc
# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
::::<bashrc

输出

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

this variant saves all formatting

:heredoc
rem %1 - file
rem %2 - label
setlocal enableextensions enabledelayedexpansion
set script=%~1
rem to start ::::>label
rem to stop ::::<label
set "startLabel=::::>%~2"
set "stopLabel=::::<%~2"
rem start position
for /f "usebackq delims=: tokens=1" %%i in (`type "%script%" ^| findstr /b /n "%startLabel%"`) do @set "startPosition=%%i"
if not defined startPosition exit /b 1
rem stop position
for /f "usebackq delims=: tokens=1" %%i in (`type "%script%" ^| findstr /b /n "%stopLabel%"`) do @set "stopPosition=%%i"
rem errors
if not defined stopPosition exit /b 2
if %startPosition% equ %stopPosition% exit /b 3
if %startPosition% gtr %stopPosition% exit /b 4
rem lines count
set /a linesCount=stopPosition - startPosition
set /a linesCount=linesCount - 1
if %linesCount% equ 0 exit /b 5
rem unique files
call :uniqueFilename temp0 || exit /b 6
call :uniqueFilename temp1 || exit /b 6
call :uniqueFilename temp2 || exit /b 6
call :uniqueFilename temp3 || exit /b 6
call :uniqueFilename temp4 || exit /b 6
(
rem make empty file
type nul > "%temp0%"
rem another variant
rem >nul copy nul "%temp0%"
rem take part from start position
more +%startPosition% "%script%" > "%temp1%"
rem then compare with emty file printing only count lines
>"%temp2%" fc /t /lb%linesCount% "%temp0%" "%temp1%"
>"%temp3%"<"%temp2%" more +2
>"%temp4%"<"%temp3%" find /v "*****"
type "%temp4%"
rem variant below adds 2 empty lines at the end
rem fc "%temp0%" "%temp1%" /lb %linesCount% /t | find /v "*****" | more +2
)
set "errorCode=%errorlevel%"
rem cleaning
del /f /q "%temp0%" "%temp1%" "%temp2%" "%temp3%" "%temp4%" >nul 2>nul
endlocal & exit /b %errorCode%

:uniqueFilename
rem %1 - variable name
setlocal enableextensions enabledelayedexpansion
for /f "usebackq delims=, tokens=2" %%i in (`tasklist /fo csv /nh ^| find "tasklist"`) do set "n=%random%%%~i"
if not defined n exit /b 1
endlocal & set "%~1=%~dp0%n%.temp"
exit /b 0

use it as

call :heredoc "%~0" bashrc
exit /b 0

::::>bashrc
# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
::::<bashrc

output will be

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

诗酒趁年少 2024-07-31 13:15:25
C:\>more >file.txt
This is line 1 of file
This is line 2 of file
^C

C:\>type file.txt
This is line 1 of file
This is line 2 of file

**它会在末尾添加一个空行,但您可以轻松解决这个问题,只需使用复制方法:

C:\>copy con file.txt >nul
This is line 1 of file
This is line 2 of file^Z

C:\>type file.txt
This is line 1 of file
This is line 2 of file

注意在每种情况下键入 ^C 和 ^Z 的位置。

C:\>more >file.txt
This is line 1 of file
This is line 2 of file
^C

C:\>type file.txt
This is line 1 of file
This is line 2 of file

**It will add an empty line at the end, but you can solve that easily, just by using the copy con method:

C:\>copy con file.txt >nul
This is line 1 of file
This is line 2 of file^Z

C:\>type file.txt
This is line 1 of file
This is line 2 of file

Beware where you type ^C and ^Z in each case.

爱的十字路口 2024-07-31 13:15:24

据我所知还没有。

我知道的最接近的是

> out.txt (
    @echo.bla
    @echo.bla
    ...
)

@ 阻止命令 shell 本身打印它正在运行的命令,而 echo. 允许您以空格开始一行。)

Not as far as I know.

The closest I know of is

> out.txt (
    @echo.bla
    @echo.bla
    ...
)

(@ prevents the command shell itself from printing the commands it's running, and echo. allows you to start a line with a space.)

司马昭之心 2024-07-31 13:15:24

这是另一种方法。

@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
<html lang="en">
    <body>
        <h3>!bodyText!</h3>
        <p>!lipsum!</p>
    </body>
</html>

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 ##
:: ########################################
:heredoc <uniqueIDX>
setlocal 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.

<html lang="en">
    <body>
        <h3>Hello world!</h3>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </body>
</html>

Thus endeth the heredoc.  :)

Here's another approach.

@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
<html lang="en">
    <body>
        <h3>!bodyText!</h3>
        <p>!lipsum!</p>
    </body>
</html>

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 ##
:: ########################################
:heredoc <uniqueIDX>
setlocal 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

Example output:

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.

<html lang="en">
    <body>
        <h3>Hello world!</h3>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </body>
</html>

Thus endeth the heredoc.  :)
关于从前 2024-07-31 13:15:24

是的,很有可能。 ^ 是字面转义字符,只需将其放在换行符之前即可。 在此示例中,我还添加了额外的换行符,以便将其正确打印在文件中:

@echo off
echo foo ^

this is ^

a multiline ^

echo > out.txt

输出:

E:\>type out.txt
foo
 this is
 a multiline
 echo

E:\>

Yes, very possible. ^ is the literal escape character, just put it before your newline. In this example, I put the additional newline in as well so that it is properly printed in the file:

@echo off
echo foo ^

this is ^

a multiline ^

echo > out.txt

Output:

E:\>type out.txt
foo
 this is
 a multiline
 echo

E:\>
白云悠悠 2024-07-31 13:15:24
@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___
<!Doctype html>
<html>
  <head>
   title></title>
  </head>
  <body>
    <svg width="900" height="600">
        <text x="230" 
              y="150"
              font-size="100"
              fill="blue"
              stroke="gray"
              stroke-width="1">
                  Hello World              
        </text>
    </svg>
  </body>
</html>
@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___
<!Doctype html>
<html>
  <head>
   title></title>
  </head>
  <body>
    <svg width="900" height="600">
        <text x="230" 
              y="150"
              font-size="100"
              fill="blue"
              stroke="gray"
              stroke-width="1">
                  Hello World              
        </text>
    </svg>
  </body>
</html>
油饼 2024-07-31 13:15:24

在 DosTips 上,siberia-man 发布了令人惊奇的行为演示错误的 GOTO 语句,格式为 (goto) 2>nul。 随后,阿西尼和杰布记录了关于这种奇怪行为的一些额外有趣的发现。 它的行为基本上类似于 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 中列出的文件夹中。 然后任何批处理脚本都可以轻松调用该实用程序来获取此处的文档功能。

以下是使用的一般语法:

call PrintHere :Label
Here doc text goes here
:Label

这怎么可能实现?...我的 PrintHere 实用程序使用了 (GOTO) 2>nul 技巧两次。

  • 第一次使用 (GOTO) 2>nul 返回调用者,这样我就可以获得调用脚本的完整路径,以便 PrintHere 知道要读取哪个文件。 然后我第二次调用 PrintHere!

  • 第二次我使用 (GOTO) 2>nul 返回调用者并转到终止标签,以便不执行此处的文档文本。

注意 - 下面的脚本在 tab 的定义中包含一个制表符 (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

-- OUTPUT --

    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

Over at DosTips, siberia-man posted a demonstration of amazing behavior of an erroneous GOTO statement in the form of (goto) 2>nul. Aacini and jeb then documented some additional interesting discoveries about the odd behavior. It basically behaves like an EXIT /B, except it allows concatenated commands within a CALLed routine to execute in the context of the parent caller.

Here is a brief script that demonstrates most of the salient points:

@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

-- OUTPUT --

var=Parent Value
How did I get here!!!!

This amazing behavior has enabled me to write an elegant batch emulation of a here doc with many of the options available to unix. I implemented PrintHere.bat as a stand-alone utility that should be placed in a folder listed within your PATH. Then any batch script can easily CALL the utility to get here doc functionality.

Here is the general syntax of usage:

call PrintHere :Label
Here doc text goes here
:Label

How can this possibly be achieved?... My PrintHere utility uses the (GOTO) 2>nul trick twice.

  • The first time I use (GOTO) 2>nul to return to the caller so I can get the full path to the calling script so that PrintHere knows what file to read from. I then CALL PrintHere a second time!

  • The second time I use (GOTO) 2>nul to return to the caller and GOTO the terminating label so that the here doc text is not executed.

Note - the script below contains a tab character (0x09) in the definition of tab, directly below the :start label. Some browsers may have difficulty displaying and copying the tab. As an alternative, you can download PrintHere.bat.txt from my dropbox, and simply rename it to PrintHere.bat.

I originally posted PrintHere.bat at DosTips, where you can track future development.

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

Full documentation is embedded within the script. Below are some demonstrations of usage:

Verbatim output

@echo off
call PrintHere :verbatim
    Hello !username!^!
    It is !time! on !date!.
:verbatim

-- OUTPUT --

    Hello !username!^!
    It is !time! on !date!.

Expand variables (delayed expansion need not be enabled)

@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.

Expand variables and trim leading spaces

@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.

Output can be redirected to a file

@echo off
call PrintHere :label >helloWorld.bat
  @echo Hello world!
:label

The output cannot be redirected as input, but it can be piped! Unfortunately, the syntax is not nearly as elegant because both sides of a pipe are executed in a new CMD.EXE process, so (GOTO) 2>nul returns to a child cmd process, and not the master script.

@echo off
call PrintHere :label "%~f0" | findstr "^" & goto :label
  Text content goes here
:label
还如梦归 2024-07-31 13:15:24

带参数的宏的用法 允许以更简单的方式编写“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
<html lang="en">
    <body>
        <h3>!bodyText!</h3>
        <p>!lipsum!</p>
    </body>
</html>
: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

The usage of a macro with parameters allows to write a "heredoc" in a simpler way:

@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
<html lang="en">
    <body>
        <h3>!bodyText!</h3>
        <p>!lipsum!</p>
    </body>
</html>
: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
南渊 2024-07-31 13:15:24

@jeb

setlocal EnableDelayedExpansion
set LF=^


REM Two empty lines are required

另一个变体:

@echo off

:)
setlocal enabledelayedexpansion
>nul,(pause&set /p LF=&pause&set /p LF=)<%0
set LF=!LF:~0,1!

echo 1!LF!2!LF!3

pause

@jeb

setlocal EnableDelayedExpansion
set LF=^


REM Two empty lines are required

another variant:

@echo off

:)
setlocal enabledelayedexpansion
>nul,(pause&set /p LF=&pause&set /p LF=)<%0
set LF=!LF:~0,1!

echo 1!LF!2!LF!3

pause
々眼睛长脚气 2024-07-31 13:15:24

参考rojo的帖子 https://stackoverflow.com/a/15032476/3627676

当然,他的解决方案就是我的解决方案寻求一些时间(当然,我可以尝试实现类似的东西,但懒惰会推动进步:))。 我想补充的一件事是对原始代码的微小改进。 我认为如果将重定向到文件写在行尾会更好。 在这种情况下,heredoc 起始线可以更严格,其分析也更简单。

@echo off

set "hello=Hello world!"
set "lorem=Lorem ipsum dolor sit amet, consectetur adipiscing elit."

call :heredoc HTML & goto :HTML
<html>
<title>!hello!</title>
<body>
<p>Variables in heredoc should be surrounded by the exclamation mark (^!).</p>
<p>!lorem!</p>
<p>Exclamation mark (^!) and caret (^^) MUST be escaped with a caret (^^).</p>
</body>
</html>
:HTML

goto :EOF

:: https://stackoverflow.com/a/15032476/3627676
:heredoc LABEL
setlocal enabledelayedexpansion
set go=
for /f "delims=" %%A in ( '
    findstr /n "^" "%~f0"
' ) do (
    set "line=%%A"
    set "line=!line:*:=!"

    if defined go (
        if /i "!line!" == "!go!" goto :EOF
        echo:!line!
    ) else (
        rem delims are ( ) > & | TAB , ; = SPACE
        for /f "tokens=1-3 delims=()>&| ,;= " %%i in ( "!line!" ) do (
            if /i "%%i %%j %%k" == "call :heredoc %1" (
                set "go=%%k"
                if not "!go:~0,1!" == ":" set "go=:!go!"
            )
        )
    )
)
goto :EOF

我通过这段代码建议什么? 让我们考虑一下。

Rojo 的代码非常严格:

  • call:heredoc 之间的字符串中不允许有多个空白字符
  • call :heredoc 粘在行的边缘(行的开头不允许有任何空格)
  • 行内某处允许重定向到文件(不是很有用) -

我建议的事情:

  • 不太严格(多个空格作为分隔符)
  • 重定向到文件仅允许出现在行尾(允许且需要圆括号),
  • 行边缘无粘性代码

更新 1
对 Heredoc 开头的检查和执行的改进:

  • 重要的命令只有 call :heredoc LABELcall :heredoc :LABEL。 因此,在打印heredoc内容后,可以跳转到另一个标签、脚本末尾或运行exit /b
  • 删除了未使用和不必要的命令 goto :next2

更新 2

  • 内部 for 的分隔符为 ( ) > ; & | TAB , ; = SPACE
  • /I 添加到 if

更新 3

通过以下链接您可以找到独立脚本的完整版本(可以嵌入到您的脚本中) https://github.com/ildar-shaimordanov/cmd.scripts/blob/master/heredoc.bat

Referring to rojo's post at https://stackoverflow.com/a/15032476/3627676

Definitely, his solution is what I am seeking for few time (of course, I could try to implement something similar to this but laziness moves progress :)). One thing I'd like to add is a minor improvement to the original code. I thought it would be better if redirection to file was written at the end of the line. In this case the heredoc starter line could be stricter and its analysis simpler.

@echo off

set "hello=Hello world!"
set "lorem=Lorem ipsum dolor sit amet, consectetur adipiscing elit."

call :heredoc HTML & goto :HTML
<html>
<title>!hello!</title>
<body>
<p>Variables in heredoc should be surrounded by the exclamation mark (^!).</p>
<p>!lorem!</p>
<p>Exclamation mark (^!) and caret (^^) MUST be escaped with a caret (^^).</p>
</body>
</html>
:HTML

goto :EOF

:: https://stackoverflow.com/a/15032476/3627676
:heredoc LABEL
setlocal enabledelayedexpansion
set go=
for /f "delims=" %%A in ( '
    findstr /n "^" "%~f0"
' ) do (
    set "line=%%A"
    set "line=!line:*:=!"

    if defined go (
        if /i "!line!" == "!go!" goto :EOF
        echo:!line!
    ) else (
        rem delims are ( ) > & | TAB , ; = SPACE
        for /f "tokens=1-3 delims=()>&| ,;= " %%i in ( "!line!" ) do (
            if /i "%%i %%j %%k" == "call :heredoc %1" (
                set "go=%%k"
                if not "!go:~0,1!" == ":" set "go=:!go!"
            )
        )
    )
)
goto :EOF

What am I suggesting by this code? Let's consider.

Rojo's code is very strict:

  • doesn't allow more than one whitespace char in the string between call and :heredoc
  • call :heredoc is sticky to the edge of the line (no any whitespaces allowed at the beginning of the line)
  • redirection to file is allowed somewhere within the line (not very useful) -

Things I am suggesting:

  • less strict (more than one whitespaces as delimiters)
  • redirection to file is allowed at the end of line only (rounded brackets are allowed and needed)
  • no sticky code to the edge of line

Update 1:
Improvements to checking and performing of the heredoc beginning:

  • important command is only call :heredoc LABEL or call :heredoc :LABEL. So after printing the heredoc content it is possible to jump to another label, end of script or run exit /b.
  • removed unused and unnecessary command goto :next2

Update 2:

  • Delimiters for inner for are ( ) > & | TAB , ; = SPACE
  • The switch /I added to if

Update 3:

By the following link you can find the full version of the standalone script (embedding in your scripts is available) https://github.com/ildar-shaimordanov/cmd.scripts/blob/master/heredoc.bat

墨落画卷 2024-07-31 13:15:24

您可以使用 FOR /F 循环创建带引号的文本块,因此不需要转义特殊字符,例如 <>|& 只有 % 具有被逃脱。
这有时很有用,比如创建 html 输出。

@echo off
setlocal EnableDelayedExpansion
set LF=^


REM Two empty lines are required
set ^"NL=^^^%LF%%LF%^%LF%%LF%^^"

for /F "tokens=* delims=_" %%a in (^"%NL%
___"One<>&|"%NL%
___"two 100%%"%NL%
___%NL%
___"three "quoted" "%NL%
___"four"%NL%
") DO (
   @echo(%%~a
)

输出

One<>&|
two 100%

three "quoted"
four

我尝试解释代码。
LF 变量包含一个换行符,NL 变量包含 ^^
这可以与百分比扩展一起使用,在行尾放置一个换行符和一个插入符号。

通常,FOR /F 将引用的文本拆分为多个标记,但仅一次。
当我插入换行符时,FOR 循环也会分成多行。
第一行和最后一行的引号只是为了创建 FOR 循环的正确语法。

任何行的前面都是 _,因为第一个字符将从前一行的多行插入符中转义,如果引号是第一个字符,它将失去转义功能。
使用 _ 分隔符,因为空格或逗号会导致 XP 出现问题(否则 XP-Bug 虚假会尝试访问垃圾文件名)。

行尾的插入符号也仅针对 XP-Bug。

当带引号的文本包含不带引号的 ,;= 字符时,XP-Bug 就会生效

for /f "tokens=*" %%a in ("a","b","c") do echo %%a

You can create a quoted block of text with a FOR /F loop, so you didn't need to escape special characters like <>|& only % have to be escaped.
This is sometimes usefull like creating a html output.

@echo off
setlocal EnableDelayedExpansion
set LF=^


REM Two empty lines are required
set ^"NL=^^^%LF%%LF%^%LF%%LF%^^"

for /F "tokens=* delims=_" %%a in (^"%NL%
___"One<>&|"%NL%
___"two 100%%"%NL%
___%NL%
___"three "quoted" "%NL%
___"four"%NL%
") DO (
   @echo(%%~a
)

Output

One<>&|
two 100%

three "quoted"
four

I try to explain the code.
The LF variable contains one newline character, the NL variable contains ^<LF><LF>^.
This can be used with the percent expansion to place ONE newline character and one caret at the line end.

Normally a FOR /F split a quoted text into multiple tokens, but only once.
As I insert newline characters the FOR-loop also splits into multiple lines.
The quote at the first and at the last line are only to create the correct syntax for the FOR-loop.

At the front of any line are _ as the first character will be escaped from the multi line caret of the previous line, and if the quote is the first character it looses the escape capability.
The _ delims is used, as spaces or commas causes problems with XP (Else the XP-Bug spurious tries to access garbage filenames).

The caret at the line end is also only against the XP-Bug.

The XP-Bug comes in effect when a quoted text contains unquoted ,;=<space> characters

for /f "tokens=*" %%a in ("a","b","c") do echo %%a
唠甜嗑 2024-07-31 13:15:24
@echo off
cls
title Drop Bomb
echo/
echo/ creating...
::                                   Creating a batchfile from within a batchfile.
echo @echo off > boom.bat
echo cls      >> boom.bat
echo color F0 >> boom.bat
echo echo/    >> boom.bat
echo echo --- B-O-O-M !!! --- >> boom.bat
echo echo/    >> boom.bat
echo pause    >> boom.bat
echo exit     >> boom.bat
::                                     Now lets set it off
start boom.bat
title That hurt my ears.
cls
echo/
echo - now look what you've done!
pause 
@echo off
cls
title Drop Bomb
echo/
echo/ creating...
::                                   Creating a batchfile from within a batchfile.
echo @echo off > boom.bat
echo cls      >> boom.bat
echo color F0 >> boom.bat
echo echo/    >> boom.bat
echo echo --- B-O-O-M !!! --- >> boom.bat
echo echo/    >> boom.bat
echo pause    >> boom.bat
echo exit     >> boom.bat
::                                     Now lets set it off
start boom.bat
title That hurt my ears.
cls
echo/
echo - now look what you've done!
pause 
写给空气的情书 2024-07-31 13:15:24

这是 ephemient 优秀解决方案的一个变体。 这允许您将多行通过管道传输到另一个程序中,而无需实际创建文本文件并将输入重定向到您的程序:

(@echo.bla
@echo.bla
) | yourprog.exe

对于一个快速、有效的示例,您可以将 yourprog.exe 替换为 more< /代码>:

(@echo.bla
@echo.bla
) | more

输出:

bla
bla

Here's a variant of ephemient's excellent solution. This allows you to pipe multiple lines into another program without actually creating a text file and input redirecting it to your program:

(@echo.bla
@echo.bla
) | yourprog.exe

For a quick, working example, you can replace yourprog.exe with more:

(@echo.bla
@echo.bla
) | more

Output:

bla
bla
饭团 2024-07-31 13:15:24

扩展 ehemient 帖子,我认为这是最好的,以下将执行一个管道:

(
    @echo.line1
    @echo.line2 %time% %os%
    @echo.
    @echo.line4
) | more

在 ehemient 的帖子中,他在开头重定向,这是一个很好的风格,但您也可以在末尾重定向:

(
    @echo.line1
    @echo.line2 %time% %os%
    @echo.
    @echo.line4
) >C:\Temp\test.txt

请注意“@echo ”。 永远不会包含在输出和“@echo”中。 其本身给出一个空行。

Expanding on ephemient post, which I feel is the best, the following will do a pipe:

(
    @echo.line1
    @echo.line2 %time% %os%
    @echo.
    @echo.line4
) | more

In ephemient's post he redirected at the beginning, which is a nice style, but you can also redirect at the end as such:

(
    @echo.line1
    @echo.line2 %time% %os%
    @echo.
    @echo.line4
) >C:\Temp\test.txt

Note that "@echo." is never included in the output and "@echo." by itself gives a blank line.

梦忆晨望 2024-07-31 13:15:24

在紧要关头,我使用以下方法(这不会削弱任何其他方法,这只是个人喜好):

我使用 for 循环遍历一组字符串:

for %%l in (
    "This is my"
    "multi-line here document"
    "that this batch file"
    "will print!"
    ) do echo.%%~l >> here.txt
  • 不需要子程序,易于记忆和执行。
  • 不需要临时文件。
  • 在批处理脚本内工作。
  • 适用于嵌入变量。
  • 但是:如果行中包含双引号,则该方法不再有效...

这是我当前正在处理的脚本中的另一个实际示例:

:intc_version:
for %%l in (
    "File         : %_SCRIPT_NAME%"
    "Version      : %_VERSION%"
    "Company      : %_COMPANY%"
    "License      : %_LICENSE%"
    "Description  : %_DESCRIPTION%"
    ""
) do echo.%%~l
exit /B 0

In a pinch, I use the following method (which doesn't diminish any other methods, this is just a personal preference):

I use a for loop through a set of strings:

for %%l in (
    "This is my"
    "multi-line here document"
    "that this batch file"
    "will print!"
    ) do echo.%%~l >> here.txt
  • Doesn't require a subroutine, easy to remember and execute.
  • Doesn't require a temporary file.
  • Works inside a batch script.
  • Works with embedded variables.
  • However: if you have double quotes in your lines, the method no longer works...

Here is another practical example from the script I'm currently working on:

:intc_version:
for %%l in (
    "File         : %_SCRIPT_NAME%"
    "Version      : %_VERSION%"
    "Company      : %_COMPANY%"
    "License      : %_LICENSE%"
    "Description  : %_DESCRIPTION%"
    ""
) do echo.%%~l
exit /B 0
变身佩奇 2024-07-31 13:15:24

在 .bat 文件中:

(
@echo.bla
@echo.bla
@echo...
) > out.txt

然后

type out.txt

会产生

bla
bla
..

一点混乱,必须将 @echo. 放在每行的开头,但它基本上可以满足您的要求:将依赖文件滚动到脚本中的能力文件本身。

还有很多其他解决方案,但所有这些都需要添加更多代码才能以更简洁的方式完成基本相同的事情。 在一种情况下,甚至需要整个其他 .bat 文件作为新的依赖项!

感谢 B.Reynolds,我的回答受到了他们的启发。

In a .bat file:

(
@echo.bla
@echo.bla
@echo...
) > out.txt

then

type out.txt

produces

bla
bla
..

A bit messier, having to put @echo. at the beginning of each line, but it does basically what you want: The ability to roll a dependancy file into the script file itself.

There are a lot of other solutions, but all of those require adding a bunch more code to do basically the same thing in a neater way. In one case, even requiring a whole other .bat file as a new dependancy!

Props to B.Reynolds, my answer was inspired by theirs.

深海里的那抹蓝 2024-07-31 13:15:24

在 Microsoft NMake makefile 中,可以按照线程所有者的要求使用真正的UNIX heredocs。 例如,这是创建文件 Deploy.sed 的显式规则:

Deploy.sed:
    type << >$@
; -*-ini-generic-*-
;
; Deploy.sed -- Self-Extracting Directives
;

[Version]
Class=IEXPRESS
SEDVersion=3
    .
    .
[Strings]
InstallPrompt=Install $(NAME)-$(VERSION).xll to your personal XLSTART directory?
DisplayLicense=H:\prj\prog\XLL\$(NAME)\README.txt
    .
    .
<<

clean:
    -erase /Q Deploy.sed

其中 << 扩展为 NMake 在执行规则时动态创建的临时文件名。 也就是说,当 Deploy.sed 不存在时。 好处是 NMake 变量也被扩展了(这里是变量 NAMEVERSION)。 将其保存为makefile。 在 makefile 目录中打开 DOS 框并使用:

> nmake Deploy.sed

创建文件,然后使用:

> nmake clean

删除它。 NMake 是所有版本的 Visual Studio C++ 的一部分,包括 Express 版本。

In a Microsoft NMake makefile one can use true UNIX heredocs, as the thread owner requested them. For example, this is an explicit rule to create a file Deploy.sed:

Deploy.sed:
    type << >$@
; -*-ini-generic-*-
;
; Deploy.sed -- Self-Extracting Directives
;

[Version]
Class=IEXPRESS
SEDVersion=3
    .
    .
[Strings]
InstallPrompt=Install $(NAME)-$(VERSION).xll to your personal XLSTART directory?
DisplayLicense=H:\prj\prog\XLL\$(NAME)\README.txt
    .
    .
<<

clean:
    -erase /Q Deploy.sed

where << expands into a temporary filename NMake creates on the fly when executing the rule. That is, when Deploy.sed does not exist. The nice thing is that NMake variables are expanded too (here the variables NAME and VERSION). Save this as makefile. Open a DOS-box in the directory of makefile and use:

> nmake Deploy.sed

to create the file, and:

> nmake clean

to remove it. NMake is part of all versions of Visual Studio C++, including the Express-editions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文