這是我第一次嘗試使用雙拼輸入法翻譯一篇日本的Liunx基礎教材上的一章。共花費一周左右。
shell的使用
前言
通過shell輸入的命令,可以通過shell script逐次順序執行。可以靈活利用這個,實現輸入命令的自動化。以下學習一下必要的技能。
9.1 shell和shell腳本
9.2 編程
9.2.1 程序例
9.2.2 程序的要素
9.3 shell腳本做成
9.3.1 shell腳本做成
9.3.2 變量
9.3.3 echo命令
9.3.4 read命令
9.3.5 shell變量
9.3.6 環境變量
9.3.7 注釋
9.3.8 引用符
9.3.9 引數
9.3.10 shift
9.3.11 轉義字符
9.3.12 代碼注釋
9.4 分支語句
9.4.1 if語句
9.4.2 晚間屬性比較
9.4.3 多條件比較
9.4.4 一對多分支
9.5 循環
9.5.1 for語句
9.5.2 while/until 語句
9.5.3 select語句
9.5.4 循環控制
9.6 方法/函數
9.6.1 函數
9.6.2 return語句
9.7 真實的shell腳本
9.7.1 啟動腳本
9.7.2 函數腳本
9.8 debug
9.8.1 sh命令
9.1 shell和shell腳本
在開始學習shell之前,先了解一下shell和shell 腳本的區別。
shell
內核是操作系統的基本組成部分,擔當著控制硬件等等一系列的機能。
shell直接翻譯是【殼】。 當操作系統內核提供的功能的時候,有必要跟操作系統進行對話。
將OS的內核部分包裹起來的機能叫做shell,提供對話功能。 shell接收命令的輸入然后執行,返回給輸入者處理結果。
shell腳本
假設一個場景,/etc和/home路徑壓縮后,拷貝到外部服務器上。
實際實行的命令如下:
# tar cvzf 120626-etc.tar.gz /etc
# tar cvzf 120626-home.tar.gz /home
# scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
# scp 120626-home.tar.gz root@backup.local.example.com:~/backup
每個命令的執行,都那么麻煩。還有就是,本來所有命令都需要手工從端末輸入,一個命令沒執行完畢的話,下一個命令就不能執行--這樣效率不怎么好。類似這種循環處理的自動化手段,寫個shell腳本就能搞定。
上面例子的一連串操作,我們創建個system-backup.sh名稱的文件,執行ystem-backup.sh來運行寫入的命令。
**shell腳本內容 **
#!/bin/bash
tar cvzf 120626-etc.tar.gz /etc
tar cvzf 120626-home.tar.gz /home
scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
scp 120626-home.tar.gz root@backup.local.example.com:~/backup
9.2 編程
對于計算機來說,按照指示順序執行的技能,被叫做程序。編寫程序的過程叫做編程。將學習過的命令,加上條件分支和循環處理之后成為可執行的內容,這個叫做shell腳本。
9.2.1 程序例
一提起程序,最初想到的可能是運動會或者演唱會。或者公開的電視節目表。按照順序進行的項目,可以稱為程序。實際上什么樣的例子比較好呢?簡單的例子,電視節目錄像預約(好像不常用。。。)。需要指定開始時間,結束時間,錄像畫質。稍微復雜的例子的話,就是空調控制。需要設定【多少度制冷】【到多少度停止制冷】,這就是一種程序。
9.2.2 程序的要素
編程,不論什么樣的程序,都有以下4種重要要素:
? 順序執行
? 條件分支
? 循環
? 子函數
這些需要好好掌握。
9.3 shell腳本做成
接下來嘗試實際做成shell腳本。開始介紹shell腳本做成到執行的過程
9.3.1 shell腳本做成
shell腳本用純文本編輯。請使用vi一類的文本編輯工具。
先嘗試創建名為lsdate.sh的shell腳本,里面執行ls和date命令。
$ vi lsdate.sh
(做成lsdate.sh)
在vi中編寫以下內容,保存終了退出。
#!/bin/bash
ls
date
shell指定
第一行的內容【#!/bin/bash】,表明利用的shell種類。shell有很多種類,我們的說明中使用的是bash。
在第一行指定shell,從第二行開始逐行輸入需要執行的命令。
permission(權限)變更
做成的shell腳本,需要更改權限,追加可執行權限。
下面給例子的腳本追加可執行權限。
首先,使用ls命令查看文件權限。
$ ls -l lsdate.sh
使用chmod命令追加可執行權限。
$ chmod u+x lsdate.sh
$ ls -l lsdate.sh
這樣執行后,就已經賦予了可執行權限。我們趕緊執行一下吧。
$ ./lsdate.sh
【./】是指定路徑。執行當前路徑下可執行腳本的意思。
與之比較的話,執行ls,cp命令的時候是路徑已經指定(通過系統的PATH),所以不指定當前路徑也可以運行。
上面的例子,是為了在當前路徑下執行特殊腳本(非PATH定義過),所以指明了路徑。
這樣的話,在lsdate.sh記述的ls和date命令就順序被執行了。
9.3.2 echo命令
echo是給定文字列的參數在標準輸出中輸出的命令。如果參數的位置是帶有$的變量,也可以輸出。
命令格式:
echo [option] 字符串
option:
-n 禁止換行
例子:
$ echo Message test
Message test
9.3.3 變量
編程時,非常重要的考慮點就是變量。
變量簡單說的話,就是一個箱子。里面存有數值啊,文字列啊等等。類似于中學時的X,Y,Z。
shell腳本編程時,將數值,文字列代入變量中,就可以使用了。
代入變量使用=(等號)。
練習:
給shell變量abc賦值,并使用echo確認內容。
$ abc=123
$ echo $abc
123
給變量abc賦值了123。
bash中也可以使用數組。要素是中括號[]把內容擴起來。
數組的內容表示的時候,$后面用大括號{}把數組變量擴起來。
$ abc[0]=123
$ abc[1]=456
$ echo ${abc[0]}
123
$ index=1
$ echo ${abc[$index]}
456
shell變量和環境變量
shell有shell變量,環境變量兩種。
shell變量只在正在執行的shell內部有效。環境變量對執行過的shell也是有效的。
從shell變量可以變成環境變量。
練習:環境變量做成
使用export命令,做成環境變量
$ export abc
shell變量abc變成環境變量
$ export xyz=234
聲明環境變量
第一行的命令是做成abc環境變量,abc中沒有賦值。
第二行的命令是做成xyz環境變量,xyz被賦值234。
我們用下面的兩個腳本 BBB.sh 和 CCC.sh來了解一下shell變量和環境變量的區別。
例子:
$ cat BBB.sh
#!/bin/bash
xxx=123
export yyy=234
./CCC.sh
$ cat CCC.sh
#!/bin/bash
echo xxx=$xxx
echo yyy=$yyy
(值表示)
$ ./BBB.sh
xxx=
yyy=234
(BBB.sh中執行CCC.sh,xxx沒有被繼承了, yyy被繼承了)
這個shell腳本執行時,CCC.sh中的xxx值沒有表示。是因為shell變量沒有被繼承。
另外,因為yyy是環境變量,所以在CCC.sh中被繼承了,值也就被表示了。
9.3.4 read命令
read命令是從標準輸入讀取數據。如果變量中已經有數據了,新讀入的數據會覆蓋上。
格式:
read 變量名
練習:
使用read命令來變更shell變量內容。
$ echo $abc
123
$ read abc
aaabbbccc
(輸入內容)
$ echo $abc
aaabbbccc
(shell變量內容被修改)
9.3.5 shell變量
如果想表示shell變量的一覽,請使用set命令。
如果想刪除shell變量,使用unset命令。
練習: shell變量的表示和刪除
$ set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
abc=aaabbbccc
(此時可以看到abc這個shell變量)
$ unset abc
(刪除abc shell變量)
9.3.6 環境變量
如果想表示環境變量的一覽,請使用env命令。
如果想刪除已經做成的環境變量,使用unset命令。
練習: 環境變量的表示和刪除
$ env
HOSTNAME=host1.alpha.jp
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
abc=aaabbbcc
(同上)
$ unset abc
(同上)
9.3.7 注釋
注釋是程序上記錄的用于提示的內容。
shell是用#開始,在程序執行時,注釋內容是被無視的。
注釋大多是程序員用于記述程序是如何處理的,也有時臨時無效化一部分代碼(comment out)。
格式:
#!/bin/bash
# assign xyz to variable abc and output it
(這一行在程序執行時被無視)
abc=xyz
echo $abc
練習:執行有注釋的代碼
首先寫一個有注釋的腳本,然后執行。
1:腳本做成
# vi comment.sh
2:記入內容
#!/bin/bash
abc=xyz
#abc=123
echo $abc
3:執行腳本
# chmod u+x comment.sh
# ./comment.sh
xyz
9.3.8 引用符
命令執行的時候,變量用引用符括起來。引用符有’(單引號)、"(雙引號)、‘(反引號)。
如果用’(單引號)括起來的話,括號內的字符串就是字符串,即使括號內是變量,也是作為字符串輸出。
如果用"(雙引號)括起來的話,括號內是變量的話,變量執行后輸出。
如果用‘(反引號)括起來的話,括號內是命令的話,命令執行后輸出。
練習:引用符的動作確認
這幾個引用符的區別,讓我們試驗一下。
$ abc=xyz
$ echo 'value abc is $abc'
value abc is $abc
($abc做為字符串輸出)
$ echo "value abc is $abc"
value abc is xyz
($abc被識別成變量了,輸出變量內容)
$ abc=`date`;
$ echo $abc
Thu Mar 20 06:08:14 JST 2012
($abc中有date命令,輸出date的執行結果)
9.3.9 參數
shell腳本執行時,可以把option作為參數使用。參數的格式$1, $2…,使用時在$后指定編號就可以。
練習:參數輸出確認
像下面一樣作成args.sh腳本,實驗一下調用參數。
$ cat args.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
echo '$0:' $0;
echo '$#:' $#;
$ ./args.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$0: ./args.sh
$#: 3
($1-$3 是參數,$0是 執行的命令名,$#是參數個數)
9.3.10 shift
shift是將參數移位的命令。比如 $2 移動到$1 ,$3 移動到$2。
練習:shift命令的實驗
先作成腳本,然后進行shift動作確認。
$ cat argsshift.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
shift
echo '$1:' $1;
echo '$2:' $2;
傳參數
$ ./argsshift.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$1: bbb
($1變成了bbb)
$2: ccc
($2變成了ccc)
9.3.11 轉義字符(Escape Sequence)
在程序中有一些特別的文字。以echo為例,輸出"(雙引號)時,像下面這樣
echo """ (用雙引號括上雙引號)
但是,有3個雙引號,無法判斷對應關系。如果執行的話會報錯。所以,為了回避這個報錯,需要聲明一下『第二個雙引號不是區分符號,而是字符串』這個時候,使用的符號叫轉義字符。在shell腳本中使用(\、日文系統使用半角的¥)
$ echo ""
"
(成功輸出雙引號)
這個例子是輸出了第二個雙引號。
還有,轉義字符也可以作為換行符。\在一行的末尾,命令的字符串可以中途換行。
適當的換行的話,命令的可讀性會提高很多。
$ echo "I am Cat.My name is nothing."
I am Cat.My name is nothing.
$ echo "I am Cat.Y
> My name is nothing."
I am Cat.My name is nothing.
(雖然命令的字符串中途加入\,但是結果中被無視了。)
\的換行,在shell腳本中也可以使用。
例子:
$ vi escape.sh
#!/bin/bash
echo "I am Cat.Y
My name is nothing."
$ ./escape.sh
I am Cat.My name is nothing
9.3.12 source命令
source是bash內部命令。作用是讀取指定的文件后,設定到shell環境中。
文件內容會作為shell命令,被解釋執行。
一般用途是,當shell環境變量文件.bashrc"和".bash_profile"修改后,不用重新登錄直接使設定在現在的shell環境上有效化。
做個例子:作成set.sh,給$abc變量定義xyz,然后使用source命令讀入。
1:作成set.sh
$ cat set.sh
(set.sh 的內容確認)
#!/bin/sh
abc=xyz
echo $abc
2:使用echo輸出$abc
$ echo $abc
(什么結果都沒有)
3:運行set.sh
$ ./set.sh
xyz
($abc變量里存入了xyz,所以有輸出結果)
4:再次使用echo輸出$abc
$ echo $abc
($abc還是沒有輸出)
(到這一步說明了$abc還沒有全局化,只是set.sh內有效)
5:使用source讀取set.sh
$ source set.sh
xyz
($abc變量里存入了xyz,所以有輸出結果)
6:再次使用echo輸出$abc
$ echo $abc
xyz
(有結果輸出,證明5中source有效了)
9.4 條件分支
根據條件切換動作的條件分支,在程序中經常出現。條件分支幾乎是所有語言都存在的功能。
當然,shell腳本里面也可以使用。
9.4.1 if語句
根據比較結果來進行分支處理的時候使用if。格式如下:
if 比較 1 then ... elif 比較2 ... else ... fi
elif...和else...的部分可以省略掉。
elif在第二個比較開始的時候使用。
else在之前的比較條件全都不符合的時候使用。
if語句的結尾fi(表面看是if逆序,實際上是finish的簡寫)
比較格式如下:
表 9.1 字符串比較
比較公式 | 比較內容 |
---|---|
a == b | 等于判斷 |
a != b | a 不等于判斷 |
表 9.2 數字比較
比較公式 | 比較內容 |
---|---|
a -eq b | 相等的話 真 |
a -ne b | 不相等的話 真 |
a -ge b | a >= b 真 |
a -le b | a =<b 真 |
a -gt b | a > b 真 |
a -lt b | a <b 真 |
9.4.2 文件屬性確認
使用以下方式。
格式:
if test -d 文件路徑 ; then.....
-d的部分是文件屬性的計算命令參數,用于判定后面的內容是不是路徑。
所以這句話的全體是,文件路徑 如果是路徑的話,返回真。
文件屬性的確認方法像以下格式一樣。也可以使用test [ ] 的方式。
格式:
if [ 條件判斷 ]; then ...
if test 條件判斷 ; then ....
表 9.3 文件屬性判斷
演算子 | 內容 |
---|---|
-f 文件名 | 普通文件的話 真 |
-d 文件名 | 路徑的話 真 |
-e 文件名 | 文件存在的話 真 |
-L 文件名 | 符號鏈接的話 真 |
-r 文件名 | 可讀文件的話 真 |
-w 文件名 | 可寫入文件的話 真 |
-x 文件名 | 文件存在且可以執行的話 真 |
-s 文件名 | 文件大小不為0的話 真 |
跟文件相關的比較方法,除了屬性還有別的。詳細參考man test。
命令:
man test
9.4.3 多條件判斷
分支判斷中可以使用多條件判斷。條件 A和條件 B 同時成立時,使用AND來表達。
同理,條件 A 或者 條件 B 成立時,使用OR來表達。
shell中存在AND和OR兩種寫法。
AND
-a 或者&&
格式:
[條件 A -a 條件 B -a 條件 C ] ....
[條件 A] && [條件 B] && [條件 C] ...
-a 和&&は、放在[ ]內側和外側效果不一樣,需要注意。
OR
-o 或者 ||
格式:
[條件 A -o 條件 B -o 條件 C ] ....
[條件 A] || [條件 B] || [條件 C] ...
9.4.4 一對多分支語句
我們來考慮一下一對多情況下的分支語句。
如果是用if語句,格式如下。
shell腳本里面還有case語句可以實現一對多的分支。
if格式:
if 條件 then
:
elif 條件 then
:
elif 條件 then
:
:
fi
case格式:
case 變量 in
值A)
處理 1;;
值B)
處理 2;;
esac
變量是A的時候,進行 處理 1,最后用esac(case的逆序)結束處理。值可以使用(|)來間隔復數個值。
例子:
$ cat case.sh
#!/bin/bash
case $1 in
a|A)
echo "參數是a或者A";;
b|B)
echo "參數是b或者B";;
esac
執行的話,得到下面的結果
$ ./case.sh a
參數是a或者A
$ ./case.sh B
參數是b或者B
另外一種情況。哪一個分支都不匹配條件的時候,使用(*)來接收。
例子:
$ cat defaultcase.sh
(做成下面內容的sh腳本)
#!/bin/bash
case $1 in
1)
echo "參數是1";;
2)
echo "參數是2";;
*)
echo "參數是1,2以外";;
esac
執行結果如下
$ ./defaultcase.sh 1
參數是1
$ ./defaultcase.sh 2
參數是2
$ ./defaultcase.sh 0
參數是1,2以外
9.5 循環處理
在程序中有跟條件分支一樣重要的機能,就是循環處理。
循環處理是,同樣的處理循環執行,直到某些條件成立之后才結束。shell腳本可以使用的有三種形式。
9.5.1 for語句
for 語句是列舉某個值,將這個值作為對象循環處理。
格式:
for 變量 in 值列表
do
處理內容
done
值列表是文字字符串的話也可以執行。
比如:
$ for i in a b c d
> do
> echo $i
> done
a
b
c
d
’a b c d’作為值列表,當 i=a執行的時候,執行echo $i,接下來i=b的時候執行echo
$i...這么一個循環處理的結果。
把命令的執行結果作為值列表,也可以執行循環。
for i in `ls`
※反引號可以執行引號內的命令。(可以參考9.3.8 引用符)
9.5.2 while/until語句
while語句是在條件成立期間一直循環處理。
until語句是條件不成立期間一直循環處理。
格式:
while 條件
do 處理
done
until 條件
do 處理
done
為了實現C語言中的for語句一樣的循環,可以使用expr命令遞增或者遞減來進行計數,處理while/until。參照下面內容做成loop.sh,嘗試執行一下。
例子:
$ cat loop.sh
#!/bin/bash
count=1
while [ $count -le 10 ]
do
echo "這個處理執行$count回了"
count=`expr $count + 1`
done
count=1 意思是初始化count為1。
while [ $count -le 10 ] 的意思是,當count小于10的時候循環處理。
得到以下結果
$ ./loop.sh
這個處理執行1回了
這個處理執行2回了
...
這個處理執行10回了
9.5.3 select語句
select語句用于提取用戶輸入內容,然后跟值列表進行比較后輸出匹配結果。
格式:
select 變量 in 值列表
do 處理
done
例子:
select name in "apple" "banana" "orange"
do
echo "You selected $name";
done
實際執行一下
1) apple
2) banana
3) orange
$? 1
You selected apple
(「Ctrl」 C 中止)
輸出1到3,會執行do--done之間的內容。
9.5.4 循環的控制
break和continue可以用來控制循環。
break循環中止,continue當前處理停止,繼續下一次循環。
例子:
$ cat ./sample.sh
while true
do
echo "Continue? (y/n)"
read input
case $input in
n) break
;;
y) continue
;;
*) echo "Please input y or n."
;;
esac
done
執行結果如下:
$ ./sample.sh
Continue? (y/n)
y
Continue? (y/n)
a
(輸入y,n以外)
Please input y or n.
Continue? (y/n)
n
$
(循環結束)
9.6 方法/函數
程序中把可以再利用的部分整理成一塊,叫做方法/函數。每個語言中都有各自的叫法,shell中稱呼為方法/函數。
格式
function 方法名
{
處理
}
或者
方法名 ()
{
處理
}
兩種寫法實際上是一樣的。參數都是記載在方法名之后。在方法內都可以使用$1, $2來讀取。
9.6.2 return語句
方法內當返回結果時,使用return
格式
return 變量名
當執行return的后,方法內處理也就結束了。把結果返回調用這個函數的地方。
9.7 真實的shell腳本
接下來我們看一下真實的shell腳本。(例子使用的是CentOS 6.3的啟動腳本,其他發型版或者CentOS其他版本會多少有些不一樣)
9.7.1 啟動腳本
我們參考一下/etc/rc。/etc/rc這個shell腳本,在啟動流的實際執行腳本中記述著啟動必要的處理。
第28行
. /etc/init.d/functions
讀入/etc/init.d/functions 。
/etc/路徑下有很多各種各種的腳本,包含一些共通用的便利函數。為了使之有效,所以需要執行28行。
/etc/init.d/functions は、/etc/の中に
第50 行
[ -d /etc/rc$runlevel.d ] || exit 0
-d 用于判斷路徑是否存在。
$runlevel 是個變量,用于取得當前運行階段。我們用3舉例。
|| 是 判斷執行。假如||前面是true,就處理結束。如果||前面是false,就開始執行||后面的內容。
exit 退出命令
0 正常退出,默認設定。如果使用變量的?話,格式是 $?
總結下來就是:判斷/etc/rc3.d是否存在,如果存在這條命令執行完成,不存在的話就退出。
第60 行
for i in /etc/rc$runlevel.d/K* ; do
繼續以$runlevel是3舉例。
/etc/rc3.d/路徑下,以K開頭的腳本循環記入$i,然后執行$i里面的腳本。
在第69行的$i stop用來停止腳本執行。
K開頭的腳邊的實體存放在/etc/init.d/。
文件名中K后面的兩位數字,實際上對應停止的順序。
9.7.2 方法/函數的腳本
/etc/init.d/functions 就是上面/etc/rc の 28 行說明的一樣,是一個便利方法/函數的集合腳本。
我們挑一個方法/函數來說明一下。
578 行
is_ignored_file() {
case "$1" in
*~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave)
return 0
;;
esac
return 1
}
is_ignored_file()這個方法/函數的作用是過濾掉 /etc/rc 下面符合in后面那一些文件名格式的文件。
如果變量$1的值(文件名) 在*~, *.bak, *.orig, *.rpmnew, *.rpmorig, *.rprmsave里面,
那么就返回0(false),否則的話返回1(true)。
當然,除了在/etc/rc 下使用,別的地方也可以調用。
9.8 debug
當做成的代碼沒有按照自己想定的運行時,有必要調查一下哪里出現問題了。這就是所謂的debug。
一般有兩種方式。第一種是在代碼中寫入很多打印變量的代碼,或者寫入很多記錄程序運行軌跡的代碼。
這個方式比較麻煩費事。第二種方式是很多語言都提供的debug的工具。這個比第一種簡單省事好多。
shell腳本里同樣提供了可以進入debug模式的命令。
9.8.1 sh命令
sh命令本身是用來執行腳本的。如果加上 -x 參數后指定執行的腳本,shell里的命令和變量一邊表示一邊執行。我們就用有循環的sample.sh腳本,使用sh -x實驗一下。
例子:
$ sh -x ./sample.sh
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
y
(輸入y)
+ case $input in
+ continue
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
n
(輸入n)
+ case $input in
+ break
以上就是shell腳本的基礎內容的全部。