Shell腳本與Windows/Dos下的批處理類似,也就是用各種命令預先放入到一個文件中,方便一次性執行的一個程序文件, Shell就是一個命令行解釋器,Shell自己是一個用C語言編寫的程序, Shell既是一種命令語言,又是一種程序設計語言(就是你所說的shell腳本)。shell腳本腳本文件一般以.sh做爲後綴名,第一行以#!開頭指定執行腳本的程序:linux
#!/usr/bin/bashshell
#是shell腳本中的行註釋符。數組
這裏咱們來使用最經常使用的bashbash
1.新建文件hello_shell函數
$ vi hello_shelloop
2. 輸入Shello命令學習
#! /bin/bashui
echo "Hello Shell!"
3、保存命令行
echo是一個輸出命令,就是輸出一句話設計
4. 賦權限
咱們要讓系統知道咱們剛纔新建的文件是可執行的,因此咱們要賦給其可執行的權限
如今咱們的文件是不可執行的,只有讀寫權限:
下面是賦權
$ chmod 711 hello_shell
5. 執行
./ 表示再當前目錄查找命令,若是什麼都不加的話,系統默認會在PATH裏尋找,而的當前目錄一般不在PATH裏,因此找不到命令。
一般有三種執行腳本的方式:
一、sh start.sh: 在終端中建立一個sh子進程執行腳本, 執行者須要擁有腳本的讀權限。該方式其實是將腳本路徑做爲參數傳遞給了sh命令。
二、source start.sh: 在終端中執行腳本,至關於將腳本中的指令逐條複製到終端執行。腳本中局部變量將保留在終端環境變量中, 腳本的pid和工做目錄等環境也與終端一致。
三、./start.sh: 根據HashBang指定的程序,在子進程中執行腳本。
1. 變量的聲明和定義
Shell裏的變量類型:字符串、數值。定義的方式實際上是同樣的,字符串用單引號或雙引號標識。
Shell裏變量命名規範:首個字符必須爲字母(a-z,A-Z)任何變量都只能由字母(包括大小寫)、數字和下劃線組成變量中不能有空格,不能使用bash裏的關鍵字(可用help命令查看保留關鍵字)
變量在使用前無需聲明,在爲變量賦值時=左右不能添加空格。
y_name="yuguiyang"
y_age=24
2. 變量的使用
使用一個定義過的變量,只要在變量名前面加美圓符號$便可
y_name="yuguiyang"
y_age=24
echo "name:$y_name"
echo "age:$y_age"
咱們也可使用 {}將變量括起來,加花括號是爲了幫助解釋器識別變量的邊界
A=a
AB=ab
echo ${A}B
能夠把命令的輸出做爲返回值, 如:
PWD=$(pwd)
在單引號標識的字符串中$不被做爲變量標識符, 而雙引號則會將$替換爲變量內容。
A="abc"
echo '$A' # $A
echo "$A" # abc
字符串拼接不須要任何運算符,只須要將它們寫在一塊兒便可:
A="abc"
B="123"
echo "$A+$B" # abc+123
echo "$A$B" # abc123
echo "$Adef" # abcdef
三、整型變量
shell僅支持整型計算, declare命令能夠聲明整型變量,let指令用於算術運算:
declare -i a=1
let a=a+1
echo $a # 2
let a+=1
echo $a # 3
let指令支持算術運算符包括:
+:加法
-: 減法
*: 乘法
/: 除法
**: 乘方
%: 取餘
let指令也支持算術運算符對應的算術賦值運算符,如+=。
4. 只讀變量
使用 readonly 命令能夠將變量定義爲只讀變量,只讀變量的值不能被改變
#!/bin/bash
y_id=1990
readonly y_id
echo "y_id:${y_id}"
y_id=2014
echo "y_id:${y_id}"
這裏咱們嘗試修改只讀變量y_id,運行時,會報錯,提示該變量爲只讀變量:
5. 刪除變量
使用 unset 命令能夠刪除變量,變量被刪除後不能再次使用;
unset 命令不能刪除只讀變量。
#!/bin/bash
y_id=1990
y_name="yuguiyang"
echo "y_id:${y_id}"
echo "y_name:${y_name}"
readonly y_id
unset y_id
unset y_name
echo "y_id:${y_id}"
echo "y_name:${y_name}"
因爲y_id 是隻讀變量,因此不會被刪除,y_name被刪除後,輸出爲空
結果:
6. 變量的做用範圍
運行shell時,會同時存在三種變量:
1) 局部變量
局部變量在腳本或命令中定義,做用域僅限執行腳本的進程,即僅在當前shell實例中有效,其餘shell啓動的程序不能訪問局部變量。
2) 環境變量(全局變量)
全部的程序,包括shell啓動的程序,都能訪問環境變量,有些程序須要環境變量來保證其正常運行。子進程能夠繼承父進程的全局變量。環境變量配置保存於/etc/profile文件中。能夠經過export來查看。也能夠經過export來設置環境變量:export xxx=」yyy」
set |grep xxx能夠查看某個具體的變量的值
3) shell變量
shell變量是由shell程序設置的特殊變量。shell變量中有一部分是環境變量,有一部分是局部變量,這些變量保證了shell的正常運行
7. 特殊變量
shell中預約義了一些特殊變量,經過這些變量能夠得到環境信息:
●$$: 執行當前腳本的進程ID(pid)
●$?: 上一條命令的返回值,若是上一個命令成功執行則$?的值爲0,不然爲其餘非零值
●$!: 上一條後臺指令的執行進程的ID
上述變量在交互式終端中一樣有效。
還有一些變量能夠得到執行腳本時傳入的參數:
●$0: 當前腳本的文件名
●$1~$n: 傳給腳本的第n個參數
●$#: 傳入腳本的參數的個數
●$@: 參數列表
●$*: 單個字符串形式的參數列表
bash中可使用圓括號定義數組,元素之間用空格分割,數組下標從0開始:
1. 定義
Shell腳本中支持一維數組,下標從0開始
咱們能夠這樣
y_books[0]="today"
y_books[1]="onepiece"
能夠這樣:
y_books=("one" "two" "three")
還能夠這樣:
y_books=(
"one"
"two"
"three")
2. 讀取數組內容
咱們使用下標來獲取數組的信息
echo "book:${y_books[0]}"
echo "book:${y_books[1]}"
3. 獲取全部的元素
使用*或者@
示例:
echo "books:${y_books[*]}"
echo "books:${y_books[@]}"
4. 獲取數組長度
echo "數組長度"
echo "length:${#y_books[*]}"
echo "length:${#y_books[@]}"
以前咱們都是直接將內容輸出,那怎樣能夠交互呢?咱們使用read命令,read能夠直接使用而不須要在前面聲明這個變量。
#!/bin/bash
echo "請輸入你的名字:"
read y_name
echo "你好,${y_name}"
#!/bin/bash
echo ""
echo "姓名:"
read y_name
echo "年齡:"
read y_age
echo ""
echo "${y_name},今年${y_age}歲。"
Linux中有3類運算符:算術運算符、邏輯運算符、比較運算符
1. 算術運算符
在學習運算符以前咱們先看個東西,以前沒有注意到:
#!/bin/bash
y_price=10.9
y_total=${y_price}+3
echo "y_total:${y_total}"
y_total應該輸出13.9?不是的,輸出值爲:10.9+3。由於Shell中默認把變量看做是字符串,因此纔會顯示成這樣。要解決這個問題,咱們須要使用let命令
let val="3 + 6"
echo "val:${val}"
a=5
b=9
let val="a + b"
echo "val:${val}"
let val="${a} + ${b}"
echo "val:${val}"
注意:等號(=)兩邊沒有空格,而加號(+)兩邊有空格,且全部的運算符兩邊都要有空格。
從上面的例子能夠發現:let表達式後,調用變量時能夠不使用$符號let也可使用(())代替
((val="10 + 30"))
echo "val:${val}"
注意:let只能夠計算整數,不能夠算浮點數
2. 邏輯運算符
3. 比較運算符
#!/bin/bash
a=3
b=4
test ${a} -eq ${b}
echo "$?"
咱們這裏使用test來判斷a和b是否相等,$?能夠返回真、假。對於運算符,咱們先說到這,咱們會在後面的練習中使用
這裏的if語句和其餘開發語言中的差很少, Shell腳本中的if語句有3種
1. if ... then ... fi
Condition若是爲真,則執行then後面的語句,爲假則結束。這裏須要注意的是:Condition和方括號之間須要有空格,不然就會報錯
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
fi
結果:
2. if ... then ... else ... fi
這個多了個else,能夠對不符合表達式時作些處理
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
else
echo "Oh,wrong."
Fi
3. if ... elif ... fi
有時咱們想要在else的時候,再作些判斷,可使用elif
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
elif [ ${y_result} == -1 ]
then
echo "Hehe,you find me."
Fi
<, >等運算符在[]中只能用於字符串的比較, 而在[[]]中<, >能夠用於整型和字符串的大小比較, 也可使用&&和||來書寫邏輯表達式。
if的條件判斷不必定使用[]或[[]]表達式,它能夠是任何一個命令。命令的返回值爲0則if判斷爲真, 非0判斷爲假。
[]和[[]]轉義表達式也能夠像普通指令同樣執行,判斷爲真則返回0,假則返回非0值。
[ 2 -gt 1 -a 3 -lt 4 ] && echo 'ok'
判斷字符串相等
if [${NAME}='tmp' ]then;
echo"name is tmp"
fi
判斷文件是否存在
if [-e ]thentmp;
echo"tmp exists"
fi
判斷tmp
是否存在,tmp
能夠是目錄或文件。
判斷是否爲普通文件
if [-f ]thentmp;
echo"file tmp exists"
fi
判斷tmp
是否爲文件,tmp
不能是目錄。
判斷是否爲目錄
if [-d ]thentmp;
echo"directory tmp exists"
fi
判斷是否具備執行權限
if [-x ]thentmp;
echo"tmp is executable"
fi
不判斷文件是否可執行,只判斷是否擁有x
權限。 所以,tmp爲有x權限的目錄時也會判斷爲真。相似的還有,-w
判斷是否擁有寫入權限, -r
判斷是否擁有讀取權限。
判斷是否爲空文件
if [-s ]thentmp;
echo"file is not empty"
fi
條件控制語句還有一個case,對於須要多個elif的可使用case嘗試下
#!/bin/bash
echo "choose a number from 1 to 4."
read y_num
case ${y_num} in
1)
echo "you select 1."
;;
2)
echo "you select 2."
;;
3)
echo "you select 3."
;;
4)
echo "you select 4."
;;
*)
echo "please choose a number from 1 to 4."
;;
esac
*能夠表明一種默認狀況
;; 與其餘語言中的 break 相似,意思是跳到整個 case 語句的最後。
前面咱們介紹了條件控制語句,這裏咱們介紹下循環控制語句while
一樣的,Condition左右都須要有空格,while循環和Java中的都差很少,這裏也不贅述什麼了
#!/bin/bash
clear
echo "while demo"
y_result="ygy"
while [ ${y_result} != "lufei" ]
do
echo "Who are you ?"
read y_result
done
echo "Haha"
這裏咱們判斷y_result變量的內容是否爲「lufei",不然一直循環
#!/bin/bash
clear
echo "乘法表"
let y=1
while [ ${y} -le 9 ]
do
let x=1
while [ ${x} -le ${y} ]
do
let rs="${x} * ${y}"
printf "${x} * ${y} = ${rs} "
let x="${x} + 1"
done
let y="${y} + 1"
echo ""
done
代碼還好,就是個邏輯,相信你們會有更好的方法來實現
for會把wordlist中的值按順序賦給變量,並執行循環體中的內容
wordlist是一個列表,咱們看個例子就知道了
#!/bin/bash
echo ""
for loop in 1 2 3 4 5
do
echo "hello ${loop} ."
done
一些命令的輸出也能夠做爲序列:
for i in $(ls); do
echo $i
done
Shell腳本里面也能夠定義函數,函數就像是腳本中的子腳本
1.函數的定義和調用
咱們使用 function 來定義一個函數,須要用括號括起來
function show_menu {
echo ""
echo "1.顯示全部聯繫人"
echo "2.退出"
echo ""
echo "請選擇:"
}
調用的話,直接使用函數名進行調用
下面的函數咱們顯示個界面,根據用戶輸入進行判斷
#!/bin/bash
#顯示菜單
function show_menu {
echo ""
echo "1.顯示全部聯繫人"
echo "2.退出"
echo ""
echo "請選擇:"
}
#循環標識,爲1時進行循環
let flag=1
while [ ${flag} == 1 ]
do
#顯示菜單信息
show_menu
#讀取輸入
read rs
case ${rs} in
1)
echo "暫無聯繫人。"
;;
2)
echo "Bye."
let flag=0
;;
*)
echo "請選擇1或者2"
;;
esac
done
2. 函數的參數
函數一樣能夠傳遞參數的,咱們可使用 ${1},${2}..來使用
#!/bin/bash
function logon {
if [ ${1} == "lufei" ] && [ ${2} == "haha" ]
then
echo "登陸成功!"
else
echo "登陸失敗!"
fi
}
let flag=1
while [ ${flag} -eq 1 ]
do
echo "請輸入用戶名:"
read u_name
echo "請輸入密碼:"
read u_password
logon ${u_name} ${u_password}
done
這裏是一個簡單的登陸驗證示例,咱們將獲取的用戶名和密碼傳遞給函數logon去驗證
4. 參數個數驗證
在這裏調用函數的時候極可能參數不夠,致使程序出錯;咱們可使用$#來獲取參數的個數進行判斷
#!/bin/bash
function logon {
#判斷參數個數,2個參數就進行信息驗證
if [ $# -eq 2 ]
then
if [ ${1} == "lufei" ] && [ ${2} == "haha" ]
then
echo "登陸成功!"
else
echo "登陸失敗!"
fi
else
#提示錯誤信息
echo "參數個數不符"
fi
}
let flag=1
while [ ${flag} -eq 1 ]
do
echo "請輸入用戶名:"
read u_name
echo "請輸入密碼:"
read u_password
#調用時,只傳了一個參數
logon ${u_name}
done
這個程序,暫時還不能退出,可使用CTRL+C強制退出
5. 函數返回值
函數能夠有返回值,return,可是這裏的話,返回值必須是0~256之間的一個整數
#!/bin/bash
function test_return {
echo "please input a number:"
read num
return ${num}
}
test_return
echo "haha,you input: $?"
在實際開發中,咱們的程序可能比較大,須要按模塊開發,有不一樣的子程序
每一個子程序都是獨立的一個 文件,咱們能夠在一個主程序中調用他們
#!/bin/bash
clear
echo ""
echo "1.Add"
echo "2.Delete"
echo "3.Display"
echo "4.Quit"
read selection
case ${selection} in
"1")
./child_add
;;
"2")
./child_delete
;;
"3")
./child_display
;;
"4")
./child_quit
;;
*)
echo "oh,no."
;;
esac
這是咱們的一個主程序,咱們根據輸入,調用不一樣的程序
就子程序的話,先說到這,就是一個簡單的例子
3. 顯示文件中的內容
#!/bin/bash
echo "顯示文件中的信息"
echo "f_users.bat"
echo "---------------"
cat f_users.bat
1. linux下經常使用的輸入輸出操做符
標準輸入(stdin): < 或者 <<
標準輸出(stdout): >或者 >>;
標準正確輸出(stderr): 1>或者1>> >或者 >> ;
標準錯誤輸出(stderr): 2>或者2>>;
2. 輸出重定向
咱們使用 > 或者 >>
咱們使用 ls命令來顯示2個文件,其中file02存在,而file03不存在,這樣咱們會輸出一條錯誤信息,一條正確信息
1. 咱們將正確信息輸出到文件中
默認會將正確信息輸出,因此這2種寫法均可以
輸出到文件一樣使用echo命令:
1. 寫入文件
#!/bin/bash
echo "write to file."
echo "iput your name:"
read y_name
echo "Hello,${y_name}" > f_users.bat
咱們這裏使用 > 將信息重定向到了f_users.bat這個文件中。若是原來已經有一個同名的文件,使用大於號(>)會覆蓋這個文件
2. 追加信息
咱們使用 >>能夠向文件中追加信息
#!/bin/bash
echo "write to file."
echo "iput your name:"
read y_name
echo "Hello,${y_name}" >> f_users.bat
在咱們以前的例子中,咱們常用echo命令將一些信息輸出,這回咱們來詳細瞭解下echo這個命令
echo -n :輸出後不會自動換行
echo -e :會對一些字符作特殊處理
printf一樣能夠輸出信,但printf沒有像echo同樣自動換行
printf能夠對輸出進行格式化
若是須要限定輸出的寬度,格式爲%flags width.precision format-specifier,width表示寬度,是整數,默認是右邊對齊,若是須要左邊對齊,在前面加「-」,
例如"%-20s"表示從左邊開始對齊,寬度爲20,若是字符串長度少於20,經過空格補齊,長度超過20,則會徹底顯示。
precision在浮點值中提供四捨五入。例如%5.6G,長度爲5,精度爲6。精度是可選的。長度和精度的值能夠參數中指定,例如printf "%*.*G/n" 5 6 $myvalue。
長度指顯示中佔的字符長度,與字符長度的同義。若是長度比實際的少,例如實際字符長度更大或者所要求的精度更大,則顯示按實際長度。
shell能夠執行一行指令後當即返回, 返回後能夠經過$?變量得到執行進程的ID:
$ sleep 10 &
[1] 79403
$ echo $!
79403
Linux中可使用分號「;」、雙and號「&&」和雙豎線「||」來鏈接多個命令。單"&"符號也算命令鏈接符號,只不過它是將其前面的命令放入後臺執行,因此能夠變相地實現命令並行執行。
command1 ; command2
命令之間沒有邏輯關係。分號鏈接的命令會按照順序從前向後依次執行,但分號兩端的命令之間沒有任何邏輯關係,全部寫出來的命令最終都會被執行,即便分號前面的命令出錯也不影響後面的命令。
[root@xuexi ~]# ls das;echo "hdakl"
ls: cannot access das: No such file or directory
hdakl
command1 && command2
邏輯與。&&鏈接的命令會按照順序從前向後執行,但只有當command1正確執行才執行command2,若是command1不正確執行,則不執行command2。在bash中,經過預約義變量「$?」來判斷命令是否正確執行,若是"$?"的值爲0則表示前一條命令正確執行,其餘任意值都表示不正確執行。
[root@xuexi ~]# echo "hdakl" && ls ds
hdakl
ls: cannot access ds: No such file or directory
[root@xuexi ~]# ls das && echo "hdakl"
ls: cannot access das: No such file or directory
command1 || command2
邏輯或。||鏈接的命令會按照順序從前向後執行,但只有當command1不正確執行才執行command2,command1正確執行則不會執行command2。||和&&都是短路符號,符號左右的命令之間具備邏輯關係。
[root@xuexi ~]# ls das || echo "hdakl"
ls: cannot access das: No such file or directory
hdakl
[root@xuexi ~]# echo "hdakl" || ls ds
hdakl
通常要聯合使用&&和||的時候,基本上都會先邏輯與再邏輯或:command1 && command2 || command3。由於在實際中,command2和command3應該都是想要執行的命令。若是command1正確執行,$?就等於0,執行command2,再看狀況執行command3,若是command1錯誤執行,$?就不等於0,因此不執行command2,根據$?爲非0值,判斷了 || 右邊的命令應該被執行。
通俗點的理解方法是根據語義判斷。「若是...就...不然...就...」的語句使用「cmd1 && cmd2 || cmd3」,「若是不...就...不然...就...」使用「!cmd1 && cmd2 || cmd3」。
例如,若是用戶user1存在,就顯示用戶已經存在,不然,就添加此用戶。
[root@xuexi tmp]# id user1 && echo "user1 exists" || useradd user1
若是用戶user2不存在,則添加此用戶,不然顯示用戶已存在。
[root@xuexi tmp]# !id user2 && useradd user2 || echo "user2 exists"
若是用戶user3不存在,則添加此用戶,並設定其密碼爲用戶名自己,不然顯示用戶已存在。
[root@xuexi tmp]# !id user3 && useradd user3 && echo "user3" | passwd --stdin user3 || echo "user3 exists"
command1 &
command1 & command2
&表示將其前面的命令放入後臺執行,放入後臺後會當即返回到bash環境讓用戶能夠繼續和bash交互。若是&符號鏈接了兩個命令,則其前面的命令被放入後臺,當即執行後面的命令,因此能夠簡單地認爲這兩個命令是並行執行的,兩端的命令之間也沒有任何邏輯關係。
須要注意的一點是,在終端的bash環境下,子shell中的後臺的進程不受終端控制,在終端被關閉時它會掛靠在init/systemd進程下,所以退出終端或腳本shell環境,沒法中斷這些後臺進程。例如:
[root@xuexi ~]# (sleep 10 &) # 終端1上執行,當即關閉該終端
[root@xuexi ~]# ps aux | grep slee[p] # 終端2上捕捉sleep進程
root 5732 0.0 0.0 107892 624 ? S 00:28 0:00 sleep 10
注意ps結果中的"?",它表示非終端進程,即脫離了終端。