
前言:
今天給大家分享一篇在我工作中常用的一個shell腳本,里面有一些我們常用到的shell操作。該腳本用於本地電腦和服務器交互上,實現以下功能:
- 自動拉取自己個人電腦上的源碼到服務器上yocto包的源碼文件夾。
- 自動運行compile 、strip
- 自動把編譯好的結果回傳到個人電腦上。
- 拷貝執行文件到機器中
作者:良知猶存
轉載授權以及圍觀:歡迎關注微信公眾號:羽林君
或者添加作者個人微信:become_me
情節介紹:
在工作中,我們經常會遇到需要把修改的代碼放到服務器去編譯,然后把編譯好的文件放到機器板卡對應的文件。這個過程如果我們使用命令的話,大概有七八條,完成操作完之后 ,大約會花費一分。雖然花費的時間不算多,但是你也會一直關注終端,然后等待輸入命令,需要花費不小精力。但是我們可以寫一些shell腳本來輔助我們的工作。今天就是給大家分享使用的腳本,一邊分享使用過程,一邊分享里面用到的shell技術點。包含,if判斷、switch case條件、字符截取、遠程調用腳本、兩種免密登陸等。

三者的使用過程為:本地通過腳本輸入參數選擇使用wifi分配的ip還是網線分配的ip,然后進行ip數據傳參形式 遠程調用預先放置好的——服務器編譯腳本,服務器腳本通過傳入的ip參數,免密scp拷貝本地的文件到服務器執行目錄,服務器執行編譯工作,然后拷貝到本地的指定目錄,最后調用本地的expect免密登陸的腳本,拷貝文件到機器中。
腳本一:本地選擇腳本auto_build.sh
腳本內容如下:
#/bin/bash
remote_ip=172.160.111.32
remote_hostname=lyn
case $1 in
1) echo -e '\033[0;42m Ethernet dhcp \033[0m'
VAR="eno1"
;;
2) echo -e '\033[0;46m wireless dhcp \033[0m'
VAR="wlo1"
;;
esac
HOST_IP=$(ifconfig $VAR | grep "inet" | grep -v inet6| awk '{ print $2}' | awk -F: '{print $1}')
echo "parse ip is:" $HOST_IP
if [[ ! -n "${HOST_IP}" ]] ;then
echo -e "\033[0;31m input local ip \033[0m"
read local_ip
else
if [[ ! $(echo "${HOST_IP}" | awk -F. '{printf $1}') == "192" ]] ;then
local_ip=$HOST_IP
else
echo -e "\033[0;31m error ip \033[0m"
exit 0
fi
fi
#exit 0
if [[ $2 == 1 ]];then
build_opt="all_build"
else
build_opt=
fi
ssh -t ${remote_hostname}@${remote_ip} "/home/lyn/build.sh ip=${local_ip} ${build_opt}"
這個部分有幾處技術使用:
switch case使用,if else、免密登陸,遠程調用腳本。
首先是一個switch case
此處作用是進行ip地址的篩選,因為的在調試過程中,我的電腦有時候用網線連接,有時候會去測試房去測試,用wifi連接,這個時候會進行網絡ip地址的區分,當我輸入./auto_build.sh 1的時候,腳本會進行解析eno1網線分配的ip地址,當我輸入./auto_build.sh 2的時候則會解析wlo1wifi分配的ip。
在里面我還用了顏色打印,進行關鍵詞的標注,如下所示:

關於顏色打印的部分這個是另一個知識,這是一個轉義的實際使用過程,通過特定符號的轉義識別,我們在Linux終端去顯示不同顏色的打印輸出,這個是我們經常使用的操作,例如log等級分級打印時候,error是紅色,正常是綠色,普通是白色等。
顏色打印大致介紹如下:
轉義序列以控制字符'ESC'開頭。該字符的ASCII碼十進制表示為27,十六進制表示為0x1B,八進制表示為033。多數轉義序列超過兩個字符,故通常以'ESC'和左括號'['開頭。該起始序列稱為控制序列引導符(CSI,Control Sequence Intro),通常由 '\033[' 或 '\e[' 代替。
通過轉義序列設置終端顯示屬性時,可采用以下格式:
\033[ Param {;Param;...}m
或
\e[ Param {;Param;...}m
其中,'\033['或'\e['引導轉義序列,'m'表示設置屬性並結束轉義序列。
因此,通過轉義序列設置終端顯示屬性時,常見格式為:
\033[顯示方式;前景色;背景色m輸出字符串\033[0m
或\e[顯示方式;前景色;背景色m輸出字符串\033[0m
其中 ,'\033[0m'用於恢復默認的終端輸出屬性,否則會影響后續的輸出。
示例:我在此處使用 echo -e '\033[0;42m Ethernet dhcp \033[0m' 進行網線端口ip分配的打印,通過轉義之后,打印顏色為帶背景色的綠色顯示。具體對應的顏色,大家可以看一下小麥老兄寫的這篇文章 printf打印還能這么玩。
注:打印log時候記得echo 要使用 -e參數。
其次還有組合使用命令實現獲取本地ip
HOST_IP=$(ifconfig $VAR | grep "inet" | grep -v inet6| awk '{ print $2}' | awk -F: '{print $1}')
我們一步步查看執行情況
第一步:ifconfig eno1
lyn@lyn:~/Documents/work-data/download_data$ ifconfig eno1
eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.16.30.147 netmask 255.255.255.0 broadcast 172.16.30.255
inet6 fe80::ca7:d954:67e0:7c60 prefixlen 64 scopeid 0x20<link>
ether f8:b4:6a:bd:dd:92 txqueuelen 1000 (Ethernet)
RX packets 3678600 bytes 3470673356 (3.4 GB)
RX errors 0 dropped 36842 overruns 0 frame 0
TX packets 2229431 bytes 995696588 (995.6 MB)
TX errors 0
我們經常使用ifconfig查看ip,但是使用ifconfig返回的數據過多,而我們實際使用的部分只是一部分而已。

所以我們使用ifconfig指定設備查找ip,篩去無用信息。
第二步:ifconfig eno1 | grep "inet"
把第一步查詢的信息通過 | 產生一個管道傳遞給下一個命令,用grep查找有inet字符的行數據,顯示如下:
lyn@lyn:~/Documents/work-data/download_data$ ifconfig eno1 | grep "inet"
inet 172.16.30.147 netmask 255.255.255.0 broadcast 172.16.30.255
inet6 fe80::ca7:d954:67e0:7c60 prefixlen 64 scopeid 0x20<link>
因為我們只需要ipv4協議的ip,所以我們要去掉inet6對應的地址
第三步:ifconfig eno1 | grep "inet" | grep -v inet6
使用grep -v命令去掉 inet6 關鍵詞的對應一行信息
lyn@lyn:~/Documents/work-data/download_data$ ifconfig eno1 | grep "inet" | grep -v inet6
inet 172.16.30.147 netmask 255.255.255.0 broadcast 172.16.30.255
第四步:ifconfig eno1 | grep "inet" | grep -v inet6 | awk '{ print$2}'
使用 awk處理文本文件的語言進行處理數據,$2 表示默認以空格分割的第二組 ,-F:-F指定分隔符為 ‘ : ’
lyn@lyn:~/Documents/work-data/download_data$ ifconfig eno1 | grep "inet" | grep -v inet6| awk '{ print$2}'
172.16.30.147
關於grep sed awk的使用大家也可以網上具體查看一下,但是我們一般使用過程中,grep 更適合單純的查找或匹配文本, sed 更適合編輯匹配到的文本,awk 更適合格式化文本,對文本進行較復雜格式處理。

這個時候我們從本機得到了ip地址。我們需要進行遠程調用服務器腳本,並把ip以參數形式傳入。
其次ssh免密登陸和ssh遠程執行任務
首先第一個部分就是ssh免密登陸
本地執行ssh到服務的相關操作命令需要免密,服務器scp本地文件也要免密登陸,那么如何設置我們ssh相關命令操作,無需密碼呢?
SSH分客戶端openssh-client和服務器openssh-server
如果你只是想登陸別的機器,只需要安裝openssh-client(ubuntu有默認安裝,如果沒有則sudo apt-get install openssh-client),如果要使別的機器登陸本機就需要在本機安裝openssh-server(sudo apt-get install openssh-server)
我們可以使用 ps -e | grep ssh 來查看對應的openssh-client和openssh-server運行情況,其中ssh是client ,sshd是server,哪個缺我們就使用apt-get install 。

sudo service ssh start 安裝之后可以使用這個命令啟動。
准備好了對應的server和client接下來,把我們生成的rsa公鑰拷貝要對應要登陸的機器,即可免密登陸。
1.客戶端生成公私鑰
ssh-keygen 命令一路回車默認生成
這個命令會在用戶目錄.ssh文件夾下創建公私鑰,id_rsa (私鑰),id_rsa.pub (公鑰)。
2.上傳公鑰到服務器
ssh-copy-id -i ~/.ssh/id_rsa.pub lyn@172.160.111.32
上面這條命令是寫到服務器上的ssh目錄下去了
vi ~/.ssh/authorized_keys
可以看到客戶端寫入到服務器的 id_rsa.pub (公鑰)內容。
3.測試免密登錄
客戶端通過ssh連接遠程服務器,就可以免密登錄了。
ssh lyn@172.160.111.32
第二個部分就是ssh遠程執行服務器腳本
有時候我們需要遠程執行一些有交互操作的命令。這個時候我們就可以使用ssh加參數進去進行遠程執行。
格式如下:
遠程執行一個命令
ssh lyn@172.160.111.32 "ls -l"
執行多條命令,使用分號把不同的命令隔起來
ssh lyn@172.160.111.32 "ls;cat test.txt "
遠程執行本地腳本
ssh lyn@172.160.111.32 < test.sh
遠程執行本地的腳本(執行帶有參數的腳本),需要為 bash
指定 -s 參數:
ssh lyn@172.160.111.32 'bash -s' < test.sh helloworld
執行遠程的腳本
ssh lyn@172.160.111.32 "/home/lyn/test.sh"
注,此時需要指定腳本的絕對路徑!
而我們使用的為遠程執行腳本,最終ssh遠程執行如下:
remote_ip=172.160.111.32
remote_hostname=lyn
local_ip=172.16.30.147
build_opt=
ssh -t ${remote_hostname}@${remote_ip} "/home/lyn/build.sh ip=${local_ip} ${build_opt}"
腳本二:服務器編譯腳本 build.sh
腳本內容如下:
#!/bin/bash -e
scp_dir=/media/lyn/win_data/lyn_workdata/working/robot-ctl
download_data=/home/lyn/Documents/work-data/download_data
build_dir=/home/lyn/projects/yocto/yocto-build/tmp/work/aarch64-poky-linux/robot-ctl/git-r0/git/
image_dir=/home/lyn/projects/yocto/yocto-build/tmp/work/aarch64-poky-linux/robot-ctl/git-r0/image/robot-ctl/
remote_exec_file_dir=/home/lyn/Documents/work-data/download_data/scp_exec.sh
all_build=No
wifi_src=No
only_scp_robot=No
strip_mode=No
ip_wireless_dhcp=170.160.111.45
ip_ethernet_dhcp=170.160.111.147
local_ip=${ip_wireless_dhcp}
host_name=lyn
date
echo -e "\033[0;31m Loading options.\033[0m"
# Load all the options.
if [ $# -eq 0 ];then
echo -e "\033[33;5m no argument \033[0m"
fi
for arg in "${@}"
do
if [[ -n "${arg}" ]] && [[ "${arg}" == "wifi" ]] ; then
wifi_src="Yes"
local_ip=${ip_ethernet_dhcp}
echo -e "local connect robot wifi \n --${wifi_src}\n --ip:${local_ip}."
fi
if [[ -n "${arg}" ]] && [[ "${arg}" == "scp" ]] ; then
only_scp_robot="Yes"
local_ip=${ip_ethernet_dhcp}
echo -e "scp robot local connect robot wifi \n --scp:${only_scp_robot} --${wifi_src}\n --ip:${local_ip}."
fi
if [[ -n "${arg}" ]] && [[ "${arg}" == "all_build" ]] ; then
all_build="Yes"
echo -e "all bulid"
fi
if [[ -n "${arg}" ]] && [[ "${arg}" == "ip" ]] ; then
echo -e "\033[32m ip:=\033[0m"
read ip_addr
local_ip=${ip_addr}
wifi_src="Yes"
echo -e "scp robot local ip insert \n --scp:${only_scp_robot} --${wifi_src}\n --ip:${local_ip}."
fi
if [[ -n "${arg}" ]] && [[ "${arg:0:3}" == "ip=" ]] ; then
echo -e "\033[32m ip put:=\033[0m" ${arg#*=}
local_ip=${arg#*=}
wifi_src="Yes"
echo -e "scp robot local ip insert \n --scp:${only_scp_robot} --${wifi_src}\n --ip:${local_ip}."
fi
if [[ -n "${arg}" ]] && [[ "${arg}" == "debug" ]] ; then
echo -e "\033[32m need \033[0m" ${arg}
strip_mode="Yes"
echo -e "scp robot local ip insert \n --scp:${only_scp_robot} --${wifi_src}\n --ip:${local_ip}."
fi
done
if [ "${only_scp_robot}" == "No" ];then
if [ ! "${wifi_src}" == "Yes" ] ; then
echo -e "local don't connect robot wifi \n --${wifi_src}\n --ip:${local_ip}."
fi
if [ -d "${build_dir}" ]; then
cd ${build_dir}
scp -rp lyn@{local_ip}:${scp_dir}/src \
lyn@{local_ip}:${scp_dir}/include \
lyn@{local_ip}:${scp_dir}/CMakeLists.txt .
//ssh ${host_name}@${local_ip} "${remote_scp_code_dir}"
#git pull
if [ "${all_build}" == "Yes" ] ; then
./../temp/run.do_generate_toolchain_file
./../temp/run.do_configure
fi
./../temp/run.do_compile
./../temp/run.do_install
#cd /home/lyn/
if [ "${strip_mode}" == "No" ]; then
#./strip_x1000.sh
cd ${image_dir}
aarch64-linux-gnu-strip pp
fi
else
echo -e "\033[0;31m dir is not exist.\033[0m"
fi
fi
scp ${image_dir}/exec ${host_name}@${local_ip}:${download_data}
if [[ "${wifi_src}" == "Yes" ]] || [[ "${only_scp_robot}" ]] ; then
ssh ${host_name}@${local_ip} "${remote_exec_file_dir}"
else
echo -e "no robot wifi\n"
fi
服務器執行的腳本內容比較長,從執行的流程來說,在這個腳本中,大致為初始化讀取腳本執行傳入的參數,通過參數配置不同的變量匹配不同機器狀態,緊接着,拷貝本地的文件到服務器編譯,服務器編譯完成之后拷貝可執行文件到本地,再調用本地的腳本把可執行文件拷貝到機器對應的目錄。
這個腳本有些使用技術和第一個腳本有重合,此處僅說沒有講到的部分。
首先第一個使用的就是 { $# } 和
下面是從菜鳥教程拷貝的shell傳參的特殊字符介紹
| 參數處理 | 說明 |
|---|---|
| $# | 傳遞到腳本的參數個數 |
| $* | 以一個單字符串顯示所有向腳本傳遞的參數。 如"$*"用「"」括起來的情況、以"$1 $2 … $n"的形式輸出所有參數。 |
| $$ | 腳本運行的當前進程ID號 |
| $! | 后台運行的最后一個進程的ID號 |
| $@ | 與$*相同,但是使用時加引號,並在引號中返回每個參數。 如"$@"用「"」括起來的情況、以"$1" "$2" … "$n" 的形式輸出所有參數。 |
| $- | 顯示Shell使用的當前選項 |
| $? | 顯示最后命令的退出狀態。0表示沒有錯誤,其他任何值表明有錯誤。 |
我在其中使用了$#用來輔助提醒輸入的參數數量,防止出錯;
if [ $# -eq 0 ];then
echo -e "\033[33;5m no argument \033[0m"
fi
然后使用$@把傳入的參數一個個解析出了來,進行變量的配置。
for arg in "${@}"
do
if [[ -n "${arg}" ]] && [[ "${arg}" == "wifi" ]] ; then
wifi_src="Yes"
local_ip=${ip_ethernet_dhcp}
echo -e "local connect robot wifi \n --${wifi_src}\n --ip:${local_ip}."
fi
...
done
其次使用了字符串截取的操作
if [[ -n "${arg}" ]] && [[ "${arg:0:3}" == "ip=" ]] ; then
echo -e "\033[32m ip put:=\033[0m" ${arg#*=}
local_ip=${arg#*=}
wifi_src="Yes"
echo -e "scp robot local ip insert \n --scp:${only_scp_robot} --${wifi_src}\n --ip:${local_ip}."
fi
${arg:0:3} 意思為從左邊第0個字符開始,字符的個數為3個
${arg#*=} 意思為 # 號截取,刪除 '='左邊字符,保留右邊字符。
至於為什么這么使用,以及其他使用的介紹,這里我摘錄其他博主的文章給大家做一個簡單的分享
摘錄自:《shell腳本字符串截取的8種方法》
假設有變量 var=http://www.aaa.com/123.htm.
1. # 號截取,刪除左邊字符,保留右邊字符。
echo ${var#*//}
其中 var 是變量名,# 號是運算符,*// 表示從左邊開始刪除第一個 // 號及左邊的所有字符
即刪除 http://
結果是 :www.aaa.com/123.htm
2. ## 號截取,刪除左邊字符,保留右邊字符。
echo ${var##*/}
##*/ 表示從左邊開始刪除最后(最右邊)一個 / 號及左邊的所有字符
即刪除 http://www.aaa.com/
結果是 123.htm
3. %號截取,刪除右邊字符,保留左邊字符
echo ${var%/*}
%/* 表示從右邊開始,刪除第一個 / 號及右邊的字符
4. %% 號截取,刪除右邊字符,保留左邊字符
echo ${var%%/*}
%%/* 表示從右邊開始,刪除最后(最左邊)一個 / 號及右邊的字符
結果是:http:
**5. 從左邊第幾個字符開始,及字符的個數 **
echo ${var:0:5}
其中的 0 表示左邊第一個字符開始,5 表示字符的總個數。
結果是:http:
6. 從左邊第幾個字符開始,一直到結束。
echo ${var:7}
其中的 7 表示左邊第8個字符開始,一直到結束。
結果是 :www.aaa.com/123.htm
7. 從右邊第幾個字符開始,及字符的個數
echo ${var:0-7:3}
其中的 0-7 表示右邊算起第七個字符開始,3 表示字符的個數。
結果是:123
8. 從右邊第幾個字符開始,一直到結束。
echo ${var:0-7}
表示從右邊第七個字符開始,一直到結束。
結果是:123.htm
注:(左邊的第一個字符是用 0 表示,右邊的第一個字符用 0-1 表示)
此外我們也可以使用awk、cut進行截取,這里就不一一列舉了。
腳本三:本地expect登陸拷貝scp_exec.sh腳本
腳本內容如下:
#!/bin/expect
set timeout 30
set host 192.168.1.1
set user root
spawn scp /home/lyn/Documents/work-data/download_data/ $user@$host:/opt/lib/exec
#spawn ssh $user@$host
expect {
"*yes/no*"
{
send "yes\r"
expect "*password:*" { send "123456\r" }
}
"*password:*"
{
send "123456\r"
}
}
expect eof
因為服務器地址相對固定,並且方便設置ssh公鑰免密登陸,但是機器而言,你需要調試的機器有很多,我就沒有考慮使用了expect命令。先給大家簡單介紹一下expect:
Expect是等待輸出內容中的特定字符。然后由send發送特定的相應。其交互流程是:
spawn啟動指定進程 -> expect獲取指定關鍵字 -> send想指定進程發送指定指令 -> 執行完成, 退出.
首先使用expect 我們需要安裝expect
sudo apt-get install tcl tk expect
因為我寫的這個部分也比較簡單,所以就一點點給大家說明里面執行細節:
#!/bin/expect
expect的目錄,類似與shell目錄
set timeout 30
set 自定義變量名”:設置超時時間,單位為秒,有些拷貝大文件的朋友可能會遇到
expect: spawn id exp4 not open這里沒有等到expect eof是,ssh連接已經關閉了。一般是超時時間太短了,
建議可以直接設置成 timeout -1,這意味着用不超時,拷貝結束之后才會斷開。
set host 192.168.1.1
set user root
spawn scp /home/lyn/Documents/work-data/download_data/ $user@$host:/opt/lib/exec
spawn(expect安裝后的命令)是進入expect環境后才可以執行的expect內部命令,它主要的功能是給ssh運行進程加個殼,用來傳遞交互指令。可以理解為啟動一個新進程 。
**`expect {
"*yes/no*"
{
send "yes\r"
expect "*password:*" { send "123456\r" }
}
"*password:*"
{
send "123456\r"
}
}`**
expect {}: 多行期望,從上往下匹配,匹配成功里面的哪一條,將執行與之的
send命令,注意,這里面的匹配字符串只會執行一個,即匹配到的那個,其余
的將不會執行,如果想匹配這句命令執行成功后(如登錄成功后等待輸入的root
@ubuntu:~#)的其他字符,需要另起一個expect命令,並保證不在expect{}里
面。
`send "yes\r"
send接收一個字符串參數,並將該參數發送到進程。這里就是執行交互動作,
與手工輸入密碼的動作等效。 命令字符串結尾別忘記加上“\r”,表示“回車
鍵”。
expect eof
expect執行結束, 退出交互程序。
這里我只是簡單描述了一下我使用expect文件,更多expect命令學習,有興趣的朋友,可以自行搜索學習。
結語
這就是我分享我在工作中使用的shell腳本,如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
作者:良知猶存,白天努力工作,晚上原創公號號主。公眾號內容除了技術還有些人生感悟,一個認真輸出內容的職場老司機,也是一個技術之外豐富生活的人,攝影、音樂 and 籃球。關注我,與我一起同行。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
推薦閱讀
【3】CPU中的程序是怎么運行起來的 必讀
本公眾號全部原創干貨已整理成一個目錄,回復[ 資源 ]即可獲得。
