之前寫過兩篇關於Bash語法的blog,分別是:
Bash特殊變量(Bash Special Variables)
個人感覺,想要通暢地讀懂bash腳本,還差一個部分,那就是符號。
個人網上的講bash符號的文章有點亂,要么有錯,要么不全,要么太深,看了也感覺用不上。很多英文的文章寫的好,寫的深,但是查閱起來不方便。於是,干脆我自己寫一篇吧,不說我這篇寫的有多好,起碼名稱中英文有個對應,有一些自己動手運行驗證過了的小例子,很多技術細節我也注明了出處。將來如果有對細節進一步的查閱的需要,可以從這里開始。
# 井號 (crosshatch/sharp/hash)
1. 井號開頭的語句被視為代碼的注釋,不會被執行。
2. 在 #!/bin/bash 中,腳本文件開頭首行的#!會告訴系統這個文件是一系列命令的集合,應該被發送給#!后面指定的解釋器來執行。大概都有哪些解釋器呢?如下:
- #!/bin/sh :Executes the script using the Bourne shell or a compatible shell, with path /bin/sh
- #!/bin/bash :Executes the script using the Bash shell.
- #!/bin/csh -f :Executes the script using C shell or a compatible shell.
- #!/usr/bin/perl -T :Executes the script using perl with the option of taint checks
- #!/usr/bin/env python :Executes the script using python by looking up the path to the python interpreter automatically from the environment variables
~ 波浪號(tilde)
Bash提供了一些用~開頭的變量,這些變量的展開叫做Tilde Expansions,也就是將這些縮寫轉換成它們代表的目錄名稱的過程。
1. 與$HOME一樣,代表當前用戶的home directory.
2. ~username, 代表其他某用戶的home directory.
3. ~+, 代表當前的工作目錄, 與$PWD一樣。
4. ~-,代表上一個工作目錄,與$OLDPWD一樣。
5. ~1, ~2, ~-1, ~-0 分別代表目錄棧中的編號為1的,編號為2的,編號為倒數第2的,倒數第一的目錄。相當於‘dirs +1’, ‘dirs +2’, ‘dirs -1’, ‘dirs –0’.
下面展示了一個例子。關於操作目錄棧的具體內容,請看這里。
; 分號(semicolon)
分號用來分隔兩個命令,比如命令 echo a; echo b 就是告訴bash,echo a 和 echo b 是兩個不同的命令,需要一個接一個地分開運行。參考這里。
;; 雙分號(double semicolon)
雙分號;; 僅用在case結構中,標志一個選項的結束,類似於C語言中的break。
1 2 3 4 |
case $answer in yes) echo 'yay!';; no) echo 'boo!';; esac |
. 點號(dot)
一個 dot 代表當前目錄,兩個 dot 代表上層目錄。如果檔案名稱以 dot 開頭,該檔案就屬特殊檔案,用 ls 指令必須加上 -a 選項才會顯示。除此之外,在 regular expression 中,一個 dot 代表匹配一個字元。
‘string' 單引號 (single quote)
被單引號用括住的內容,將被視為單一字串。在引號內的代表變量的$符號,沒有作用,也就是說,$符號被視為一般符號處理,防止任何變量替換。
“string” 雙引號 (double quote)
被雙引號用括住的內容,將被視為單一字串。它防止通配符擴展,但允許變量擴展。這點與單引數的處理方式不同。
`command` 反引號 (backtick)
反引號不是一種引號,它有特殊的意義。任何被反引號括起來的東西都會在主命令執行之前先被shell求值(或執行),並且反引號括起來的東西的求值(或執行)的輸出會被主命令使用到。
在前面的單雙引號,括住的是字串,但如果該字串是一列命令列,會怎樣?命令會執行么?
答案是不會執行。要處理這種情況,我們得用反引號。
在反引號內的 date +%F 會被視為指令,執行的結果會帶入 $fdv 變量中。
下面的例子里,先創建一個名為lucky的user,可以看到他的uid為1001.
查看當前用戶的信息,名為yunlong的用戶的uid為1000. 可以看到/home/lucky 是lucky用戶的home directory,owner是lucky。
然后,我們利用命令 chown `id -u` /home/lucky, 把/home/lucky的owner換成了yunlong。注意這里對於反引號的使用。
關於反引號的更多信息,看這里。
, 逗號 (comma)
逗號可以有下面這些用途:
1. 括號擴展(brace expansion)
tee 命令的作用是從標准輸入讀取(standard input)之后,將讀取來的信息同時寫入標准輸出(standard output)和一個或多個文件中。詳情見這里。
2. 在let語句中,或者在與其作用等價的(())構造中分隔算數操作。
3. 可以在for語句中,分隔不同的index變量,避免在for循環體中進行操作。
4. 逗號還可以用在Bash4中,把字符串變成小寫。
關於逗號在bash中的用途,原文看這里。
/ 斜杠 (forward slash)
在路徑表示時,斜杠代表目錄。通常單一的斜杠 / 代表 root 根目錄的意思;在四則運算中,斜杠代表除法的符號。
\ 反斜杠(backslash/escape)
在交互模式下的escape符號,有幾個作用:
1. 放在指令前,有取消 aliases 的作用;
# type rm
rm is aliased to `rm -i'
# \rm .\*.log
上例,作者在 rm 指令前加上 escape 字符,作用是暫時取消別名的功能,將 rm 指令還原。
2. 轉義字符,放在特殊符號前,則該特殊符號的作用消失;
3. 放在指令的最末端,表示指令連接下一行。
| 豎線 bar/pipe/vertical bar
pipeline 是 UNIX 系統,基礎且重要的觀念。連結上個指令的標准輸出,做為下個指令的標准輸入。
善用這個觀念,對精簡 script 有相當的幫助。
! 嘆號 exclamation mark/bang
通常它代表反邏輯的作用,譬如條件偵測中,用 != 來代表”不等於”
# if [ "$?" != 0 ] ; then echo “Executes error”; elif then echo "Executes successfully"; fi;
這個例子里,除了展示了嘆號的用法,還包含了如何把if語句寫在一行里的方法,還有一個利用上面講過的反斜杠把一個一行的if語句寫成多行的方法。
在正則表達式里,可以使用!
字符在正則表達式之前對其求反。也就是說,僅當字符串與表達式的其余部分不匹配時,才認為字符串已匹配。
注意,網上的很多名為《shell腳本中一些特殊符號》的文章里,在講嘆號是給出的例子里使用嘆號是不正確的。
應該使用^符號,來對字符范圍取反。如下:
: 冒號 colon
在 bash 中,這是一個內建指令, 這個指令”什么事都不干(no-op)”,但返回狀態值 0 (Bash里返回值為0表示正常執行結束,非0表示出了問題)。
比如,if語句利用上冒號可以寫成這樣:
# if [ "$?" != 0 ]; then :; else echo "Execute successfully"; fi
關於冒號在Bash中的應用,具體可以看這里。
? 問號 (question mark)
在文件名的匹配中,用作單字符的通配符。注意,問號代表的是只有一個字符的通配符,數字和字母都可以匹配。
* 星號 (asterisk/star)
星號是bash中的任意字符序列通配符,包括沒有字符的情況都可以匹配。
** 雙星號 (double-asterisk)
雙星號(**)在多個段中嘗試匹配零個或多個字符,它用於對嵌套目錄中的文件進行通配。
舉例:
Tests/**/*.js
這里,匹配的文件被限制在Test目錄中。會被匹配的文件包括Tests/HelloWorld.js
, Tests/UI/HelloWorld.js
, Tests/UI/Feature1/HelloWorld.js
更多信息,看這里。
另一個就是冪運算。
$ 美元符號(dollar sign)
變量替換(Variable Substitution)的代表符號。 一個變量名字前面帶上美元符號,表明這是個變量。可以用美元符帶上這個變量的名字對其進行引用。
另外,在 Regular Expressions 里被定義為匹配行尾 (end-of-line)。
還有很多其他特殊變量是用美元符號開頭的,參考這里。
${} 變量擴展(variable substitution)
${ }用於變量替換。一般情況下,$var 與${var} 並沒有啥不一樣。但是用 ${ } 會比較精確的界定變量名稱的范圍。
$ AB=string0
$ A=string1
$ echo $ABstring0
# 原本是打算先將 $A 的結果替換出來,然后再補一個 B 字母於其后.# 但在命令行上,真正的結果卻是只會提換變量名稱為 AB 的值出來…
# 若使用 ${ } 就沒問題了:
$ echo ${A}Bstring1B
$() 命令替換 (command substitution,與一對反引號相同)
在 bash shell 中,$( ) 與` ` (反引號) 都是用來做命令替換用(command substitution)的。
例如version=$(uname -r)和version=`uname -r`都可以是version得到內核的版本號。
1. 反引號的命令替換基本上可在全部的 unix shell 中使用,若寫成 shell script ,其移植性比較高。但是,反引號容易打錯或看錯。
2. $()並不是所有shell都支持。
$(()) 和 $[] 算數擴展(arithmetic expansion)
它們是一樣的,都是進行數學運算的。支持+ - * / %:分別為 “加、減、乘、除、取模”。但是注意,bash只能作整數運算,對於浮點數是當作字符串處理的。
二者的區別是$[]已經是淘汰的,棄用的語法了,現在已經完全被$(())替代了。
() 小括號(parentheses) 指令群組 (command group)
小括號之內的一系列命令,會在一個新創建的subshell中執行。由於小括號內的命令是在subshell中執行的,所以其內部的變量賦值在執行之后不會持續。
Placing a list of commands between parentheses causes a subshell environment to be created (see Command Execution Environment), and each of the commands in list to be executed in that subshell. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
{} 花括號(braces)指令群組 (command group)
花括號之內的一系列指令會在當前的shell的上下文中執行。不會創建subshell。 列表之后的分號(或者換行)是必須要有的。
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The semicolon (or newline) following list is required.
具體解釋參考這里。
(()) 雙括號 (Double-Parentheses)
跟let命令一樣,雙括號允許在其中進行算數擴展和求值。 最簡單的一個例子, a=$((5+3)), 會將5+3的結果賦值給a。 雙括號還是一個在bash中使用的C語言風格的操作變量的方法,舉個例子 ((var++)).
詳情看這里。
[] 單方括號 (Single Square Brackets)
單方括號是內置命令test的另一種形式,方括號括住的命令會被驗證其是否為真。 注意,空字符串為false,非空字符串為true。
既然是test命令的另一種形式,那么test命令的各種選項就也是可以應用的。
具體看這里。
[[]] 雙方括號 (Double Square Brackets)
[[ 是新的,提高版的[, 雙方括號是個關鍵字,不是一個程序。雙方框更好用,比較見下表。
英文原文如下:
[ : test implements the old, portable syntax of the command. In almost all shells (the oldest Bourne shells are the exception), [ is a synonym for test (but requires a final argument of ]).
[[ : is a new, improved version of it, and it is a keyword rather than a program. This makes it easier to use
&& 邏輯與, || 邏輯或 (Logical Operators)
雙&&符號在Bash中意思是邏輯與(AND),並且可以用來分隔幾個順序執行的命令。
舉例:
#
cd /root/ && echo "I've got root"
具體看這里。
& 后台工作 (Single Ampersand)
在Bash中, &是控制字符,放在命令的末尾,其作用是讓命令在后台運行,也就是說在另外的一個subshell里,以異步的方式,如同一個job一樣的執行。當前的Shell會立即回到等待用戶輸入的狀態,並且返回值為0.
舉例:
# shell會給出后台運行命令的process ID (PID), 這個ID會存儲在$! 變量中。
$ ./myscript.py &
[1] 1337
# 后面可以通過$!變量對該進程進行引用。
$ echo $!
1337
# 一旦生成了新的后台進程,這個進程會出現在job列表中。
$ jobs
[1]+ Running ./myscript.py &
# foregroud命令可以把這個進程拿回前台。
fg
具體看這里。
+ 加號, -減號, * 乘號, / 除號
比較簡單,不說了。
= 等號(賦值), == 雙等號 (判斷相等), != 不等於號
簡單,也不說了。
^ 折音號 (circumflex/caret)
這個符號在正則表達式中,代表行的開頭位置,在[]中也與!(嘆號)一樣表示非,即求反。
>, >>, <, <<, >&, <&, <<tag, 2>, 2>>, 2>&1 輸入輸出重定向
這里符號比較多,一個一個簡介吧。
1. command > file --- 將輸出重定向到file
2. command < file --- 將輸入重定向到file,確切的說是把file中的內容拿來作為command的輸入。
3. command >> file --- 將輸出以追加的方式重定向到file
4. n >& m --- 將輸出文件m和n合並
5. n <& m --- 將輸入文件m和n合並
6. << tag --- 將開始標記 tag 和結束標記 tag 之間的內容作為輸入。
輸入重定向舉例:
一般情況下,每個 Unix/Linux 命令運行時都會打開三個文件:
- 標准輸入文件(stdin):stdin的文件描述符為0,Unix程序默認從stdin讀取數據。
- 標准輸出文件(stdout):stdout 的文件描述符為1,Unix程序默認向stdout輸出數據。
- 標准錯誤文件(stderr):stderr的文件描述符為2,Unix程序會向stderr流中寫入錯誤信息。
默認情況下,command > file 將 stdout 重定向到 file,command < file 將stdin 重定向到 file。
如果希望 stderr 重定向到 file,可以這樣寫:
$ command 2>file
如果希望 stderr 追加到 file 文件末尾,可以這樣寫:
$ command 2>>file
2 表示標准錯誤文件(stderr)。如果希望將 stdout 和 stderr 合並后重定向到 file,可以這樣寫:
$ command > file 2>&1
$ # 或者
$ command >> file 2>&1
更多信息,看這里。
參考資料
=============
Shell中的特殊符號和含義簡明總結
https://blog.csdn.net/wejfoasdbsdg/article/details/53289589
shell腳本中一些特殊符號
https://www.cnblogs.com/xuxm2007/archive/2011/10/20/2218846.html
(#!/bin/bash ) What exactly is this ?
https://medium.com/@codingmaths/bin-bash-what-exactly-is-this-95fc8db817bf
目錄堆棧
https://wangdoc.com/bash/stack.html
How to Create Users in Linux (useradd Command)
https://linuxize.com/post/how-to-create-users-in-linux-using-the-useradd-command/
The Magic ~: Bash Tilde Expansion with 5 Examples