概述
首先,咱們來了解一下,什么是Shell
。操作系統內核給我們提供了各種接口,同時也提供了各種用戶層的庫,理論上我們基于這些可以編寫程序實現各種我們想要的功能,不過問題是,咱們不可能做什么事情都要重新編寫程序,這樣使用起來也太困難了。因此,操作系統(包括Linux)通常都會引入一個Shell
這樣的特殊程序,這個程序會接受輸入的命令然后執行,并可能將執行結果呈現出來。總結來說,Shell
是一個從輸入設備或者文件讀取命令,并且解釋、執行的用戶態程序。
在Linux系統中,通常使用的Shell
程序包括有:
- Sh (Bourne Shell)
- Bash (Bourne Again Shell)
- Csh (C Shell)
- Ksh (Korn Shell)
一般來說,Bash
應該是使用最多的Shell
程序了,本文也主要基于Bash
來展開。
Shell展開(Shell Expansion)
Shell
程序是一個命令解釋器,因此在終端輸入命令之后,Shell
將掃描命令并做適當的修改,這個過程稱為Shell展開。Shell展開是Shell解釋執行之前極為重要的一步,了解它將有利于你對Shell命令或者腳本的理解,本章節將逐步帶大家來了解這個過程。
命令參數解析
這里的空格包括了制表符(Tab)。當Shell程序掃描輸入的命令時,會以連續的空格為界,將命令切分成一組參數,因此你輸入多個空格為界跟輸入一個空格的效果是一樣的。通常來講,第一個參數就是要執行的命令,而后面的參數則是改命令的參數。一下幾個命令其實是等效的:
# echo Hello World
Hello World
# echo Hello World
Hello World
# echo Hello World
Hello World
引號
當然,有時候你需要在一個參數中包括空格,這樣的話你就需要將這個參數以引號引起來,引號包括了單引號'
跟雙引號"
,兩者都可以。shell
會將引號中的字符串視為一個參數,不論里面有沒有空格。當然,特別指出的是,不要用反引號`
,反引號將在后面詳細講述。
如命令echo 'Hello World!'
在shell
解析之后會有兩個參數,分別為echo
跟Hello World!
。而如果不用引號echo Hello World!
,則將解析為三個參數。
特別提一下,對于
echo
命令,如果需要輸出需要轉義的字符,如回車等,則需要執行echo -e "Hello World!\n"
,如果不加-e
,則\n
會被直接顯示出來。# echo "hello\n" hello\n # echo -e "hello\n" hello
命令
對于shell
來說,命令有內部命令(Builtin Commands)跟外部命令(External Commands)之分,所謂內部命令指的是包含在shell
解析器中的命令。內部命令一般有4種類型:
-
sh
內部命令這些內部命令來源于
Bourne Shell
,通常包括了以下命令:
: . break cd continue eval exec exit export getopts hash pwd readonly return shift test/[ times trap umask unset
。 -
bash
內部命令這些內部命令來源于
Bourne Again Shell
,通常包括了以下命令:
alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias
。 -
修改
shell
行為的內部命令這些內部命令用來修改
shell
的默認行為。包括了set shopt
命令。 -
特殊內部命令
由于歷史原因,POSIX標準將一些內部命令劃分為特殊內部命令,特殊的之處在于這些命令的查找時間以及命令運行后的狀態等方面,只有當Bash以POSIX模式運行時,這些命令才是特殊命令,否則它們跟其它內部命令沒啥區別。特殊內部命令包括了
break : . continue eval exec exit export readonly return set shift trap unset
。
內部命令可能會被提前至于內存中,因此運行起來會比外部命令要快。對于外部命令,可以認為除了內部命令之后就可以認為是外部命令了,通常來講,/bin
跟/sbin
下的都是外部命令,當然,應用有關的通常也是外部命令。
我們可以通過type
命令來查看一個命令是否是內部命令:
# type cd
cd is a shell builtin
# type awk
awk is /usr/bin/awk
另外,對于很多內部命令,它們可能對應的會有外部命令版本,可以通過type
命令來查看:
# type -a echo
echo is a shell builtin
echo is /usr/bin/echo
# type -a cd
cd is a shell builtin
cd is /usr/bin/cd
反過來,我們一般可以通過命令which
來查詢一個命令是否是外部命令:
# which awk
/usr/bin/awk
# which .
/usr/bin/which: no . in (/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin)
總結一下,通過which
查詢出來的是其外部命令版本,通過type
默認查詢出來的是內部命令:
# which echo
/usr/bin/echo
# type echo
echo is a shell builtin
對于內部命令的詳細說明,可以查看GNU的文檔。
別名
可以用alias
命令給一個命令取一個別名:
# alias print=echo
# print "hello"
hello
# type print
print is aliased to `echo'
別名一個常用的用法是用來縮寫已知的命令:
# type ls
ls is aliased to `ls --color=auto'
可見ls
命令實際上是命令ls --color=auto
的別名,這樣就相當于改變了ls
命令的默認行為了。
前面咱們通過type
命令來查看命令的別名,實際上更加推薦采用alias
或者which
來查看:
# alias ls
alias ls='ls --color=auto'
# which ls
alias ls='ls --color=auto'
/usr/bin/ls
如果要取消別名,則可以采用unalias
命令:
# which ls
alias ls='ls --color=auto'
/usr/bin/ls
# unalias ls
# which ls
/usr/bin/ls
顯示shell展開的結果
由于shell
展開的存在,你輸入的命令被展開之后可能會發生變化,如果需要知道shell
展開之后的命令,可以使用內部命令set
來修改shell
的默認參數來顯示:
# set -x
++ printf '\033]0;%s@%s:%s\007' root traffic-base1 '~'
# echo hello world
+ echo hello world
hello world
++ printf '\033]0;%s@%s:%s\007' root traffic-base1 '~'
其中,以+
開頭的就是展開之后的命令,可見展開之后,shell
將多余的空格去掉了。如果不要再顯示了,可以輸入命令set +x
。
shell控制操作符 (Control Operators)
$?
操作符
每個命令執行完后都會有個退出碼(Exit Code),其值為0時表示命令成功,否則命令失敗。這個退出碼可以通過$?
來訪問,執行完命令后立馬訪問$?
可以獲取該命令的退出碼,并以此來判斷命令是否成功。每個命令的執行都會產生新的退出碼,所以請務必在命令執行完,立刻訪問$?
來獲取退出碼。
初看起來,$?
似乎是一個shell
變量,但實際上并非如此,因為你無法對$?
賦值。$?
準確來說是shell
的一個內部參數。
分號;
shell
命令輸入時,你可以將多個命令輸入在一行,只要在不同命令之間以分號;
隔開,當然分號不能是在引號中。
必須注意的是,如果將多個命令以
;
連接在一起,執行的結果通過$?
查詢出來將只是最后一個命令的結果
&
符號
通常情況下,shell
會在前臺執行命令,并等待命令結束才返回。如果需要將命令放到后臺去執行,可以使用&
符號放在命令最后面,這樣的話命令會被放在后臺執行,shell
會立刻返回而不用等待命令結束。
注意的是,即便放在后臺執行,但是如果不處理好命令的輸入,則命令的輸出可能會繼續在當前的終端輸出,后面會講述如何處理命令的輸出。
&&
操作符
此操作符表示邏輯與,你可以將兩個命令用此操作符連接起來,如cmd1 && cmd2
,只有當cmd1
執行成功之后,cmd2
才會被執行。這里的成功指的是cmd1
的退出碼是0。
# hello && echo world
-bash: hello: command not found
# echo hello && echo world
hello
world
當然,&&
也可以將多個命令連接起來,其執行類似,只有當前面的命令成功,后面的才會執行。因此,將多個命令寫在一行用&&
可以實現,只不過&&
必須按照邏輯與的關系執行,而;
號的話會執行所有的命令。
||
操作符
很顯然,與&&
相對,||
操作符表示邏輯或的關系,同樣可以連接兩個命令,如cmd1 || cmd2
,只有當cmd1
失敗了,才會執行cmd2
,這里的失敗指的是cmd1
的退出碼非0。
&&
與||
混合
這兩個操作符是可以混合使用的,其遵循的原則保持一致,且是從左向右依次判斷,結合這兩種操作符,可以實現類似于if then else
的邏輯結構。如cmd1 && cmd2 || cmd3
意思就是如果cmd1
成功,則執行cmd2
,否則執行cmd3
。但務必注意的是,此處并非真正意思上的if then else
邏輯,因為如果cmd2
也執行失敗,cmd3
其實也會被執行。如下例:
# echo hello && echo ok || echo world
hello
ok
# echo hello && rm dfsdf || echo world
hello
rm: cannot remove ‘dfsdf’: No such file or directory
world
&&
相當于將兩條命令邏輯上連成了一條命令,這樣就變成了cmd1-2 || cmd3
,其中cmd1-2
就是cmd1 && cmd2
,因此,cmd3
只要在cmd1-2
失敗的情況下都會被執行,而cmd1-2
失敗的情況有兩種,一種是cmd1
失敗,一種是cmd1
成功但是cmd2
失敗。同樣的,||
也會將兩條命令連成一條命令,如cmd1-2 || cmd3 && cmd4
就相當于cmd1-2_3 && cmd4
,cmd4
是否會執行,決定于cmd1-2_3
是否失敗,以具體例子說明:
# echo hello && echo ok || echo world && rm dsdfsf || echo end
hello
ok
rm: cannot remove ‘dsdfsf’: No such file or directory
end
這行命令相當于cmd1 && cmd2 || cmd3 && cmd4 || cmd5
,可以看出cmd1
,cmd2
,cmd4
還是有cmd5
被執行了,而cmd3
沒有執行。咱們來解析一下,為何是如此的執行結果。首先,shell
從左往右掃描執行:
- 發現
cmd1 && cmd2
,由&&
連成一個命令cmd1-2
,因為兩個命令都是成功的,所以都被執行了,這樣可以認為cmd1-2
成功 - 執行成功之后,接下來是
||
操作符,這里并不會因為前面的命令是成功的,而不再執行后面所有的命令,而是||
操作符相當于將cmd1-2
與cmd3
連接成了cmd1-2_3
,因為cmd1-2
成功了,所以cmd3
不再執行,但是cmd1-2_3
相當于執行成功了 - 繼續執行,發現是
&&
操作符,同樣將cmd1-2_3
與cmd4
連接起來,記為cmd1-2_3-4
,因為cmd1-2_3
執行成功了,所以cmd4
也被執行,但是cmd4
執行失敗了,所以cmd1-2_3-4
相當于執行失敗 - 繼續執行,發現是
||
操作符,同樣將cmd1-2_3-4
與cmd5
連成cmd1-2_3-4_5
,因為cmd1-2_3-4
執行失敗,所以cmd5
被執行
可見,shell
永遠都是從左往右掃描執行,&&
跟||
會將前后兩個命令連接起來,根據兩種操作符的規則就可以知道多個連起來的命令是如何執行的了。
#
符號
跟其它很多語言一樣,#
在shell
里面用來注釋。
\
轉義符號
\
符號可以用來轉義一些特殊符號,如$
,#
等。
特別指出的是,如果轉義符號放在行末單獨使用,則用來連接下一行。
shell變量
基本概念
定義跟引用
shell
中也可以使用變量,變量不需要像其它語言一樣需要預先申明。shell
中賦值給一個不存在的變量就相當于定義了變量,如name="Mr. Hao"
,就定義了name
變量,后續如果再對name
賦值,就相當于改變改變量的值。與很多語言不同的是,shell
中變量引用以$
符號開頭,后面跟變量的名字。如前面的變量,引用如下echo "$name"
。需要注意的是,在shell
中,變量名是大小寫敏感的。
在shell
展開中會自動展開變量的引用,即便該變量處在雙引號中。但是,如果變量引用在單引號中,shell
不會對其進行解析。
# name="Mr. Hao"
# echo "$name"
Mr. Hao
# set -x
# echo '$name'
+ echo 'Mr. Hao'
$name
查找變量
可以使用set
命令來查找所定義的變量:
# set | grep -E '^name='
name='Mr. Hao'
刪除變量
與很多語言不同的是,在shell
中定義的變量是可以刪除的,使用unset
命令刪除定義的變量。
# set | grep -E '^name='
name='Mr. Hao'
# unset name
# set | grep -E '^name='
export
聲明
通常情況下,shell
在執行命令的時候會為該命令創建子進程。如果希望將當前的變量作用到子進程,則需要將變量export
聲明,這種變量稱之為環境變量,如:
# var1="hello"
# export var2="world"
# bash
# echo "var1=$var1, var2=$var2"
var1=, var2=world
其中,bash
命令開啟了一個新的shell
,可見只有export
聲明的變量在新的shell
中才是可見的。環境變量可以通過env
命令列舉出來,在后面一節會詳細講述。此外,如果需要將非export
變量重新聲明為export
變量,則只需要用export
重新聲明一下即可:
# var1=hello
# env | grep var1
# export var1
# env | grep var1
var1=hello
env
命令
如果需要查看當前shell
中有哪些export
聲明的變量,可以使用env
命令,該命令會列出當前所有export
聲明的變量。請注意與set
命令的區別,set
命令會列出所有的變量,包括哪些不是export
聲明的變量。通常,我們把env
命令輸出的變量稱之為環境變量
。
此外,env
也常用來為子shell
預先定義一些臨時變量,如:
# var1="hello"
# env var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello
其中,用env
命令定義了臨時變量var1
,然后bash
命令開啟了一個子shell
,并在子shell
中執行了echo "$var1"
命令。可見,輸出了定義的臨時變量,在命令結束后,又回到之前的shell
,輸出的也是之前shell
中定義的值。當然,在使用env
定義臨時變量的時候,為了方便,通常我們可以省略env
命令,如:
# var1="hello"
# var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello
另外,env
命令還有一種常用的用法,就是用來開啟一個干凈的子shell
,即在子shell
中不繼承所有的變量,即便這些變量在之前的shell
中采用export
聲明,此時env
命令需要加入-i
的參數,如:
# export var1="hello world"
# bash -c 'echo "var1=$var1"'
var1=hello world
# env -i bash -c 'echo "var1=$var1"'
var1=
可見,使用env -i
之后,即便var1
被export
聲明,但是在子shell
中也沒有被繼承。
變量解釋
在前面章節,我們知道shell
采用$
符號引用變量,在$
符號后緊跟變量的名字。而shell
在提取變量名字的時候一般以非字母數字(non-alphanumeric)為邊界,這有時候就會產生問題,如:
# prefix=Super
# echo Hello $prefixman and $prefixgirl
Hello and
可見,shell
并不能提取我們定義的變量prefix
,因為其后并沒有非字母數字的字符為界。這種情況下,我們可以使用{}
將變量名保護起來。
# prefix=Super
# echo Hello ${prefix}man and ${prefix}girl
Hello Superman and Supergirl
非綁定(unbound)變量
所謂非綁定(unbound)變量其實指的是沒有預先定義的變量,或者說不存在的變量。默認情況下,shell
在解釋這種變量的時候會以空字符串替代:
# echo $unbound_var
如果需要shell
在這種情況下報錯,可以配置shell
選項set -o nounset
,或者簡寫為set -u
:
# echo $unbound_var
bash: unbound_var: unbound variable
# set +u
# echo $unbound_var
當然,由例子中可以看到,要取消該配置,可以相應的設置set +o nounset
,或者簡寫為set +u
。
特殊變量
在shell
中預定義了很多特殊的變量,這一節咱們來說一下常見的幾個變量。
$PS1
變量
在shell
終端輸入命令時,咱們總是可以看到在輸入行首總是會有提示符,如:
mrhao:~$
其中,mrhao:~$
就是提示符,這個字串實際上是由shell
變量$PS1
決定的。如果咱們改變一下該變量的值,提示符也會相應的改變:
mrhao:~$ PS1="hello > "
hello > echo "PS1 value is '$PS1'"
PS1 value is 'hello > '
hello >
為了方便在提示符中顯示系統的某些實時信息,$PS1
變量定義了一些特殊的字符:
字符 | 說明 |
---|---|
\w | 表示工作目錄 |
\u | 表示用戶名 |
\h | 表示系統的hostname |
當然,這里只列舉了幾個,詳細的可以查看Linux手冊。另外,$PS1
中還可以對對其中不同部分采用不同顏色顯示,如:
# RED='\[\033[01;31m\]'
# WHITE='\[\033[01;00m\]'
# GREEN='\[\033[01;32m\]'
# BLUE='\[\033[01;34m\]'
# PS1="$GREEN\u$WHITE@$BLUE\h$WHITE\w\$ "
mrhao@mrhao-host~$ echo "$PS1"
\[\033[01;32m\]\u\[\033[01;00m\]@\[\033[01;34m\]\h\[\033[01;00m\]\w$
$PATH
變量
當我們在Linux的terminal里面輸入命令的時候,shell
需要在一系列的目錄中查找輸入的命令,如果沒有查找到會直接報command not found
的錯誤。而這些查找的目錄就定義在$PATH
變量中。
# echo $PATH
/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
其中,每個目錄以:
隔開,如果需要增加目錄,可以:
# PATH=$PATH:/opt/local/bin
# echo $PATH
/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/opt/local/bin
加入新的路徑的時候請務必帶上之前的路徑,
$PATH:<new path>
否則,很多默認的系統路徑將被覆蓋,導致很多命令失效。
特別注意的是,$PATH
變量中目錄的順序是很重要的,如果shell
在前面的目錄中找到了命令,則不會查找后面的目錄。如果你想把某個重名的命令優先執行,就需要把它對應的目錄放在$PATH
的前面。
網絡代理變量
在Linux系統中,很多時候我們需要訪問外部網絡,比如使用curl
命令下載文件等等。而有的時候,訪問訪問外部網絡咱們需要設置代理,在Linux系統中,使用網絡代理非常簡單,只要配置幾個變量即可:
變量 | 說明 |
---|---|
http_proxy | 設置訪問http 請求所需要的代理,如http_proxy=http://10.10.10.100:80
|
https_proxy | 設置訪問https 請求所需要的代理,如https_proxy=http://10.10.10.100:80
|
ftp_proxy | 設置訪問ftp 請求所需要的代理,如ftp_proxy=http://10.10.10.100:80
|
no_proxy | 設置哪些域名或者IP不需要走代理,如no_proxy=localhost,127.0.0.1
|
$PWD
變量
PWD
變量是一個由shell
自動設置的變量,其值表示當前目錄的絕對路徑,與命令pwd
輸出相同。
shell
嵌入與shell
選項
shell
嵌入(shell embedding)
shell
可以嵌入在同一個命令行中,也就是shell
在掃描解釋命令行的時候,可能會從當前的shell
進程中fork
出一個新的shell
進程,并將有關命令放在新進程中運行。如下例:
# var1=hello
# echo $(var1=world; echo $var1)
world
# echo $var1
hello
如其中$()
便開啟了一個新的shell
進程,或者成為子shell
,并在此shell
中運行命令var1=world; echo $var1
,此時輸出的是子shell
中定義的var1
。當命令結束后,子shell
進程退出,并將輸出的結果world
返回給之前的shell
(或者父shell
)的echo
命令,父shell
最后輸出world
。而且,在子shell
中定義相同的var1
變量并不會改變父shell
中的變量。
特別注意的是,因為子shell
是fork
出來的進程,根據Linux進程fork
的特點,子進程將共享父進程的數據空間,而只在寫的時候拷貝新的數據空間,因此,創建出來的子shell
是會繼承所有父shell
的變量,不論該變量是否被export
聲明
# var1=hello
# var2="$(echo $var1 world)"
# echo $var2
hello world
可見,雖然var1
變量沒有export
聲明,但是在子shell
中還是可見的。這點與使用bash -c
開啟的shell
是不同的。
用$()
可以將子shell
嵌入到命令行中,當然,$()
是可以嵌套使用的,這樣可以用來在子shell
中開啟它的子shell
。
# A=shell
# echo $C$B$A $(B=sub;echo $C$B$A; echo $(C=sub;echo $C$B$A))
shell subshell subsubshell
反引號(backticks)
在上面我們可以通過$()
將子shell
嵌入命令行中,為了方便,我們同樣可以用反引號`
將子shell
嵌入。
# var1=hello
# echo `var1=world; echo $var1`
world
# echo $var1
hello
但是,使用反引號不能夠嵌套子shell
,因此如果需要嵌套子shell
時,只能使用$()
。
反引號跟單引號是本質的不同的,單引號與雙引號一樣,用來將連續的字串作為整體引起來,只不過單引號中將不執行變量的引用解析,而反引號則是嵌入子
shell
。
shell
選項
其實在前面咱們已經使用了不少shell
的選項,如set -u
在變量不存在是報錯,set -x
將shell
展開的結果顯示出來等。此外,可以才用echo $-
將當期設置的shell
選項打印出來。
shell
歷史記錄
在shell
中執行命令的時候,shell
會將最近的命令使用歷史記錄下來,這樣你可以很方便的查看最近做了什么操作。
查看歷史記錄
命令history
可以用來查看shell
的歷史記錄,里面記錄了你最近輸入的所有命令。當然,很多時候你更加關心最近的幾個命令,你可以使用history 10
來顯示最近的10個命令。另外,shell
通常還會將最近的歷史記錄寫在~/.bash_history
文件中,因此查看該文件同樣可以查看歷史記錄。
執行歷史的命令
shell
提供了很多高級用法使得你可以很方便的執行以前執行過的命令。
首先,咱們先顯示一下過去的10個命令,可以看到每個命令前面都有其對應的序號。
# history 10
1000 history
1001 history 10
1002 echo "hello world"
1003 ls -l
1004 ps -ef | grep named
1005 env | grep http
1006 grep hello /var/log/messages
1007 tmux ls
1008 find . -name "hello"
1009 history 10
下面列舉比較常用的shell
重復執行歷史記錄中命令的方法:
命令 | 說明 |
---|---|
!! | 在shell 中輸入兩個感嘆號會執行上一個命令 |
!keyword | 輸入一個感嘆號后跟關鍵字,會搜索歷史記錄中最先以該關鍵字開始的命令。如!find 會執行序號為1008的命令。 |
!n | 其中n代表歷史記錄中的序號,表示執行序號為n的命令。 |
另外,對于!keyword
的用法,還有一個高級功能,你可以將符合該條件的命令進行改造后執行,如:
# echo "test1"
test1
# !ec:s/1/2/
echo "test2"
test2
其中,:s/1/2/
將命令echo "test1"
替換成echo "test2"
然后執行了。
搜索歷史記錄
在shell
終端中按Ctrl-r會打開shell
的搜索模式,在改模式下輸入關鍵字會顯示最近包含改關鍵字的命令,再按一下Ctrl-r會繼續顯示前面一條符合條件的命令,找到你需要的命令后回車就可以執行改命令了。
修改歷史記錄的有關配置
有多個配置可以用來改變歷史記錄的有關信息,通常都是通過有關環境變量來配置:
環境變量 | 說明 |
---|---|
$HISTSIZE | 這個變量用來配置shell 應該保持多少行的歷史記錄,在很多發行版本中,默認值一般為500或者1000 |
$HISTFILE | 這個變量用來配置歷史記錄文件存放的位置,通常來講,默認路徑為~/.bash_history
|
$HISTFILESIZE | 這個變量用來配置歷史記錄文件可以存放多少行的歷史記錄 |
阻止記錄某些命令
在有些時候,我們并不想把某些命令記錄在歷史記錄中,比如有的命令里面包括了敏感信息如密碼等。在新版本的shell
中,通常我們可以在輸入的命令前面加入空格,這樣shell
就不會記錄這樣的命令,當然,如果你的發行版本默認并不支持,你可以配置環境變量來打開這個功能:
export HISTIGNORE="[ \t]*"
例如:
# history 5
1023 ls -l
1024 echo ""
1025 history 5
1026 ls
1027 history 5
# echo "password=123456"
password=123456
# history 5
1025 history 5
1026 ls
1027 history 5
1028 echo "password=123456"
1029 history 5
# export HISTIGNORE="[ \t]*"
# history 5
1027 history 5
1028 echo "password=123456"
1029 history 5
1030 export HISTIGNORE="[ \t]*"
1031 history 5
# echo "password=123456"
password=123456
# history 5
1027 history 5
1028 echo "password=123456"
1029 history 5
1030 export HISTIGNORE="[ \t]*"
1031 history 5
可見,在設置$HISTIGNORE
變量之后,在前面加了空格的命令將不再記錄。這在保護敏感信息的時候非常有用。
文件匹配(File Globbing)
文件匹配(File Globbing)又成為動態文件名生成,用它可以非常方便的在shell
中輸入文件名。
*
星號
*
星號在shell
中用來匹配任意數量的字符,比如文件名File*.mp4
,將匹配以File
開頭,.mp4
結尾的任何文件名。shell
在掃描解釋命令的時候會自動去查找符合該匹配的所有文件或目錄。當然,你也可以只用*
來匹配所有的文件及目錄,但請注意,只使用*
跟不帶*
還是有所區別的,
# ls
definition.yaml example __init__.py tags.yaml test.py test_sample.html test_sample.py
# ls *
definition.yaml __init__.py tags.yaml test.py test_sample.html test_sample.py
example:
testcase
可見,帶上*
后不僅把當前目錄的所有文件及目錄顯示出來,而且還把目錄下的內容顯示出來了。
?
問號
問號用來匹配一個字符,如File?.mp4
可以匹配File1.mp4
。
[]
方括號
[]
方括號也用來匹配一個字符,但是在括號里面可以指定一個字符集用來限定匹配的字符必須在該字符集內,字符集里面的字符順序沒有關系。
# ls
file1 file2 file3 File4 File55 FileA fileab Fileab FileAB fileabc
# ls File[5A]
FileA
# ls File[A5]
FileA
# ls File[A5][5b]
File55
如果需要匹配不在某個字符集里面的字符,可以在[]
第一個字符加入!
:
# ls file[!5]*
file1 file2 file3 fileab fileabc
特別的,為了方便,[]
中可以使用-
來定義一些連續的字符集(Range匹配),常用的這類字符集包括:
字符集 | 說明 |
---|---|
0-9 | 表示數字字符集 |
a-z | 表示小寫字母字符集 |
A-Z | 表示大寫字母字符集 |
當然,你也不必要把所有范圍都包括在內,如[a-d]
可以用來限定從a
到d
的小寫字母集。另外,用-
連起來的字符集還可以跟其它字符集一起使用,如[a-d_]
表示a
到d
的小寫字母加上_
所組成的字符集。
-
Range匹配的大小寫問題
對于
[]
的Range匹配,還有一點很重要。在很多發行版本中,默認情況下,[]
的Range匹配是忽略大小寫的# ls Test1 test2 # ls [a-z]* Test1 test2 # ls [A-Z]* Test1 test2 # ls [t]* test2 # ls [T]* Test1
注意,是
[]
的Range匹配會忽略大小寫,而如果不是Range匹配還是大小寫敏感的:# ls Test1 test2 # ls [T]* Test1 # ls [t]* test2
如果需要大小寫敏感,可以設置環境變量
LC_ALL
:# LC_ALL=C # ls [a-z]* test2 # ls [A-Z]* Test1
當然,請務必注意,
LC_ALL
的會改變當前的語言環境,還請慎重使用,建議只在臨時的子shell
中使用。
阻止文件匹配(File Globbing)
有時候我們就是需要輸出*
等匹配符號,這個時候就需要阻止shell
做相應的匹配。可以使用轉義符號\
來做到這點,或者將匹配符號放在引號中:
# echo *
Test1 test2
# echo \*
*
# echo '*'
*
# echo "*"
*
本博文還可以在博主個人主頁中找到。