BASH下改变PWD的另一种思路

有很多时候希望能通过命令改变当前进程的工作目录。
例如我曾厌倦于终端下很麻烦的才能从很深的子目录中切换到项目的根目录,没有办法让我通过一条简单的命令就直接做到么?我那时的想法是写一个sh文件,然后丢到PATH里,然后执行。但是我发现这样是行不通的,这个shell脚本会作为子进程执行,我尝试过export变量,然而如果了解UNIX原理的话会知道子进程是没法改变父进程的环境变量的,认识到这个事实让当时的我很沮丧。

最近在BYR论坛上有人提问,需求和我当初差不多,我有拾起了这个问题。我需要的是在我当前的进程上下文执行的程序来完成工作目录的转变,那么alias行么?可惜的是bash的alias不能加参数。source行么?这样做倒是能让程序由当前进程执行,但是必须这样用source scriptname,很明显没人会对这个结果满意。我当时就回复,这个功能是不可能实现的,自以为是的写了很长一段,现在看来奇怪的是居然没人反驳我。

最近遇到需求要用BASH,于是打算把SHELL捡起来,打算从BASH的man开始看。五千多行,2天多已经看了一半了,然而我几天前却以为需要几个月。于是在man page中意外的找到了解决办法

方法就是用sh的function,function会比alias和builtin先执行,并且是在当前上下文执行,这样需求可以完美的实现。
我立即把自己当时的想法写出来,那时候是为了方便的回到rails项目的根目录,但是现在已经很久不动ruby了。
代码如下:

function cd() {
    if [[ ! $PWD =~ ^$HOME ]]
    then
        builtin cd $1
        #echo "changed into here:" $PWD
    elif [[ $# -ne 0 ]]
    then
        builtin cd $1
        #echo "switched into here:" $PWD
    else
        while [[ "$PWD" != "$HOME" ]]
        do
            [[ -e .project_root ]] && break
            builtin cd ..
        done
    fi
}


把这个函数放在.bashrc里就可以。项目的根目录手动建一个.project_root文件。这时当在项目的子目录中直接cd的话就会回到项目根目录而不是用户的HOME目录。

顺便把bash的启动文件理清了一下
.bash_profile 当bash作为login interactive shell读取
.bashrc 当bash作为nonlogin interactive shell读取
$BASH_ENV 当bash作为noninteractive shell读取(当然需要这个变量存在,并且的确是一个可读文件)
ssh登录时读.bashrc
当bash作为sh启动时有点不一样
.profile login interactive shell
$ENV nonlogin interactive shell
noninteractive shell不会读任何启动文件
ssh登录时不读任何文件

有的时候困难看似不可战胜,其实可能这时的你对困难了解的还不够多。

再见,胡子大叔

 

我被C及Unix的简洁与强大所深深的折服,感谢您和ken为我们带来的杰作,至少使我个人不至于对未来毫无理想。

sicp 练习 1.23

10000000改进函数

(define (find-devisor n test-devisor)
  (cond ((> (square test-devisor) n) n)
	((divides? test-devisor n) test-devisor)
	(else (find-devisor n (next test-devisor)))))

(define (next n)
  (if (= n 2)
      3
      (+ n 2)))


10000000 times 1000
---------------------
10000019 0.341
10000079 0.343
10000103 0.342

100000000 times 1000
---------------------
100000007 1.079
100000037 1.048
100000039 1.035

1000000000 times 500
---------------------
1000000007 3.708
1000000009 3.692
1000000021 3.81

10000000000 times 250
---------------------
10000000019 12.088
10000000033 11.94
10000000061 11.956


100000000000 times 100
---------------------
100000000003 37.91
100000000019 38.44
100000000057 38.14

1000000000000 times 30
---------------------
1000000000039 118.967
1000000000061 121.333
1000000000063 118.9

10000000000000 times 10
----------------------
10000000000037 386.7
10000000000051 384.5
10000000000099 385.8

用上面的结果列表:

num avg-1 avg-2 ratio-1 ratio-2 avg-1/avg-2
10000000 0.5523 0.345    /    / 1.601
100000000 1.741 1.054 3.152 3.055 1.652
1000000000 6.14 3.737 3.527 3.546 1.643
10000000000 20.453 11.995 3.331 3.210 1.705
100000000000 65.4 38.163 3.198 3.182 1.714
1000000000000 208.3 119.733 3.180 3.137 1.740
10000000000000 655.167 385.667 3.145 3.221 1.699

可以看到比值并不是2,一个直观的解释是我们定义的next是一个函数,对一个数的检测要调用next√n / 2 次,造成以一定的开销损耗。可以观察到,其实我们找出来的素数都是奇数,也就不需要为2单独判断,于是可以把next函数改为(+ test-divisor 2),再进行一次实验。

 

10000000 times 1000
---------------------
10000019 0.276
10000079 0.275
10000103 0.282

100000000 times 1000
---------------------
100000007 0.866
100000037 0.868
100000039 0.868

1000000000 times 500
---------------------
1000000007 3.054
1000000009 3.084
1000000021 3.058

10000000000 times 250
---------------------
10000000019 9.956
10000000033 10.292
10000000061 10.18


100000000000 times 100
---------------------
100000000003 32.36
100000000019 33.35
100000000057 32.49

1000000000000 times 30
---------------------
1000000000039 101.333
1000000000061 103.4
1000000000063 102.433

10000000000000 times 10
----------------------
10000000000037 319.6
10000000000051 331.1
10000000000099 341.9

 

num avg-1 avg-2* ratio-1 ratio-2* avg-1/avg-2*
10000000 0.5523 0.277    /    / 1.994
100000000 1.741 0.867 3.152 3.130 2.008
1000000000 6.14 3.065 3.527 3.535 2.003
10000000000 20.453 10.143 3.331 3.309 2.016
100000000000 65.4 32.733 3.198 3.227 1.998
1000000000000 208.3 102.389 3.180 3.128 2.034
10000000000000 655.167 330.867 3.145 3.231 1.980

可以看到调整之后的比例基本都在2的附近,所以可以肯定损耗的原因是next函数带来的开销了。

 

 

sicp 练习 1.22

本来我都是在目录下完成练习,不过这道题实在比较特殊,光用文本解答可能效果不好。

我在这道题上卡了蛮久,一是因为最近放假,荒废起光阴来有了些冠冕堂皇的借口;二是这道题及后面几道相关的题并不像前面的练习一样把代码写出来就完事了,需要的是一种探索式的方法,这种方法可能是科学研究中最基本的,而这也恰恰是我所没有的。

题目里告诉我们大多数lisp实现都包括一个runtime过程,能以微秒的单位返回系统已运行的时间。坏消息是我用来做练习的guile并没有这个函数,好消息是我在这里[1]找到了一个替代方案

 

(define (runtime) (tms:clock (times)))

题目的要求是给定一个起始数字,检测连续一段范围内奇数的素性。并且题目里给出了一段代码,要求你调用这段代码来完成练习。

 

(define (timed-prime-test n)
  (newline)
  (display n)
  (start-prime-test n (runtime)))

(define (start-prime-test n start-time)
  (if (prime? n)
      (report-prime (- (runtime) start-time))))

(define (report-prime elapsed-time)
    (display " *** ")
    (display elapsed-time))
  elapsed-time)

这是我给出的实现:

(define (search-for-primes start range)
  (if (> range 0)
      (timed-prime-test start))
  (if (> range 0)
      (search-for-primes (+ start 2) (- range 2)))

 

问题是在我看来这段代码并不好用,首先题目中这几个函数都没有返回值,也就是他们都是带有某种副作用的过程。没有返回值也就意味这没法根据不同的返回值进行不同的处理。并且题目中要求找出分别大于1e3, 1e4, 1e5, 1e6的三个最小的素数,那么只能给range指定一个足够大的数让它能至少包括三个素数,虽然带有一定的人工成分,但是完成这个任务依然是比较容易的。然而有一个棘手的问题是现在的电脑运行的太快,以至于题目中给出的几个数都被cpu轻松运算完,结果基本上都是0。解决的方法是把数加大,我简单乘了1w。

10000000
---------------------
10000019
10000079
10000103

100000000
---------------------
100000007
100000037
100000039

1000000000
---------------------
1000000007
1000000009
1000000021

10000000000
---------------------
10000000019
10000000033
10000000061

100000000000
---------------------
100000000003
100000000019
100000000057

1000000000000
---------------------
1000000000039
1000000000061
1000000000063

10000000000000
----------------------
10000000000037
10000000000051
10000000000099

我没有列出每个数runtime运行的结果,因为我发现对于小的数来说结果太有浮动性,任意取一次恐怕获得的结果不太好,于是又了运行多次求平均的简单想法。

 

(define (get-avg-runtime n times)
  (define (sum-runtime times sum)
    (if (= times 0)
	sum
	(sum-runtime (- times 1) (+ sum (timed-prime-test n)))))
  (/ (sum-runtime times 0) (+ times 0.0)))

然后把上面几个函数稍微改一下让他们返回值,对于已知的每个素数都运行一次,对越小的数取越大的times,得到结果。虽然结果依然会有一定的浮动,但浮动范围已经很小了。

10000000 times 1000
---------------------
10000019 0.55
10000079 0.549
10000103 0.558

100000000 times 1000
---------------------
100000007  1.743
100000037  1.746
100000039  1.734

1000000000 times 500
---------------------
1000000007 6.146
1000000009 6.14
1000000021 6.134

10000000000 times 250
---------------------
10000000019 20.416
10000000033 20.488
10000000061 20.456

100000000000 times 100
---------------------
100000000003 65.04
100000000019 65.59
100000000057 65.57

1000000000000 times 30
---------------------
1000000000039 211.47
1000000000061 206.6
1000000000063 206.83

10000000000000 times 10
----------------------
10000000000037 653.0
10000000000051 656.0
10000000000099 656.5

得到结果后,进行一个简单的统计#1

number avg ratio
10000000 0.5523     /
100000000 1.741 3.152
1000000000 6.14 3.527
10000000000 20.453 3.331
100000000000 65.4 3.198
1000000000000 208.3 3.180
10000000000000 655.167

3.145

√10大概是3.162, 基本上可以对题目做肯定回答,运行的步骤和时间成正比。

 

 

[1] http://community.schemewiki.org/?SICP

#1 这里得到的时间的单位据说是毫秒,但我感觉不对,不过单位对这个练习没有任何影响。

 

cool command在此集结{^_^}

学rails时候按照书里的步骤做,需要把这个地址里面的图片全下过来,想到了wget,经过漫长的折腾,终于搞定。

 

wget -nd -r http://media-origin.pragprog.com/titles/rails4/code/depot_b/public/images/ -A .jpg,.png

-nd --no-directories
代表不产生目录

-r
递归下载

-A --accept
后接用逗号分割的模式列表,选择需要下载的文件,相应的有相反的选项-R --reject

reference:

http://roclinux.cn/?p=2107  其实你不懂wget的心系列文章(适合入门,快速了解wget的基本功能)

man wget(推荐)

 

 

gnu readline
 
删除
ctrl + d      删除光标所在位置上的字符相当于VIM里x或者dl
ctrl + h      删除光标所在位置前的字符相当于VIM里hx或者dh
ctrl + k      删除光标后面所有字符相当于VIM里d shift+$
ctrl + u      删除光标前面所有字符相当于VIM里d shift+^
ctrl + w      删除光标前一个单词相当于VIM里db
ctrl + y      恢复ctrl+u上次执行时删除的字符
ctrl + ?      撤消前一次输入
alt  + r      撤消前一次动作
alt  + d     删除光标所在位置的后单词
 
移动
ctrl + a      将光标移动到命令行开头相当于VIM里shift+^
ctrl + e      将光标移动到命令行结尾处相当于VIM里shift+$
ctrl + f      光标向后移动一个字符相当于VIM里l
ctrl + b      光标向前移动一个字符相当于VIM里h
ctrl + 方向键左键    光标移动到前一个单词开头
ctrl + 方向键右键    光标移动到后一个单词结尾
ctrl + x       在上次光标所在字符和当前光标所在字符之间跳转
alt  + f      跳到光标所在位置单词尾部
 
 
替换
ctrl + t       将光标当前字符与前面一个字符替换
alt  + t     交换两个光标当前所处位置单词和光标前一个单词
alt  + u     把光标当前位置单词变为大写
alt  + l      把光标当前位置单词变为小写
alt  + c      把光标当前位置单词头一个字母变为大写
^oldstr^newstr    替换前一次命令中字符串   
 
历史命令编辑
ctrl + p   返回上一次输入命令字符
ctrl + r       输入单词搜索历史命令
alt  + p     输入字符查找与字符相接近的历史命令
alt  + >     返回上一次执行命令
 
其它
ctrl + s      锁住终端
ctrl + q      解锁终端
ctrl + l        清屏相当于命令clear
ctrl + c       另起一行
ctrl + i       类似TAB健补全功能
ctrl + o      重复执行命令
alt  + 数字键  操作的次数 

ldd查看文件有那些so依赖

Hello World!

程序员的好习惯,新开始要Hello World!的。