40

UNIXシェルのヒアドキュメントと同様の方法で複数行の文字列をバッチで指定する方法はありますか? 次のようなもの:

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

アイデアは、テンプレート ファイルからカスタマイズされたファイルを作成することです。

4

19 に答える 19

34

私が知る限りではありません。

私が知っている最も近いのは

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

(@コマンド シェル自体が実行中のコマンドを出力しecho.ないようにし、スペースで行を開始できるようにします。)

于 2009-06-18T22:10:27.967 に答える
25

はい、非常に可能です。^ はリテラルのエスケープ文字で、改行の前に置くだけです。この例では、追加の改行も入れて、ファイルに適切に出力されるようにします。

@echo off
echo foo ^

this is ^

a multiline ^

echo > out.txt

出力:

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

E:\>
于 2009-06-19T06:12:23.667 に答える
10
@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>
于 2011-08-18T09:53:06.680 に答える
9

DosTips で、siberia-manは、誤った GOTO ステートメントの驚くべき動作のデモンストレーションを(goto) 2>nul. その後、Aacini と jeb は、奇妙な動作に関するいくつかの興味深い発見を文書化しました。基本的には のように動作EXIT /Bしますが、呼び出されたルーチン内の連結されたコマンドを親呼び出し元のコンテキストで実行できることを除きます。

以下は、重要なポイントのほとんどを示す簡単なスクリプトです。

@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トリックを 2 回使用します。

  • 最初(GOTO) 2>nulに呼び出し元に戻るために使用するので、呼び出し元のスクリプトへの完全なパスを取得して、PrintHere がどのファイルから読み取るかを知ることができます。次に、PrintHere をもう一度呼び出します。

  • 2回目(GOTO) 2>nulは、呼び出し元に戻り、終了ラベルにGOTOして、ヒアドキュメントテキストが実行されないようにします。

:start注 - 以下のスクリプトには、ラベルのすぐ下のタブの定義にタブ文字 (0x09) が含まれています。一部のブラウザでは、タブの表示とコピーがうまくいかない場合があります。別の方法として、ドロップボックスから PrintHere.bat.txt をダウンロードして、名前を PrintHere.bat に変更することもできます。

私は最初にPrintHere.bat を DosTipsに投稿しました。ここでは、将来の開発を追跡できます。

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

- 出力 -

    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

- 出力 -

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
于 2015-07-04T04:29:26.267 に答える
5

パラメータ付きのマクロを使用すると、「ヒアドキュメント」をより簡単な方法で記述できます。

@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
于 2014-04-14T03:21:33.653 に答える
3

https://stackoverflow.com/a/15032476/3627676の rojo の投稿を参照

間違いなく、彼の解決策は私がしばらくの間求めていたものです (もちろん、これに似たものを実装しようとすることもできますが、怠惰は進歩を進めます:))。追加したいことの 1 つは、元のコードに対するマイナーな改善です。行末にファイルへのリダイレクトを書いた方が良いと思いました。この場合、ヒアドキュメントの開始行をより厳密にし、その分析をより単純にすることができます。

@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 : ヒアドキュメントの開始のチェックと実行の改善:

  • 重要なコマンドは、call :heredoc LABELまたはのみcall :heredoc :LABELです。したがって、ヒアドキュメントの内容を印刷した後、別のラベル、スクリプトの終わり、または実行にジャンプすることができますexit /b
  • 未使用で不要なコマンド goto :next2 を削除

更新 2 :

  • 内部の区切り文字for( ) > & | TAB , ; = SPACE
  • /I追加されたスイッチif

更新 3 :

次のリンクから、スタンドアロン スクリプトのフル バージョンを見つけることができます (スクリプトへの埋め込みが可能です) https://github.com/ildar-shaimordanov/cmd.scripts/blob/master/heredoc.bat

于 2015-03-29T13:50:40.537 に答える
3

@ジェブ

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
于 2011-08-18T10:45:59.613 に答える
2

<>|&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変数には1つの改行文字が含まれ、NL変数には。が含まれます^<LF><LF>^
これをパーセント拡張とともに使用して、行末に1つの改行文字と1つのカレットを配置できます。

通常、FOR / Fは、引用符で囲まれたテキストを複数のトークンに分割しますが、1回だけです。
改行文字を挿入すると、FORループも複数の行に分割されます。
最初と最後の行の引用符は、FORループの正しい構文を作成するためだけのものです。

行の先頭には_、最初の文字が前の行の複数行のキャレットからエスケープされ、引用符が最初の文字である場合、エスケープ機能が失われます。スペースまたはコンマがXPで問題を引き起こすため、delimsが使用されます(そうでない場合、XP-Bugスプリアスはガベージファイル名にアクセスしようとします)
_

ラインエンドのカレットもXP-Bugに対してのみです。

XP-Bugは、引用符で囲まれたテキストに引用符で囲まれていない文字が含まれている場合に有効になり,;=<space>ます

for /f "tokens=*" %%a in ("a","b","c") do echo %%a
于 2011-08-18T02:59:32.017 に答える
1

Microsoft NMake makefileでは、スレッド所有者が要求したとおり、真のUNIX ヒアドキュメントを使用できます。たとえば、これはファイル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 は、Express エディションを含む、Visual Studio C++ のすべてのバージョンの一部です。

于 2011-08-16T11:23:22.527 に答える
-1

これはさらに簡単で、cat << EOF > out.txt によく似ています。

C:\>copy con out.txt
これが最初のテキスト行です。
これが私のテキストの最後の行です。
^Z
1 個のファイルがコピーされました。

出力は次のようになります。

C:\>type out.txt
これがテキストの最初の行です。
これが私のテキストの最後の行です。

(con + out.txt をコピーし、入力内容を入力してから Ctrl-Z を押すと、ファイルがコピーされます)

COPY CON は「コンソールからのコピー」を意味します (ユーザー入力を受け入れます)

于 2009-08-20T03:25:51.313 に答える
-5
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

**最後に空行が追加されますが、copy con メソッドを使用するだけで簡単に解決できます。

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 を入力する場所に注意してください。

于 2015-11-17T18:53:35.190 に答える