HereDoc 全名叫作 Here Document
,中文能夠稱之爲 嵌入文檔
。對它的叫法實際上不少,here文檔,hereis,here-string 等等都是它。html
嵌入文檔是 Shell I/O 重定向功能的一種替代。咱們已經知道 Shell 的 I/O 重定向是一種文件句柄的傳輸。例如:程序員
COMMAND 1>/tmp/1.lst 2>&1
複製代碼
將命令的標準輸出爲一個文件,同時也將錯誤輸出到同一個文件中。shell
而下例:bash
cat /etc/passwd | grep '^admin:'
複製代碼
則經過管道將前一命令的輸出當作後一命令的標準輸入。app
Here Document 就是標準輸入的一種替代品。它使得腳本開發人員能夠沒必要使用臨時文件來構建輸入信息,而是直接就地生產出一個文件並用做命令的標準輸入。通常來講其格式是這樣的:ssh
COMMAND <<IDENT
...
IDENT
複製代碼
在這裏,<<
是引導標記,IDENT
是一個限定符,由開發人員自行選定,兩個 IDENT
限定符之間的內容將被當作是一個文件並用做 COMMAND 的標準輸入。例如echo大段文本時,咱們可使用 cat file
的語法:socket
cat <<EOF
SOME TEXT
HERE
!
EOF
複製代碼
此例中,咱們使用 EOF
短語做爲限定符。編輯器
Here Document 是能夠嵌套的,只要雙層分別使用不一樣的
IDENT
限定符且保證正確的嵌套關係便可:idessh user@host <<EOT ls -la --color cat <<EOF from a remote host EOF [ -f /tmp/1.tmp ] && rm -f /tmp/1.tmp EOT 複製代碼
看起來有點怪?其實還好啦。函數
實際上,限定符能夠取得很是長,只要是字母開頭且只包含字母和數字(一般,下劃線和短橫線也是有效的,不過根據 bash 的版本不一樣、宿主實現的不一樣,可能會有必定的出入)便可。
abs
中有一個例子,節選以下:
wall <<zzz23EndOfMessagezzz23
fdjsldj
fdsjlfdsjfdls
zzz23EndOfMessagezzz23
複製代碼
這是正確有效的,不過這個其實更怪一些。
在 bash, ksh 和 zsh 中,還可使用 Here String:
$ tr a-z A-Z <<<"Yes it is a string"
YES IT IS A STRING
複製代碼
此時也可使用變量:
$ tr a-z A-Z <<<"$var"
複製代碼
有沒有可能將HEREDOC存儲爲一個文件?顯然是能夠:
cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
This line is indented.
EOF
複製代碼
你能夠注意到這種寫法不一樣於常常性的寫法:
cat >/tmp/1<<EOF
s
EOF
複製代碼
但二者都是對的。
但當須要 root 權限時,'>' 並不能很好地工做,此時須要 sudo tee
上場:
cat <<EOF | sudo tee /opt/1.log
s
EOF
複製代碼
標準輸出的重定向,還能夠經過子 shell 的方式來構造:
(echo '# BEGIN OF FILE | FROM'
cat <<- _EOF_
LogFile /var/log/clamd.log
LogTime yes
DatabaseDirectory /var/lib/clamav
LocalSocket /tmp/clamd.socket
TCPAddr 127.0.0.1
SelfCheck 1020
ScanPDF yes
_EOF_
echo '# END OF FILE'
) > /etc/clamd.conf
複製代碼
這個例子只是一個示意,由於實際上該例子用不着那麼麻煩,單個 cat HEREDOC
足夠達到目的了,也不須要開子 shell 那麼重。
cat <<EOF
的少見的變形let() {
res=$(cat)
}
let <<'EOF'
...
EOF
複製代碼
元芳,你怎麼看?
還能夠寫做這樣:
let() {
eval "$1"'=$(cat)'
}
let res<<'EOF'
...
EOF
複製代碼
固然,其實它和單行指令是等效的:
{ res=$(cat); } <<'EOF'
...
EOF
複製代碼
{}
是語句塊,而不是子shell,於是更省力。根據具體狀況來使用它,有時候你但願子 shell 的變量無污染的效果,或者別的期待,那你就使用 ()
。
variable=$(cat <<SETVAR
This variable
runs over multiple lines.
SETVAR
)
echo "$variable"
複製代碼
示例展現了在 $()
語法中能夠隨意地嵌入 HEREDOC。
若是你只是須要爲變量用 HEREDOC 賦值,read var
一般是更好的主意:
read i <<!
Hi
!
echo $i # Hi
複製代碼
GetPersonalData () {
read firstname
read lastname
read address
read city
read state
read zipcode
} # This certainly appears to be an interactive function, but . . .
# Supply input to the above function.
GetPersonalData <<RECORD001
Bozo
Bozeman
2726 Nondescript Dr.
Bozeman
MT
21226
RECORD001
echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo
複製代碼
能夠看到,只要函數可以接收標準輸入,那就能夠將 HEREDOC 套用上去。
#!/bin/bash
# filename: aa.sh
: <<TESTVARIABLES
${UX?}, ${HOSTNAME?} | ${USER?} | ${MAIL?} # Print error message if one of the variables not set.
TESTVARIABLES
exit $?
複製代碼
這個示例中,若是變量沒有被設置,則會產生一條錯誤消息,而該 HEREDOC 的用處其實是用來展開要確認的變量,HEREDOC產生的結果做爲 :
的標準輸入,實際上被忽略了,最後只有 HEREDOC 展開的狀態碼被返回,用以確認是否是有某個變量還沒有被設置:
$ ./aa; echo $?
./aa: line 3: UX: parameter null or not set
1
複製代碼
因爲 UX 變量缺失,所以調用的結果是一行錯誤輸出,以及調用的退出碼爲 1,也就是 false 的意思。
:
是true
命令的同義詞。就好像.
是source
命令的同義詞同樣。
除了用來一次性檢測一大批變量有否被賦值的效果以外,匿名的 HEREDOC 也經常被用做大段的註釋。
cat >/dev/null<<COMMENT
...
COMMENT
: <<COMMENT
...
COMMENT
複製代碼
這些寫法均可以,看你的我的喜愛。Bash 程序員的通常風格是能省鍵盤就省鍵盤。但有時候他們也喜歡能炫就炫:
:<<-!
____ _ ____ _
/ ___| ___ ___ __| | / ___| ___ ___ __| |
| | _ / _ \ / _ \ / _` | | | _ / _ \ / _ \ / _` |
| |_| | (_) | (_) | (_| | | |_| | (_) | (_) | (_| |
\____|\___/ \___/ \__,_| \____|\___/ \___/ \__,_|
____ _ _
/ ___|| |_ _ _ __| |_ _
\___ \| __| | | |/ _` | | | |
___) | |_| |_| | (_| | |_| |
|____/ \__|\__,_|\__,_|\__, |
|___/
!
複製代碼
當咱們須要讀一個csv文件時,咱們會用到 while read 結構。
將 csv 文件改成 HEREDOC:
while read pass port user ip files directs; do
sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
PASS PORT USER IP FILES DIRECTS
. . . . . .
. . . . . .
. . . . . .
PASS PORT USER IP FILES DIRECTS
____HERE
複製代碼
因爲不一樣格式的 CSV 的處理並不是本文的主題,所以這裏再也不展開討論具體狀況了。
對於 while … done
來講,標準輸入的重定向應該寫在 done
以後。一樣的,for … do … done
也是如此,until … done
也是如此。
while
while [ "$name" != Smith ] # Why is variable $name in quotes?
do
read name # Reads from $Filename, rather than stdin.
echo $name
let "count += 1"
done <"$Filename" # Redirects stdin to file $Filename.
複製代碼
until
until [ "$name" = Smith ] # Change != to =.
do
read name # Reads from $Filename, rather than stdin.
echo $name
done <"$Filename" # Redirects stdin to file $Filename.
複製代碼
for
for name in `seq $line_count` # Recall that "seq" prints sequence of numbers.
# while [ "$name" != Smith ] -- more complicated than a "while" loop --
do
read name # Reads from $Filename, rather than stdin.
echo $name
if [ "$name" = Smith ] # Need all this extra baggage here.
then
break
fi
done <"$Filename" # Redirects stdin to file $Filename.
複製代碼
<<-IDENT
是新的語法,市面上的 Bash 均已支持這種寫法。它的特殊之處就在於 HEREDOC 正文內容中的全部前綴 TAB 字符都會被刪除。
這種語法每每被用在腳本的 if 分支,case 分支或者其餘的代碼有縮進的場所,這樣 HEREDOC 的結束標記沒必要非要在新的一行的開始之處不可。一方面視覺效果上 HEREDOC 跟隨了所在代碼塊的縮進層次,可讀性被提高,另外一方面對於許多懶惰的編輯器來講,不會發生面對 HEREDOC 時語法分析出錯、代碼摺疊的區塊判斷不正確的狀況。
function a () {
if ((DEBUG)); then
cat <<-EOF
French
American
- Uses UTF-8
Helvetica
- Uses RTL
EOF
fi
}
複製代碼
如上的腳本段落中,結束標記EOF能夠沒必要處於行首第一個字母,只要EOF以及其上的HEREDOC正文都以TAB字符進行縮進就能夠了。
注意若是TAB字符縮進在這裏沒有被嚴格遵照的話,Bash解釋器可能會報出錯誤。
像在正文中的 - Uses UTF-8
除開行首的 TAB字符縮進以外,還包含兩個空格字符,這不會受到 <<-
的影響而被刪除。
通常狀況下,HEREDOC 中的 ${VAR}
,$(pwd)
,$((1+1))
等語句會被展開,當你想要編寫 ssh 指令時,可能你但願的是不要展開 $
標記。
這能夠用 <<"EOF"
來實現。
只須要在 IDENT
標記上加上引號包圍就能夠達到效果,結束標記則無需引號。
cat <<"EOF"
Command is:
$ lookup fantasy
EOF
# 若是不想展開,則你須要對 $ 字符進行轉義
cat <<EOF
\$ lookup fantasy
EOF
複製代碼
這個例子中,請注意單個的 $
字符實際上是不會展開也不會報錯的,因此咱們只是爲了編寫一個示例而已。
引號包圍呢,單引號、雙引號均可以,都會一樣地生效。
甚至,你可使用轉義語法,也就是說:
cat <<\EOF
Command is:
$ lookup fantasy
EOF
複製代碼
也能禁止參數展開。
上面兩個新的語法特性,是能夠被同時組合和運用的:
cat <<-"EOF"
Command is:
$ lookup fantasy
EOF
複製代碼
雖然你可能根本不須要遇到這樣的情形。