腳本進擊之漢諾塔tatatata……
操作環境依舊是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……