goto,一个用于快速切换目录的脚本

在shell下经常在不同的目录之间切换的话,希望有一个更好用的cd或pushd/popd命令。以前曾尝试过autojump, asdf 等工具,发现这些工具有些“重”了,它们背后通过ruby或python记录你每次cd的路径,统计出每个路径的频率,算出一个权重,根据权重优先来匹配跳转。我希望有一个更轻的(不用依赖ruby或python),不用那么“聪明”的,能够记录最近的路径并可以方便跳转过去的工具。

我的需求是列出之前切换过的10个目录,用序号记录,按时间排序,最近一次切换的目录排在第一位,效果如下:

$ dirs
 1  /data/server/tomcat
 2  /System/Library/Frameworks/JavaVM.framework/Versions/jdk1.8
 3  /data/work/opensources/openjdk
 4  /tmp/a b
 5  /data/work/opensources/apache
 6  /tmp
 7  /Users/hongjiang
 8  /Applications
 9  /data/downloads
10  /data/tools

当我想要切换到某个目录时,只需要命令后边跟序号就可以,如下:

 $ goto 2
 $ pwd
 /System/Library/Frameworks/JavaVM.framework/Versions/jdk1.8
 $ goto 9
 $ pwd
 /data/downloads

这个效果可以通过几个函数来实现,首先是要把cd命令alias为pushd,这样每次切换目录都会压栈:

pushd() {
    if [ $# -eq 0 ]; then
        DIR="${HOME}"
    else
        DIR="$1"
    fi

    builtin pushd "${DIR}" > /dev/null
}

alias cd='pushd'

然后对内置的dirs命令进行包装:

dirs() {
    tmphash="/tmp/.dirs"

    builtin dirs -plv |
    awk -F'\t' '{
        if (a[$2]=="") a[$2]=$1;
        if (length(a)==10) exit
    }END{for(i in a) print a[i],i}' |
    sort -nk1 | cut -d' ' -f2- > $tmphash

    nl $tmphash
}

这样在每次执行dirs函数时,会通过内置的dirs命令过滤最近10次切换过的目录并保持在”/tmp/.dirs”文件里。最后实现goto函数:

goto() {
    tmphash="/tmp/.dirs"
    if [ ! -f $tmphash ]; then
        echo "no record"
        return 1
    fi
    if [ $# -eq 0 ]; then
        return 1
    fi

    dest=`awk -v n=$1 'NR==n&&1' $tmphash`

    if [[ $dest == "~" ]]; then
        dest=${HOME}
    elif [[ $dest == "~/"* ]]; then
        dest="${HOME}/${dest:2}"
    elif [[ $dest == "~"* ]]; then
        dest=`eval "echo $dest"`
    fi

    pushd $dest
}

将上面几个函数放入.zshrc里,source一下或重启shell即可,只在zsh上测试过,还未在bash上验证。

最近看过的电影(7)

刚在影院看完了《空中营救》,前半部情节很紧凑,但后边的编剧有硬伤,不能自圆其说。很奇怪豆瓣上为何这部电影的分数还挺高。其实主要还是冲着主演”连姆·尼森”去看的。他的《Taken》系列我非常喜欢,思路清晰,动作干净利索。不过我对他印象最深的倒是一部评价不那么高的电影《人狼大战》(The Grey)

检测最耗cpu的线程的脚本

这个脚本用于定位出当前java进程里最耗cpu的那个线程,给出cpu的占用率和当前堆栈信息。这个脚本仅限于linux上,我没有找到在mac下定位线程使用cpu情况的工具,如果你知道请告诉我一下。

先模拟一个耗cpu的java进程,启动一个scala的repl并在上面跑一段死循环:

scala> while(true) {}

脚本执行效果:

$ ./busythread.sh `pidof java`
tid: 431  cpu: %98.8
"main" prio=10 tid=0x00007f777800a000 nid=0x1af runnable [0x00007f7781c2e000]
    java.lang.Thread.State: RUNNABLE
    at $line3.$read$$iw$$iw$.<init>(<console>:8)
    at $line3.$read$$iw$$iw$.<clinit>(<console>)
    at $line3.$eval$.$print$lzycompute(<console>:7)
    - locked <0x00000000fc201758> (a $line3.$eval$)
    at $line3.$eval$.$print(<console>:6)
    at $line3.$eval.$print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:739)
    at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:986)
    at scala.tools.nsc.interpreter.IMain$WrappedRequest$$anonfun$loadAndRunReq$1.apply(IMain.scala:593)
    at scala.tools.nsc.interpreter.IMain$WrappedRequest$$anonfun$loadAndRunReq$1.apply(IMain.scala:592)
    at scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
    at scala.reflect.internal.util.AbstractFileClassLoader.asContext(AbstractFileClassLoader.scala:19)
    at scala.tools.nsc.interpreter.IMain$WrappedRequest.loadAndRunReq(IMain.scala:592)
    at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:524)
    at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:520)

脚本内容:

#!/bin/bash

if [ $# -eq 0 ];then
    echo "please enter java pid"
    exit -1
fi

pid=$1
jstack_cmd=""

if [[ $JAVA_HOME != "" ]]; then
    jstack_cmd="$JAVA_HOME/bin/jstack"
else
    r=`which jstack 2>/dev/null`
    if [[ $r != "" ]]; then
        jstack_cmd=$r
    else
        echo "can not find jstack"
        exit -2
    fi
fi

#line=`top -H  -o %CPU -b -n 1  -p $pid | sed '1,/^$/d' | grep -v $pid | awk 'NR==2'`

line=`top -H -b -n 1 -p $pid | sed '1,/^$/d' | sed '1d;/^$/d' | grep -v $pid | sort -nrk9 | head -1`
echo "$line" | awk '{print "tid: "$1," cpu: %"$9}'
tid_0x=`printf "%0x" $(echo "$line" | awk '{print $1}')`
$jstack_cmd $pid | grep $tid_0x -A20 | sed -n '1,/^$/p'

脚本已放到服务器上,可以通过下面的方式执行:

$ bash <(curl -s https://hongjiang.info/busythread.sh)  java_pid

update: 感谢容若的反馈,很多环境的procps版本较低,top还不支持-o参数,排序那块用sort解决了,脚本已更新。

mac上通过ssh连接docker的container

下午尝试了一下在mac上通过ssh的方式连接到container,因为之前采用交互方式启动container,一个实例只有一个交互终端,有时需要多个终端只能通过screen一类的工具,而screen有个不爽的地方是它的屏幕缓冲,不能方便的通过滚屏的方式看之前输出内容。还是通过多个终端ssh到container上比较方便。

在mac上docker依赖virtualbox,想要与container之间通讯,需要两层端口映射,一层是在mac与virtualbox之间,另一层是在宿主机与container之间。

在宿主与container之间的端口映射,docker命令中就有参数支持,使用-p参数:

$ docker run -d -p 2222:22 ubuntu /usr/sbin/sshd -D

上面以daemon的方式启动一个ubuntu的container实例,在启动时运行了sshd,并把container的22端口映射到了宿主的2222端口。

现在需要再把mac与宿主的2222端口打通,需要通过virtualbox的命令来设置(需要先boot2docker stop):

$ VBoxManage modifyvm "boot2docker-vm" --natpf1 "containerssh,tcp,,2222,,2222"

上面的命令把virtualbox里的宿主机2222端口映射到了本地的2222,现在可以通过访问mac本地的2222端口来连接container实例了:

$ ssh root@localhost -p2222

注意,Ubuntu和CentOS的openssh-server默认配置都是禁止root用户通过ssh访问的,需要的话可以去修改ssh-server的配置。

如果想要修改virtualbox的映射,可以删除之前的映射规则,不记得映射规则名称的话,可以通过showvminfo的信息grep一下

$ VBoxManage showvminfo boot2docker-vm  | grep 2222
NIC 1 Rule(0):   name = containerssh, protocol = tcp, host ip = , host port = 2222, guest ip = , guest port = 2222

然后通过name删除这条规则:

$ VBoxManage controlvm boot2docker-vm natpf1 delete "containerssh"