[TOC]
# shell編程
> Shell是用戶與內核進行交互操作的一種接口,目前最流行的Shell稱為bash Shell
> Shell也是一門編程語言<解釋型的編程語言>,即shell腳本<就是在用linux的shell命令編程>
> 一個系統可以存在多個shell,可以通過cat /etc/shells命令查看系統中安裝的shell,不同的shell可能支持的命令語法是不相同的
## 1.1 基本格式
~~~
代碼寫在普通文本文件中,通常以 .sh為后綴名
vi hello.sh
#!/bin/bash ## 表示用哪一種shell解析器來解析執行我們的這個腳本程序
echo "hello world" ## 注釋也可以寫在這里
## 這是一行注釋
執行腳本
sh hello.sh
或給腳本添加x權限,直接執行
chmod 755 hello.sh
./hello.sh
~~~
## 2.2 基本語法
### 2.2.1 系統變量
> Linux Shell中的變量分為“系統變量”和“用戶自定義變量”
> 可以通過set命令查看系統變量

> 系統變量:$HOME、$PWD、$SHELL、$USER等等
### 2.2.2 自定義變量
#### 1、語法
~~~
變量=值 (例如STR=abc)
等號兩側不能有空格
變量名稱一般習慣為大寫
使用變量: $arg
雙引號和單引號有區別,
雙引號僅將空格脫意,
單引號會將變量引用比如$param脫意
~~~
#### 2、示例
~~~
STR="hello world"
A=9
echo $A
echo $STR
如果想打印 hello worlds is greater 怎么辦?
echo $STRs is greate 行嗎?
不行,正確寫法是:
echo ${STR}s is greate
unset A 撤銷變量 A
readonly B=2 聲明靜態的變量 B=2,不能 unset
export A #可把變量提升為當前shell進程中的全局環境變量,可供其他子shell程序使用
注意理解export:
[root@shizhan01 scripts]# vi a.sh
#!/bin/bash
a="a in a.sh"
echo $a
/root/scripts/b.sh
[root@shizhan01 scripts]# vi b.sh
#!/bin/bash
b="b in b.sh"
echo $b
echo $a
~~~
> 然后執行 ./a.sh ,會發現 b腳本中并沒有把a腳本中定義的a變量打印出來
> 如果要在b中打印出a腳本的變量a,需要在a腳本中把變量a做export定義
> 此時,a變量就成了a.sh腳本所在bash進程的全局變量,該進程的所有子進程都能訪問到變量a
> 另一種方式:
> 如果在a.sh腳本中用如下方式調用b.sh腳本
> . ./b.sh ## 注意:重點關注最前面那個 “.”號
> 或者
> source ./b.sh ##
> 則,b.sh就在a.sh所在的bash進程空間中運行
> 總結:
> 1、a.sh中直接調用b.sh腳本,會讓b.sh在a所在的bash進程的“子進程”空間中執行
> 2、而子進程空間只能訪問父進程中用export定義的變量
> 3、一個shell進程無法將自己定義的變量提升到父進程空間中去
> 4、“.”號執行腳本時,會讓腳本在調用者所在的shell進程空間中執行
#### 3、反引號賦值
~~~
A=`ls -la` ## 反引號,運行里面的命令,并把結果返回給變量A
A=$(ls -la) ## 等價于反引號
~~~
#### 4、特殊變量
~~~
$? 表示上一個命令退出的狀態碼
$$ 表示當前進程編號
$0 表示當前腳本名稱
$n 表示n位置的輸入參數(n代表數字,n>=1)
$# 表示參數的個數,常用于循環
$*和$@ 都表示參數列表
~~~
> 注:$*與$@區別
> $* 和 $@ 都表示傳遞給函數或腳本的所有參數
> ? 不被雙引號" "包含時——
> $* 和 $@ 都以$1 $2 … $n 的形式組成參數列表
> ? 當它們被雙引號" "包含時——
> "$*" 會將所有的參數作為一個整體,以"$1 $2 … $n"的形式組成一個整串;
> "$@" 會將各個參數分開,以"$1" "$2" … "$n" 的形式組成一個參數列表
## 2.3 運算符
### 2.3.1 算數運算
#### 1、用expr
> 格式 expr m + n 或$((m+n)) 注意expr運算符間要有空格
> 例如計算(2+3 )×4 的值
~~~
1 .分步計算
S=`expr 2 + 3`
expr $S \* 4 ## *號需要轉義
2.一步完成計算
expr `expr 2 + 3 ` \* 4
echo `expr \`expr 2 + 3\` \* 4`
~~~
#### 2、用(())
~~~
((1+2))
(((2+3)*4))
count=1
((count++))
echo $count
~~~
> 但是要想取到運算結果,需要用$引用
> a=$((1+2))
#### 3、用$[]
~~~
a=$[1+2]
echo $a
~~~
## 2.5 流程控制
### 2.5.1 if語法
#### 1、語法格式
~~~
if condition
then
statements
[elif condition
then statements. ..]
[else
statements ]
fi
~~~
#### 2、示例
~~~
#!/bin/bash
read -p "please input your name:" NAME ## read命令用于從控制臺讀取輸入數據
## printf '%s\n' $NAME
if [ $NAME = root ]
then
echo "hello ${NAME}, welcome !"
elif [ $NAME = itcast ]
then
echo "hello ${NAME}, welcome !"
else
echo "SB, get out here !"
fi
~~~
#### 3、判斷條件
##### 1/ 條件判斷基本語法
> [ condition ] (注意condition前后要有空格)
> #非空返回true,可使用$?驗證(0為true,>1為false)
> [ itcast ]
> #空返回false
> [ ]
> 注意[ ]內部的=周邊的空格,有區別:
~~~
[root@shizhan01 scripts]# if [ a = b ];then echo ok;else echo notok;fi
notok
[root@shizhan01 scripts]# if [ a=b ];then echo ok;else echo notok;fi
ok
~~~
> 短路(理解為三元運算符)
> [ condition ] && echo OK || echo notok
> 條件滿足,執行&&后面的語句;條件不滿足,執行|| 后面的語句
##### 2/ 條件判斷組合
> 注:[] 與[[ ]] 的區別:[[ ]] 中邏輯組合可以使用 && || 符號
> 而[] 里面邏輯組合可以用 -a -o
> [root@mini ~]# if [ a = b && b = c ]; then echo ok;else echo notok;fi
> -bash: [: missing `]'
> notok
~~~
[root@mini ~]# if [ a = b -a b = b ]; then echo ok;else echo notok;fi
notok
[root@mini ~]# if [ a = b -o b = b ]; then echo ok;else echo notok;fi
ok
[root@mini ~]# if [[ a = b && b = b ]]; then echo ok;else echo notok;fi
notok
[root@mini ~]# if [[ a = b || b = b ]]; then echo ok;else echo notok;fi
ok
~~~
##### 3/ 常用判斷運算符
> 字符串比較:= !=
~~~
-z 字符串長度是為0返回true
-n 字符串長度是不為0返回true
if [ 'aa' = 'bb' ]; then echo ok; else echo notok;fi
if [ -n "aa" ]; then echo ok; else echo notok;fi
if [ -z "" ]; then echo ok; else echo notok;fi
~~~
> 整數比較:
~~~
-lt 小于
-le 小于等于
-eq 等于
-gt 大于
-ge 大于等于
-ne 不等于
~~~
> 文件判斷:
~~~
-d 是否為目錄
if [ -d /bin ]; then echo ok; else echo notok;fi
-f 是否為文件
if [ -f /bin/ls ]; then echo ok; else echo notok;fi
-e 是否存在
if [ -e /bin/ls ]; then echo ok; else echo notok;fi
~~~
### 2.5.2 while語法
#### 1、方式一
~~~
while expression
do
command
…
done
~~~
#### 2、方式二
~~~
i=1
while ((i<=3))
do
echo $i
let i++
done
~~~
### 2.5.3 case語法
~~~
case $1 in
start)
echo "starting"
;;
stop)
echo "stoping"
;;
*)
echo "Usage: {start|stop}"
esac
~~~
### 2.5.4 for語法
#### 1、方式一
~~~
for N in 1 2 3
do
echo $N
done
或
for N in 1 2 3; do echo $N; done
或
for N in {1..3}; do echo $N; done
~~~
#### 2、方式二
~~~
for ((i = 0; i <= 5; i++))
do
echo "welcome $i times"
done
或
for ((i = 0; i <= 5; i++)); do echo "welcome $i times"; done
~~~
### 2.6 函數使用
#### 2.6.1 函數定義
~~~
#!/bin/sh
# func1.sh
hello() ## 函數定義
{
echo "Hello there today's date is `date +%Y-%m-%d`"
# return 2 ###返回值其實是狀態碼,只能在[0-255]范圍內
}
hello
# echo $? 獲取函數的return值
echo "now going to the function hello"
echo "back from the function"
~~~
> 函數調用:
> function hello()
> 或 function hello
> 或 hello
> 注意:
> 1.必須在調用函數地方之前,先聲明函數,shell腳本是逐行運行。不會像其它語言一樣先預編譯
> 2.函數返回值,只能通過$? 系統變量獲得,可以顯示加:return 返回,如果不加,將以最后一條命令運行結果,作為返回值。 return后跟數值n(0-255)
> 腳本調試:
~~~
sh -vx helloWorld.sh
或者在腳本中增加set -x
~~~
#### 2.6.2 函數參數
~~~
#!/bin/bash
# fun1.sh
funWithParam(){
echo "第一個參數為 $1 !"
echo "第二個參數為 $2 !"
echo "第十個參數為 $10 !"
echo "第十個參數為 ${10} !"
echo "第十一個參數為 ${11} !"
echo "參數總數有 $# 個!"
echo "作為一個字符串輸出所有參數 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
~~~
> 注意,$10 不能獲取第十個參數,獲取第十個參數需要${10}。當n>=10時,需要使用${n}來獲取參數。
#### 2.6.3 函數返回值
~~~
#!/bin/bash
# fun2.sh
funWithReturn(){
echo "這個函數會對輸入的兩個數字進行相加運算..."
echo "輸入第一個數字: "
read aNum
echo "輸入第二個數字: "
read anotherNum
echo "兩個數字分別為 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "輸入的兩個數字之和為 $? !"
~~~
#### 2.6.4 跨腳本調用函數
> 假如上述的腳本文件fun2.sh保存在此路徑: /root/fun2.sh
> 則可在腳本fun_other.sh中調用腳本fun2.sh中的函數
~~~
#!/bin/bash
# fun_other.sh
. /root/fun2.sh ## 注: . 和 / 之間有空格
# 或者 source /root/fun2.sh
funWithParam 11 22 33 44 55 66 77 88 99 100 101
~~~
## 2.7 shell編程綜合練習
### 1、需求描述
> 公司內有一個N個節點的集群,需要統一安裝一些軟件(jdk)
> 需要開發一個腳本,實現對集群中的N臺節點批量自動下載、安裝jdk
### 2、思路
> 1/ 編寫一個啟動腳本,用來發送一個軟件安裝腳本到每一臺機器
> 2/ 然后啟動每臺機器上的軟件安裝腳本來執行軟件下載和安裝

### 3、expect的使用
> 痛點:使用scp命令遠程拷貝文件時,會有人機交互的過程,如何讓腳本完成人機交互?
> 妙藥: expect
> 用法示例:
> 先觀察 ssh localhost 的過程
> 再看expect的功能
~~~
#!/bin/bash/expect
## exp_test.sh
set timeout -1;
spawn ssh localhost;
expect {
"(yes/no)" {send "yes\r";exp_continue;}
"password:" {send "hadoop\r";exp_continue;}
eof {exit 0;}
}
~~~
> 執行: expect -f exp_test.sh
### 4.腳本開發
#### 1、啟動腳本
~~~
vi boot.sh
#!/bin/bash
SERVERS="mini1 mini2"
PASSWORD=hadoop
BASE_SERVER=192.168.33.11
## 實現免密登陸配置的函數
auto_ssh_copy_id() {
expect -c "set timeout -1;
spawn ssh-copy-id $1;
expect {
*(yes/no)* {send -- yes\r;exp_continue;}
*assword:* {send -- $2\r;exp_continue;}
eof {exit 0;}
}";
}
ssh_copy_id_to_all() {
for SERVER in $SERVERS
do
auto_ssh_copy_id $SERVER $PASSWORD
done
}
## 調用免密登陸配置函數,實現母雞到各仔雞的免密登陸配置
ssh_copy_id_to_all
## 完成分發install.sh到各仔雞的操作
## 并讓仔雞啟動install.sh
for SERVER in $SERVERS
do
scp install.sh root@$SERVER:/root
ssh root@$SERVER /root/install.sh
done
~~~
#### 2、安裝執行腳本
> vi install.sh
~~~
#!/bin/bash
BASE_SERVER=192.168.33.11
## 為本機安裝wget命令
yum install -y wget
## 使用wget從母雞的web服務器上下載jdk壓縮包
wget $BASE_SERVER/soft/jdk-7u67-linux-x64.gz
## 將下載的壓縮包解壓
tar -zxvf jdk-7u67-linux-x64.gz -C /usr/local
## 修改profile配置文件
cat >> /etc/profile << EOF
export JAVA_HOME=/usr/local/jdk1.7.0_67
export PATH=\$PATH:\$JAVA_HOME/bin
EOF
~~~
#### 3、啟動腳本
> 只要在baseServer即mini上啟動boot.sh即可
## 2.8 操作中遇到的問題
~~~
cat file1 | while read LINE
do
echo "this is $LINE"
ssh
done
~~~
> while中使用重定向機制,file1文件中的信息都已經讀入并重定向給了整個while語句,
> 所以當我們在while循環中再一次調用read語句,就會讀取到下一條記錄,
> 但是,因為ssh會讀取存在的緩存,調用完ssh語句后,輸入緩存中已經都被讀完了,
> 當read語句再讀的時候當然也就讀不到紀錄,循環也就退出了。
> ssh的幫助手冊提到一個參數就是-n 需要重定向 防止從stdin 讀取數據
> -n
> 把 stdin 重定向到 /dev/null (實際上防止從 stdin 讀取數據). 在后臺運行時一定會用到這個選項.
> 它的常用技巧是遠程運行 X11 程序. 例如, ssh -n shadows.cs.hut.fi emacs 將會在 shadows.cs.hut.fi
> 上啟動 emacs, 同時自動在加密通道中轉發 X11 連接. 在后臺運行.
> 下面是之前我的文檔
#### SSH eats stdin of while loop
> tags: ssh | bash
> Bash script is helpful if you want to do something automatically, especially in batch mode. Recently I want to upgrade a package on several hosts so I write a small script within it there is a while loop to read hosts from a file and ssh to every one and run some commands. However, the weird thing is that commands only done on the first host apparently and the while loop broke.
> That did surprised me a little, see below simple scripts
~~~
#!/bin/bash
echo -e "host1\nhost2\nhost3" | while read host;
do
echo "ssh to $host"
ssh $host "echo hello"
done
~~~
> The output as I expected was
> ssh to host1 hello ssh to host2 hello ssh to host3 hello
> However, the out was
> ssh to host1 hello
> Apparently something gone wrong, as I expected if comment the line ssh $host “echo hello”, it works fine and print three *ssh to * lines. So the magic brought by **ssh**.
> Though google is not always here (in China) but it’s helpful and I found answer here and there.
> So here comes the short answer, ssh eat the stdin of the while loop, so host2\nhost3\nwas never sent to the while loop but eaten by ssh, we can verify that like below.
~~~
#!/bin/bash
echo -e "host1\nhost2\nhost3" | while read host;
do echo "ssh to $host"
ssh $host "cat"
done
~~~
> We’ll get the output like
> ssh to host1 host2 host3
> A simple solution is use ssh with -n option to redirect its stdin from /dev/null like below.
~~~
#!/bin/bash
echo -e "host1\nhost2\nhost3" | while read host;
do echo "ssh to $host"
ssh -n $host "echo hello"
done
~~~
> It will works as we expected.
> ssh to host1 hello ssh to host2 hello ssh to host3 hello
- hadoop
- linux基礎
- Linux入門
- Linux進階
- shell
- Zookeeper
- Zookeeper簡介及部署
- Zookeeper使用及API
- Redis
- Redis簡介安裝部署
- Redis使用及API
- Java高級增強
- Java多線程增強
- Maven簡介及搭建
- Hive
- Hive簡介及安裝
- Hive操作
- HIve常用函數
- Hive數據類型
- Flume
- Flume簡介及安裝
- flume 攔截器(interceptor)
- azkaban
- azKaban簡介及安裝
- Sqoop
- Sqoop簡介及安裝
- HDFS
- HDFS原理
- HDFS操作API
- MAPREDUCE原理
- MAPREDUCE圖片資源
- MAPREDUCE加強
- HBASE
- HBASE簡介及安裝
- HBASE操作及API
- HBASE內部原理
- Storm
- Storm簡介及安裝
- Storm原理
- kafka
- kafka簡介及安裝
- kafka常用操作及API
- kafka原理
- kafka配置詳解
- Scala
- Scala簡介及安裝
- Scala基礎語法
- Scala實戰