shell的格式
shell可以在直接在命令行下輸入,也可以保存成shell腳本文件運行。當命令簡單並且不需要重復使用,在命令行輸入直接執行即可,否則就寫成腳本。shell腳本默認文件擴展名為.sh。在shell腳本中,寫入的內容,會默認當成一條命令來執行。
例如:
#!/bin/bash
echo 'hello world'
- 第1行 指定shell腳本的解釋器
- 第2行 執行echo命令
將上面的代碼存為test.sh,並將可執行權限賦予它chmod +x test.sh ,執行./test.sh運行腳本。
上面的腳本將會輸出:
hello world
這和在命令行或者終端模擬器下輸入echo 'hello world'並按下回車得到的結果是一樣的
注釋
和所有的編程語言一樣,shell也有注釋,在shell中,#號和它后面的內容來表示一個注釋:
# Print a message
echo "I'm a shell script."
輸出內容
echo命令用於向輸出流輸出內容,例如:
echo "hello world"
echo輸出控制字符
echo -e "KEY\tVALUE\nluoye\t30"
輸出
KEY VALUE
luoye 30
用printf命令格式化輸出
printf "My name is %s,I'm %d years old." luoyesiqiu 20
輸出
My name is luoyesiqiu,I'm 20 years old.
輸入內容
read用於輸入一條內容:
read input
echo $input
上面的代碼中,read命令從輸入流讀取一個值並賦予input,然后將input的內容打印出來
1. 變量
1.1 定義變量和賦值
變量的命名規則和C語言差不多,支持英文字母和下划線。shell中變量名前不需要聲明類型,"="號兩邊不能有空格,例如:
var1='hello'
var2=90
1.2 讀取變量
$后接變量名意為讀取一個變量的值,例如:
var="hello"
echo $var
也可以用${var}方式訪問到變量值,例如:
var="hello"
echo ${var}
訪問變量的時候
$var和${var}是等效的,推薦后者來訪問一個變量
1.3 變量作用域
1.3.1 全局變量
沒有任何命令修飾的變量是一個全局變量,全局變量在同一個shell會話中都是有效的。
function func(){
a=90
}
func
echo $a
輸出:
90
$ a=90
$ echo ${a}
$ bash
$ echo ${a}
輸出:
90
空值
1.3.2 局部變量
local命令用於聲明一個局部變量
function func(){
local a=90
}
func
echo $a
輸出:
空值
1.3.3 環境變量
用export命令修飾的變量稱為環境變量,在父shell會話中聲明一個環境變量,子shell中都可以訪問
$ export path="/system/bin"
$ bash #創建一個新的shell會話
$ echo ${path}
1.3.4 特殊變量
| 變量 | 含義 |
|---|---|
| $0 | 當前腳本的文件名 |
| $n(n≥1) | 傳遞給腳本或函數的參數。n 是一個數字,表示第幾個參數。例如,第一個參數是 $1,第二個參數是 $2 |
| $# | 傳遞給腳本或函數的參數個數 |
| $* | 傳遞給腳本或函數的所有參數 |
| $@ | 傳遞給腳本或函數的所有參數 |
| $? | 上個命令的退出狀態,或函數的返回值 |
| $$ | 當前 Shell 進程 ID。對於 Shell 腳本,就是這些腳本所在的進程 ID |
1.3.5 $*和$@的區別
- $*得到所有參數被當成字符串
- $@得到所有參數都會被當成獨立的參數
#!/bin/bash
for val in "$*"
do
echo "\$@ : ${val}"
done
for val in "$@"
do
echo "\$* : ${val}"
done
假如將上面的代碼存為test.sh,運行./test.sh 1 2 3
將輸出:
$@ : 1 2 3
$* : 1
$* : 2
$* : 3
2. 獲取一條命令的執行結果
2.1 用 ` 將一條命令包裹起來
` 這個符號,在鍵盤上的位置是在Esc鍵的下方
ret=`pwd`
echo ${ret}
在 ` 包裹起來的命令中,也可以訪問到變量
path='/'
ret=`ls -l ${path}`
echo ${ret}
2.2 以$(command)這種方式執行命令
ret=$(pwd)
echo ${ret}
用$(command)這種方式也可以訪問到變量
path='/'
ret=$(ls -l ${path})
echo ${ret}
上面的例子中,如果想打印命令結果中的換行符,則:
path='/'
ret=$(ls -l ${path})
echo "${ret}"
$(command)方式來執行命令更加直觀,但是要注意,$(command) 僅在 Bash Shell 中有效,而反引號可在多種 Shell 中都可使用
3. 字符串
shell有三種方式可以表示字符串
3.1 字符串的表示
(1)變量名后直接跟上字符
str=hello
echo ${str}
輸出:
hello
這種方式的字符串遇到空格就會被終止
(2)單引號
str=hello
echo '${str}'
輸出:
${str}
單引號里的內容是字符串原始的樣子,不存在轉義
(3)雙引號
str=shell
echo "${str}:\"hello wolrd\""
輸出:
shell:"hello world"
雙引號中可以訪問變量和轉義
3.2 獲取字符串的長度
str="hello"
echo ${#str}
輸出:
5
3.3 字符串拼接
兩個變量放在一起訪問就可以拼接
a='hello'
b='world'
c=${a}${b}
echo ${c}
輸出:
helloworld
也可以這樣
echo 'hello'"world"
3.4 字符串截取
(1) 從左邊開始截取字符串,格式:${string: start :length},length可省略,省略時,是截取到字符串末尾
msg="hello world"
echo ${msg: 6: 5}
輸出:
world
(2) 在指定位置截取字符
- 截取chars后面的字符:
${string#*chars}
其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),是通配符的一種,表示任意長度的字符串。chars連起來使用的意思是:忽略左邊的所有字符,直到遇見 chars(chars 不會被截取)。
-
截取最后一次出現chars的位置后面的內容:
${string##*chars} -
使用 % 截取左邊字符
使用%號可以截取指定字符(或者子字符串)左邊的所有字符,具體格式如下:
${string%chars*}
請注意 * 的位置,因為要截取 chars 左邊的字符,而忽略 chars 右邊的字符,所以 * 應該位於chars的右側。其他方面%和#的用法相同
4. 運算符和流程控制
4.1 基本運算
| 運算符 | 作用 |
|---|---|
| + | 加(需要結合expr命令使用) |
| - | 減(需要結合expr命令使用) |
| * | 乘(需要結合expr命令使用) |
| / | 除(需要結合expr命令使用) |
| % | 求余(需要結合expr命令使用) |
| = | 賦值 |
| == | 判斷數值是否相等,需要結合[]使用 |
| != | 判斷數值是否不相等,需要結合[]使用 |
a=8
b=4
echo "a=$a,b=$b"
var=`expr ${a} + ${b}`
echo "加法結果:${var}"
var=`expr ${a} - ${b}`
echo "減法結果:${var}"
# 注意:乘號需要轉義
var=`expr ${a} \* ${b}`
echo "乘法結果:${var}"
var=`expr ${a} / ${b}`
echo "除法結果:${var}"
var=`expr ${a} % ${b}`
echo "求余結果:${var}"
var=$[${a} == ${b}]
echo "是否相等:${var}"
var=$[${a} != ${b}]
echo "是否不相等:${var}"
輸出:
a=8,b=4
加法結果:12
減法結果:4
乘法結果:32
除法結果:2
求余結果:0
是否相等:0
是否不相等:1
上面的例子中,調用expr命令和使用[],得到表達式的值,並將它們輸出
請注意表達式兩邊的空格,shell中表達式兩邊要有空格
4.2 關系運算
| 運算符 | 作用 |
|---|---|
| -eq | 全稱:Equal,判斷兩個數是否相等 |
| -ne | 全稱:Not equal,判斷兩個數是否不相等 |
| -gt | 全稱:Greater than,判斷前面那個數是否大於后面那個數 |
| -lt | 全稱:Less than,判斷前面那個數是否小於后面那個數 |
| -ge | 全稱:Greater equal,判斷前面那個數是否大於等於后面那個數 |
| -le | 全稱:Less than,判斷前面那個數是否小於等於后面那個數 |
4.3 布爾運算
| 運算符 | 作用 |
|---|---|
| ! | 非運算 |
| -o | 或運算 |
| -a | 並運算 |
4.4 邏輯運算
| 運算符 | 作用 |
|---|---|
| && | 邏輯並 |
|| |
邏輯或 |
- 用
&&連接起來的兩個命令,前面的執行失敗就不執行后面的命令
cd /bin && ls /bin
其實和C語言中的是差不多的,只要前面的條件不滿足,后面那個就不用去執行它了
- 用
||連接起來的兩個命令,前面的執行失敗才會執行后面的命令
cd /bin || ls /bin
在這里順便說一下;這個運算符,這個運算符用於連接多個語句,使多個語句能夠在同一行。用;連接起來的語句,不管前面的執行成不成功,都會執行后面的
mkdir luoye;cd luoye;pwd
4.5 文件判斷
| 運算符 | 作用 |
|---|---|
| -e | 判斷對象是否存在 |
| -d | 判斷對象是否存在,並且為目錄 |
| -f | 判斷對象是否存在,並且為常規文件 |
| -L | 判斷對象是否存在,並且為符號鏈接 |
| -h | 判斷對象是否存在,並且為軟鏈接 |
| -s | 判斷對象是否存在,並且長度不為0 |
| -r | 判斷對象是否存在,並且可讀 |
| -w | 判斷對象是否存在,並且可寫 |
| -x | 判斷對象是否存在,並且可執行 |
| -O | 判斷對象是否存在,並且屬於當前用戶 |
| -G | 判斷對象是否存在,並且屬於當前用戶組 |
| -nt | 判斷file1是否比file2新 |
| -ot | 判斷file1是否比file2舊 |
4.6 流程控制語句
(1) if語句
if語句格式如下:
if <condition>
then
#do something
elif <condition>
then
#do something
else
#do something
fi
如果想把then和if放同一行
if <condition> ; then
#do something
elif <condition> ; then
#do something
else
#do something
fi
其中elif和else可以省略
例子:
read file
if [ -f ${file} ] ; then
echo 'This is normal file.'
elif [ -d ${file} ] ; then
echo 'This is dir'
elif [ -c ${file} -o -b ${file} ] ; then
echo 'This is device file.'
else
echo 'This is unknown file.'
fi
邏輯判斷也可以用test命令,它和[]的作用是一樣的
#!/bin/bash
a=4
b=4
if test $[a+1] -eq $[b+2]
then
echo "表達式結果相等"
else
echo "表達式結果不相等"
fi
輸出:
表達式結果不相等
(2) for 語句
if語句格式如下:
for <var> in [list]
do
# do something
done
例子:
read input
for val in ${input} ; do
echo "val:${val}"
done
輸入:
1 2 3 4 5
輸出:
val:1
val:2
val:3
val:4
val:5
(3) while 語句
while <condition>
do
#do something
done
例子:
a=1
sum=0
while [ ${a} -le 100 ] ;do
sum=`expr ${sum} + ${a}`
a=`expr ${a} + 1`
done
echo ${sum}
輸出:
5050
5. 數組
5.1 定義和基本用法
shell中也有數組,數組的格式是用英文小括號元素包裹起來,元素與元素之前用若干個分割符隔開(空格,制表符,換行符),這個分割符定義在IFS變量中,我們可以通過設置IFS變量自定義分隔符。來看看如何定義一個數組:
array=(1 2 3 4 5 'hello')
也可以定義一個空數組
array=()
訪問和修改數組元素的格式如下:
array[index]=value
和大多數編程語言一樣,shell的數組索引也是從0開始的,假如想要分別修改數組的第一個和第二個元素為88和77:
array[0]=88
array[1]=77
上面的代碼,如果array為空,88和77將被添加到數組中。
5.2 遍歷
遍歷數組太常見了,如果想對數組每個元素都進行特定的操作(訪問,修改)就需要遍歷。
在shell中,有兩個方式可以得到數組的全部元素,${array_name[*]}和${array_name[@]},有了這個知識,我們就遍歷數組了
#!/bin/bash
array=("My favoriate number is" 65 22 )
idx=0
for elem in ${array[*]}
do
echo "Array element ${idx} is:${elem}"
idx=$(expr $idx + 1)
done
輸出:
Array element 0 is:My
Array element 1 is:favoriate
Array element 2 is:number
Array element 3 is:is
Array element 4 is:65
Array element 5 is:22
在上面的代碼中我們可能以為自己定義了一個字符串和兩個數字在數組中,應該打印出一行字符串和兩個數字。但是卻不是這樣的,只要有空白符,shell會把它們當成數組的分隔符,這些被隔開的部分就會被當成數組的元素。
6. 函數
- 用function關鍵字來定義一個函數
- 直接寫一個函數名來調用一個無參數的函數
- 函數有參數,調用時,在函數名后面寫上參數,多個參數用空格隔開
- 調用函數時傳遞參數,在函數體內部,通過 $n的形式來獲取參數的值,例如:$1表示第1個參數,$2表示第2個參數...
6.1 函數的結構
function foo(){
# do something...
}
6.2 函數的用法示例
function foo(){
local name=$1
local age=$2
echo "My name is ${name},I'm ${age} years old."
}
foo "luoye" 26
輸出:
My name is luoye,I'm 26 years old.
7. 重定向
重定向可以理解把一個東西傳送到另個地方
| 重定向符 | 作用 |
|---|---|
| output > file | 將輸出流重定向到文件 |
| output >> file | 將輸出流追加到文件末尾 |
| input < file | 將文件的內容重定向到輸入流 |
7.1 輸出到文件
例子:
echo 'hello' > out.txt
echo 'world' >> out.txt
cat out.txt
輸出:
hello
world
上面的例子,將hello從輸出流重定向文件,將world追加到out.txt文件尾,最后用cat命令讀取並打印out.txt的文件內容
重定向符還可以配合數字(0,1,2)使用
- 0 代表標准輸入流
- 1 代表標准輸出流,上面的例子沒有指定數字,就是默認輸出流
- 2 代表標准錯誤流
ls / 1> out.txt
cat out.txt
執行上面的命令,會將根目錄下的文件名和目錄名輸出到out.txt
ls /luoye 2> out.txt
cat out.txt
執行上面的命令,會將執行ls /luoye命令時的錯誤信息輸出到out.txt
ls /;ls /luoye 2>&1
執行上面的代碼,將錯誤流重定向到輸出流,這種做法在某些場合是很有用的。
7.2 特殊文件
7.2.1 /dev/null
所有重定向到這個文件的內容都會消失,常常同於忽略錯誤輸出。
ls /luoye 2> /dev/null
如果不存在/luoye這個目錄或者文件,就沒有什么提示
7.2.2 /dev/zero
這個文件會不斷產出空的數據,該文件常被dd命令使用
dd if=/dev/zero of=out.txt bs=1 count=16
從/dev/zero輸入,輸出到out.txt,生成一個大小為16字節的空文件
8. 管道
管道是Linux中的一種跨進程通信的機制,和重定向不同,管道用做進程與進程之間傳送數據。做為Linux中默認的腳本語言,shell中也是可以使用管道的,在shell中,管道用|表示
(1)使用管道進行數據篩選內容中包含root的行
ls -l /|grep root
這個例子中,ls命令輸出的內容傳給了grep命令進行篩選
(2)也可以同時用多個管道
使用多個管道把數據篩選並統計
ls -l /|grep root|wc -l
這個例子中,ls命令輸出的內容傳給了grep命令進行篩選,然后轉給wc命令統計行數。
為了更好的理解管道,寫兩個腳本來體驗一下:
in.sh文件
#! /bin/bash
read msg
echo "Receive :${msg}"
out.sh文件
#! /bin/bash
echo 'hello'
在命令行中執行./out.sh |./in.sh
輸出:
Receive :hello
符合我們預期,字符串hello從out.sh傳送到了in.sh
