脚本进击之汉诺塔tatatata……

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了脚本进击之汉诺塔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这大概是阿拉不是正经班子出身,没什么编程素质的原因。

这个不能算差距,但很容易拉开距离。对于计算机或信息技术的学生而言,电脑就像自家后院,在这样的环境里另学一门语言,是有很多可借用工具的。就像阿拉学过网页,htmlphp之类的有所涉猎,搭网站的时候心里就稍微有点谱。虽然这个过程并不清楚,至少不虚。

但这样也有不利之处。学开发的转运维难免会带着之前搞开发时的习惯。这样反而初学者更占优势。老师也有说学了运维学开发容易,学过开发转运维难。

还是要多看点这方面的书,像《鸟哥的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……的主要内容,如果未能解决你的问题,请参考以下文章

python算法之汉诺塔

python汉诺塔非递归

数据结构之汉诺塔问题

Python函数递归之汉诺塔

汉诺塔之递归学习

《C#零基础入门之百识百例》(五十)嵌套类和嵌套方法 -- 汉诺塔游戏