操作环境依旧是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: 798761864@qq.com # 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: 798761864@qq.com # 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: 798761864@qq.com # 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: 798761864@qq.com # 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
转载于:https://blog.51cto.com/amelie/1966100