shell腳本在日常的Linux系統(tǒng)管理工作中是必不可少的。如果不會(huì)寫(xiě)shell腳本,你就不算是一個(gè)合格的管理員。目前,很多單位在招聘Linux系統(tǒng)管理員時(shí),shell腳本的編寫(xiě)是必考的題目。有的單位甚至用shell腳本的編寫(xiě)能力來(lái)衡量這個(gè)Linux系統(tǒng)管理員的經(jīng)驗(yàn)是否豐富。所以,你必須認(rèn)真學(xué)習(xí)shell腳本并不斷練習(xí)。只要shell腳本寫(xiě)得好,相信你的Linux求職之路就會(huì)輕松得多。
阿銘在這一章中只是帶你進(jìn)入shell腳本的世界,如果你很感興趣,可以到網(wǎng)上下載相關(guān)的資料或者到書(shū)店購(gòu)買(mǎi)shell相關(guān)的圖書(shū)。
在學(xué)習(xí)shell腳本之前,需要你了解很多相關(guān)的知識(shí),這些知識(shí)是編寫(xiě)shell腳本的基礎(chǔ),希望你能夠熟練掌握。
11.1 什么是shell
shell是系統(tǒng)跟計(jì)算機(jī)硬件交互時(shí)使用的中間介質(zhì),它只是系統(tǒng)的一個(gè)工具。實(shí)際上,在shell和計(jì)算機(jī)硬件之間還有一層?xùn)|西——系統(tǒng)內(nèi)核。如果把計(jì)算機(jī)硬件比作一個(gè)人的軀體,那系統(tǒng)內(nèi)核就是人的大腦。至于shell,把它比作人的五官似乎更貼切些。言歸正傳,用戶(hù)直接面對(duì)的不是計(jì)算機(jī)硬件而是shell,用戶(hù)把指令告訴shell,然后shell再傳輸給系統(tǒng)內(nèi)核,接著內(nèi)核再去支配計(jì)算機(jī)硬件去執(zhí)行各種操作。
阿銘接觸的Linux發(fā)布版本(RHEL/Rocky)默認(rèn)安裝的shell版本是bash(即Bourne Again Shell),它是sh(即Bourne Shell)的增強(qiáng)版本。Bourn Shell是最早流行起來(lái)的一個(gè)shell版本。其創(chuàng)始人是Steven Bourne,為了紀(jì)念他而將其命名為BournShell,簡(jiǎn)稱(chēng)sh。那么,這個(gè)bash有什么特點(diǎn)呢?
11.1.1 記錄命令歷史
我們執(zhí)行過(guò)的命令Linux都會(huì)記錄,預(yù)設(shè)可以記錄1000條歷史命令。這些命令保存在用戶(hù)的家目錄的.bash_history文件中。但需要注意的是,只有當(dāng)用戶(hù)正常退出當(dāng)前shell時(shí),在當(dāng)前shell中運(yùn)行的命令才會(huì)保存至.bash_history文件中。那什么情況才算正常退出?敲exit命令或者按Ctrl D快捷鍵都可以正常退出。而意外斷電或者斷網(wǎng)就不算正常退出。
!是與命令歷史有關(guān)的一個(gè)特殊字符,該字符常用的應(yīng)用有以下3個(gè)。
1)!! :連續(xù)兩個(gè)!表示執(zhí)行上一條指令。示例命令如下:
?
# pwd /root # !! pwd /root
?
2)!n :這里的n是數(shù)字,表示執(zhí)行命令歷史中的第n條指令。例如,!1002表示執(zhí)行命令歷史中的第1002個(gè)命令,如下所示:
?
# history |grep 1002 1002 pwd 1015 history |grep 1002 # !1002 pwd /root
?
上例中的history命令如果未改動(dòng)過(guò)環(huán)境變量,默認(rèn)可以把最近執(zhí)行的1000條命令歷史打印出來(lái)。
3)!字符串(字符串大于等于1):例如!pw表示執(zhí)行命令歷史中最近一次以pw開(kāi)頭的命令。示例代碼如下:
?
# !pw pwd /root
?
11.1.2 命令和文件名補(bǔ)全
最開(kāi)始阿銘就介紹過(guò),按tab鍵可以幫我們補(bǔ)全一個(gè)指令、一個(gè)路徑或者一個(gè)文件名。連續(xù)按兩次tab鍵,系統(tǒng)則會(huì)把所有的命令或者文件名都列出來(lái)。
11.1.3 別名
前面的章節(jié)中也曾提到過(guò)alias,它也是bash所特有的功能之一。我們可以通過(guò)alias把一個(gè)常用的并且很長(zhǎng)的指令另取名為一個(gè)簡(jiǎn)單易記的指令。如果不想用了,還可以使用unalias命令解除別名功能。直接執(zhí)行alias命令,會(huì)看到目前系統(tǒng)預(yù)設(shè)的別名,如下所示:
?
#?alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot' alias xzegrep='xzegrep --color=auto' alias xzfgrep='xzfgrep --color=auto' alias xzgrep='xzgrep --color=auto' alias zegrep='zegrep --color=auto' alias zfgrep='zfgrep --color=auto' alias zgrep='zgrep --color=auto'
?
另外,你也可以自定義命令的別名,其格式為alias [命令別名]=['具體的命令'],示例命令如下:
?
#?alias?aming='pwd' #?aming /root #?unalias?aming #?aming bash: aming: command not found... Failed to search for file: Cannot update read-only repo
?
11.1.4 通配符
在bash下,可以使用*來(lái)匹配零個(gè)或多個(gè)字符,用?匹配一個(gè)字符。示例命令如下:
?
#?ls?-d?/tmp/4_6/test* /tmp/4_6/test1 /tmp/4_6/test4 /tmp/4_6/test5 #?touch?/tmp/4_6/test111 #?ls?-d?/tmp/4_6/test? /tmp/4_6/test1 /tmp/4_6/test4 /tmp/4_6/test5
?
11.1.5 輸入/輸出重定向
輸入重定向用于改變命令的輸入,輸出重定向用于改變命令的輸出。輸出重定向更為常用,它經(jīng)常用于將命令的結(jié)果輸入到文件中,而不是屏幕上。輸入重定向的命令是<,輸出重定向的命令是>。另外,還有錯(cuò)誤重定向命令2>以及追加重定向命令>>,示例命令如下:
?
#?mkdir?/tmp/10 #?cd?/tmp/10 #?echo?"123"?>?1.txt #?echo?"123"?>>?1.txt #?cat?1.txt 123 123
?
11.1.6 管道符
前面已經(jīng)提過(guò)管道符|,它用于將前一個(gè)指令的輸出作為后一個(gè)指令的輸入,如下所示:
?
# cat /etc/passwd|wc -l
?
11.1.7 作業(yè)控制
當(dāng)運(yùn)行進(jìn)程時(shí),你可以使它暫停(按Ctrl+Z組合鍵),然后使用fg(foreground的簡(jiǎn)寫(xiě))命令恢復(fù)它,或是利用bg(background的簡(jiǎn)寫(xiě))命令使它到后臺(tái)運(yùn)行。此外,你也可以使它終止(按Ctrl+C組合鍵)。示例命令如下:
?
#?vi?test1.txt testtestsstststst
?
阿銘使用vi命令編輯test1.txt,隨便輸入一些內(nèi)容,按Esc鍵后,使用Ctrl+Z組合鍵暫停任務(wù),如下所示:
?
#?vi?test1.txt [1]+ 已停止 vi test1.txt
?
此時(shí)提示vi test1.txt已經(jīng)停止了,然后使用fg命令恢復(fù)它,此時(shí)又進(jìn)入剛才的vi窗口了。再次使其暫停,然后輸入jobs,可以看到被暫?;蛘咴诤笈_(tái)運(yùn)行的任務(wù),如下所示:
?
#?jobs [1]+ 已停止 vi test1.txt
?
如果想把暫停的任務(wù)放在后臺(tái)重新運(yùn)行,就使用bg命令,如下所示:
?
# bg [1]+ vi test1.txt & [1]+ 已停止 vi test1.txt
?
但是vi似乎并不支持在后臺(tái)運(yùn)行,那阿銘換一個(gè)其他的命令,如下所示:
?
#?vmstat?1?>?/tmp/1.log ^Z //此處按ctrl + z [2]+ 已停止 vmstat 1 > /tmp/1.log #?jobs [1]- 已停止 vi test1.txt [2]+ 已停止 vmstat 1 > /tmp/1.log #?bg?2 [2]+ vmstat 1 > /tmp/1.log &
?
在上面的例子中,又出現(xiàn)了一個(gè)新的知識(shí)點(diǎn),那就是多個(gè)被暫停的任務(wù)會(huì)有編號(hào),使用jobs命令可以看到兩個(gè)任務(wù),使用bg命令或者fg命令時(shí),則需要在后面加編號(hào)。這里阿銘使用命令bg 2把第2個(gè)暫停的任務(wù)放到后臺(tái)重新運(yùn)行(需要在命令后邊加符號(hào)&,且中間有個(gè)空格)。本例中的vmstat 1是用來(lái)觀察系統(tǒng)狀態(tài)的一個(gè)命令,阿銘以后再介紹。
如何關(guān)掉在后臺(tái)運(yùn)行的任務(wù)呢?如果你沒(méi)有退出剛才的shell,那么應(yīng)該先使用命令fg 編號(hào)把任務(wù)調(diào)到前臺(tái),然后按Ctrl+C組合鍵結(jié)束任務(wù)。如下所示:
?
#?fg?2 vmstat 1 > /tmp/1.log ^C //此處按ctrl + c
?
另一種情況則是,關(guān)閉當(dāng)前的shell,再次打開(kāi)另一個(gè)shell時(shí),使用jobs命令并不會(huì)顯示在后臺(tái)運(yùn)行或者被暫停的任務(wù)。要想關(guān)閉這些任務(wù),則需要先知道它們的pid。如下所示:
?
#?vmstat?1?>?/tmp/1.log?& [1] 32689 #?ps?aux?|grep?vmstat root 32689 0.1 0.0 41192 2012 pts/2 S 22:14 0:00 vmstat 1 root 32691 0.0 0.0 9184 1084 pts/2 R+ 22:14 0:00 grep --color=auto vmstat
?
使用&把任務(wù)放到后臺(tái)運(yùn)行時(shí),會(huì)顯示pid信息。如果忘記這個(gè)pid,還可以使用ps aux命令找到那個(gè)進(jìn)程(關(guān)于ps命令,阿銘會(huì)在以后講解)。如果想結(jié)束該進(jìn)程,需要使用kill命令,如下所示:
?
#?kill?32689 #?jobs [1]+ Terminated vmstat 1 > /tmp/1.log
?
kill命令很簡(jiǎn)單,直接在后面加pid即可。如果遇到結(jié)束不了的進(jìn)程時(shí),可以在kill后面加一個(gè)選項(xiàng),即kill -9 [pid]。
在該節(jié)結(jié)束時(shí),大家不要忘記把后臺(tái)的vi給結(jié)束掉,免得以后遇到一些困擾。具體怎么結(jié)束,阿銘相信,經(jīng)過(guò)前面的學(xué)習(xí),你應(yīng)該知道答案了。
11.2 變量
阿銘在前面章節(jié)中介紹過(guò)環(huán)境變量PATH,它是shell預(yù)設(shè)的一個(gè)變量。通常,shell預(yù)設(shè)的變量都是大寫(xiě)的。變量就是使用一個(gè)較簡(jiǎn)單的字符串來(lái)替代某些具有特殊意義的設(shè)定以及數(shù)據(jù)。就拿PATH來(lái)講,這個(gè)PATH就代替了所有常用命令的絕對(duì)路徑的設(shè)定。有了PATH這個(gè)變量,我們運(yùn)行某個(gè)命令時(shí),就不再需要輸入全局路徑,直接輸入命令名即可。你可以使用echo命令顯示變量的值,如下所示:
?
#?echo?$PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin #?echo?$HOME /root #?echo?$PWD /root #?echo?$LOGNAME root
?
除了PATH、HOME和LOGNAME外,系統(tǒng)預(yù)設(shè)的環(huán)境變量還有哪些呢?
11.2.1 命令env
使用env命令,可列出系統(tǒng)預(yù)設(shè)的全部系統(tǒng)變量,如下所示:
?
#?env LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.m4a=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.oga=01;36:*.opus=01;36:*.spx=01;36:*.xspf=01;36: SSH_CONNECTION=192.168.18.1 62926 192.168.18.119 22 LANG=zh_CN.UTF-8 HISTCONTROL=ignoredups HOSTNAME=localhost.localdomain XDG_SESSION_ID=15 USER=root SELINUX_ROLE_REQUESTED= PWD=/tmp/10 HOME=/root SSH_CLIENT=192.168.18.1 62926 22 SELINUX_LEVEL_REQUESTED= XDG_DATA_DIRS=/root/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share SSH_TTY=/dev/pts/2 MAIL=/var/spool/mail/root TERM=xterm SHELL=/bin/bash SELINUX_USE_CURRENT_RANGE= SHLVL=1 LOGNAME=root DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus XDG_RUNTIME_DIR=/run/user/0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin HISTSIZE=1000 LESSOPEN=||/usr/bin/lesspipe.sh %s _=/usr/bin/env OLDPWD=/root
?
登錄不同的用戶(hù),這些環(huán)境變量的值也不同。當(dāng)前顯示的是root賬戶(hù)的環(huán)境變量。下面阿銘簡(jiǎn)單介紹一下常見(jiàn)的環(huán)境變量。
HOSTNAME:表示主機(jī)的名稱(chēng)。
SHELL:表示當(dāng)前用戶(hù)的shell類(lèi)型。
HISTSIZE:表示歷史記錄數(shù)。
MAIL:表示當(dāng)前用戶(hù)的郵件存放目錄。
PATH:該變量決定了shell將到哪些目錄中尋找命令或程序。
PWD:表示當(dāng)前目錄。
LANG:這是與語(yǔ)言相關(guān)的環(huán)境變量,多語(yǔ)言環(huán)境可以修改此環(huán)境變量。
HOME:表示當(dāng)前用戶(hù)的家目錄。
LOGNAME:表示當(dāng)前用戶(hù)的登錄名。
env命令顯示的變量只是環(huán)境變量,系統(tǒng)預(yù)設(shè)的變量其實(shí)還有很多,你可以使用set命令把系統(tǒng)預(yù)設(shè)的全部變量都顯示出來(lái)。
11.2.2 命令set
set命令和env命令類(lèi)似,也可以輸出環(huán)境變量,如下所示:
?
#?set BASH=/bin/bash BASHOPTS=checkwinsizecomplete_fullquoteextglobforce_fignoreinteractive_commentsprogcompsourcepath BASHRCSOURCED=Y BASH_ALIASES=() BASH_ARGC=() BASH_ARGV=() BASH_CMDS=() BASH_COMPLETION_VERSINFO=([0]="2" [1]="7") BASH_LINENO=() BASH_REMATCH=() BASH_SOURCE=() BASH_VERSINFO=([0]="4" [1]="4" [2]="19" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu") BASH_VERSION='4.4.19(1)-release' COLUMNS=189 COMP_WORDBREAKS=$' "'><=;|&(:' DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus DIRSTACK=() EUID=0 FINAL_LIST= GLUSTER_BARRIER_OPTIONS=$' {enable}, {disable} '
?
阿銘并沒(méi)有把全部?jī)?nèi)容都列出來(lái),set命令不僅可以顯示系統(tǒng)預(yù)設(shè)的變量,也可以顯示用戶(hù)自定義的變量。比如,我們自定義一個(gè)變量,如下所示:
?
#?myname=Aming #?echo?$myname Aming #?set?|grep?myname myname=Aming
?
雖然你可以自定義變量,但是該變量只能在當(dāng)前shell中生效,如下所示:
?
#?echo?$myname Aming #?bash?//執(zhí)行該命令,會(huì)進(jìn)入一個(gè)子shell環(huán)境中 #?echo?$myname #?exit exit #?echo?$myname Aming
?
使用bash命令可以再打開(kāi)一個(gè)shell,此時(shí)先前設(shè)置的myname變量已經(jīng)不存在了,退出當(dāng)前shell回到原來(lái)的shell,myname變量還在。如果想讓設(shè)置的環(huán)境變量一直生效,該怎么做呢?這分以下兩種情況。
1)允許系統(tǒng)內(nèi)所有用戶(hù)登錄后都能使用該變量。具體的操作方法是:在/etc/profile文件的最后一行加入exportmyname=Aming,然后運(yùn)行source/etc/profile就可以生效了。此時(shí)再運(yùn)行bash命令或者切換到其他賬戶(hù)(如su - test)就可以看到效果。如下所示:
?
# echo "export myname=Aming" >> /etc/profile # source !$ source /etc/profile # bash # echo $myname Aming # exit exit # su - test $ echo $myname Aming
?
2)僅允許當(dāng)前用戶(hù)使用該變量。具體的操作方法是:在用戶(hù)主目錄下的.bashrc文件的最后一行加入exportmyname=Aming,然后運(yùn)行source .bashrc就可以生效了。這時(shí)再登錄test賬戶(hù),myname變量則不會(huì)生效了。這里source命令的作用是將目前設(shè)定的配置刷新,即不用注銷(xiāo)再登錄也能生效。
阿銘在上例中使用myname=Aming來(lái)設(shè)置變量myname,那么,在Linux下設(shè)置自定義變量,有哪些規(guī)則呢?
q設(shè)定變量的格式為a=b,其中a為變量名,b為變量的內(nèi)容,等號(hào)兩邊不能有空格。
q變量名只能由字母、數(shù)字以及下劃線(xiàn)組成,而且不能以數(shù)字開(kāi)頭。
q當(dāng)變量?jī)?nèi)容帶有特殊字符(如空格)時(shí),需要加上單引號(hào)。示例命令如下:
?
# myname='Aming Li' # echo $myname Aming Li
?
有一種情況需要你注意,就是變量?jī)?nèi)容中本身帶有單引號(hào),這時(shí)就需要加雙引號(hào)了。示例命令如下:
?
#?myname="Aming's" #?echo?$myname Aming's
?
如果變量?jī)?nèi)容中需要用到其他命令,運(yùn)行結(jié)果則可以使用反引號(hào)。示例命令如下:
?
#?myname=`pwd` #?echo?$myname /root
?
變量?jī)?nèi)容可以累加其他變量的內(nèi)容,但需要加雙引號(hào)。示例命令如下:
?
#?myname="$LOGNAME"Aming #?echo?$myname rootAming
?
如果你不小心把雙引號(hào)錯(cuò)加為單引號(hào),則得不到你想要的結(jié)果。示例命令如下:
?
#?myname='$LOGNAME'Aming #?echo?$myname $LOGNAMEAming
?
通過(guò)上面幾個(gè)例子,也許你能看出使用單引號(hào)和雙引號(hào)的區(qū)別。使用雙引號(hào)時(shí),不會(huì)取消雙引號(hào)中特殊字符本身的作用(這里是$),而使用單引號(hào)時(shí),里面的特殊字符將全部失去其本身的作用。
在前面的例子中,阿銘多次使用了bash命令,如果在當(dāng)前shell中運(yùn)行bash指令,則會(huì)進(jìn)入一個(gè)新的shell,這個(gè)shell就是原來(lái)shell的子shell。你不妨用pstree指令來(lái)查看一下,示例命令如下:
?
#?pstree?|grep?bash |-login---bash |-sshd---sshd---bash-+-grep #?bash #?pstree?|grep?bash |-login---bash |-sshd---sshd---bash---bash-+-grep
?
如果沒(méi)有該命令,請(qǐng)運(yùn)行yum install psmisc命令安裝,pstree命令會(huì)把Linux系統(tǒng)中的所有進(jìn)程以樹(shù)形結(jié)構(gòu)顯示出來(lái)。限于篇幅,阿銘沒(méi)有全部列出,你可以直接輸入pstree查看。在父shell中設(shè)定變量后,進(jìn)入子shell時(shí),該變量是不會(huì)生效的。如果想讓這個(gè)變量在子shell中生效,則要用到export指令。示例命令如下:
?
#?abc=123 #?echo?$abc 123 #?bash #?echo?$abc #?exit exit #?export?abc #?echo?$abc 123 #?bash #?echo?$abc 123
?
其實(shí)export命令就是聲明一下這個(gè)變量,讓該shell的子shell也知道變量abc的值是123。設(shè)置變量之后,如果想取消某個(gè)變量,只要輸入unset 變量名即可。示例命令如下:
?
#?echo?$abc 123 #?unset?abc #?echo?$abc
?
審核編輯:湯梓紅
評(píng)論
查看更多