Here文檔
here文檔[1],又稱作heredoc、hereis、here-字串或here-腳本,是一種在命令行shell(如sh、csh、ksh、bash、PowerShell和zsh)和程式語言(像Perl、PHP、Python和Ruby)裡定義一個字串的方法。它可以保存文字裡面的換行或是縮排等空白字元。一些語言允許在字串里執行變量替換和命令替換。
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
END_TEXT
被用作標識符。它指定了here文檔的開始和結束ONE TWO THREE
和UNO 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
輸出:
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]。第一種方法創建一個臨時文件。第二種創建(或覆蓋)特定文件。所有的行內文件都終止於獨自成行的<<
,後邊可以添加不區分大小寫的KEEP
或NOKEEP
來決定該文件是否保留。兩個都不添加和加入NOKEEP
效果一樣[12]。
target0: dependent0
command0 <<
临时行内文件
...
<<
target1: dependent1
command1 <<
临时行内文件,但保留
...
<<KEEP
target2: dependent2
command2 <<filename2
专有行内文件,但用完后删除
...
<<NOKEEP
target3: dependent3
command3 <<filename3
专有行内文件
...
<<KEEP
參見
參考
- ^ Bash Shell 的 HERE 文档 (cat << EOF). [2012-07-16]. (原始內容存檔於2012-05-03).
- ^ unix系统下here文档的详解. [2012-07-16]. (原始內容存檔於2016-03-10).
- ^ Q. What is a here-string in Windows PowerShell?. [2012-07-16]. (原始內容存檔於2013-04-28).
- ^ Variable expansion in strings and here-strings - Windows PowerShell Blog. [2012-07-16]. (原始內容存檔於2012-06-30).
- ^ Perl operators and precedence. [2012-07-16]. (原始內容存檔於2012-07-17).
- ^ Heredoc in PHP manual. [2012-07-16]. (原始內容存檔於2012-07-12).
- ^ PHP: Strings - Manual. [2012-07-16]. (原始內容存檔於2012-07-03).
- ^ Here string in Racket Documentation. [2012-07-16]. (原始內容存檔於2011-09-03).
- ^ @ Syntax in Racket Documentation. [2012-07-16]. (原始內容存檔於2012-01-22).
- ^ 10.0 10.1 10.2 Ruby's here document mini tutorial.. [2012-07-16]. (原始內容存檔於2012-07-12).
- ^ Specifying an Inline File. [2012-07-16]. (原始內容存檔於2019-10-17).
- ^ Creating Inline File Text. [2012-07-16]. (原始內容存檔於2016-05-17).
外部連結
- Here document(頁面存檔備份,存於網際網路檔案館)。超過15種語言裡的here文檔。