修复 Mac OS X 新版 Terminal 在 SSH 时候出现的 LANGUAGE WARNING

自从升级了新版的 Mac OS X 之后,使用 Terminal SSH 到别的机器上总是能看到这样的警告:

-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory

在执行 perl 脚本的时候还能看到这样的警告:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
    LANGUAGE = (unset),
    LC_ALL = (unset),
    LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

找了很多方法,包括改服务端 sshd config 什么的,都觉得不好。因为这应该不是服务端的问题,不应该在服务端解决。于是仔细搜索之后发现这个回答完美解决了这个问题:

http://stackoverflow.com/questions/2499794/how-can-i-fix-a-locale-warning-from-perl/7413863#7413863

Here is how to solve it on Mac OS Lion (10.7):

Add the following lines to your bashrc or bash_profile on the host machine:

# Setting for the new UTF-8 terminal support in Lion
export LC_CTYPE=en_US.UTF-8
export LC_ALL=en_US.UTF-8

If you are using zsh, edit zshrc:

# Setting for the new UTF-8 terminal support in Lion
LC_CTYPE=en_US.UTF-8
LC_ALL=en_US.UTF-8

Linux find 与 rm 联动删除符合条件的文件

xargs 方法

find . -name 'file*' -size 0 -print0 | xargs -0 rm

首先 find 找到符合条件的文件并输出文件名,然后管道传递给 xargs,然后由 xargs 拼接出结果。

需要注意的是 -print0 的意思是用一个 \0 作为分隔符,是用来配合 xargs 的 -0 (使用\0作为分隔符)使用的。

GUN find 法

find -name 'file*' -size 0 -delete

GUN find 直接支持了 -delete 参数,甚至还有 -ls 参数,可以自动在找到符合要求的文件之后进行删除或列出详细信息的操作。目前大部分 Linux 发行版用的都是 GUN find,所以该方法比较通用。但是如果你的 Linux 发行版中安装的 find 不支持这两个参数,那么就不能使用这个方法了。

find -exec 法

find . -name file* -exec rm {} \;
find . -name file* -exec rm {} \+

第一行的写法是将找到的文件名拼接到 {} 中,然后执行命令。第二行类似,不同的是把所有文件名拼接成一条命令。也就是说,用方法一,如果 find 的结果是

1
2
3

那么最终就会看到:

rm 1
rm 2
rm 3

这样三个进程。但是使用方法二,则会看到:

rm 1 2 3

只有一个进程,效率更高。

但是需要注意的是,命令参数是有长度限制的,如果文件过多,可能会出现:

cannot execute [Argument list too long]

这是因为参数过多,超过了系统限制。这时候用方法一就能避免这个问题。

Update: 这里说一个 find 命令的小坑。如果想用 find 找到指定大小的文件并删除的话,应该这么写条件:

find ./ -size 100k
find ./ -size 100M
find ./ -size 100G

注意此处是精确匹配。不过可以在数值之前加 +/- 来进行大于或小于的匹配:

find ./ -size +100k #找所有大于 100K 的文件
find ./ -size -100M #找所有小于 100M 的文件
find ./ -size +100G

但是!我上面说的最低的单位都是 K,如果是 Byte 作为单位要怎么办呢?下面两种哪种对?还是都可以?

find ./ -size 100b
find ./ -size 100

实际上,这两种都是错的。来看看实际操作:

➜  test ll
total 8
drwxr-xr-x    3 John  staff   102B May 24 09:53 ./
drwxr-xr-x  118 John  staff   3.9K May 24 09:53 ../
-rw-r--r--    1 John  staff    14B May 24 09:53 test
➜  test find ./ -size 14
➜  test find ./ -size 14b
find: -size: 14b: illegal trailing character
➜  test find ./ -size 14c
.//test
➜  test

可以看出,当单位是 Byte 的时候,正确的写法是用 c 作为单位后缀。具体可见 find 的 man page

       -size n[ckMGTP]
             True if the file's size, rounded up, in 512-byte blocks is n.  If n is followed by a c, then the primary is true if the file's size is n bytes (characters).  Similarly if n is followed by a scale indicator then the file's size is compared to n scaled as:

             k       kilobytes (1024 bytes)
             M       megabytes (1024 kilobytes)
             G       gigabytes (1024 megabytes)
             T       terabytes (1024 gigabytes)
             P       petabytes (1024 terabytes)
       -size n[cwbkMG]
              File uses n units of space.  The following suffixes can be used:

              `b'    for 512-byte blocks (this is the default if no suffix is used)

              `c'    for bytes

              `w'    for two-byte words

              `k'    for Kilobytes (units of 1024 bytes)

              `M'    for Megabytes (units of 1048576 bytes)

              `G'    for Gigabytes (units of 1073741824 bytes)

              The  size  does  not  count indirect blocks, but it does count blocks in sparse files that are not actually allocated.  Bear in mind that the `%k' and `%b' format specifiers of -printf handle sparse files
              differently.  The `b' suffix always denotes 512-byte blocks and never 1 Kilobyte blocks, which is different to the behaviour of -ls.

关于这个默认为什么是 512-byte block,我查了一下相关资料,应该是因为早起在 IBM AIX 系统上 du 的默认单位就是 512-byte block,这是一个 POSIX 标准。这点从 dd 等命令的默认大小也能看出来:

➜  test dd if=/dev/random of=test count=1
1+0 records in
1+0 records out
512 bytes transferred in 0.000074 secs (6905092 bytes/sec)
➜  test ll
total 8
drwxr-xr-x    3 John  staff   102B May 24 09:53 ./
drwxr-xr-x  118 John  staff   3.9K May 24 09:53 ../
-rw-r--r--    1 John  staff   512B May 24 10:02 test

而且在造出了一个刚好 512B 的文件之后,我将其复制一份加了一个字符,达到 515B,然后再来试试 find 命令:

➜  test ll
total 16
drwxr-xr-x    4 John  staff   136B May 24 10:02 ./
drwxr-xr-x  118 John  staff   3.9K May 24 09:53 ../
-rw-r--r--    1 John  staff   512B May 24 10:02 test
-rw-r--r--    1 John  staff   515B May 24 10:02 test2
➜  test find ./ -size 1
./
.//test
➜  test find ./ -size 2
.//test2
➜  test

可以看到,正如文档中描述了,find 会直接向上取整来进行比较,然后返回结果。

Git 统计代码量

最近有一个需求是要统计 Git 仓库里每个人的代码量,于是上网搜了一下,找了一些相关命令:

指定用户名版

git log --author="_Your_Name_Here_" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

这句话可以输出当前项目内指定用户名的用户的代码量统计,结果如下:

added lines: 30400 removed lines: 21317 total lines: 9083

使用 ls-file 实现不指定用户版统计行数版

git ls-files -z | xargs -0n1 git blame -w | ruby -n -e '$_ =~ /^.*\((.*?)\s[\d]{4}/; puts $1.strip' | sort -f | uniq -c | sort -n

这段代码比较有意思,它扫描了当前分支的每个文件,然后用 Git 的 blame 功能输出每个人的代码行数,最后用系统命令 sort 和 uniq 实现计数。

结果如下:

   8 aaa
   9 bbb
 145 ccc
 146 ddd
 261 eee

扫描 Log 统计增删行数版

git log --shortstat --pretty="%cE" | sed 's/\(.*\)@.*/\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\1 0 insertions\(+\), \2/;s/\(\+\)$/\(\+\), 0 deletions\(-\)/;s/insertions?\(\+\), //;s/ deletions?\(-\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net";}'

这段比较复杂,我也没有认真解读,直接贴结果吧:

aaa,: 353 files changed, 9359 insertions(+), 3844 deletions(-), 5515 net
aaa,bbb,: 4 files changed, 144 insertions(+), 2 deletions(-), 142 net
ccc,: 114 files changed, 2301 insertions(+), 481 deletions(-), 1820 net
ddd,: 27 files changed, 1856 insertions(+), 757 deletions(-), 1099 net
eee,: 1726 files changed, 32841 insertions(+), 22719 deletions(-), 10122 net
eee,fff,: 13 files changed, 209 insertions(+), 211 deletions(-), -2 net
ggg,: 53 files changed, 1153 insertions(+), 1170 deletions(-), -17 net
fff,: 2445 files changed, 69875 insertions(+), 62148 deletions(-), 7727 net
fff,eee,: 30 files changed, 394 insertions(+), 472 deletions(-), -78 net
bbb,: 37 files changed, 781 insertions(+), 216 deletions(-), 565 net
hhh,: 4 files changed, 34 insertions(+), 4 deletions(-), 30 net

比较奇怪的是会列出两个人同时修改,可能是 merge 操作,没有深究。

扫描 Log 单独统计每个人的增删行数加强版

git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done

直接上输出:

aaa	added lines: 34, removed lines: 4, total lines: 30
bbb	added lines: 2301, removed lines: 481, total lines: 1820
ccc	added lines: 1856, removed lines: 757, total lines: 1099
ddd	added lines: 30400, removed lines: 21317, total lines: 9083
eee	added lines: 1153, removed lines: 1170, total lines: -17
fff	added lines: 1153, removed lines: 1170, total lines: -17
ggg	added lines: 72886, removed lines: 64233, total lines: 8653
hhh	added lines: 814, removed lines: 216, total lines: 598
iii	added lines: 9503, removed lines: 3846, total lines: 5657

第三方小工具版

使用这个工具可以直接输出非常漂亮的统计表格:

https://github.com/oleander/git-fame-rb

gem install git_fame
cd /path/to/gitdir && git fame
Total number of files: 2,053
Total number of lines: 63,132
Total number of commits: 4,330

+------------------------+--------+---------+-------+--------------------+
| name                   | loc    | commits | files | percent            |
+------------------------+--------+---------+-------+--------------------+
| Johan Sørensen         | 22,272 | 1,814   | 414   | 35.3 / 41.9 / 20.2 |
| Marius Mathiesen       | 10,387 | 502     | 229   | 16.5 / 11.6 / 11.2 |
| Jesper Josefsson       | 9,689  | 519     | 191   | 15.3 / 12.0 / 9.3  |
| Ole Martin Kristiansen | 6,632  | 24      | 60    | 10.5 / 0.6 / 2.9   |
| Linus Oleander         | 5,769  | 705     | 277   | 9.1 / 16.3 / 13.5  |
| Fabio Akita            | 2,122  | 24      | 60    | 3.4 / 0.6 / 2.9    |
| August Lilleaas        | 1,572  | 123     | 63    | 2.5 / 2.8 / 3.1    |
| David A. Cuadrado      | 731    | 111     | 35    | 1.2 / 2.6 / 1.7    |
| Jonas Ängeslevä        | 705    | 148     | 51    | 1.1 / 3.4 / 2.5    |
| Diego Algorta          | 650    | 6       | 5     | 1.0 / 0.1 / 0.2    |
| Arash Rouhani          | 629    | 95      | 31    | 1.0 / 2.2 / 1.5    |
| Sofia Larsson          | 595    | 70      | 77    | 0.9 / 1.6 / 3.8    |
| Tor Arne Vestbø        | 527    | 51      | 97    | 0.8 / 1.2 / 4.7    |
| spontus                | 339    | 18      | 42    | 0.5 / 0.4 / 2.0    |
| Pontus                 | 225    | 49      | 34    | 0.4 / 1.1 / 1.7    |
+------------------------+--------+---------+-------+--------------------+