1. 程式人生 > >腳本進擊之漢諾塔tatatata……

腳本進擊之漢諾塔tatatata……

linux

操作環境依舊是centos7與centos6。阿拉的腳本都是放在7上了,6裏的通用性大概有0.5%左右的誤差,錯誤和可完善之處盡請指正。


請忽略中二的標題>_<。

嘛,某種意義上,這個標題還算貼切。因為這個問題咋一看到就是會給人一種頭大的感覺,踏踏踏踏踏,塔塔塔塔塔塔……

哦急死尅。先看過題目再來說頭大的問題吧。

原題如下:


漢諾塔(又稱河內塔)問題是源於印度一個古老傳說。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞著 64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放 在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間 一次只能移動一個圓盤 利用函數,實現N片盤的漢諾塔的移動步驟。



事實上,本篇阿拉準備了三個腳本。漢諾塔是魚肉正菜,順便還有開胃菜斐波那契數列和飯後甜點命令復制的腳本。

哈哈哈,阿拉是覺得這個漢諾塔還挺復雜的了。有牽扯到遞歸和階乘思想,而遞歸調用,是一不小心就會掉進坑裏的。米娜桑可以自己先做做看嘛。從最難的入手,老刺激了。要是攻克了,那成就感,賊拉爽了。

好了好了,都先動手試試。

技術分享

做出來了嗎?

嘛,不管你做出來與否,阿拉都是要嘮叨些什麽的。做出來的就當交流經驗了,沒做出的,如果阿拉的思路能幫上忙,那就再好不過了。

遞歸用的好,這個程序寫下來是很簡單的。然而並不容易。

遞歸本身就很繞。邏輯套邏輯。技術分享

思維邏輯梳理通了,然後就是把思路轉化為程序語言的問題了。


漢諾塔思路分析

三個柱子。假設為A為放置N片盤的原始柱子,C為要移至的目標柱子,B則是移動過程中必須用到的中繼柱。

N為1時,A柱上放置一片盤,A移至C,完成。

N為2時,假設最下面的盤為2號盤,A上的1號盤移動至B,A上的2號盤移動至C,再把B上的1號盤移動至C上,完成。

N為3時,A上的1號盤移動至C上,A上的2號盤移動至B上,C上的1號盤移動至B,A上的3號盤再移動至C,B上1號盤移動至A,B上2號盤移動至C,A上1號盤移動至C,完成。N為3時步驟如圖所示。

技術分享

……

知道了移動的步驟,其實距離寫出能實現如此功能的腳本還差的遠。


阿拉就是一個一直能得上老師思路,自習效率低的一塌糊塗的家夥。之前也是仗著這個小聰明,耍了很長一段時間的帥。高中就開始吃虧了,自主學習能力低的一比,又加上青春年少太猖狂的因素……總之,結果就是後來使出吃屎的力氣才勉強夠到別人的衣角。這裏的別人指當初一些和阿拉水平差不多的家夥們。

大學裏實行的教育是老師點到為止。百分之八九十靠自覺。這個過程的更多意義在於尋找自我。於是興趣愛好如雨後春筍般茁壯成長。然後阿拉就自己去找尋自己想做的事情找尋了三四年技術分享

看阿拉現在做的事情就知道了。沒錯,這就是阿拉給自己的答案。技術分享

答案就是——自主學習的能力真是很重要啊!

科科,沒有扯犢子。認真的說,嘗試接觸新事物的自己和窩在宿舍追番的自己,都是阿拉喜歡的自己。追過了兩千多集番劇的阿拉,思想和覺悟都得到了洗禮。

兩千不虛。海賊700火影600加銀魂300,這就1600了,型月、宮崎、新海誠、富奸、鋼煉等經典之流,加起來這個數字只多不少。其他的長篇番也有涉獵,因為有更想看的就暫且擱置了,開坑未補之作更是數不勝數。

動漫一集24分鐘,去除op、ed,按22分鐘來算(畢竟有些op和ed是不跳的)……這個結果沒什麽意義。

阿拉頂多算是個真愛粉,某些時候該給動漫大佬遞茶還是要遞的。技術分享

不知道有沒有表達出什麽。

培訓教室外面的墻上有句標語——我沒有失敗,我只是發現了一千條行不通的路。也好像是九百九十九條行不通的路,啊管他呢。

阿拉想說的是,一遍一遍的嘗試不是沒有用的。經驗都會成為財富,在你意識不到的時候顯露出價值。

動漫對阿拉的三觀影響不小,而成為一個自己更想成為的自己,會讓自己更開心,然後學習效率也棒棒噠。渾身帶著幹勁做事,老開心了。

而這一切的成長,距離自己給定的目標還遠的不行,但這不妨礙每天以全新的態度繼續前進。

(會寫這些無關的事情是因為阿拉某個奇怪而偏執的自我想法,嫌阿拉啰嗦的話就去把Fate/stay nigit補完然後去吐槽型月工廠吧。阿拉的啰嗦跟那個有說不清的關系。覺得動漫都是小孩子的東西的就嘗試看下Fate/Zero吧,那個是一群大人的故事。相信阿拉,以上兩者,認真看完,你不會後悔的。哦,資源的話,B站上都有的。傳送門:https://www.bilibili.com/)

總之暫且把漢諾塔放一邊,現在,只要知道這個移動的方式,知道這個問題可以解決就足夠了。

現在,我們來看個有意思的小題目。


由斐波那契數列寫遞歸函數

函數的使用跟腳本基礎其實無甚關系。大概是因為邏輯太繞,老師一般會把他放到後面講。其實早些弄清這裏的道道,普通腳本根本就是小菜一碟吶。

就是細節註意下就好了,腳本這種事,切忌眼高手低。沒錯,阿拉栽這不知道少次了。技術分享

rabbit喜歡嗎?那就用rabbit當作腳本名字好了。題目的話,是下面醬紫。


斐波那契數列又稱黃金分割數列,因數學家列昂納多·斐波那 契以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的 是這樣一個數列:0、1、1、2、3、5、8、13、21、34、 ……,斐波納契數列以如下被以遞歸的方法定義:F(0)=0, F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2) 利用函數,求n階斐波那契數列


這個是求出第n個斐波那契數列上的數值。打印整個數列不是重點,阿拉就直接跳過了哦。

這是一個典型遞歸調用。遞歸調用函數,需要註意的是函數值傳遞,如何把上一層函數得出的結果順利傳遞給下一層使用。

這個阿拉昨天的初版腳本。而且文不對題。0.0事實上應該說是阿拉寫的第一個遞歸調用的腳本,這腳本實際上實現的功能更像是從1加到n,1+2+3+...+n|bc的結果。

然而還是寫的一團糟。

沒思緒的時候,簡單的錯誤都要測試數次——還不一定測的出來。技術分享

#!/bin/bash
# ------------------------------------------
# Filename: rabbit.sh
# Revision: 1.0
# Date: 2017-09-14
# Author: zhangsan
# Email: [email protected]
# http://www.ardusty.com/
# boke://amelie.blog.51cto.com/
# Description: 
# ------------------------------------------
fact() 
{
for i in `seq $1|sort -nr`;do
        if [ $i -eq 1 ]; then
                sum=$i
        else
                sum=$[$i+$(fact $[i-1])]
        fi
done
echo $sum
}
read -p "Please input a int: " n
[[ "$n" =~ ^[0-9]+$ ]] || { echo "input error";exit 1; }
        fact $n

這之前,阿拉還走了些說出來都是恥辱的彎路。

遞歸腳本測試,一不小心主機就崩了技術分享。所以,請在實驗環境中進行吶。

那麽上面腳本的毛病,你看出來了嗎。

假設執行時輸入值為9,9為n,傳遞至fact函數中,執行for循環,本來阿拉是想實現$i+$(i-1)+$(i-2)+...+0,所以seq那裏用了倒序,這樣i的第一個值就是9,執行sum=$[9+$(fact $8)],然後就會再調用fact函數,到最後,就是sum=$[9+8+7+6+5+4+3+2+$(fact 1)],fact 1時sum被重新賦值,即為sum=1。等於之前的sum=$[9+8+7+6+5+4+3+2+$(fact 1)]白賦值了。這時,由於不用再調用函數,第一次的循環總算結束了。然後是第二次循環,i值為8……

這樣就有問題了。而for循環執行結束,才會輸出sum的值。

其實這個還好了,最起碼不是一個死循環0.0。使用bash -x rabbit.sh能看到程序執行的一步步過程,最後果然,輸入結果為1。

遞歸函數本身就相當於循環了,這裏再用循環是錯誤的。


好吧。想要用遞歸來求1+2+3+...+n的值,又該如何設計呢?

既然用遞歸,就先把for循環扔一邊。

寫一個函數,調用自身實現累加。

受限於之前的編程思想,一般會設置變量sum存儲累加值,輸出sum。

假設n為最大值,同此表示累加次數。

n為1,sum值為1。

n為2,則sum=n+(n+1)。因為不是在腳本裏,$符號阿拉就省略了哈。

這是典型的for循環思維。

函數調用,通常只得到一個返回值就足夠了。所以,echo足矣。

鬧清楚這點對阿拉而言真的是得來全不費工夫啊。老師講的時候一筆而過,阿拉也就象征性的瞅了眼。太多次都是栽在這了。0.0這大概是阿拉不是正經班子出身,沒什麽編程素質的原因。

這個不能算差距,但很容易拉開距離。對於計算機或信息技術的學生而言,電腦就像自家後院,在這樣的環境裏另學一門語言,是有很多可借用工具的。就像阿拉學過網頁,html和php之類的有所涉獵,搭網站的時候心裏就稍微有點譜。雖然這個過程並不清楚,至少不虛。

但這樣也有不利之處。學開發的轉運維難免會帶著之前搞開發時的習慣。這樣反而初學者更占優勢。老師也有說學了運維學開發容易,學過開發轉運維難。

還是要多看點這方面的書,像《鳥哥的linux私房菜》基礎學習篇和服務器架設篇。最好和老師的課堂講解配合,這樣,比起那些同期的有基礎的家夥們,才會不虛。腳本方面有shell腳本學習之類的書籍。時間充足的可以看看。也別喧賓奪主就是了0.0。

阿拉也喜歡鉆研。可有些知識點就是不會,那就找資料看。承認弱點,然後缺什麽補什麽。了解弱點的最優途徑無疑是錯誤。既然在遞歸函數上栽了,那就查資料,看例題,搞會就是了。

首推看例題。

當當當。下面的腳本是阿拉參照老師寫的階乘的例題寫出來的。

#!/bin/bash
# ------------------------------------------
# Filename: rabbit.sh
# Revision: 1.0
# Date: 2017-09-14
# Author: zhangsan
# Email: [email protected]
# http://www.ardusty.com/
# boke://amelie.blog.51cto.com/
# Description: 
# ------------------------------------------
fact() 
{
if [ $1 -eq 1 ]; then
        echo $1
else
        echo $[$1+$(fact $[$1-1])]
fi
}
read -p "Please input a int: " n
[[ "$n" =~ ^[0-9]+$ ]] || { echo "input error";exit 1; }
[ $n = 0 ] && { echo "num must be a int and great than 0";exit 2;}
        fact $n

用echo返回函數執行結果,執行結果值內嵌套遞歸調用。

還是假設接收到的輸入值為9,執行函數,echo $[9+$(fact 8)],fact 8會返回$[8+$(fact 7)]的值,fact 2返回$[2+$(fact 1)],執行fact 1,會輸出1,這樣fact 2的值就是$[2+1],以此類推,fact 9就會得到$[9+8+7+6+5+4+3+2+1]。註意別忘了對輸入值是否為正整數的判斷。函數本身接收值為負,那就是死循環了0.0。包括接收值為0,也是不可以的哦。

正整數的判斷寫到函數裏也可以。但不如寫到外面好。應該說根據需求。linux編程和操作環境相對自由,然後,自我約束就要自己來做了。流浪漢的自由和馬雲的自由肯定不是一個定義。linux在運維人員手裏是法寶,在常人眼裏甚至根本用不著。馬哥常說,君子慎獨。

就是說,linux的環境人人皆可得到,怎樣讓他發揮最大價值,怎樣寫編程最優,就看user的了。

我本root,無限囂張。哈哈哈。

讓函數功能強大,就把好輸入的關卡。函數功能需要定制,且僅執行某功能,那就函數好好寫,輸入輕松些。

嘛,就相當於大公司的監控軟件吧。到了另一個公司可能完全用不上。但在本公司,那就是法寶利器。


正經的斐波那契數列腳本。

#!/bin/bash
# ------------------------------------------
# Filename: rabbit.sh
# Revision: 1.0
# Date: 2017-09-14
# Author: zhangsan
# Email: [email protected]
# http://www.ardusty.com/
# boke://amelie.blog.51cto.com/
# Description: 
# ------------------------------------------
fact() 
{
if [ $1 -eq 1 ]; then
        echo 0
elif [ $1 -eq 2 ];then
        echo 1
else    
        echo $[$(fact $[$1-1])+$(fact $[$1-2])]
fi
}
read -p "Please input a int: " n
[[ "$n" =~ ^[0-9]+$ ]] || { echo "input error";exit 1; }
[ $n = 0 ] && { echo "num must be a int and great than 0";exit 2;}
        fact $n

怎麽樣,你寫出來了嗎?


漢諾塔思路實現

上面我們已經知道,一片盤的時候需要移動一次,兩片盤需要移動兩次,三片盤需要移動7次,自己移動試試,四片盤需要15次。

4片盤時,其實前7次和3片盤時是一樣的。

嘛,就是目標盤好像和過渡盤的要換一下了。

4片盤時,假設B柱是目標柱。假設最大盤是4,最小盤是1。

第1步,從A柱移動1號盤到C。

第2步,從A柱移動2號盤到B。

第3步,從B柱移動1號盤到C。

第4步,從A柱移動3號盤到B。

第5步,從C柱移動1號盤到A。

第6步,從B柱移動2號盤到C。

第7步,從A柱移動1號盤到C。到此為止都和開始那張圖一樣。

第8步,從A柱移動4號盤到B。

第9步,從A柱移動1號盤到B。

第10步,從C柱移動2號盤到A。

第11步,從B柱移動1號盤到A。

第12步,從C柱移動3號盤到B。

第13步,從A柱移動1號盤到C。

第14步,從A柱移動2號盤到B。

第15步,從C柱移動1號盤到A。

如果我們要打印這樣的移動效果,無疑我們需要定義4個變量。

而且我們可以發現,第n片盤和第n-1片的前面是完全一樣的,只是目標盤和過渡盤在不斷切換。而最後移至的是哪個盤,題目沒有要求,這個麻煩就可以不用理會了。假設我們寫的函數名為hanoi,那麽前面的部分我們就可以直接調用函數hanoi $[$n-1],這以後就是重點了。

其實我們可以這麽理解。移動分為三部分。

第一部分。將前(n-1)片盤全部移動至中繼柱。hanoi $[$n-1]。

第二部分。移動最大盤至目標盤。因為最大的盤只能在下面。也就是說最大盤只需移動這一次。如上第8步。

第三部分。將前(n-1)片盤從中繼柱移動至目標柱。剛好這部分的步驟和第一部分是相同的。看每次移動的盤號是完全相同的。只是源和目的都發生了變化。

於是有如下代碼。

本來想自己寫的。結果迫於能力有限,阿拉也只能理解優先了。還參考了老師提供的代碼>_<。。

n為1時,從A柱移動1號盤到C。

n片盤時,對應上面的三部分,實現如下。

#!/bin/bash
#
step=0
hanoi(){
[[ ! $1 =~ ^[1-9][0-9]*$ ]]&&echo "error! please input a positive interger" && exit
if [ $1 -eq  1 ];then
let step++
echo "$step:  move plate $1   $2 -----> $4"
else
hanoi "$[$1-1]" $2 $4 $3
let step++
echo "$step:  move plate $1   $2 -----> $4"
hanoi  "$[$1-1]" $3  $2  $4
fi
}
read -p "please input the number of plates: "  number
hanoi $number A B C

代入n為3,則有hanoi 2 A C B,move plate 3 A -----> C,hanoi 2 B A C。

hanoi 2 A C B 執行也分三步,第一步hanoi 1 A B C,即1: move plate 1 A -----> C。第二步2: move plate 2 A -----> B。第三步hanoi 1 C A B,即3: move plate 1 C -----> B。

4:move plate 3 A -----> C。

hanoi 2 B A C執行也是三部分。第一部分hanoi 1 B C A,即5: move plate 1 B -----> A。第二步6: move plate 2 B -----> C。第三步hanoi 1 A B C,即7: move plate 1 A -----> C。

這樣,n的值再大,最後都會調用hanoi 1。變化的只是後面的參數,也就是註意$2 $3 $4每次值的變化,即是ABC的變化。每次移動step增加1。

阿拉也是在這個每次ABC柱的切換之前很頭大。這樣子沿著程序執行的過程來一遍,就有點明白設計者的想法了。遞歸調用其實是化整為零,將復雜一步步拆分為簡單的過程。不要題目搞的頭大,在宏觀的概念上絆住腳啊。這話也算是阿拉對自己說的吧。

還可以這樣實現啊。這是阿拉看別人的程序時常常發出的感慨。

兩片盤用一片盤來表示,要改變哪些參數。然後用三片盤驗證。畢竟三片盤就是兩個二片盤加上中間最大盤移動嘛。大概就是這麽回事吧。啊啊,下次阿拉能不能自己寫出來呢?

(參考自知乎:如何理解漢諾塔的遞歸?https://www.zhihu.com/question/24385418


復制命令腳本

作為運維人員,常備一個定制版的小linux在身邊是很有安全感的。裏面拷貝好需要的常備命令,放在小巧精致的u盤裏,簡直走遍天下都不怕啊。哈哈,不是打火機了。

寫下面這個腳本是實現這一功能的基礎。

先看下面這個題目。


編寫腳本/root/bin/copycmd.sh

(1) 提示用戶輸入一個可執行命令名稱

(2) 獲取此命令所依賴到的所有庫文件列表

(3) 復制命令至某目標目錄(例如/mnt/sysroot)下的對應路徑下 ; 如:/bin/bash ==> /mnt/sysroot/bin/bash /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd

(4) 復制此命令依賴到的所有庫文件至目標目錄下的對應路徑下 : 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2

(5)每次復制完成一個命令後,不要退出,而是提示用戶鍵入新 的要復制的命令,並重復完成上述功能;直到用戶輸入quit退出

這個是……飯後甜點了技術分享。來來來。大魚大肉之後,我們來點清淡的。這裏阿拉就先鬥膽獻醜了。

#!/bin/bash
# ------------------------------------------
# Filename: copycmd.sh
# Revision: 1.0
# Date: 2017-09-14
# Author: zhangsan
# Email: [email protected]
# http://www.ardusty.com/
# boke://amelie.blog.51cto.com/
# Description: 
# ------------------------------------------
copy ()
{
cmd=$1
which $cmd &> /dev/null || { echo "this command can‘t be copy";exit 1; }

echo "cmd depends on libs: "
ldd `which $cmd` 2> /dev/null |grep ‘=>‘|| { echo "$cmd not a dynamic executable";exit 3; }

#sysroot是否存在,以及是文件不是目錄的情況0.0
cd /mnt/sysroot &> /dev/null || { rm -rf /mnt/sysroot;mkdir -p /mnt/sysroot;cd /mnt/sysroot; }

#別名命令對應絕對文件絕對路徑過濾
if `which $cmd |grep alias &> /dev/null`;then
        cmdpath=`which $cmd|grep ^[[:space:]]|grep -o ‘\/.*$‘`
else
        cmdpath=`which $cmd`
fi

for cmdpathper in $cmdpath ;do

#which cmd得出兩個命令的絕對路徑,怎麽處理??如which
        cmdpathdirper=`dirname $cmdpathper|sed -r ‘s/^\/(.*)$/\1/g‘`
        [ -e $cmdpathdirper ] && `cp -a $cmdpathper $cmdpathdirper` || { mkdir -p $cmdpathdirper;cp -a $cmdpathper $cmdpathdirper; }
#命令對應的文件不可執行,則跳出本次循環,如service
        ldd $cmdpathper &> /dev/null || continue
        libpath=`ldd $cmdpathper |grep ‘=>‘|grep /|sed -r ‘s/^.* (\/.*) .*$/\1/g‘|uniq`
#庫文件存在則跳過,不存在則復制。庫文件為軟鏈接的情況請註意
        for libpathper in $libpath ;do
                libpathdirper=`dirname $libpathper|sed -r ‘s/^\/(.*)$/\1/g‘`
                cd /mnt/sysroot
                cd $libpathdirper/ &> /dev/null || { rm -rf $libpathdirper;mkdir -p $libpathdirper;cd /mnt/sysroot/; }
                if [ -h /mnt/sysroot$libpathper -o ! -e /mnt/sysroot$libpathper ];then
                        rm -rf /mnt/sysroot$libpathper
                        cp -L $libpathper /mnt/sysroot/$libpathdirper
                        echo -e "\033[1;33;5m$libpathper ==> /mnt/sysroot$libpathper\033[00m" 
                else
                        echo "/mnt/sysroot$libpathper already exits"
                fi
        done
        echo -e "\033[1;31;5m$cmdpathper ==> /mnt/sysroot$cmdpathper\033[00m"
done

unset cmd cmdpath cmdpathper cmdpathdirper libpath libpathper libpathdirper 
}      

primary ()
{
read -p "Please input excute cmdS, one or much ,quit means exit(eg: ls): " cmds
[ $cmds = quit ] &> /dev/null &&  exit 0

for cmdsper in $cmds;do
        echo $cmdsper
        copy $cmdsper
done
primary
}

primary


本文出自 “RightNow” 博客,請務必保留此出處http://amelie.blog.51cto.com/12850951/1966100

腳本進擊之漢諾塔tatatata……