标签归档:mac

zsh的字符串替换引起的卡顿

我的mac系统每次启动后第一次打开iterm2的时候,oh-my-zsh的启动总是明显的卡顿一下,而之后退出iterm2重启动则不会有这个卡顿,也就是只在第一次启动iterm2的时候发生。对启动的zsh增加了-xv参数后观察,发现这个卡顿发生在git_compare_version函数的第4行:

找到这个函数后,发现第4行的操作并不是git等网络操作,而是一个字符串替换的操作,它使用zsh内置的字符串替换功能:INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]})

非常的不符合直觉(直觉上以为卡顿是因为网络阻塞引起的),模拟一下,在一个脚本里使用这个字符串替换操作,看看具体的耗时情况:

$ cat zsh-test.sh
#!/usr/bin/env zsh -xv
export PS4=$'%D{%M%S%.} %N:%i> '

INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null));
INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]});
echo "$INSTALLED_GIT_VERSION"

然后再启动时调用这个zsh脚本:

$ cat run.sh
#!/usr/bin/env zsh -xv
export PS4=$'%D{%M%S%.} %N:%i> '
./zsh-test.sh

重启系统,启动后在bash下执行run.sh脚本:

INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null));
5649865 ./zsh-test.sh:4> INSTALLED_GIT_VERSION=5649867 ./zsh-test.sh:4> git --version
5649865 ./zsh-test.sh:4> INSTALLED_GIT_VERSION=( git version 2.8.4 '(Apple' 'Git-73)' ) 
INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]});
5650999 ./zsh-test.sh:5> INSTALLED_GIT_VERSION=( 2 8 4 )

看到zsh-test.sh里的第5行字符串替换的操作耗时用了1秒多时间,如果再次执行的话会降到几个毫秒。这真是个蹊跷的问题,发邮件给 zsh-works@zsh.org 好几周也没有人回复,先在博客里记录一下这个问题,以后再追踪。zsh版本是:5.2 (x86_64-apple-darwin16.0.0)。

Sierra三宗罪

1) Sierra下很多原有软件无法运行

有些通过mac store下载的需要重新下载才可运行:

搜狗拼音无法使用,需要下载最新版本重新安装。

2) 有个photoanalysisd的进程在后台分析你的图片,导致cpu占用率很高,可以用下面方式停止:

 ➜  cat stop-photoanalysisd.sh
#!/bin/bash

launchctl stop com.apple.photoanalysisd
launchctl unload -w /System/Library/LaunchAgents/com.apple.photoanalysisd.plist

3) 原先VPN配置里的PPTP模式都丢失了

lantern的bug导致safari/mail无法访问网络

lantern似乎一直存在bug,有时将其退出后会导致mac上某些自带的应用(比如safari和mail)无法正常访问网络,而chrome等非mac自带的工具却正常。在我的机器上这种情况经常发生,影响了mail应用不及时收取邮件,不得不通过kill networkd重启网络来解决,谁要是知道更好的方式请告知我。

➜  cat ~/bin/restart-network
#!/bin/bash

pid=$( ps -ef | grep "/usr/libexec/networkd$" | awk '{print $2}' )
sudo kill -9 $pid

shell前边的连字符含义

在一个脚本里,要获取其父shell时,使用了下面的方式:

#!/bin/bash 
ps -ocomm= -p $(ps -oppid= $$)

它在某些环境下,父shell会显示为 “/usr/local/bin/zsh” 或者 “bash” ,而某些环境下却会显示为”-bash”或”-zsh”;这个开头多出来的连字符是怎么回事?查了一下,原来是表示的是”login shell”。

linux下可以在”/etc/passwd”里看到用户的login shell,而在mac下要确认当前用户的login shell,要通过下面的命令:

$ dscl . read /users/$USER UserShell
UserShell: /bin/bash

在mac下,当打开终端程序(Terminal.app)时,终端shell是login进程的子进程(不管你配置那种command):

$ pstree
 ...
 |-+= 01272 hongjiang /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal
 | \-+= 01275 root login -pf hongjiang
 |   \-+= 01276 hongjiang -bash
 ...

$ ps -ef | grep login
0  1275  1272   0  2:05PM ttys000    0:00.05 login -pf hongjiang 

# 把Terminal的启动命令修改为zsh也一样

 |-+= 01988 hongjiang /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal
     | \-+= 01991 root login -pf hongjiang /bin/zsh  |   \-+= 01992 hongjiang -zsh

而在mac的 iTerm.app 下,不管如果你配置的command是”Login shell”还是修改为其他shell,启动后shell没有再挂在login进程下:

$ pstree
 |-+= 00574 hongjiang /Volumes/Data/program/iTerm.app/Contents/MacOS/iTerm
 | \-+= 02177 hongjiang /usr/local/bin/zsh  

但在iTerm下如果使用的是“Login shell”显示的名称前边却是有连字符的,而其他shell则没有

$ ps $$
  PID   TT  STAT      TIME COMMAND
 2220 s001  Ss     0:00.01 -/bin/bash

# 修改启动shell为zsh

➜  ps $$
  PID   TT  STAT      TIME COMMAND
 2524 s000  Ss     0:00.16 /usr/local/bin/zsh

要想在iTerm下保持跟Terminal一致也用login来启动,应该在配置里修改启动命令为:

login -pf $username /usr/local/bin/zsh   

在linux下,从tty登录shell也是一样由login进程启动的

$ ps -ef | grep bash
 hongjia+  2241   467  0 14:09 tty1     00:00:00 -bash

$ pstree 
systemd─┬─NetworkManager─┬─2*[dhclient]
    │                └─3*[{NetworkManager}]
    ├─...
    ├─login───bash
    ...   

$ ps -ef | grep login
root       467  0.0  0.2  84584  2328 ?        Ss   14:08   0:00 login -- hongjiang    

使用su命令切换到一个用户shell下,默认情况这个shell并不是”login shell”不会去执行/etc/profile和home目录下相关配置:

$ sudo su hongjiang

$ ps -ef | grep $$
hongjia+  2796  2795  0 14:57 pts/0    00:00:00 bash

要以”login shell”方式启动,需要对su指定一个参数“-“

$ sudo su - hongjiang

$ ps -ef | grep $$
hongjia+  3188  3187  0 15:35 pts/0    00:00:00 -bash

从bash文档里可以看到,要以”login shell”方式启动一个shell,要么第一个参数给一个特定的连字符“-”,要么显式的对bash设定”–login”参数。

vpn脚本的改进

感谢巨石的验证和建议,之前小看了云梯vpn的规模,以为他们只有那么几台服务器,所以在脚本里把域名写死了;后来发现不同用户的服务域名还是不一样的,改进了一下脚本,不局限于云梯的vpn,适用所有的vpn;代码在这里

安装方式:

$ mkdir -p ~/bin
$ curl -s http://hongjiang.info/vpn.sh > ~/bin/vpn

选项:

$ vpn
Usage: /Users/hongjiang/bin/vpn {list|ping|optimal|conn|close|status}

自动连接:

$ vpn conn
ok, jp3.vpnplease.com connected.

可以指定连接协议:

$ vpn conn L2TP #或PPTP
ok, jp2.vpnplease.com connected.

断开连接:

$ vpn close
ok, jp3.vpnplease.com disconnected.

查看vpn列表(名称:地址):

$ vpn list
VPN (PPTP): jp2.vpnplease.com
jp3.vpnplease.com: jp3.vpnplease.com
us1.vpnplease.com: us1.vpnplease.com
...

查看所有vpn的状态:

$ vpn status

* (Disconnected)   2599A4CB-7D76-4878-8FAF-7496E5F6C58F PPP --> L2TP       "us2.vpnplease.com"              [PPP:L2TP]
* (Disconnected)   57A3276C-505F-4E13-B402-0CB6EC5C9AD7 PPP --> PPTP       "VPN (PPTP)"                     [PPP:PPTP]
* (Connected)      90784ECA-DCFA-4197-B2E5-FBE84AFB9C00 PPP --> L2TP       "jp3.vpnplease.com"              [PPP:L2TP]
...     

获取最优的服务名称(协议参数可选):

$ vpn optimal pptp  #或 l2tp
VPN (PPTP)

测试连接速度:

$ vpn ping
jp2.vpnplease.com  52.190
jp1.vpnplease.com  53.681
sg2.vpnplease.com  171.482
...

选择网速最快的vpn进行连接的脚本

下午github上下载速度只有几K,用了代理也不到10K,后来发现所用的vpn服务器选择了一个慢的。写了这段脚本,用来从可用的vpn选出最快的那个。脚本只适用于mac,并且前提是你已创建好了所有可选的vpn,如图:

脚本内容:

#!/bin/bash

PROG_NAME=$0
ACTION=$1

DOMAIN="vpnplease.com"
declare -a OPTION=("jp" "us" "sg")

usage() {
  echo "Usage: $PROG_NAME {test|conn|close|status}"
  exit 1
}

vpn_test() {

  for c in "${OPTION[@]}";do
    for i in `seq 1 3`; do
      proxy="$c$i.$DOMAIN"
      ping -c3 $proxy 2>/dev/null | tail -1 | cut -d'=' -f2 | cut -d'/' -f2 | xargs -I {} echo "$proxy " {} 2>/dev/null &
    done
  done

  children=$(jobs -p | xargs)
  trap 'kill $children >/dev/null 2>&1' SIGINT SIGTERM
  wait
}

get_fastest() {
  vpn_test 2>/dev/null | sort -nk2 | head -1 | awk '{print $1}'
}

vpn_status() {
  for c in "${OPTION[@]}";do
    for i in `seq 1 3`; do
      proxy="$c$i.$DOMAIN"
      scutil --nc status $proxy 2>/dev/null | head -1 | xargs -I {} echo "$proxy " {}
    done
  done
}

vpn_connect() {
  local name=$1

/usr/bin/env osascript <<-EOF
  tell application "System Events"
    tell current location of network preferences
      set VPN to service "$name" -- your VPN name here
      if exists VPN then connect VPN
      repeat while (current configuration of VPN is not connected)
        delay 1
      end repeat
    end tell
  end tell
EOF
}

vpn_disconnect() {
  local name=$(vpn_status | awk '$2~/Connected/{print $1}')
  [ -z $name ] && return 0

/usr/bin/env osascript <<-EOF
  tell application "System Events"
    tell current location of network preferences
      set VPN to service "$name" -- your VPN name here
      if exists VPN then disconnect VPN
    end tell
  end tell
  return
EOF
}

case "$ACTION" in
    test)
        vpn_test
    ;;
    conn)
        vpn_connect $(get_fastest)
    ;;
    close)
        vpn_disconnect
    ;;
    status)
        vpn_status
    ;;
    *)
        usage
    ;;
esac

使用方式:

1) 自动选择最优的vpn进行连接

$ vpn conn

2) 查看连接状态

$ vpn status

jp1.vpnplease.com  Connected
jp2.vpnplease.com  Disconnected
jp3.vpnplease.com  Disconnected
us1.vpnplease.com  Disconnected
us2.vpnplease.com  Disconnected
us3.vpnplease.com  Disconnected
sg1.vpnplease.com  Disconnected
sg2.vpnplease.com  Disconnected

3) 测试网速,时间最小的最优

$ vpn test

jp2.vpnplease.com  76.314
jp1.vpnplease.com  75.793
jp3.vpnplease.com  136.161
sg1.vpnplease.com  169.494
us3.vpnplease.com  242.685
us2.vpnplease.com  249.032
us1.vpnplease.com  266.427
sg2.vpnplease.com  168.750

4) 断开连接

$ vpn close 

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"

查询ip来源的脚本

查询ip地址的方式很多,linux里有一个geoip的工具,我通过homebrew安装过并不work(可能是网络的问题无法更新这个地址库),所以实现了一个通过ip138.com来查询的. 只在mac上测试过,mac自带的sed很不好用,我用的gnu-sed,可以通过homebrew安装:

#!/bin/sh

if [ $# -lt 1 ]; then
    echo "input ip address" && exit -1;
fi

URL="http://ip138.com/ips138.asp?ip="$1
result=`curl -s $URL | iconv -f GBK -t UTF-8 | grep "td align=\"center\"" | awk 'NR==2||NR==3'`
echo $result | gsed 's/[<|>]/\n/g' | gsed '/ul\|li\|h1\|td/d' | awk NF

测试效果:

$ ip138 8.8.8.8
您查询的IP:8.8.8.8

本站主数据:美国 Google免费DNS
参考数据一:美国
网友提交的IP:美国 Google免费DNS

mac tips

1) 开启locate的数据检索

sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist

2) 终端下的拷贝粘贴用 pbcopy, pbpaste,为避免pbcopy的时候多一个换行符,可以截去:

$ alias copy='tr -d "\n" | pbcopy'
$ uname | copy
$ pbpaste
Darwin

3) 在linux下为了更好的看man文档,可以用vim来看:

$ man ps | col -b | vi -

mac下,可以用preview预览程序来看:

$ man -t ps | open -fa /Applications/Preview.app

前面的man -t是使用/usr/bin/groff -Tps -mandoc -c来格式化

4) 终端下安装dmg或iso

$ hdid diskimage.dmg

5) 查看cpu的信息:

$ sysctl -a hw | grep -i cpu

6) 查看某个进程在执行什么文件操作:这个和lsof有点像,可以对比一下

$ sudo fs_usage 1204

7) 查看系统版本

$ sw_vers

8) system_profiler也是查看软硬件的工具

$ system_profiler -detailLevel full  SPHardwareDataType

或直接:

$ system_profiler SPHardwareDataType

查看内存条的硬件信息

$ system_profiler -listDataTypes  | grep -i mem
$ system_profiler -detailLevel full SPMemoryDataType 

9) 修改截屏保存的格式,默认是png格式,修改为jpg的话:

$ defaults write com.apple.screencapture type jpg

mac下控制上网的脚本

livejournal的创始人(也是memcached的作者),说有次他坐飞机,无聊拿出笔记本写程序,写着写着就习惯性的打开浏览器访问互联网,当然访问不了,这时他突然意识到自己平 时写代码也是这样不专注,随意上网浏览都成了一种习惯。后来他为此专门找了个iptables的脚本来控制自己。对于控制力不强的同学确实需要。在mac 上可以用ipfw来替代iptables,下面的脚本用来控制我在家里只能访问局域网不能访问外网:

$ cat quiet
#!/bin/sh
sudo ipfw -f flush
#home
sudo ipfw add allow tcp from me to 192.168.1.0/16
sudo ipfw add allow ip from me to 192.168.1.0/16
#company
sudo ipfw add allow tcp from me to 10.0.0.0/8
sudo ipfw add allow ip from me to 10.0.0.0/8
sudo ipfw add allow tcp from me to 119.38.218.129
sudo ipfw add allow ip from me to 119.38.218.129
sudo ipfw add allow tcp from me to 110.75.167.30
sudo ipfw add allow ip from me to 110.75.167.30
#aliwangwang
sudo ipfw add allow tcp from me to 110.75.161.98/24
sudo ipfw add allow ip from me to 110.75.161.98/24
#evernote
#dict.youdao.com
sudo ipfw add allow tcp from me to 220.181.76.13/24
#block
sudo ipfw add deny tcp from me to any
sudo ipfw add deny ip from me to any

恢复的话是:

$ sudo ipfw -f flush

另一个,比较简单的,控制sns网站的dns的:

$ cat disable-sns.sh
#!/bin/sh
cat << EOF >> /etc/hosts
### sns-sites
127.0.0.1   www.laiwang.com
127.0.0.1   laiwang.com
127.0.0.1   www.cnbeta.com
127.0.0.1   cnbeta.com
127.0.0.1   www.weibo.com
127.0.0.1   weibo.com
127.0.0.1   news.sina.com.cn
127.0.0.1   www.douban.com
127.0.0.1   douban.com
127.0.0.1   www.facebook.com
127.0.0.1   facebook.com
### sns-sites
EOF