Share to: share facebook share twitter share wa share telegram print page

 

ヒアドキュメント

ヒアドキュメント(別の呼び方としてヒア文字列heredocなど)は、文字列リテラルを、シェルスクリプトプログラミング言語のソースコード中に埋め込むための1つの方法である。

概要

ヒアドキュメントが構文に存在する言語として、shcshkshBashzshなどUnixシェルシェルスクリプトPerlPHPPythonRubyといったスクリプト言語があるが、これらに限られるものではない。改行ホワイトスペースなどが、書いたとおりに適用される。言語によっては、ヒアドキュメント内で変数展開やコマンド呼び出しを行える場合もある。

ヒアドキュメントのいちばん一般的な記法としては、<<の後に区切り文字となる識別子を続け、その次の行から対象となる文字列を書き、そして最初に指定した識別子だけの行で終わりとなる。Unixシェルでは、コマンドの入力を与えるのに使われることが多い。

言語ごとの実装

以下の節では、言語・環境ごとに実装の詳細を記述する。記法としては、Unixシェルと似たものが多いが、環境によっては別な構文で同等の機能を果たすことができたり、同様の構文を別な名前で呼んでいたりする例がある。また、一部の例ではシンタックスハイライトが正しく効いていないことがあるので、注意が必要である。

コマンドラインシェル

UNIXシェル

ヒアドキュメントは標準入力として扱われるため、標準入力を扱えるコマンドと組み合わせて使用する必要がある。文字列リテラルやファイルしか受け取らないようなコマンドで使用する場合は、変数を介して渡したり、bash固有の機能のProcess Substitutionを利用したりする必要がある。

以下の例では、ヒアドキュメントのテキストがtrコマンドへの入力となっている。

 $ tr a-z A-Z <<END_TEXT
 > one two three
 > uno dos tres
 > END_TEXT
 ONE TWO THREE
 UNO DOS TRES

ここではEND_TEXTを区切り文字としてあり、ヒアドキュメントの始めと終わりの位置を規定している。下2行のONE TWO THREEUNO DOS TRESは、trコマンドを実行して出力された結果である。

ヒアドキュメントを開始するところで<<-とすると、行頭のタブが無視されるようになり、シェルスクリプトのインデントを崩さずにヒアドキュメントを書けるようになる(なお、コマンドラインにタブ文字を入力するには特殊な操作が必要となる。また、以下の例はタブではなくスペースで書いてあるので、そのままコピペしても動作しない)。

 $ tr a-z A-Z <<-END_TEXT
 >         one two three
 >         uno dos tres
 > END_TEXT
 ONE TWO THREE
 UNO DOS TRES

デフォルトで、変数展開やバッククオートによるコマンド展開も行われる。

 $ cat << EOF
 > Working dir $PWD
 > EOF
 Working dir /home/user

これらは、区切り文字を引用符で囲むことで無効にできる。

 $ cat << "EOF"
 > Working dir $PWD
 > EOF
 Working dir $PWD

また、bash、ksh、zshではヒアストリングという機能も存在する。

 $ tr a-z A-Z <<<"Yes it is a string"
 YES IT IS A STRING

Windows PowerShell

Windows PowerShellでは、同様な機能がヒア文字列と呼ばれている。ヒア文字列は開始識別子の@"あるいは@'で始まり、対応する"@または'@だけの行で終わりとなり、間に入ったものすべてが文字列に含まれる。@""@のヒア文字列では変数展開が有効となるが、@''@では変数展開はなされない。ヒア文字列内の変数展開は、$xのような単純な変数、あるいは$(式)という形の式が対象となる。

以下のPowerShellコードでは、ヒア文字列が関数への引数となっている。

function ConvertTo-UpperCase($string) { $string.ToUpper() }

ConvertTo-UpperCase @'
one two three
eins zwei drei
'@

出力は以下のとおりである。

ONE TWO THREE
EINS ZWEI DREI

次の例では、二重引用符で囲まれたヒア文字列内の変数展開やコマンドの実行が行われている。

$doc, $marty = 'Dr. Emmett Brown', 'Marty McFly'
$time = [DateTime]'Friday, October 25, 1985 8:00:00 AM'
$diff = New-TimeSpan -Minutes 25
@"
$doc : Are those my clocks I hear?
$marty : Yeah! Uh, it's $($time.Hour) o'clock!
$doc : Perfect! My experiment worked! They're all exactly $($diff.Minutes) minutes slow.
$marty : Wait a minute. Wait a minute. Doc... Are you telling me that it's $(($time + $diff).ToShortTimeString())?
$doc : Precisely.
$marty : Damn! I'm late for school!
"@

出力は以下のとおりである。

Dr. Emmett Brown : Are those my clocks I hear?
Marty McFly : Yeah! Uh, it's 8 o'clock!
Dr. Emmett Brown : Perfect! My experiment worked! They're all exactly 25 minutes slow.
Marty McFly : Wait a minute. Wait a minute. Doc... Are you telling me that it's 08:25?
Dr. Emmett Brown : Precisely.
Marty McFly : Damn! I'm late for school!

一重引用符でヒア文字列を書いた場合、出力は以下のようになる。

$doc : Are those my clocks I hear?
$marty : Yeah! Uh, it's $($time.Hour) o'clock!
$doc : Perfect! My experiment worked! They're all exactly $($diff.Minutes) minutes slow.
$marty : Wait a minute. Wait a minute. Doc... Are you telling me that it's $(($time + $diff).ToShortTimeString())?
$doc : Precisely.
$marty : Damn! I'm late for school!

プログラミング言語

C#

C#では逐語的文字列リテラル (verbatim string literal) と呼ばれる。アットマーク@をプレフィックスとする。正規表現のパターン文字列の記述などにも便利である。

using System.Text.RegularExpressions;
...
string s = @"1. Item One
2. Item Two
3. Item Three";
System.Console.WriteLine(s);
string path1 = "C:\\temp\\test.txt";
string path2 = @"C:\temp\test.txt";
System.Console.WriteLine(path1);
System.Console.WriteLine(path2);
System.Console.WriteLine(path1 == path2);
System.Console.WriteLine(@"<button id=""btn1""/>"); // 逐語的文字列内にダブルクォーテーションを含める場合。
System.Console.WriteLine(Regex.Match(path1, "^.+\\.(.+)$").Success);
System.Console.WriteLine(Regex.Match(path2, @"^.+\.(.+)$").Success);

C++

C++11では、Raw文字列リテラルという機能が導入されている。Raw文字列リテラルは、Rをプレフィックスとし、続く"識別子()識別子"の間に書く。識別子としては、0文字から16文字の範囲内で、 空白文字、括弧、バックスラッシュ以外の文字ならソースコードに書ける任意の文字を使って付けることができる。

// Raw文字列内のエスケープシーケンスは展開されない。
char const *a = R"('\n'というエスケープシーケンスは改行文字を表す。)";

// 識別子を使えば、Raw文字列の中に同様な表現を入れることができる。
wchar_t const *b = LR"...(Raw文字列はR"(...)"のように書く)...";

char16_t const *b = uR"xyz(
"\u20AC"のような、Unicode文字を表すエスケープシーケンスも
Raw文字列中では展開されないので、ソースコードのエンコードとして適切なものを選んで、
"€"のように直接書く以外に、Raw文字列中に入れる方法はない。
)xyz";

D

D言語では、バージョン2.0以降で、qをプレフィックスとした文字列がサポートされている。これらの文字列では、括弧 (()、<>、{}、[]のいずれか)あるいは改行の直後に識別子を置いて終端を示す。

括弧で囲まれるものは、このようになる。

int main() {
    string list = q"[1. Item One
2. Item Two
3. Item Three]";
    writef( list );
}

識別子によるものは、このようになる。

int main() {
    string list = q"IDENT
1. Item One
2. Item Two
3. Item Three
IDENT";
    writef( list );
}

Lua

Luaでは、文字列を[[]]で挟んで文字列リテラルとすることができ、この間では改行もそのまま適用されるほか、エスケープ文字も認識されない。この文字列に他の目的で]]を含ませられないという問題があったので、 Lua 5.1では、開始地点となる2つの[の間に等号をいくつか入れて、ちょうど同じ数だけ等号を挟んだ]まで終わらせない、ということができるようになった。

local ls = [[
最初の改行は文字列の一部とはならない。
つまり、この文字列は2行である。]]

-- 等号を使った記法(ネスト可能)
local lls = [==[
この記法は、Windowsのパスを表現する場合にも便利である。
local path = [=[C:\Windows\Fonts]=]
]==]

Perl

Perlでは、ヒアドキュメントを実現する方法が何通りか存在する[1]。ヒアドキュメントを引用符で囲むと、一重引用符ならヒアドキュメント内の変数展開が行われないようになり、二重引用符では変数が展開される。またバッククオートならヒアドキュメントの中身がシェルで実行されるというように、普通の文字列を囲む引用符と同様にヒアドキュメントの機能が変化する(何も囲まなければ変数展開が行われる)。 ヒアドキュメントを終わらせる識別子は、行頭に書かなければその役割を果たさない。

なお、ヒアドキュメントは開始の識別子を書いた直後ではなく、その次の行頭から始まるので、ヒアドキュメントを含む命令の続きは識別子の後ろへ書くこととなる。

ダブルクオートで識別子を囲むと、以下のようになる。

my $sender = "Buffy the Vampire Slayer";
my $recipient = "Spike";

print <<"END";

Dear $recipient, 

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender

END

出力は以下のようになる。

Dear Spike,

I wish you to leave Sunnydale and never return.

Not Quite Love,
Buffy the Vampire Slayer

上の例をシングルクォートにすると、

print <<'END';
Dear $recipient,

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
END

出力は以下のようになる。

Dear $recipient,

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender

バッククオートで囲んだ例を以下に示す(環境依存)。

my $shell_script_stdout = <<`END`;
echo foo
echo bar
END

1行で複数のヒアドキュメントを始めることもできる。

say(<<BEGIN . "this is the middle\n" . <<END);
This is the beginning:
BEGIN
And now it is over!
END

# 以下と同じ動作となる
say("This is the beginning:\nthis is the middle\nAnd now it is over!\n");

タグ自体に空白文字を含めれば、ヒアドキュメントの終了タグをインデントすることもできる。

  say <<'  END';
Hello World
  END

PHP

PHPにもヒアドキュメントが存在するが、開始の不等号は3つとなっている。

<?php
 
$name       = "Joe Smith";
$occupation = "Programmer";
echo <<<EOF

	This is a heredoc section.
	For more information talk to $name, your local $occupation.

	Thanks!

EOF;

$toprint = <<<EOF

	Hey $name! You can actually assign the heredoc section to a variable!

EOF;
echo $toprint;

?>

出力は以下のようになる。

This is a heredoc section.
For more information talk to Joe Smith, your local Programmer.
 
Thanks!
  
Hey Joe Smith! You can actually assign the heredoc section to a variable!

ヒアドキュメントの後に続くプログラムは、ヒアドキュメントの終端IDの次の行から書くことになるが、文がヒアドキュメントの直後で終わる場合には、終端IDの直後にセミコロンを書くことができる。この場合のセミコロンを除いて、終端IDと同じ行に(空白文字を含め)余計な文字を書いてしまうと、それは終端として処理されなくなってしまう。正しい終端が見つからないと、PHPはスクリプトの最後でパースエラーとなる[2]

PHP 5.3以降では、ヒアドキュメントの識別子を一重引用符で囲むことで、内部での変数展開が行われない、Nowdocという機能も登場している[2]

$x = <<<'END'
Dear $recipient,

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
END;

さらに、同じくPHP 5.3以降では、ヒアドキュメントを開始する識別子を二重引用符で囲むこともできるようになっている。ただし、ヒアドキュメントの動作としては何も囲まない場合と同じである。

Python

Pythonでは、区切り文字として引用符を3つ続ける(''' あるいは """)形での文字列リテラルをサポートしている。これらの文字列リテラルは途中に改行を入れることができるなど、ヒアドキュメントとしての特徴を持っている。

Perlの例と同様の動作をするものをPython 3で書くと、以下のようになる。

message="""Dear {recipient},

I wish you to leave Sunnydale and never return.

Not Quite Love,
{sender}
"""
print(message.format(sender='Buffy the Vampire Slayer', recipient='Spike'))

Python 3.0未満では、printは関数ではなくキーワードとして存在している。また、Templateクラス[3]変数置換と同様の機能を果たし、引用符3つの文字列と組み合わせて使うこともできる。

R

R言語では通常の文字列リテラルに改行を入れることができるが、変数置換には対応していない。文字列をtextConnection()関数に与えることでファイル識別子として扱えるようになり、下の例のようにソースコードに埋め込んだデータを表の形で処理することができる。

str <-
"State          Population Income Illiteracy Life.Exp Murder HS.Grad Frost
Alabama              3615   3624        2.1    69.05   15.1    41.3    20
Alaska                365   6315        1.5    69.31   11.3    66.7   152
Arizona              2212   4530        1.8    70.55    7.8    58.1    15
Arkansas             2110   3378        1.9    70.66   10.1    39.9    65"
x <- read.table(textConnection(str), header=TRUE, row.names=1)

Racket

Racketでは、「ヒア文字列」として、#<<の後に終端の識別子を続ける形の書式が存在する[4]。文字列の中身には、#<<直後の行から終端の識別子だけの行の手前まで、全てのものが含まれる(終端の識別子の直前にある改行は文字列の一部とはならない)。

#lang racket

(displayln
 #<<HERESTRING
 Racketによるヒア文字列のサンプル
  * その1
  * その2
  * その3
HERESTRING
 )

出力は以下のようになる。

Racketによるヒア文字列のサンプル。
 * その1
 * その2
 * その3

ヒア文字列の中でエスケープシーケンスは処理されず、何を書いても文字通りに認識される。

#lang racket

(displayln
 #<<Racketのヒア文字列 
この文字列は複数行からなっており
またUnicodeにあるどんな文字でも使うことができる
☠、♡に①でも一向にかまわない

この次の行が終端だが終端の区切り文字にもUnicode文字ならなんでも使える
Racketのヒア文字列 
 )

上の例を実行すると、以下のように出力される。

この文字列は複数行からなっており、
またUnicodeにあるどんな文字でも使うことができる。
€や☠、♡に①でも一向にかまわない。
この次の行が終端だが、終端の区切り文字にもUnicode文字ならなんでも使える。

通常の文字列が使えるところなら、たいていヒア文字列を使うことができる。

#lang racket

(printf #<<END
Dear ~a,

Thanks for the insightful conversation ~a.

                ~a

END
        "Isaac"
        "yesterday"
        "Carl")

出力は以下のようになる。

Dear Isaac,

Thanks for the insightful conversation yesterday.

                Carl

at-exp拡張を使うことで、@記法を使えるようになる[5]。 具体的には、以下のように書ける。

#lang at-exp racket

(displayln @string-append{
  This is a long string,
  very convenient when a
  long chunk of text is
  needed.
  
  No worries about escaping
  "quotes". It's also okay
  to have λ, γ, θ, ...
  
  Embed code: @|(number->string (+ 3 4))|
  })

出力は以下のようになる。

This is a long string,
very convenient when a
long chunk of text is
needed.

No worries about escaping
"quotes". It's also okay
to have λ, γ, θ, ...

 Embed code: 7

@記法は文字列限定の機能ではなく、他の場面でも使うことができる。

Ruby

Rubyにもヒアドキュメントが存在し、それは <<識別子 を含む行の次の行から識別子だけの行の直前までを文字列とする行指向のリテラルである[6]。例えば、

print <<EOS      # 識別子 EOS までがリテラルになる
  the string
  next line
EOS

のように書くと、以下と同じように出力される。

print "  the string\n  next line\n"

ヒアドキュメントでは、開始ラベル<<識別子が文法要素としての式に当たる。つまり、開始ラベルを使ってヒアドキュメント全体を引数に渡したりレシーバにしたりすることができる。

# 式の中に開始ラベルを書く
# method の第二引数には "    ヒアドキュメント\n" が渡される
method(arg1, <<LABEL, arg2)
    ヒアドキュメント
LABEL

# ヒアドキュメントをレシーバにメソッドを呼ぶ
p  <<LABEL.upcase
the lower case string
LABEL

# => "THE LOWER CASE STRING\n"

開始ラベルの次の行は常にヒアドキュメントとなる。

printf('%s%d',
       <<EOS,
       3055 * 2 / 5)   # <- この行はヒアドキュメントに含まれてしまう
This line is a here document.
EOS

開始ラベルを<<-識別子のように - を付けて書くことで終端行をインデントすることができるが、これ以外では、終端行に、余分な空白やコメントさえも書くことはできない。

if need_define_foo
  eval <<-EOS   # '<<-' を使うと……
    def foo
      print "foo\n"
    end
  EOS
  #↑終端行をインデントできます。
end

Ruby 2.3以降では、開始ラベルを <<~識別子 のように ~ を付けて書くことで、以下のようなヒアドキュメントを書くことができる。

    expected_result = <<~SQUIGGLY_HEREDOC
      This would contain specially formatted text.

      That might span many lines
    SQUIGGLY_HEREDOC

最もインデントが少ない行を基準にして、全ての行の先頭から空白が取り除かれる。インデントの深さを決定するために主にタブやスペースで構成された行は無視される。ただし、エスケープされたタブやスペースは通常の文字と同じように扱われる。

また、一行に複数のヒアドキュメントを書くことも可能である。

print <<FIRST, <<SECOND
   これは一つめのヒアドキュメントです。
   まだ一つめです。
FIRST
   この行からは二つめのヒアドキュメントです。
   この行で終わります。
SECOND

開始ラベル <<識別子識別子 を引用符で囲むことで、ヒアドキュメントとなる文字列リテラルの性質は対応する文字列リテラルと同じ扱いとなる。つまり、一重引用符で囲めばヒアドキュメントは変数展開やエスケープシーケンスの処理も行われない。また、二重引用符であれば通常のヒアドキュメントと同様、変数展開やエスケープシーケンスが処理される。バッククォートで囲めば、ヒアドキュメントの内容がシェルで実行される。

# バックスラッシュ記法、式展開が有効
print <<"EOS"
The price is #{$price}.
EOS

# 上のものと同じ結果
print <<EOS
The price is #{$price}.
EOS

# 式展開はできない
print <<'EOS'
The price is #{$price}.
EOS

# コマンドを実行
print <<`EOC`
date
diff test.c.org test.c
EOC

Tcl

Tclには、通常の文字列中に改行を入れることができるため、「ヒアドキュメント」としての特別な文法構造は存在しない。中括弧で区切った場合、変数置換は行われない。

puts {
Grocery list
----
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
}

二重引用符で区切った場合、変数置換が実行時に行われる。

set sender "Buffy the Vampire Slayer"
set recipient "Spike"

puts "
Dear $recipient, 

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
"

中括弧区切りの場合、エスケープしない中括弧については左右のバランスがとれている必要がある。二重引用符区切りの場合は中括弧のバランスは無関係となるが、バックスラッシュ・ドル記号・角カッコにはそれぞれに特別な機能が存在する。また、エスケープされていない二重引用符が現れたところで文字列が終了する。

なお、上の2つの例では、いずれも(書いてあるとおりに)文字列の最初と最後の文字が改行文字となる。もしもこれが邪魔なら、string trimで取り除くことができる。

puts [string trim "
Dear $recipient, 

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
" \n]

同様に、string mapをインデントの解除や中括弧のエスケープなど、書き方を工夫する上で使うことができる。

プログラミング言語以外

Microsoft NMAKE

Microsoft NMAKEでは、ヒアドキュメントと同様な概念として「インラインファイル」が存在する[7]。インラインファイルは<<(一時ファイル)あるいは<<ファイル名(指定のファイルを作成/上書き)という書式で始まる。 そして、<<だけの行で終わるが、生成したファイルを保存するかのオプションを指定する場合は<<KEEPあるいは<<NOKEEP(大文字小文字は問わない)のようになる。

target0: dependent0
    command0 <<
一時インラインファイル
...
<<

target1: dependent1
    command1 <<
一時ファイルだけど処理後にも残るインラインファイル
...
<<KEEP

target2: dependent2
    command2 <<filename2
名前付きだけど処理後に削除されるインラインファイル
...
<<NOKEEP

target3: dependent3
    command3 <<filename3
名前付きインラインファイル
...
<<KEEP

Data URI Scheme

たいていのブラウザで、「data:」で始まるURIとして別なドキュメントを埋め込むというData URI schemeが実装されている。

Job Control Language

Job Control Language (JCL) では、使用するデータを指定するDD文において「DD *」を指定すると、以降は80バイトのカードイメージのデータと解釈される。このデータは、1カラム目から2カラム目に「/*」と記述するか、次のJCLステートメント(1カラム目から2カラム目が「//」で始まる行)の記述が始まることで終了する。

脚注

  1. ^ Perl operators and precedence
  2. ^ a b 文字列(PHPマニュアル)
  3. ^ PEP 292 (Simpler String Substitutions)
  4. ^ Here string in Racket Documentation
  5. ^ @ Syntax in Racket Documentation
  6. ^ ヒアドキュメント (行指向文字列リテラル) Ruby2.6.0リファレンスマニュアル(2019年1月9日閲覧)。
  7. ^ メイクファイルのインライン ファイル Microsoft Developer Network(2013年7月24日閲覧)。

関連項目

Kembali kehalaman sebelumnya