引號和轉義
Bash 只有一種數據類型,就是字符串。不管用戶輸入什么數據,Bash 都視為字符串。因此,字符串相關的引號和轉義,對 Bash 來說就非常重要。
轉義
某些字符在 Bash 里面有特殊含義(比如$
、&
、*
)。
$ echo $date
$
上面例子中,輸出$date
不會有任何結果,因為$
是一個特殊字符。
如果想要原樣輸出這些特殊字符,就必須在它們前面加上反斜杠,使其變成普通字符。這就叫做“轉義”(escape)。
$ echo \$date
$date
上面命令中,只有在特殊字符$
前面加反斜杠,才能原樣輸出。
反斜杠本身也是特殊字符,如果想要原樣輸出反斜杠,就需要對它自身轉義,連續使用兩個反斜線(\\
)。
$ echo \\
\
上面例子輸出了反斜杠本身。
反斜杠除了用於轉義,還可以表示一些不可打印的字符。
\a
:響鈴\b
:退格\n
:換行\r
:回車\t
:制表符
如果想要在命令行使用這些不可打印的字符,可以把它們放在引號里面,然后使用echo
命令的-e
參數。
$ echo a\tb
atb
$ echo -e "a\tb"
a b
上面例子中,命令行直接輸出不可打印字符\t
,Bash 不能正確解釋。必須把它們放在引號之中,然后使用echo
命令的-e
參數。
換行符是一個特殊字符,表示命令的結束,Bash 收到這個字符以后,就會對輸入的命令進行解釋執行。換行符前面加上反斜杠轉義,就使得換行符變成一個普通字符,Bash 會將其當作空格處理,從而可以將一行命令寫成多行。
$ mv \
/path/to/foo \
/path/to/bar
# 等同於
$ mv /path/to/foo /path/to/bar
上面例子中,如果一條命令過長,就可以在行尾使用反斜杠,將其改寫成多行。這是常見的多行命令的寫法。
單引號
Bash 允許字符串放在單引號或雙引號之中,加以引用。
單引號用於保留字符的字面含義,各種特殊字符在單引號里面,都會變為普通字符,比如星號(*
)、美元符號($
)、反斜杠(\
)等。
$ echo '*'
*
$ echo '$USER'
$USER
$ echo '$((2+2))'
$((2+2))
$ echo '$(echo foo)'
$(echo foo)
上面命令中,單引號使得 Bash 擴展、變量引用、算術運算和子命令,都失效了。如果不使用單引號,它們都會被 Bash 自動擴展。
由於反斜杠在單引號里面變成了普通字符,所以如果單引號之中,還要使用單引號,不能使用轉義,需要在外層的單引號前面加上一個美元符號($
),然后再對里層的單引號轉義。
# 不正確
$ echo it's
# 不正確
$ echo 'it\'s'
# 正確
$ echo $'it\'s'
不過,更合理的方法是改在雙引號之中使用單引號。
$ echo "it's"
it's
雙引號
雙引號比單引號寬松,大部分特殊字符在雙引號里面,都會失去特殊含義,變成普通字符。
$ echo "*"*
上面例子中,通配符*
是一個特殊字符,放在雙引號之中,就變成了普通字符,會原樣輸出。這一點需要特別留意,這意味着,雙引號里面不會進行文件名擴展。
但是,三個特殊字符除外:美元符號($
)、反引號(`
)和反斜杠(\
)。這三個字符在雙引號之中,依然有特殊含義,會被 Bash 自動擴展。
$ echo "$SHELL"/bin/bash$ echo "`date`"Mon Jan 27 13:33:18 CST 2020
上面例子中,美元符號($
)和反引號(`
)在雙引號中,都保持特殊含義。美元符號用來引用變量,反引號則是執行子命令。
$ echo "I'd say: \"hello!\""I'd say: "hello!"$ echo "\\"\
上面例子中,反斜杠在雙引號之中保持特殊含義,用來轉義。所以,可以使用反斜杠,在雙引號之中插入雙引號,或者插入反斜杠本身。
換行符在雙引號之中,會失去特殊含義,Bash 不再將其解釋為命令的結束,只是作為普通的換行符。所以可以利用雙引號,在命令行輸入多行文本。
$ echo "helloworld"helloworld
上面命令中,Bash 正常情況下會將換行符解釋為命令結束,但是換行符在雙引號之中就失去了這種特殊作用,只用來換行,所以可以輸入多行。echo
命令會將換行符原樣輸出,顯示的時候正常解釋為換行。
雙引號的另一個常見的使用場合是,文件名包含空格。這時就必須使用雙引號(或單引號),將文件名放在里面。
$ ls "two words.txt"
上面命令中,two words.txt
是一個包含空格的文件名,否則就會被 Bash 當作兩個文件。
雙引號會原樣保存多余的空格。
$ echo "this is a test"this is a test
雙引號還有一個作用,就是保存原始命令的輸出格式。
# 單行輸出$ echo $(cal)一月 2020 日 一 二 三 四 五 六 1 2 3 ... 31# 原始格式輸出$ echo "$(cal)" 一月 2020日 一 二 三 四 五 六 1 2 3 4 5 6 7 8 9 10 1112 13 14 15 16 17 1819 20 21 22 23 24 2526 27 28 29 30 31
上面例子中,如果$(cal)
不放在雙引號之中,echo
就會將所有結果以單行輸出,丟棄了所有原始的格式。
Here 文檔
Here 文檔(here document)是一種輸入多行字符串的方法,格式如下。
<< tokentexttoken
它的格式分成開始標記(<< token
)和結束標記(token
)。開始標記是兩個小於號 + Here 文檔的名稱,名稱可以隨意取,后面必須是一個換行符;結束標記是單獨一行頂格寫的 Here 文檔名稱,如果不是頂格,結束標記不起作用。兩者之間就是多行字符串的內容。
下面是一個通過 Here 文檔輸出 HTML 代碼的例子。
$ cat << _EOF_<html><head> <title> The title of your page </title></head><body> Your page content goes here.</body></html>_EOF_
Here 文檔內部會發生變量替換,同時支持反斜杠轉義,但是不支持通配符擴展,雙引號和單引號也失去語法作用,變成了普通字符。
$ foo='hello world'$ cat << _example_$foo"$foo"'$foo'_example_hello world"hello world"'hello world'
上面例子中,變量$foo
發生了替換,但是雙引號和單引號都原樣輸出了,表明它們已經失去了引用的功能。
如果不希望發生變量替換,可以把 Here 文檔的開始標記放在單引號之中。
$ foo='hello world'$ cat << '_example_'$foo"$foo"'$foo'_example_$foo"$foo"'$foo'
上面例子中,Here 文檔的開始標記(_example_
)放在單引號之中,導致變量替換失效了。
Here 文檔的本質是重定向,它將字符串重定向輸出給某個命令,相當於包含了echo
命令。
$ command << token stringtoken# 等同於$ echo string | command
上面代碼中,Here 文檔相當於echo
命令的重定向。
所以,Here 字符串只適合那些可以接受標准輸入作為參數的命令,對於其他命令無效,比如echo
命令就不能用 Here 文檔作為參數。
$ echo << _example_hello_example_
上面例子不會有任何輸出,因為 Here 文檔對於echo
命令無效。
此外,Here 文檔也不能作為變量的值,只能用於命令的參數。
Here 字符串
Here 文檔還有一個變體,叫做 Here 字符串(Here string),使用三個小於號(<<<
)表示。
<<< string
它的作用是將字符串通過標准輸入,傳遞給命令。
有些命令直接接受給定的參數,與通過標准輸入接受參數,結果是不一樣的。所以才有了這個語法,使得將字符串通過標准輸入傳遞給命令更方便,比如cat
命令只接受標准輸入傳入的字符串。
$ cat <<< 'hi there'# 等同於$ echo 'hi there' | cat
上面的第一種語法使用了 Here 字符串,要比第二種語法看上去語義更好,也更簡潔。
$ md5sum <<< 'ddd'# 等同於$ echo 'ddd' | md5sum
上面例子中,md5sum
命令只能接受標准輸入作為參數,不能直接將字符串放在命令后面,會被當作文件名,即md5sum ddd
里面的ddd
會被解釋成文件名。這時就可以用 Here 字符串,將字符串傳給md5sum
命令。