認識 Here Document

基礎

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 限定符且保證正確的嵌套關係便可:ide

ssh 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
複製代碼

這是正確有效的,不過這個其實更怪一些。

Here String

在 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

但當須要 root 權限時,'>' 並不能很好地工做,此時須要 sudo tee 上場:

cat <<EOF | sudo tee /opt/1.log
s
EOF
複製代碼
子shell

標準輸出的重定向,還能夠經過子 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 的變量無污染的效果,或者別的期待,那你就使用 ()

在參數展開語法中使用 HEREDOC

variable=$(cat <<SETVAR
This variable
runs over multiple lines.
SETVAR
)

echo "$variable"
複製代碼

示例展現了在 $() 語法中能夠隨意地嵌入 HEREDOC。

若是你只是須要爲變量用 HEREDOC 賦值,read var 一般是更好的主意:

read i <<!
Hi
!
echo $i  # Hi
複製代碼

對函數使用 HEREDOC

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 套用上去。

匿名的 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 程序員的通常風格是能省鍵盤就省鍵盤。但有時候他們也喜歡能炫就炫:

:<<-!
  ____                 _    ____                 _
 / ___| ___   ___   __| |  / ___| ___   ___   __| |
| |  _ / _ \ / _ \ / _` | | |  _ / _ \ / _ \ / _` |
| |_| | (_) | (_) | (_| | | |_| | (_) | (_) | (_| |
 \____|\___/ \___/ \__,_|  \____|\___/ \___/ \__,_|

 ____  _             _
/ ___|| |_ _   _  __| |_   _
\___ \| __| | | |/ _` | | | |
 ___) | |_| |_| | (_| | |_| |
|____/ \__|\__,_|\__,_|\__, |
                       |___/
!
複製代碼

while read

當咱們須要讀一個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. 
複製代碼

新的縮進和對齊語法

刪除 TAB 縮進字符

<<-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
複製代碼

雖然你可能根本不須要遇到這樣的情形。

參考

相關文章
相關標籤/搜索