摘抄自廖雪峰
變量
環(huán)境變量
env
命令或printenv
命令,可以顯示所有環(huán)境變量痹雅。
$ env
# 或者
$ printenv
下面是一些常見的環(huán)境變量仰担。
-
BASHPID
:Bash 進程的進程 ID。 -
BASHOPTS
:當前 Shell 的參數(shù)绩社,可以用shopt
命令修改摔蓝。 -
DISPLAY
:圖形環(huán)境的顯示器名字,通常是:0
愉耙,表示 X Server 的第一個顯示器贮尉。 -
EDITOR
:默認的文本編輯器。 -
HOME
:用戶的主目錄朴沿。 -
HOST
:當前主機的名稱猜谚。 -
IFS
:詞與詞之間的分隔符,默認為空格悯仙。 -
LANG
:字符集以及語言編碼龄毡,比如zh_CN.UTF-8
。 -
PATH
:由冒號分開的目錄列表锡垄,當輸入可執(zhí)行程序名后沦零,會搜索這個目錄列表。 -
PS1
:Shell 提示符货岭。 -
PS2
: 輸入多行命令時路操,次要的 Shell 提示符。 -
PWD
:當前工作目錄千贯。 -
RANDOM
:返回一個0到32767之間的隨機數(shù)屯仗。 -
SHELL
:Shell 的名字。 -
SHELLOPTS
:啟動當前 Shell 的set
命令的參數(shù)搔谴。 -
TERM
:終端類型名魁袜,即終端仿真器所用的協(xié)議。 -
UID
:當前用戶的 ID 編號。 -
USER
:當前用戶的用戶名峰弹。
很多環(huán)境變量很少發(fā)生變化店量,而且是只讀的,可以視為常量鞠呈。由于它們的變量名全部都是大寫融师,所以傳統(tǒng)上,如果用戶要自己定義一個常量蚁吝,也會使用全部大寫的變量名旱爆。
查看單個環(huán)境變量的值,可以使用printenv
命令或echo
命令窘茁。
$ printenv PATH
# 或者
$ echo $PATH
注意怀伦,printenv
命令后面的變量名,不用加前綴$
。
自定義變量
自定義變量是用戶在當前 Shell 里面自己定義的變量,僅在當前 Shell 可用桑腮。一旦退出當前 Shell,該變量就不存在了吴攒。
set
命令可以顯示所有變量(包括環(huán)境變量和自定義變量),以及所有的 Bash 函數(shù)砂蔽。
$ set
創(chuàng)建變量
用戶創(chuàng)建變量的時候洼怔,變量名必須遵守下面的規(guī)則。
- 字母左驾、數(shù)字和下劃線字符組成镣隶。
- 第一個字符必須是一個字母或一個下劃線,不能是數(shù)字诡右。
- 不允許出現(xiàn)空格和標點符號安岂。
變量聲明的語法如下。
variable=value
上面命令中帆吻,等號左邊是變量名域那,右邊是變量。注意猜煮,等號兩邊不能有空格次员。(于 python 不同,= 兩邊有空格)
如果變量的值包含空格王带,則必須將值放在引號中淑蔚。
myvar="hello world"
Bash 沒有數(shù)據(jù)類型的概念,所有的變量值都是字符串愕撰。
下面是一些自定義變量的例子刹衫。
a=z # 變量 a 賦值為字符串 z
b="a string" # 變量值包含空格醋寝,就必須放在引號里面
c="a string and $b" # 變量值可以引用其他變量的值
d="\t\ta string\n" # 變量值可以使用轉(zhuǎn)義字符
e=$(ls -l foo.txt) # 變量值可以是命令的執(zhí)行結(jié)果
f=$((5 * 7)) # 變量值可以是數(shù)學運算的結(jié)果
變量可以重復(fù)賦值,后面的賦值會覆蓋前面的賦值绪妹。
$ foo=1
$ foo=2
$ echo $foo
2
上面例子中甥桂,變量foo
的第二次賦值會覆蓋第一次賦值柿究。
如果同一行定義多個變量邮旷,必須使用分號(;
)分隔。
$ foo=1;bar=2
上面例子中蝇摸,同一行定義了foo
和bar
兩個變量婶肩。
讀取變量
讀取變量的時候,直接在變量名前加上$
就可以了貌夕。
$ foo=bar
$ echo $foo
bar
每當 Shell 看到以$
開頭的單詞時律歼,就會嘗試讀取這個變量名對應(yīng)的值。
如果變量不存在啡专,Bash 不會報錯险毁,而會輸出空字符。
讀取變量的時候们童,變量名也可以使用花括號{}
包圍畔况,比如$a
也可以寫成${a}
。這種寫法可以用于變量名與其他字符連用的情況慧库。
$ a=foo
$ echo $a_file
$ echo ${a}_file
foo_file
上面代碼中跷跪,變量名a_file
不會有任何輸出,因為 Bash 將其整個解釋為變量齐板,而這個變量是不存在的吵瞻。只有用花括號區(qū)分$a
,Bash 才能正確解讀甘磨。
事實上橡羞,讀取變量的語法$foo
,可以看作是${foo}
的簡寫形式济舆。
如果變量的值本身也是變量卿泽,可以使用${!varname}
的語法,讀取最終的值吗冤。
$ myvar=USER
$ echo ${!myvar}
ruanyf
上面的例子中又厉,變量myvar
的值是USER
,${!myvar}
的寫法將其展開成最終的值椎瘟。
如果變量值包含連續(xù)空格(或制表符和換行符)覆致,最好放在雙引號里面讀取。
$ a="1 2 3"
$ echo $a
1 2 3
$ echo "$a"
1 2 3
上面示例中肺蔚,變量a
的值包含兩個連續(xù)空格煌妈。如果直接讀取,Shell 會將連續(xù)空格合并成一個。只有放在雙引號里面讀取璧诵,才能保持原來的格式汰蜘。
刪除變量
unset
命令用來刪除一個變量。
unset NAME
這個命令不是很有用之宿。因為不存在的 Bash 變量一律等于空字符串族操,所以即使unset
命令刪除了變量,還是可以讀取這個變量比被,值為空字符串色难。
所以,刪除一個變量等缀,也可以將這個變量設(shè)成空字符串枷莉。
$ foo=''
$ foo=
上面兩種寫法,都是刪除了變量foo
尺迂。由于不存在的值默認為空字符串笤妙,所以后一種寫法可以在等號右邊不寫任何值。
輸出變量噪裕,export 命令
用戶創(chuàng)建的變量僅可用于當前 Shell蹲盘,子 Shell 默認讀取不到父 Shell 定義的變量。為了把變量傳遞給子 Shell州疾,需要使用export
命令辜限。這樣輸出的變量,對于子 Shell 來說就是環(huán)境變量严蓖。
export
命令用來向子 Shell 輸出變量薄嫡。
NAME=foo
export NAME
上面命令輸出了變量NAME
。變量的賦值和輸出也可以在一個步驟中完成颗胡。
export NAME=value
上面命令執(zhí)行后毫深,當前 Shell 及隨后新建的子 Shell,都可以讀取變量$NAME
毒姨。
子 Shell 如果修改繼承的變量哑蔫,不會影響父 Shell。
# 輸出變量 $foo
$ export foo=bar
# 新建子 Shell
$ bash
# 讀取 $foo
$ echo $foo
bar
# 修改繼承的變量
$ foo=baz
# 退出子 Shell
$ exit
# 讀取 $foo
$ echo $foo
bar
上面例子中弧呐,子 Shell 修改了繼承的變量$foo
闸迷,對父 Shell 沒有影響。
特殊變量
Bash 提供一些特殊變量俘枫。這些變量的值由 Shell 提供腥沽,用戶不能進行賦值。
(1)$?
$?
為上一個命令的退出碼鸠蚪,用來判斷上一個命令是否執(zhí)行成功今阳。返回值是0
师溅,表示上一個命令執(zhí)行成功;如果不是零盾舌,表示上一個命令執(zhí)行失敗墓臭。
$ ls doesnotexist
ls: doesnotexist: No such file or directory
$ echo $?
1
上面例子中,ls
命令查看一個不存在的文件妖谴,導(dǎo)致報錯窿锉。$?
為1,表示上一個命令執(zhí)行失敗窖维。
(2)$$
$$
為當前 Shell 的進程 ID榆综。
$ echo $$
10662
這個特殊變量可以用來命名臨時文件。
LOGFILE=/tmp/output_log.$$
(3)$_
$_
為上一個命令的最后一個參數(shù)铸史。
$ grep dictionary /usr/share/dict/words
dictionary
$ echo $_
/usr/share/dict/words
(4)$!
$!
為最近一個后臺執(zhí)行的異步命令的進程 ID。
$ firefox &
[1] 11064
$ echo $!
11064
上面例子中怯伊,firefox
是后臺運行的命令琳轿,$!
返回該命令的進程 ID。
(5)$0
$0
為當前 Shell 的名稱(在命令行直接執(zhí)行時)或者腳本名(在腳本中執(zhí)行時)耿芹。
$ echo $0
bash
上面例子中崭篡,$0
返回當前運行的是 Bash。
(6)$-
$-
為當前 Shell 的啟動參數(shù)吧秕。
$ echo $-
himBHs
(7)$@
和$#
$#
表示腳本的參數(shù)數(shù)量琉闪,$@
表示腳本的參數(shù)值,參見腳本一章砸彬。
變量的默認值
Bash 提供四個特殊語法颠毙,跟變量的默認值有關(guān),目的是保證變量不為空砂碉。
${varname:-word}
上面語法的含義是蛀蜜,如果變量varname
存在且不為空,則返回它的值增蹭,否則返回word
滴某。它的目的是返回一個默認值,比如${count:-0}
表示變量count
不存在時返回0
滋迈。
${varname:=word}
上面語法的含義是霎奢,如果變量varname
存在且不為空,則返回它的值饼灿,否則將它設(shè)為word
幕侠,并且返回word
。它的目的是設(shè)置變量的默認值赔退,比如${count:=0}
表示變量count
不存在時返回0
橙依,且將count
設(shè)為0
证舟。
${varname:+word}
上面語法的含義是,如果變量名存在且不為空窗骑,則返回word
女责,否則返回空值。它的目的是測試變量是否存在创译,比如${count:+1}
表示變量count
存在時返回1
(表示true
)抵知,否則返回空值。
${varname:?message}
上面語法的含義是软族,如果變量varname
存在且不為空刷喜,則返回它的值,否則打印出varname: message
立砸,并中斷腳本的執(zhí)行掖疮。如果省略了message
,則輸出默認的信息“parameter null or not set.”颗祝。它的目的是防止變量未定義浊闪,比如${count:?"undefined!"}
表示變量count
未定義時就中斷執(zhí)行,拋出錯誤螺戳,返回給定的報錯信息undefined!
搁宾。
上面四種語法如果用在腳本中,變量名的部分可以用數(shù)字1
到9
倔幼,表示腳本的參數(shù)盖腿。
filename=${1:?"filename missing."}
上面代碼出現(xiàn)在腳本中,1
表示腳本的第一個參數(shù)损同。如果該參數(shù)不存在翩腐,就退出腳本并報錯。
declare 命令
declare
命令可以聲明一些特殊類型的變量揖庄,為變量設(shè)置一些限制栗菜,比如聲明只讀類型的變量和整數(shù)類型的變量。
它的語法形式如下蹄梢。
declare OPTION VARIABLE=value
declare
命令的主要參數(shù)(OPTION)如下疙筹。
-
-a
:聲明數(shù)組變量。 -
-f
:輸出所有函數(shù)定義禁炒。 -
-F
:輸出所有函數(shù)名而咆。 -
-i
:聲明整數(shù)變量。 -
-l
:聲明變量為小寫字母幕袱。 -
-p
:查看變量信息暴备。 -
-r
:聲明只讀變量。 -
-u
:聲明變量為大寫字母们豌。 -
-x
:該變量輸出為環(huán)境變量涯捻。
declare
命令如果用在函數(shù)中浅妆,聲明的變量只在函數(shù)內(nèi)部有效,等同于local
命令障癌。
不帶任何參數(shù)時凌外,declare
命令輸出當前環(huán)境的所有變量,包括函數(shù)在內(nèi)涛浙,等同于不帶有任何參數(shù)的set
命令康辑。
$ declare
(1)-i
參數(shù)
-i
參數(shù)聲明整數(shù)變量以后,可以直接進行數(shù)學運算轿亮。
$ declare -i val1=12 val2=5
$ declare -i result
$ result=val1*val2
$ echo $result
60
上面例子中疮薇,如果變量result
不聲明為整數(shù),val1*val2
會被當作字面量我注,不會進行整數(shù)運算按咒。另外,val1
和val2
其實不需要聲明為整數(shù)仓手,因為只要result
聲明為整數(shù)胖齐,它的賦值就會自動解釋為整數(shù)運算。
注意嗽冒,一個變量聲明為整數(shù)以后,依然可以被改寫為字符串补履。
$ declare -i var=12
$ var=foo
$ echo $var
0
上面例子中添坊,變量var
聲明為整數(shù),覆蓋以后箫锤,Bash 不會報錯贬蛙,但會賦以不確定的值,上面的例子中可能輸出0谚攒,也可能輸出的是3阳准。
(2)-x
參數(shù)
-x
參數(shù)等同于export
命令,可以輸出一個變量為子 Shell 的環(huán)境變量馏臭。
$ declare -x foo
# 等同于
$ export foo
(3)-r
參數(shù)
-r
參數(shù)可以聲明只讀變量野蝇,無法改變變量值,也不能unset
變量括儒。
$ declare -r bar=1
$ bar=2
bash: bar:只讀變量
$ echo $?
1
$ unset bar
bash: bar:只讀變量
$ echo $?
1
上面例子中绕沈,后兩個賦值語句都會報錯,命令執(zhí)行失敗帮寻。
(4)-u
參數(shù)
-u
參數(shù)聲明變量為大寫字母乍狐,可以自動把變量值轉(zhuǎn)成大寫字母。
$ declare -u foo
$ foo=upper
$ echo $foo
UPPER
(5)-l
參數(shù)
-l
參數(shù)聲明變量為小寫字母固逗,可以自動把變量值轉(zhuǎn)成小寫字母浅蚪。
$ declare -l bar
$ bar=LOWER
$ echo $bar
lower
(6)-p
參數(shù)
-p
參數(shù)輸出變量信息藕帜。
$ foo=hello
$ declare -p foo
declare -- foo="hello"
$ declare -p bar
bar:未找到
上面例子中,declare -p
可以輸出已定義變量的值惜傲,對于未定義的變量洽故,會提示找不到。
如果不提供變量名操漠,declare -p
輸出所有變量的信息收津。
$ declare -p
(7)-f
參數(shù)
-f
參數(shù)輸出當前環(huán)境的所有函數(shù),包括它的定義浊伙。
$ declare -f
(8)-F
參數(shù)
-F
參數(shù)輸出當前環(huán)境的所有函數(shù)名撞秋,不包含函數(shù)定義。
$ declare -F
readonly 命令
readonly
命令等同于declare -r
嚣鄙,用來聲明只讀變量吻贿,不能改變變量值,也不能unset
變量哑子。
$ readonly foo=1
$ foo=2
bash: foo:只讀變量
$ echo $?
1
上面例子中舅列,更改只讀變量foo
會報錯,命令執(zhí)行失敗卧蜓。
readonly
命令有三個參數(shù)帐要。
-
-f
:聲明的變量為函數(shù)名。 -
-p
:打印出所有的只讀變量弥奸。 -
-a
:聲明的變量為數(shù)組榨惠。
Bash 啟動環(huán)境
Session
用戶每次使用 Shell,都會開啟一個與 Shell 的 Session(對話)盛霎。
Session 有兩種類型:登錄 Session 和非登錄 Session赠橙,也可以叫做 login shell 和 non-login shell。
登錄 Session
登錄 Session 是用戶登錄系統(tǒng)以后愤炸,系統(tǒng)為用戶開啟的原始 Session期揪,通常需要用戶輸入用戶名和密碼進行登錄。
登錄 Session 一般進行整個系統(tǒng)環(huán)境的初始化规个,啟動的初始化腳本依次如下凤薛。
-
/etc/profile
:所有用戶的全局配置腳本。 -
/etc/profile.d
目錄里面所有.sh
文件 -
~/.bash_profile
:用戶的個人配置腳本绰姻。如果該腳本存在枉侧,則執(zhí)行完就不再往下執(zhí)行。 -
~/.bash_login
:如果~/.bash_profile
沒找到狂芋,則嘗試執(zhí)行這個腳本(C shell 的初始化腳本)榨馁。如果該腳本存在,則執(zhí)行完就不再往下執(zhí)行帜矾。 -
~/.profile
:如果~/.bash_profile
和~/.bash_login
都沒找到翼虫,則嘗試讀取這個腳本(Bourne shell 和 Korn shell 的初始化腳本)屑柔。
Linux 發(fā)行版更新的時候,會更新/etc
里面的文件珍剑,比如/etc/profile
掸宛,因此不要直接修改這個文件。如果想修改所有用戶的登陸環(huán)境招拙,就在/etc/profile.d
目錄里面新建.sh
腳本唧瘾。
如果想修改你個人的登錄環(huán)境,一般是寫在~/.bash_profile
里面别凤。下面是一個典型的.bash_profile
文件饰序。
# .bash_profile
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
PATH=$PATH:$HOME/bin
SHELL=/bin/bash
MANPATH=/usr/man:/usr/X11/man
EDITOR=/usr/bin/vi
PS1='\h:\w\$ '
PS2='> '
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
export PATH
export EDITOR
可以看到,這個腳本定義了一些最基本的環(huán)境變量规哪,然后執(zhí)行了~/.bashrc
求豫。
bash
命令的--login
參數(shù),會強制執(zhí)行登錄 Session 會執(zhí)行的腳本诉稍。
$ bash --login
bash
命令的--noprofile
參數(shù)蝠嘉,會跳過上面這些 Profile 腳本。
$ bash --noprofile
非登錄 Session
非登錄 Session 是用戶進入系統(tǒng)以后杯巨,手動新建的 Session蚤告,這時不會進行環(huán)境初始化。比如服爷,在命令行執(zhí)行bash
命令罩缴,就會新建一個非登錄 Session。
非登錄 Session 的初始化腳本依次如下层扶。
-
/etc/bash.bashrc
:對全體用戶有效。 -
~/.bashrc
:僅對當前用戶有效烙荷。
對用戶來說镜会,~/.bashrc
通常是最重要的腳本。非登錄 Session 默認會執(zhí)行它终抽,而登錄 Session 一般也會通過調(diào)用執(zhí)行它戳表。每次新建一個 Bash 窗口,就相當于新建一個非登錄 Session昼伴,所以~/.bashrc
每次都會執(zhí)行匾旭。注意,執(zhí)行腳本相當于新建一個非互動的 Bash 環(huán)境圃郊,但是這種情況不會調(diào)用~/.bashrc
价涝。
bash
命令的--norc
參數(shù),可以禁止在非登錄 Session 執(zhí)行~/.bashrc
腳本持舆。
$ bash --norc
bash
命令的--rcfile
參數(shù)色瘩,指定另一個腳本代替.bashrc
伪窖。
$ bash --rcfile testrc
.bash_logout
~/.bash_logout
腳本在每次退出 Session 時執(zhí)行,通常用來做一些清理工作和記錄工作居兆,比如刪除臨時文件覆山,記錄用戶在本次 Session 花費的時間。
如果沒有退出時要執(zhí)行的命令泥栖,這個文件也可以不存在簇宽。
啟動選項
為了方便 Debug,有時在啟動 Bash 的時候吧享,可以加上啟動參數(shù)魏割。
-
-n
:不運行腳本,只檢查是否有語法錯誤耙蔑。 -
-v
:輸出每一行語句運行結(jié)果前见妒,會先輸出該行語句。 -
-x
:每一個命令處理之前甸陌,先輸出該命令须揣,再執(zhí)行該命令。
$ bash -n scriptname
$ bash -v scriptname
$ bash -x scriptname
鍵盤綁定
Bash 允許用戶定義自己的快捷鍵钱豁。全局的鍵盤綁定文件默認為/etc/inputrc
耻卡,你可以在主目錄創(chuàng)建自己的鍵盤綁定文件.inputrc
文件。如果定義了這個文件牲尺,需要在其中加入下面這行卵酪,保證全局綁定不會被遺漏。
$include /etc/inputrc
.inputrc
文件里面的快捷鍵谤碳,可以像這樣定義溃卡,"\C-t":"pwd\n"
表示將Ctrl + t
綁定為運行pwd
命令。
模式擴展
Shell 接收到用戶輸入的命令以后蜒简,會根據(jù)空格將用戶的輸入瘸羡,拆分成一個個詞元(token)。然后搓茬,Shell 會擴展詞元里面的特殊字符犹赖,擴展完成后才會調(diào)用相應(yīng)的命令。
這種特殊字符的擴展卷仑,稱為模式擴展(globbing)峻村。其中有些用到通配符,又稱為通配符擴展(wildcard expansion)锡凝。Bash 一共提供八種擴展粘昨。
- 波浪線擴展
-
?
字符擴展 -
*
字符擴展 - 方括號擴展
- 大括號擴展
- 變量擴展
- 子命令擴展
- 算術(shù)擴展
本章介紹這八種擴展。
Bash 是先進行擴展,再執(zhí)行命令雾棺。因此膊夹,擴展的結(jié)果是由 Bash 負責的,與所要執(zhí)行的命令無關(guān)捌浩。命令本身并不存在參數(shù)擴展放刨,收到什么參數(shù)就原樣執(zhí)行。這一點務(wù)必需要記住尸饺。
模塊擴展的英文單詞是globbing
进统,這個詞來自于早期的 Unix 系統(tǒng)有一個/etc/glob
文件,保存擴展的模板浪听。后來 Bash 內(nèi)置了這個功能螟碎,但是這個名字就保留了下來。
模式擴展與正則表達式的關(guān)系是迹栓,模式擴展早于正則表達式出現(xiàn)掉分,可以看作是原始的正則表達式。它的功能沒有正則那么強大靈活克伊,但是優(yōu)點是簡單和方便酥郭。
Bash 允許用戶關(guān)閉擴展。
$ set -o noglob
# 或者
$ set -f
下面的命令可以重新打開擴展愿吹。
$ set +o noglob
# 或者
$ set +f
波浪線擴展
波浪線~
會自動擴展成當前用戶的主目錄不从。
$ echo ~
/home/me
~/dir
表示擴展成主目錄的某個子目錄,dir
是主目錄里面的一個子目錄名犁跪。
# 進入 /home/me/foo 目錄
$ cd ~/foo
~user
表示擴展成用戶user
的主目錄椿息。
$ echo ~foo
/home/foo
$ echo ~root
/root
上面例子中,Bash 會根據(jù)波浪號后面的用戶名坷衍,返回該用戶的主目錄寝优。
如果~user
的user
是不存在的用戶名,則波浪號擴展不起作用枫耳。
$ echo ~nonExistedUser
~nonExistedUser
?
字符擴展
?
字符代表文件路徑里面的任意單個字符倡勇,不包括空字符。比如嘉涌,Data???
匹配所有Data
后面跟著三個字符的文件名。
# 存在文件 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt
*
字符擴展
*
字符代表文件路徑里面的任意數(shù)量的任意字符夸浅,包括零個字符仑最。
# 存在文件 a.txt、b.txt 和 ab.txt
$ ls *.txt
a.txt b.txt ab.txt
上面例子中帆喇,*.txt
代表后綴名為.txt
的所有文件警医。
方括號擴展
方括號擴展的形式是[...]
,只有文件確實存在的前提下才會擴展。如果文件不存在预皇,就會原樣輸出。括號之中的任意一個字符。比如匀钧,[aeiou]
可以匹配五個元音字母中的任意一個梅鹦。
# 存在文件 a.txt 和 b.txt
$ ls [ab].txt
a.txt b.txt
# 只存在文件 a.txt
$ ls [ab].txt
a.txt
上面例子中,[ab]
可以匹配a
或b
鲁豪,前提是確實存在相應(yīng)的文件潘悼。
[start-end] 擴展
方括號擴展有一個簡寫形式[start-end]
,表示匹配一個連續(xù)的范圍爬橡。比如治唤,[a-c]
等同于[abc]
,[0-9]
匹配[0123456789]
糙申。
# 存在文件 a.txt宾添、b.txt 和 c.txt
$ ls [a-c].txt
a.txt
b.txt
c.txt
# 存在文件 report1.txt、report2.txt 和 report3.txt
$ ls report[0-9].txt
report1.txt
report2.txt
report3.txt
...
下面是一些常用簡寫的例子柜裸。
-
[a-z]
:所有小寫字母缕陕。 -
[a-zA-Z]
:所有小寫字母與大寫字母。 -
[a-zA-Z0-9]
:所有小寫字母粘室、大寫字母與數(shù)字榄檬。 -
[abc]*
:所有以a
、b
衔统、c
字符之一開頭的文件名鹿榜。 -
program.[co]
:文件program.c
與文件program.o
。 -
BACKUP.[0-9][0-9][0-9]
:所有以BACKUP.
開頭锦爵,后面是三個數(shù)字的文件名舱殿。
這種簡寫形式有一個否定形式[!start-end]
,表示匹配不屬于這個范圍的字符险掀。比如沪袭,[!a-zA-Z]
表示匹配非英文字母的字符。
$ ls report[!1–3].txt
report4.txt report5.txt
上面代碼中樟氢,[!1-3]
表示排除1冈绊、2和3。
大括號擴展
大括號擴展{...}
表示分別擴展成大括號里面的所有值埠啃,各個值之間使用逗號分隔死宣。比如,{1,2,3}
擴展成1 2 3
碴开。
$ echo {1,2,3}
1 2 3
$ echo d{a,e,i,u,o}g
dag deg dig dug dog
$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back
注意毅该,大括號擴展不是文件名擴展博秫。它會擴展成所有給定的值,而不管是否有對應(yīng)的文件存在眶掌。
$ ls {a,b,c}.txt
ls: 無法訪問'a.txt': 沒有那個文件或目錄
ls: 無法訪問'b.txt': 沒有那個文件或目錄
ls: 無法訪問'c.txt': 沒有那個文件或目錄
上面例子中挡育,即使不存在對應(yīng)的文件,{a,b,c}
依然擴展成三個文件名朴爬,導(dǎo)致ls
命令報了三個錯誤即寒。
另一個需要注意的地方是,大括號內(nèi)部的逗號前后不能有空格寝殴。否則蒿叠,大括號擴展會失效。
$ echo {1 , 2}
{1 , 2}
上面例子中蚣常,逗號前后有空格市咽,Bash 就會認為這不是大括號擴展,而是三個獨立的參數(shù)抵蚊。
逗號前面可以沒有值施绎,表示擴展的第一項為空。
$ cp a.log{,.bak}
# 等同于
# cp a.log a.log.bak
{start..end} 擴展
大括號擴展有一個簡寫形式{start..end}
贞绳,表示擴展成一個連續(xù)序列谷醉。比如,{a..z}
可以擴展成26個小寫英文字母冈闭。
$ echo {a..c}
a b c
$ echo d{a..d}g
dag dbg dcg ddg
$ echo {1..4}
1 2 3 4
這個寫法的另一個常見用途俱尼,是直接用于for
循環(huán)。
for i in {1..4}
do
echo $i
done
上面例子會循環(huán)4次萎攒。
這種簡寫形式還可以使用第二個雙點號(start..end..step
)遇八,用來指定擴展的步長。
$ echo {0..8..2}
0 2 4 6 8
上面代碼將0
擴展到8
耍休,每次遞增的長度為2
刃永,所以一共輸出5個數(shù)字。
多個簡寫形式連用羊精,會有循環(huán)處理的效果斯够。
$ echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3
變量擴展
Bash 將美元符號$
開頭的詞元視為變量,將其擴展成變量值喧锦。
$ echo $SHELL
/bin/bash
變量名除了放在美元符號后面读规,也可以放在${}
里面。
$ echo ${SHELL}
/bin/bash
${!string*}
或${!string@}
返回所有匹配給定字符串string
的變量名燃少。
$ echo ${!S*}
SECONDS SHELL SHELLOPTS SHLVL SSH_AGENT_PID SSH_AUTH_SOCK
上面例子中掖桦,${!S*}
擴展成所有以S
開頭的變量名。
子命令擴展
$(...)
可以擴展成另一個命令的運行結(jié)果供汛,該命令的所有輸出都會作為返回值。
$ echo $(date)
Tue Jan 28 00:01:13 CST 2020
上面例子中,$(date)
返回date
命令的運行結(jié)果怔昨。
還有另一種較老的語法雀久,子命令放在反引號之中,也可以擴展成命令的運行結(jié)果趁舀。
$ echo `date`
Tue Jan 28 00:01:13 CST 2020
$(...)
可以嵌套赖捌,比如$(ls $(pwd))
。
算術(shù)擴展
$((...))
可以擴展成整數(shù)運算的結(jié)果矮烹。
$ echo $((2 + 2))
4
字符類
[[:class:]]
表示一個字符類越庇,擴展成某一類特定字符之中的一個。常用的字符類如下奉狈。
-
[[:alnum:]]
:匹配任意英文字母與數(shù)字 -
[[:alpha:]]
:匹配任意英文字母 -
[[:blank:]]
:空格和 Tab 鍵卤唉。 -
[[:cntrl:]]
:ASCII 碼 0-31 的不可打印字符。 -
[[:digit:]]
:匹配任意數(shù)字 0-9仁期。 -
[[:graph:]]
:A-Z桑驱、a-z、0-9 和標點符號跛蛋。 -
[[:lower:]]
:匹配任意小寫字母 a-z熬的。 -
[[:print:]]
:ASCII 碼 32-127 的可打印字符。 -
[[:punct:]]
:標點符號(除了 A-Z赊级、a-z押框、0-9 的可打印字符)。 -
[[:space:]]
:空格理逊、Tab橡伞、LF(10)、VT(11)挡鞍、FF(12)骑歹、CR(13)。 -
[[:upper:]]
:匹配任意大寫字母 A-Z墨微。 -
[[:xdigit:]]
:16進制字符(A-F道媚、a-f、0-9)翘县。
請看下面的例子最域。
$ echo [[:upper:]]*
A.txt
上面命令輸出所有大寫字母開頭的文件名。
字符類的第一個方括號后面锈麸,可以加上感嘆號!
镀脂,表示否定。比如忘伞,[![:digit:]]
匹配所有非數(shù)字薄翅。
$ echo [![:digit:]]*
上面命令輸出所有不以數(shù)字開頭的文件名沙兰。
字符類也屬于文件名擴展,如果沒有匹配的文件名翘魄,字符類就會原樣輸出鼎天。
# 不存在以大寫字母開頭的文件
$ echo [[:upper:]]*
[[:upper:]]*
上面例子中,由于沒有可匹配的文件暑竟,字符類就原樣輸出了斋射。
量詞語法
量詞語法用來控制模式匹配的次數(shù)。它只有在 Bash 的extglob
參數(shù)打開的情況下才能使用但荤,不過一般是默認打開的罗岖。下面的命令可以查詢。
$ shopt extglob
extglob on
如果extglob
參數(shù)是關(guān)閉的腹躁,可以用下面的命令打開桑包。
$ shopt -s extglob
量詞語法有下面幾個。
-
?(pattern-list)
:模式匹配零次或一次潜慎。 -
*(pattern-list)
:模式匹配零次或多次捡多。 -
+(pattern-list)
:模式匹配一次或多次。 -
@(pattern-list)
:只匹配一次模式铐炫。 -
!(pattern-list)
:匹配給定模式以外的任何內(nèi)容垒手。
$ ls abc?(.)txt
abctxt abc.txt
上面例子中,?(.)
匹配零個或一個點倒信。
$ ls abc?(def)
abc abcdef
上面例子中科贬,?(def)
匹配零個或一個def
。
$ ls abc@(.txt|.php)
abc.php abc.txt
上面例子中鳖悠,@(.txt|.php)
匹配文件有且只有一個.txt
或.php
后綴名榜掌。
$ ls abc+(.txt)
abc.txt abc.txt.txt
上面例子中,+(.txt)
匹配文件有一個或多個.txt
后綴名乘综。
$ ls a!(b).txt
a.txt abb.txt ac.txt
上面例子中憎账,!(b)
表示匹配單個字母b
以外的任意內(nèi)容,所以除了ab.txt
以外卡辰,其他文件名都能匹配胞皱。
量詞語法也屬于文件名擴展,如果不存在可匹配的文件九妈,就會原樣輸出反砌。
# 沒有 abc 開頭的文件名
$ ls abc?(def)
ls: 無法訪問'abc?(def)': 沒有那個文件或目錄
上面例子中,由于沒有可匹配的文件萌朱,abc?(def)
就原樣輸出宴树,導(dǎo)致ls
命令報錯。
引號和 Here 文檔
單引號
Bash 允許字符串放在單引號或雙引號之中晶疼,加以引用酒贬。
單引號用于保留字符的字面含義又憨,各種特殊字符在單引號里面,都會變?yōu)槠胀ㄗ址Ф郑热缧翘枺?code>*)竟块、美元符號($
)、反斜杠(\
)等耐齐。
$ echo '*'
*
$ echo '$USER'
$USER
$ echo '$((2+2))'
$((2+2))
$ echo '$(echo foo)'
$(echo foo)
上面命令中,單引號使得 Bash 擴展蒋情、變量引用埠况、算術(shù)運算和子命令,都失效了棵癣。如果不使用單引號辕翰,它們都會被 Bash 自動擴展。
雙引號
雙引號比單引號寬松狈谊,大部分特殊字符在雙引號里面喜命,都會失去特殊含義,變成普通字符河劝。
$ echo "*"
*
雙引號還有一個作用壁榕,就是保存原始命令的輸出格式。
# 單行輸出
$ echo $(cal)
一月 2020 日 一 二 三 四 五 六 1 2 3 ... 31
# 原始格式輸出
$ echo "$(cal)"
一月 2020
日 一 二 三 四 五 六
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
上面例子中赎瞎,如果$(cal)
不放在雙引號之中牌里,echo
就會將所有結(jié)果以單行輸出,丟棄了所有原始的格式务甥。
Here 文檔
Here 文檔(here document)是一種輸入多行字符串的方法牡辽,格式如下。
<< token
text
token
它的格式分成開始標記(<< token
)和結(jié)束標記(token
)敞临。開始標記是兩個小于號 + Here 文檔的名稱态辛,名稱可以隨意取,后面必須是一個換行符挺尿;結(jié)束標記是單獨一行頂格寫的 Here 文檔名稱奏黑,如果不是頂格,結(jié)束標記不起作用票髓。兩者之間就是多行字符串的內(nèi)容攀涵。
Examples of cat <<EOF
syntax usage in Bash:
1. Assign multi-line string to a shell variable
$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)
The $sql
variable now holds the new-line characters too. You can verify with echo -e "$sql"
.
2. Pass multi-line string to a file in Bash
$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF
The print.sh
file now contains:
#!/bin/bash
echo $PWD
echo /home/user
3. Pass multi-line string to a pipe in Bash
$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF
The b.txt
file contains bar
and baz
lines. The same output is printed to stdout
.
Here 字符串
Here 文檔還有一個變體,叫做 Here 字符串(Here string)洽沟,使用三個小于號(<<<
)表示以故。
<<< string
它的作用是將字符串通過標準輸入,傳遞給命令裆操。
有些命令直接接受給定的參數(shù)怒详,與通過標準輸入接受參數(shù)炉媒,結(jié)果是不一樣的。所以才有了這個語法昆烁,使得將字符串通過標準輸入傳遞給命令更方便吊骤,比如cat
命令只接受標準輸入傳入的字符串。
$ cat <<< 'hi there'
# 等同于
$ echo 'hi there' | cat
上面的第一種語法使用了 Here 字符串静尼,要比第二種語法看上去語義更好白粉,也更簡潔。
$ md5sum <<< 'ddd'
# 等同于
$ echo 'ddd' | md5sum
上面例子中鼠渺,md5sum
命令只能接受標準輸入作為參數(shù)鸭巴,不能直接將字符串放在命令后面,會被當作文件名拦盹,即md5sum ddd
里面的ddd
會被解釋成文件名鹃祖。這時就可以用 Here 字符串,將字符串傳給md5sum
命令普舆。
字符串操作
字符串的長度
獲取字符串長度的語法如下恬口。
${#varname}
下面是一個例子。
$ myPath=/home/cam/book/long.file.name
$ echo ${#myPath}
29
大括號{}
是必需的沼侣,否則 Bash 會將$#
理解成腳本的參數(shù)個數(shù)祖能,將變量名理解成文本。
$ echo $#myvar
0myvar
上面例子中华临,Bash 將$#
和myvar
分開解釋了芯杀。
子字符串
字符串提取子串的語法如下。
${varname:offset:length}
上面語法的含義是返回變量$varname
的子字符串雅潭,從位置offset
開始(從0
開始計算)揭厚,長度為length
。
$ count=frogfootman
$ echo ${count:4:4}
foot
上面例子返回字符串frogfootman
從4號位置開始的長度為4的子字符串foot
扶供。
這種語法不能直接操作字符串筛圆,只能通過變量來讀取字符串,并且不會改變原始字符串椿浓。
# 報錯
$ echo ${"hello":2:3}
上面例子中太援,"hello"
不是變量名,導(dǎo)致 Bash 報錯扳碍。
如果省略length
提岔,則從位置offset
開始,一直返回到字符串的結(jié)尾笋敞。
$ count=frogfootman
$ echo ${count:4}
footman
上面例子是返回變量count
從4號位置一直到結(jié)尾的子字符串碱蒙。
如果offset
為負值,表示從字符串的末尾開始算起。注意赛惩,負數(shù)前面必須有一個空格哀墓, 以防止與${variable:-word}
的變量的設(shè)置默認值語法混淆。這時還可以指定length
喷兼,length
可以是正值篮绰,也可以是負值(負值不能超過offset
的長度)。
$ foo="This string is long."
$ echo ${foo: -5}
long.
$ echo ${foo: -5:2}
lo
$ echo ${foo: -5:-2}
lon
上面例子中季惯,offset
為-5
吠各,表示從倒數(shù)第5個字符開始截取,所以返回long.
勉抓。如果指定長度length
為2
走孽,則返回lo
;如果length
為-2
琳状,表示要排除從字符串末尾開始的2個字符,所以返回lon
盒齿。
搜索和替換
Bash 提供字符串搜索和替換的多種方法念逞。
(1)字符串頭部的模式匹配。
以下兩種語法可以檢查字符串開頭边翁,是否匹配給定的模式翎承。如果匹配成功,就刪除匹配的部分符匾,返回剩下的部分叨咖。原始變量不會發(fā)生變化。
# 如果 pattern 匹配變量 variable 的開頭啊胶,
# 刪除最短匹配(非貪婪匹配)的部分甸各,返回剩余部分
${variable#pattern}
# 如果 pattern 匹配變量 variable 的開頭,
# 刪除最長匹配(貪婪匹配)的部分焰坪,返回剩余部分
${variable##pattern}
上面兩種語法會刪除變量字符串開頭的匹配部分(將其替換為空)趣倾,返回剩下的部分。區(qū)別是一個是最短匹配(又稱非貪婪匹配)某饰,另一個是最長匹配(又稱貪婪匹配)儒恋。
匹配模式pattern
可以使用*
、?
黔漂、[]
等通配符诫尽。
$ myPath=/home/cam/book/long.file.name
$ echo ${myPath#/*/}
cam/book/long.file.name
$ echo ${myPath##/*/}
long.file.name
上面例子中,匹配的模式是/*/
炬守,其中*
可以匹配任意數(shù)量的字符牧嫉,所以最短匹配是/home/
,最長匹配是/home/cam/book/
劳较。
下面寫法可以刪除文件路徑的目錄部分驹止,只留下文件名浩聋。
$ path=/home/cam/book/long.file.name
$ echo ${path##*/}
long.file.name
上面例子中,模式*/
匹配目錄部分臊恋,所以只返回文件名衣洁。
下面再看一個例子。
$ phone="555-456-1414"
$ echo ${phone#*-}
456-1414
$ echo ${phone##*-}
1414
如果匹配不成功抖仅,則返回原始字符串坊夫。
$ phone="555-456-1414"
$ echo ${phone#444}
555-456-1414
上面例子中,原始字符串里面無法匹配模式444
撤卢,所以原樣返回环凿。
如果要將頭部匹配的部分,替換成其他內(nèi)容放吩,采用下面的寫法智听。
# 模式必須出現(xiàn)在字符串的開頭
${variable/#pattern/string}
# 示例
$ foo=JPG.JPG
$ echo ${foo/#JPG/jpg}
jpg.JPG
上面例子中,被替換的JPG
必須出現(xiàn)在字符串頭部渡紫,所以返回jpg.JPG
到推。
(2)字符串尾部的模式匹配。
以下兩種語法可以檢查字符串結(jié)尾惕澎,是否匹配給定的模式莉测。如果匹配成功,就刪除匹配的部分唧喉,返回剩下的部分捣卤。原始變量不會發(fā)生變化。
# 如果 pattern 匹配變量 variable 的結(jié)尾八孝,
# 刪除最短匹配(非貪婪匹配)的部分董朝,返回剩余部分
${variable%pattern}
# 如果 pattern 匹配變量 variable 的結(jié)尾,
# 刪除最長匹配(貪婪匹配)的部分干跛,返回剩余部分
${variable%%pattern}
上面兩種語法會刪除變量字符串結(jié)尾的匹配部分(將其替換為空)益涧,返回剩下的部分。區(qū)別是一個是最短匹配(又稱非貪婪匹配)驯鳖,另一個是最長匹配(又稱貪婪匹配)闲询。
$ path=/home/cam/book/long.file.name
$ echo ${path%.*}
/home/cam/book/long.file
$ echo ${path%%.*}
/home/cam/book/long
上面例子中,匹配模式是.*
浅辙,其中*
可以匹配任意數(shù)量的字符扭弧,所以最短匹配是.name
,最長匹配是.file.name
记舆。
下面寫法可以刪除路徑的文件名部分鸽捻,只留下目錄部分。
$ path=/home/cam/book/long.file.name
$ echo ${path%/*}
/home/cam/book
上面例子中,模式/*
匹配文件名部分御蒲,所以只返回目錄部分衣赶。
下面的寫法可以替換文件的后綴名。
$ file=foo.png
$ echo ${file%.png}.jpg
foo.jpg
上面的例子將文件的后綴名厚满,從.png
改成了.jpg
府瞄。
下面再看一個例子。
$ phone="555-456-1414"
$ echo ${phone%-*}
555-456
$ echo ${phone%%-*}
555
如果匹配不成功碘箍,則返回原始字符串遵馆。
如果要將尾部匹配的部分,替換成其他內(nèi)容丰榴,采用下面的寫法货邓。
# 模式必須出現(xiàn)在字符串的結(jié)尾
${variable/%pattern/string}
# 示例
$ foo=JPG.JPG
$ echo ${foo/%JPG/jpg}
JPG.jpg
上面例子中,被替換的JPG
必須出現(xiàn)在字符串尾部四濒,所以返回JPG.jpg
换况。
(3)任意位置的模式匹配。
以下兩種語法可以檢查字符串內(nèi)部盗蟆,是否匹配給定的模式复隆。如果匹配成功,就刪除匹配的部分姆涩,換成其他的字符串返回。原始變量不會發(fā)生變化惭每。
# 如果 pattern 匹配變量 variable 的一部分骨饿,
# 最長匹配(貪婪匹配)的那部分被 string 替換,但僅替換第一個匹配
${variable/pattern/string}
# 如果 pattern 匹配變量 variable 的一部分台腥,
# 最長匹配(貪婪匹配)的那部分被 string 替換宏赘,所有匹配都替換
${variable//pattern/string}
上面兩種語法都是最長匹配(貪婪匹配)下的替換,區(qū)別是前一個語法僅僅替換第一個匹配黎侈,后一個語法替換所有匹配察署。
$ path=/home/cam/foo/foo.name
$ echo ${path/foo/bar}
/home/cam/bar/foo.name
$ echo ${path//foo/bar}
/home/cam/bar/bar.name
上面例子中,前一個命令只替換了第一個foo
峻汉,后一個命令將兩個foo
都替換了贴汪。
下面的例子將分隔符從:
換成換行符。
$ echo -e ${PATH//:/'\n'}
/usr/local/bin
/usr/bin
/bin
...
上面例子中休吠,echo
命令的-e
參數(shù)扳埂,表示將替換后的字符串的\n
字符,解釋為換行符瘤礁。
模式部分可以使用通配符阳懂。
$ phone="555-456-1414"
$ echo ${phone/5?4/-}
55-56-1414
上面的例子將5-4
替換成-
。
如果省略了string
部分,那么就相當于匹配的部分替換成空字符串岩调,即刪除匹配的部分巷燥。
$ path=/home/cam/foo/foo.name
$ echo ${path/.*/}
/home/cam/foo/foo
上面例子中,第二個斜杠后面的string
部分省略了号枕,所以模式.*
匹配的部分.name
被刪除后返回缰揪。
前面提到過,這個語法還有兩種擴展形式堕澄。
# 模式必須出現(xiàn)在字符串的開頭
${variable/#pattern/string}
# 模式必須出現(xiàn)在字符串的結(jié)尾
${variable/%pattern/string}
改變大小寫
下面的語法可以改變變量的大小寫邀跃。
# 轉(zhuǎn)為大寫
${varname^^}
# 轉(zhuǎn)為小寫
${varname,,}
下面是一個例子。
$ foo=heLLo
$ echo ${foo^^}
HELLO
$ echo ${foo,,}
hello
其它轉(zhuǎn)換大小寫的方法
$ echo "$a" | tr '[:upper:]' '[:lower:]'
hi all
$ echo "$a" | awk '{print tolower($0)}'
hi all
Non-POSIX
You may run into portability issues with the following examples:
$ echo "${a,,}"
hi all
$ echo "$a" | sed -e 's/\(.*\)/\L\1/'
hi all
# this also works:
$ sed -e 's/\(.*\)/\L\1/' <<< "$a"
hi all