Heredoc

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

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

## 實例

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

### 命令行 shell

#### Unix shell

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

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

在 << 後面添加一個減號，可以使 TAB 字元被忽略。這允許在 shell 腳本中縮排 here 文檔而不改變它們的值。（注意在命令行上可能會需要輸入 _Ctrl-v TAB_ 來真正地輸入一個制表符。下邊的例子用空格模擬制表符；不要複製粘貼。）

默認地，會進行變量替換和命令替換：

這可以通過使用引號包裹標識符來禁用。可以使用單引號或雙引號：

bash，ksh 或 zsh 中也可以用 here - 字串：

#### Windows 命令行

等價的代碼目前沒有找到。下列代碼較為有用。

#### Windows PowerShell

在 Windows PowerShell 里，here 文檔表示的是 **here - 字串**。一個 here - 字串是由 `@"` 或 `@'` 開始，由獨立成行的 `"@` 或 `'@` 結束的字串。所有在開始符號和結束符號之間的字符都被當做字面的字串。

使用雙引號引起來的 here - 字串允許變量替換，而單引號不行。

變量替換隻發生於簡單變量（如 `$x`，但不是 `$x.y` 或 `$x[0]`）。

可以將命令放進 `$()` 中來獲取執行結果。

在如下的 PowerShell 的代碼中，文字使用 here - 字串傳遞給一個函數。這個函數 `ConvertTo-UpperCase` 定義如下：

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

```
PS> ConvertTo-UpperCase @'
>> one two three
>> eins zwei drei
>> '@
>>
ONE TWO THREE
EINS ZWEI DREI
```

下邊是一個證明了雙引號的 here - 字串里的變量替換和命令替換的例子：

```
$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!
```

如果用單引號的 here - 字串代替，輸出看起來會像這樣：

```
$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++11 引入了原始字面字串。原始字面字串的前綴有一個「R」，以 **`"分隔符(`** 開始，以 **`)分隔符"`** 結束。分隔符可以是 0 到 16 字符長，可以包括簡單的字符，除開空格，括號與反斜槓。

#### D 語言

從 2.0 版本開始，D 語言支持用「q」引導的 here - 字串。這些字串以一個括號（<>，[]，()，{}）或者單獨成行的標識符開始。

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

使用標識符：

#### Lua

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

#### Perl

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

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

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

輸出：

```
Dear Spike,

I wish you to leave Sunnydale and never return.

Not Quite Love,
Buffy the Vampire Slayer
```

這是使用單引號的例子：

輸出：

```
Dear $recipient,

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
```

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

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

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

#### PHP

輸出：

```
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_：

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

#### Python

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

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

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

#### R

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

#### Racket

Racket 的 here 字串以 `#<<` 開始，緊跟定義字串終止的標識符。

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

輸出：

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

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

輸出：

```
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 字串可以像一般的字串一樣使用：

輸出：

```
Dear Isaac,

Thanks for the insightful conversation yesterday.

                Carl
```

一個有趣的替代方案是使用語言的擴展 `at-exp` 來寫 @- 表達式。

它們看起來像這樣：

輸出：

```
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 文檔顯示了一個列表：

輸出：

```
Grocery list
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*

* Organic
```

寫入文件：

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

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

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

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

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

#### Tcl

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

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

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

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

### 其它

#### 微軟 NMAKE

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

## 參見

* tr 關於 tr 程式的說明
* 管道 (Unix) 關於管道的信息。

## 參考

## 外部連結

* Here document（頁面存檔備份，存於網際網路檔案館）。超過 15 種語言裡的 here 文檔。