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