shell腳本在日常的Linux系統(tǒng)管理工作中是必不可少的。如果不會寫shell腳本,你就不算是一個合格的管理員。目前,很多單位在招聘Linux系統(tǒng)管理員時,shell腳本的編寫是必考的題目。有的單位甚至用shell腳本的編寫能力來衡量這個Linux系統(tǒng)管理員的經(jīng)驗是否豐富。所以,你必須認(rèn)真學(xué)習(xí)shell腳本并不斷練習(xí)。只要shell腳本寫得好,相信你的Linux求職之路就會輕松得多。
阿銘在這一章中只是帶你進(jìn)入shell腳本的世界,如果你很感興趣,可以到網(wǎng)上下載相關(guān)的資料或者到書店購買shell相關(guān)的圖書。
在學(xué)習(xí)shell腳本之前,需要你了解很多相關(guān)的知識,這些知識是編寫shell腳本的基礎(chǔ),希望你能夠熟練掌握。
11.1 什么是shell
shell是系統(tǒng)跟計算機(jī)硬件交互時使用的中間介質(zhì),它只是系統(tǒng)的一個工具。實際上,在shell和計算機(jī)硬件之間還有一層?xùn)|西——系統(tǒng)內(nèi)核。如果把計算機(jī)硬件比作一個人的軀體,那系統(tǒng)內(nèi)核就是人的大腦。至于shell,把它比作人的五官似乎更貼切些。言歸正傳,用戶直接面對的不是計算機(jī)硬件而是shell,用戶把指令告訴shell,然后shell再傳輸給系統(tǒng)內(nèi)核,接著內(nèi)核再去支配計算機(jī)硬件去執(zhí)行各種操作。
阿銘接觸的Linux發(fā)布版本(RHEL/Rocky)默認(rèn)安裝的shell版本是bash(即Bourne Again Shell),它是sh(即Bourne Shell)的增強(qiáng)版本。Bourn Shell是最早流行起來的一個shell版本。其創(chuàng)始人是Steven Bourne,為了紀(jì)念他而將其命名為BournShell,簡稱sh。那么,這個bash有什么特點(diǎn)呢?
11.1.1 記錄命令歷史
我們執(zhí)行過的命令Linux都會記錄,預(yù)設(shè)可以記錄1000條歷史命令。這些命令保存在用戶的家目錄的.bash_history文件中。但需要注意的是,只有當(dāng)用戶正常退出當(dāng)前shell時,在當(dāng)前shell中運(yùn)行的命令才會保存至.bash_history文件中。那什么情況才算正常退出?敲exit命令或者按Ctrl D快捷鍵都可以正常退出。而意外斷電或者斷網(wǎng)就不算正常退出。
!是與命令歷史有關(guān)的一個特殊字符,該字符常用的應(yīng)用有以下3個。
1)!! :連續(xù)兩個!表示執(zhí)行上一條指令。示例命令如下:
?
# pwd /root # !! pwd /root
?
2)!n :這里的n是數(shù)字,表示執(zhí)行命令歷史中的第n條指令。例如,!1002表示執(zhí)行命令歷史中的第1002個命令,如下所示:
?
# history |grep 1002 1002 pwd 1015 history |grep 1002 # !1002 pwd /root
?
上例中的history命令如果未改動過環(huán)境變量,默認(rèn)可以把最近執(zhí)行的1000條命令歷史打印出來。
3)!字符串(字符串大于等于1):例如!pw表示執(zhí)行命令歷史中最近一次以pw開頭的命令。示例代碼如下:
?
# !pw pwd /root
?
11.1.2 命令和文件名補(bǔ)全
最開始阿銘就介紹過,按tab鍵可以幫我們補(bǔ)全一個指令、一個路徑或者一個文件名。連續(xù)按兩次tab鍵,系統(tǒng)則會把所有的命令或者文件名都列出來。
11.1.3 別名
前面的章節(jié)中也曾提到過alias,它也是bash所特有的功能之一。我們可以通過alias把一個常用的并且很長的指令另取名為一個簡單易記的指令。如果不想用了,還可以使用unalias命令解除別名功能。直接執(zhí)行alias命令,會看到目前系統(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下,可以使用*來匹配零個或多個字符,用?匹配一個字符。示例命令如下:
?
#?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é)果輸入到文件中,而不是屏幕上。輸入重定向的命令是<,輸出重定向的命令是>。另外,還有錯誤重定向命令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)提過管道符|,它用于將前一個指令的輸出作為后一個指令的輸入,如下所示:
?
# cat /etc/passwd|wc -l
?
11.1.7 作業(yè)控制
當(dāng)運(yùn)行進(jìn)程時,你可以使它暫停(按Ctrl+Z組合鍵),然后使用fg(foreground的簡寫)命令恢復(fù)它,或是利用bg(background的簡寫)命令使它到后臺運(yùn)行。此外,你也可以使它終止(按Ctrl+C組合鍵)。示例命令如下:
?
#?vi?test1.txt testtestsstststst
?
阿銘使用vi命令編輯test1.txt,隨便輸入一些內(nèi)容,按Esc鍵后,使用Ctrl+Z組合鍵暫停任務(wù),如下所示:
?
#?vi?test1.txt [1]+ 已停止 vi test1.txt
?
此時提示vi test1.txt已經(jīng)停止了,然后使用fg命令恢復(fù)它,此時又進(jìn)入剛才的vi窗口了。再次使其暫停,然后輸入jobs,可以看到被暫停或者在后臺運(yùn)行的任務(wù),如下所示:
?
#?jobs [1]+ 已停止 vi test1.txt
?
如果想把暫停的任務(wù)放在后臺重新運(yùn)行,就使用bg命令,如下所示:
?
# bg [1]+ vi test1.txt & [1]+ 已停止 vi test1.txt
?
但是vi似乎并不支持在后臺運(yùn)行,那阿銘換一個其他的命令,如下所示:
?
#?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)了一個新的知識點(diǎn),那就是多個被暫停的任務(wù)會有編號,使用jobs命令可以看到兩個任務(wù),使用bg命令或者fg命令時,則需要在后面加編號。這里阿銘使用命令bg 2把第2個暫停的任務(wù)放到后臺重新運(yùn)行(需要在命令后邊加符號&,且中間有個空格)。本例中的vmstat 1是用來觀察系統(tǒng)狀態(tài)的一個命令,阿銘以后再介紹。
如何關(guān)掉在后臺運(yùn)行的任務(wù)呢?如果你沒有退出剛才的shell,那么應(yīng)該先使用命令fg 編號把任務(wù)調(diào)到前臺,然后按Ctrl+C組合鍵結(jié)束任務(wù)。如下所示:
?
#?fg?2 vmstat 1 > /tmp/1.log ^C //此處按ctrl + c
?
另一種情況則是,關(guān)閉當(dāng)前的shell,再次打開另一個shell時,使用jobs命令并不會顯示在后臺運(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ù)放到后臺運(yùn)行時,會顯示pid信息。如果忘記這個pid,還可以使用ps aux命令找到那個進(jìn)程(關(guān)于ps命令,阿銘會在以后講解)。如果想結(jié)束該進(jìn)程,需要使用kill命令,如下所示:
?
#?kill?32689 #?jobs [1]+ Terminated vmstat 1 > /tmp/1.log
?
kill命令很簡單,直接在后面加pid即可。如果遇到結(jié)束不了的進(jìn)程時,可以在kill后面加一個選項,即kill -9 [pid]。
在該節(jié)結(jié)束時,大家不要忘記把后臺的vi給結(jié)束掉,免得以后遇到一些困擾。具體怎么結(jié)束,阿銘相信,經(jīng)過前面的學(xué)習(xí),你應(yīng)該知道答案了。
11.2 變量
阿銘在前面章節(jié)中介紹過環(huán)境變量PATH,它是shell預(yù)設(shè)的一個變量。通常,shell預(yù)設(shè)的變量都是大寫的。變量就是使用一個較簡單的字符串來替代某些具有特殊意義的設(shè)定以及數(shù)據(jù)。就拿PATH來講,這個PATH就代替了所有常用命令的絕對路徑的設(shè)定。有了PATH這個變量,我們運(yùn)行某個命令時,就不再需要輸入全局路徑,直接輸入命令名即可。你可以使用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
?
登錄不同的用戶,這些環(huán)境變量的值也不同。當(dāng)前顯示的是root賬戶的環(huán)境變量。下面阿銘簡單介紹一下常見的環(huán)境變量。
HOSTNAME:表示主機(jī)的名稱。
SHELL:表示當(dāng)前用戶的shell類型。
HISTSIZE:表示歷史記錄數(shù)。
MAIL:表示當(dāng)前用戶的郵件存放目錄。
PATH:該變量決定了shell將到哪些目錄中尋找命令或程序。
PWD:表示當(dāng)前目錄。
LANG:這是與語言相關(guān)的環(huán)境變量,多語言環(huán)境可以修改此環(huán)境變量。
HOME:表示當(dāng)前用戶的家目錄。
LOGNAME:表示當(dāng)前用戶的登錄名。
env命令顯示的變量只是環(huán)境變量,系統(tǒng)預(yù)設(shè)的變量其實還有很多,你可以使用set命令把系統(tǒng)預(yù)設(shè)的全部變量都顯示出來。
11.2.2 命令set
set命令和env命令類似,也可以輸出環(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} '
?
阿銘并沒有把全部內(nèi)容都列出來,set命令不僅可以顯示系統(tǒng)預(yù)設(shè)的變量,也可以顯示用戶自定義的變量。比如,我們自定義一個變量,如下所示:
?
#?myname=Aming #?echo?$myname Aming #?set?|grep?myname myname=Aming
?
雖然你可以自定義變量,但是該變量只能在當(dāng)前shell中生效,如下所示:
?
#?echo?$myname Aming #?bash?//執(zhí)行該命令,會進(jìn)入一個子shell環(huán)境中 #?echo?$myname #?exit exit #?echo?$myname Aming
?
使用bash命令可以再打開一個shell,此時先前設(shè)置的myname變量已經(jīng)不存在了,退出當(dāng)前shell回到原來的shell,myname變量還在。如果想讓設(shè)置的環(huán)境變量一直生效,該怎么做呢?這分以下兩種情況。
1)允許系統(tǒng)內(nèi)所有用戶登錄后都能使用該變量。具體的操作方法是:在/etc/profile文件的最后一行加入exportmyname=Aming,然后運(yùn)行source/etc/profile就可以生效了。此時再運(yùn)行bash命令或者切換到其他賬戶(如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)前用戶使用該變量。具體的操作方法是:在用戶主目錄下的.bashrc文件的最后一行加入exportmyname=Aming,然后運(yùn)行source .bashrc就可以生效了。這時再登錄test賬戶,myname變量則不會生效了。這里source命令的作用是將目前設(shè)定的配置刷新,即不用注銷再登錄也能生效。
阿銘在上例中使用myname=Aming來設(shè)置變量myname,那么,在Linux下設(shè)置自定義變量,有哪些規(guī)則呢?
q設(shè)定變量的格式為a=b,其中a為變量名,b為變量的內(nèi)容,等號兩邊不能有空格。
q變量名只能由字母、數(shù)字以及下劃線組成,而且不能以數(shù)字開頭。
q當(dāng)變量內(nèi)容帶有特殊字符(如空格)時,需要加上單引號。示例命令如下:
?
# myname='Aming Li' # echo $myname Aming Li
?
有一種情況需要你注意,就是變量內(nèi)容中本身帶有單引號,這時就需要加雙引號了。示例命令如下:
?
#?myname="Aming's" #?echo?$myname Aming's
?
如果變量內(nèi)容中需要用到其他命令,運(yùn)行結(jié)果則可以使用反引號。示例命令如下:
?
#?myname=`pwd` #?echo?$myname /root
?
變量內(nèi)容可以累加其他變量的內(nèi)容,但需要加雙引號。示例命令如下:
?
#?myname="$LOGNAME"Aming #?echo?$myname rootAming
?
如果你不小心把雙引號錯加為單引號,則得不到你想要的結(jié)果。示例命令如下:
?
#?myname='$LOGNAME'Aming #?echo?$myname $LOGNAMEAming
?
通過上面幾個例子,也許你能看出使用單引號和雙引號的區(qū)別。使用雙引號時,不會取消雙引號中特殊字符本身的作用(這里是$),而使用單引號時,里面的特殊字符將全部失去其本身的作用。
在前面的例子中,阿銘多次使用了bash命令,如果在當(dāng)前shell中運(yùn)行bash指令,則會進(jìn)入一個新的shell,這個shell就是原來shell的子shell。你不妨用pstree指令來查看一下,示例命令如下:
?
#?pstree?|grep?bash |-login---bash |-sshd---sshd---bash-+-grep #?bash #?pstree?|grep?bash |-login---bash |-sshd---sshd---bash---bash-+-grep
?
如果沒有該命令,請運(yùn)行yum install psmisc命令安裝,pstree命令會把Linux系統(tǒng)中的所有進(jìn)程以樹形結(jié)構(gòu)顯示出來。限于篇幅,阿銘沒有全部列出,你可以直接輸入pstree查看。在父shell中設(shè)定變量后,進(jìn)入子shell時,該變量是不會生效的。如果想讓這個變量在子shell中生效,則要用到export指令。示例命令如下:
?
#?abc=123 #?echo?$abc 123 #?bash #?echo?$abc #?exit exit #?export?abc #?echo?$abc 123 #?bash #?echo?$abc 123
?
其實export命令就是聲明一下這個變量,讓該shell的子shell也知道變量abc的值是123。設(shè)置變量之后,如果想取消某個變量,只要輸入unset 變量名即可。示例命令如下:
?
#?echo?$abc 123 #?unset?abc #?echo?$abc
?
審核編輯:湯梓紅
評論
查看更多