here文檔[1],又稱作heredochereishere-字串here-腳本,是一種在命令行shell(如shcshkshbashPowerShellzsh)和程序語言(像PerlPHPPythonRuby)裡定義一個字串的方法。它可以保存文字裡面的換行或是縮排等空白字元。一些語言允許在字串里執行變量替換和命令替換

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

實例

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

命令行 shell

Unix shell

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

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

[2]

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

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

 $ 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中也可以用here-字串:

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

Windows 命令行

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

set GREETING=Hello
echo %GREETING%
cmd /k 
  echo %GREETING%
  set GREETING=Goodbye
  echo %GREETING% 
exit
echo %GREETING%

C:\>
C:\>set GREETING=Hello

C:\>echo %GREETING%
Hello

C:\>cmd /k
C:\>  echo %GREETING%
Hello

C:\>  set GREETING=Goodbye

C:\>  echo %GREETING%
Goodbye

C:\>exit

C:\>echo %GREETING%
Hello

C:\>

Windows PowerShell

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

使用雙引號引起來的here-字串允許變量替換,而單引號不行[4]

變量替換隻發生於簡單變量(如$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字符長,可以包括簡單的字符,除開空格,括號與反斜槓。

char const *a = R"(The escape sequence '\n' represents a newline character.)";

wchar_t const *b = LR"...(Raw strings look like R"(...)")...";

char16_t const *b = uR"xyz(
Universal character names such as "\u5367\u864E\u85CF\u3863" are not
processed in raw string literals. Therefore the above can be written
as "臥虎藏龍" in a raw string literal, but only if the source character
set contains those characters.
)xyz";

D語言

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

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

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使用[[]]定義字面字串,字面字串中的換行會原樣保留,不允許含有轉義字符。這不便放置長的注釋(--[[注释]])和一些字串(x = a[b[c]])。所以在版本5.1時,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文檔[5]。在here文檔的標籤名前後加括號的效果和一般的字面字串效果是一樣的:標籤前後加雙引號允許變量擴展,單引號則不行,不加引號的和加雙引號的效果一樣。加反引號將會把here文檔當做shell腳本執行,並獲取輸出。需要保證結束標籤必須在一行的開始,不然這個標籤不會被直譯器認出。

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

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

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

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

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文檔不會破壞縮進

  say <<'  END';
Hello World
  END

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就會繼續尋找一個。如果沒有找到關閉標識符,分析錯誤會發生在最後一行[6]

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

$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例子一樣:

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()函數轉化為文件描述符。例如,以下代碼將一個嵌入源碼的數據錶轉化為一個數據框架變量:

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字串以#<<開始,緊跟定義字串終止的標識符[8]

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

#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字串中的轉義字符不被識別;字串(和終止符)中所有的字符都會保持原樣。

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

#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來寫@-表達式[9]

它們看起來像這樣:

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

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

[10]

輸出:

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

* Organic

寫入文件:

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個特性:

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

但是如果標識符是用單引號引起來的,則當做單引號內的字串對待[10]

類似於Perl,Ruby允許在一行內開始多個here文檔[10]

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文檔設立特殊的語法,因為一般的字串語法已經允許嵌入換行和保持縮進。用括號括起來的字串,沒有擴展:

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
"

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

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

puts [string trim "
Dear $recipient, 

I wish you to leave Sunnydale and never return.

Not Quite Love,
$sender
" \n]

其它

微軟 NMAKE

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

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

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

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

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

參見

參考

  1. ^ Bash Shell 的 HERE 文档 (cat << EOF). [2012-07-16]. (原始內容存檔於2012-05-03). 
  2. ^ unix系统下here文档的详解. [2012-07-16]. (原始內容存檔於2016-03-10). 
  3. ^ Q. What is a here-string in Windows PowerShell?. [2012-07-16]. (原始內容存檔於2013-04-28). 
  4. ^ Variable expansion in strings and here-strings - Windows PowerShell Blog. [2012-07-16]. (原始內容存檔於2012-06-30). 
  5. ^ Perl operators and precedence. [2012-07-16]. (原始內容存檔於2012-07-17). 
  6. ^ Heredoc in PHP manual. [2012-07-16]. (原始內容存檔於2012-07-12). 
  7. ^ PHP: Strings - Manual. [2012-07-16]. (原始內容存檔於2012-07-03). 
  8. ^ Here string in Racket Documentation. [2012-07-16]. (原始內容存檔於2011-09-03). 
  9. ^ @ Syntax in Racket Documentation. [2012-07-16]. (原始內容存檔於2012-01-22). 
  10. ^ 10.0 10.1 10.2 Ruby's here document mini tutorial.. [2012-07-16]. (原始內容存檔於2012-07-12). 
  11. ^ Specifying an Inline File. [2012-07-16]. (原始內容存檔於2019-10-17). 
  12. ^ Creating Inline File Text. [2012-07-16]. (原始內容存檔於2016-05-17). 

外部連結