Here文檔

here文檔，又稱作heredoc、hereis、here-字串或here-腳本，是一種在命令行shell（如sh、csh、ksh、bash、PowerShell和zsh）和程序語言（像Perl、PHP、Python和Ruby）裡定義一個字串的方法。它可以保存文字裡面的換行或是縮排等空白字元。一些語言允許在字串里執行變量替換和命令替換。

here文檔最通用的語法是`&lt;&lt;`緊跟一個標識符，從下一行開始是想要引用的文字，然後再在單獨的一行用相同的標識符關閉。在Unix shell里，here文檔通常用於給命令提供輸入內容。

## 實例

以下幾節提供了不同語言和環境中的例子。

### 命令行 shell

#### Unix shell

在以下幾個例子中，文字用here文檔傳遞給tr命令。

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

`END_TEXT`被用作標識符。它指定了here文檔的開始和結束`ONE TWO THREE`和`UNO DOS TRES`是執行後`tr`的輸出。

在，{}）或者單獨成行的標識符開始。

下列D代碼展示了使用括號和標識符的here-字串。

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

使用標識符：

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

#### Lua

Lua使用``和`]]`定义字面字串，字面字串中的换行会原样保留，不允许含有转义字符。这不便放置长的注释（`--注釋`）和一些字串（`x = a[b[c]]`）。所以在版本5.1時，Lua添加了一個新語法：起始的兩個括號中間可以加入任意多的等號，並且只有相同的等號數字才能關閉字串。

```Lua
local ls = [[
Initial newline isn't part of the string.
Two lines.]]
local lls = [==[
This notation can be used for Windows paths: 
local path = [=[C:\Windows\Fonts]=]
]==]
```

#### Perl

在Perl里有許多不同的方法使用here文檔。在here文檔的標籤名前後加括號的效果和一般的字面字串效果是一樣的：標籤前後加雙引號允許變量擴展，單引號則不行，不加引號的和加雙引號的效果一樣。加反引號將會把here文檔當做shell腳本執行，並獲取輸出。需要保證結束標籤必須在一行的開始，不然這個標籤不會被直譯器認出。

注意here文檔不是從標籤開始的，而是從下一行開始的。所以包含標籤的語句將會在標籤後繼續。

這是一個使用雙引號的例子：

```Perl
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
```

這是使用單引號的例子：

```Perl
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
```

另外一個使用反引號的例子（可能不具有可移植性）：

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

可以在同一行上開始多個here文檔：

```Perl
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!");
```

標籤本身可以使用空格，這允許here文檔不會破壞縮進。

```Perl
  say <<'  END';
Hello World
  END
```

#### PHP

```PHP
<?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!
```

包含關閉標識符的行不得包含除了（可選的）分號的任何其他字符。不然它就不會被識別為關閉標識符，PHP就會繼續尋找一個。如果沒有找到關閉標識符，分析錯誤會發生在最後一行。

在PHP 5.3和以後的版本中，就像Perl一樣，可以用單引號包裹標識符阻止變量擴展；這叫作nowdoc：

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

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
END;
```

在PHP5.3和以後的版本中，也可以用雙引號包裹標識符，像Perl一樣，和不用引號的效果一樣。

#### Python

Python支持使用三個連續單引號或雙引號的字面字串（如`'''`或`"""`）。這些字面字串可以跨越多行，支持here文檔的功能。

一個簡單的Python3兼容的例子給出像上邊第一個Perl例子一樣：

```Python
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'))
```

在Python3.0以前的版本中，用print關鍵字代替print函數。

#### R

R語言在字串里使用空格，包括換行。不執行變量替換。字串可以用`textConnection()`函數轉化為文件描述符。例如，以下代碼將一個嵌入源碼的數據錶轉化為一個數據框架變量：

```rsplus
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的here字串以`#<<`開始，緊跟定義字串終止的標識符。

字串的內容包括所有的在`#<<`一行和僅包括定義了的終止符的那一行。即：字串的內容開始於`#<<`後的新行，結束於終止符之前的一行。

```Scheme
#lang racket

(displayln
 #<<HERESTRING
This is a simple here string in Racket.
  * One
  * Two
  * Three
HERESTRING
 )
```

輸出：

```
This is a simple here string in Racket.
  * One
  * Two
  * Three
```

here字串中的轉義字符不被識別；字串（和終止符）中所有的字符都會保持原樣。

```Scheme
#lang racket

(displayln
 #<<A here string in Racket ☺
This string spans for multiple lines
and can contain any Unicode symbol.
So things like λ, ☠, α, β, are all fine.

In the next line comes the terminator. It can contain any Unicode symbol as well, even spaces and smileys!
A here string in Racket ☺
 )
```

輸出：

```
This string spans for multiple lines
and can contain any Unicode symbol.
So things like λ, ☠, α, β, are all fine.

In the next line comes the terminator. It can contain any Unicode symbol as well, even spaces and smileys!
```

here字串可以像一般的字串一樣使用：

```Scheme
#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`來寫@-表達式。

它們看起來像這樣：

```Scheme
#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代碼用here文檔顯示了一個列表：

```Ruby
puts <<GROCERY_LIST
Grocery list

1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
GROCERY_LIST
```

輸出：

```
Grocery list

1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*

* Organic
```

寫入文件：

```Ruby
File::open("grocery-list", "w") do |f|
  f << <<GROCERY_LIST
Grocery list

1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
GROCERY_LIST
end
```

Ruby也允許標識符不起始於行首，需要以`<<-`起始here文檔。

另外，Ruby對待here文檔就像一個雙引號括起來的字串，即可以使用#{}來進行代碼替換。

以下例子展示了這2個特性：

```Ruby
now = Time.now
puts <<-EOF
  It's #{now.hour} o'clock John, where are your kids?
  EOF
```

但是如果標識符是用單引號引起來的，則當做單引號內的字串對待。

類似於Perl，Ruby允許在一行內開始多個here文檔：

```Ruby
puts <<BEGIN + "<--- middle --->\n" + <<END
This is the beginning:
BEGIN
And now it is over!
END

# 以上相等于：
puts "This is the beginning:\n<--- middle --->\nAnd now it is over!"
```

#### Tcl

Tcl沒有為here文檔設立特殊的語法，因為一般的字串語法已經允許嵌入換行和保持縮進。用括號括起來的字串，沒有擴展：

```text
puts {
Grocery list

1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
}
```

用引號括起來的字串在運行時執行替換：

```Tcl
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
"
```

在括號包裹的字串里，起始括號和終止括號數量應該一樣多。在引號包裹的字串里，括號可以不一樣多，但是反斜槓，美元符號和括號都會被替換，此時第一個沒有被轉義的雙引號會結束字串。

需要注意的一點是：上邊的字串的第一個和最後一個字符都是換行。`string trim`可以用來刪除頭尾空行：

```Tcl
puts [string trim "
Dear $recipient, 

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
" \n]
```

### 其它

#### 微軟 NMAKE

在微軟NMAKE里，here文檔是行內的文件。行內文件以`&lt;&lt;`或`&lt;&lt;文件名`開始。第一種方法創建一個臨時文件。第二種創建（或覆蓋）特定文件。所有的行內文件都終止於獨自成行的`&lt;&lt;`，後邊可以添加不區分大小寫的`KEEP`或`NOKEEP`來決定該文件是否保留。兩個都不添加和加入`NOKEEP`效果一樣。

```make
target0: dependent0
    command0 <<
临时行内文件
...
<<

target1: dependent1
    command1 <<
临时行内文件，但保留
...
<<KEEP

target2: dependent2
    command2 <<filename2
专有行内文件，但用完后删除
...
<<NOKEEP

target3: dependent3
    command3 <<filename3
专有行内文件
...
<<KEEP
```

## 參見

- tr (Unix)關於tr程序的說明。
- 管道 (Unix)關於管道的資訊。

## 外部連結

- Here document。超過15種語言裡的here文檔。