为 bash 提示符加上 git 状态

在有一次手误合错分支以后,就决定为 bash 提示符加上显示当前分支,以及提交状态,这样就可以更清楚地知道当前在哪个分支,以及是不是 commit 了,是不是 push 了。代码如下:

function __git_prompt() {
  local s=$(git status -b --porcelain 2>/dev/null)
  if [[ -z "$s" ]]; then
    PS1="\h:\W \u$ "
  else
    local l=$(echo "$s"|wc -l)
    local br=$(echo "$s"|head)
    if [[ "${br%[ahead*}" != "$br" ]]; then
      local np=1
    fi
    br="${br#\#\# }"
    br="${br%...*}"
    if [[ "$l" -gt 1 ]]; then
      local g="(git:\[$(tput setaf 9)\]$br\[$(tput sgr0)\])" # dirty: red
    elif [[ -z "$np" ]]; then
      local g="(git:\[$(tput setaf 10)\]$br\[$(tput sgr0)\])" # clean: green
    else
      local g="(git:\[$(tput setaf 11)\]$br\[$(tput sgr0)\])" # not pushed: yellow
    fi
    PS1="\h:\W \u $g$ "
  fi
}


PROMPT_COMMAND='__git_prompt'

加入到 .bash_profile 即可。这里是按 MacOS 默认的 PS1 基础上改的,实际使用的时候可以根据需要调整。

动态修改运行中进程的 rlimit

有时候发现线上运行的程序没有修改 ulimit -n ,导致文件描述符不够用,但是又不能随便重启服务,或者碰到运行中的程序可能不定时会 segment fault ,但是由于没 ulimit -c ,无法得到 core 文件调试。但是 linux 的 ulimit 命令只对当前会话有效,已经启动的进程是没法用这个命令修改限制的。这时候如果能动态修改进程的 rlimit 就会方便很多。linux 内核版本 2.6.36 以后,多了一个 prlimit 调用,可以实现动态修改某个进程的 rlimit。

例如,这个 C 写的小程序就可以放开指定进程的 core dump 限制。

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>

int main(int argc, char** argv) {
    pid_t pid;
    struct rlimit new_limit;
    int result;
    if (argc < 2) {
        return 1;
    }
    pid = atoi(argv[1]);
    new_limit.rlim_cur = RLIM_INFINITY;
    new_limit.rlim_max = RLIM_SAVED_MAX;
    result = prlimit(pid, RLIMIT_CORE, &new_limit, NULL);
    return result;
}

rlimit 相关常量在 /usr/include/bits/resource.h 中有定义。这里 prlimit 的具体定义也可以通过 man 2 prlimit 查看文档。

如果有幸可以用上 util-linux 2.21 以上版本(http://www.kernel.org/pub/linux/utils/util-linux/),那么直接就会有一个 prlimit 命令可用。那就非常方便了。

但是如果内核版本较低,没有这功能又怎么办呢?其实还可以通过万能的 gdb 钩进进程调用 setrlimit 来实现。于是,可以包装一个脚本出来:

#!/bin/bash

f="0"
while getopts ":c:d:e:f:i:l:m:n:p:q:r:s:t:u:v:x" opt; do
  if [ "$f" == "1" ]; then
    echo "too many arguments" >&2;
    exit 1
  fi
  f="1"
  v="$OPTARG"
  case $opt in
  c) r=4  ;; # RLIMIT_CORE       core file size
  d) r=2  ;; # RLIMIT_DATA       data seg size
  e) r=13 ;; # RLIMIT_NICE       scheduling priority
  f) r=1  ;; # RLIMIT_FSIZE      file size
  i) r=11 ;; # RLIMIT_SIGPENDING pending signals
  l) r=8  ;; # RLIMIT_MEMLOCK    max locked memory
  m) r=5  ;; # RLIMIT_RSS        max memory size
  n) r=7  ;; # RLIMIT_NOFILE     open files
  q) r=12 ;; # RLIMIT_MSGQUEUE   POSIX message queues
  r) r=14 ;; # RLIMIT_RTPRIO     real-time priority
  s) r=3  ;; # RLIMIT_STACK      stack size
  t) r=0  ;; # RLIMIT_CPU        cpu time
  u) r=6  ;; # RLIMIT_NPROC      max user processes
  v) r=9  ;; # RLIMIT_AS         virtual memory
  x) r=10 ;; # RLIMIT_LOCKS      file locks

  ?) echo "bad argument $opt" >&2; exit 1 ;;
  esac
done

shift $(($OPTIND - 1))

if echo "$v" | grep -q -E "^\-?[0-9]+$"; then
  true
else
  echo "bad rlimti value $v" >&2
  exit 2
fi

if [ `echo "$v==-1 || ($v>=0 && $v<1048576)"|bc` == '0' ];then
  echo "bad rlimti value $v" >&2
  exit 2
fi

pid=$1
bin=`readlink /proc/$pid/exe`
if [ -z "$bin" ]; then
  echo "process $pid not found" >&2
  exit 3
fi

cmd='
set $rlim=&{-1ll,-1ll}
print getrlimit('$r',$rlim)
set *$rlim[0]='$v'
print setrlimit('$r',$rlim)
quit
'

result=`echo "$cmd" | gdb $bin $pid 2>/dev/null | grep '(gdb) \$2 ='`
result="${result##*= }"
exit $result

sudo ./rlimit.sh -c -1 12345

就可以 12345 放开进程的 core dump

参数同 ulimit ,-1 表示 unlimited。不过用这种方法,只能调整 soft rlimit ,而且没法超过原有的 hard rlimit。

修改以后,可以通过 cat /proc/<pid>/limits 查看效果

补充(感谢zolker):
对于 redhat 系,如 centos 6.2 之后可以通过

echo -n 'Max processes=10240:10240' >/proc/<pid>/limits

来修改。这里的 limit 的名称就是

cat /proc/<pid>/limits

里看到的

bash 技巧笔记

alias 可以对一个或一组命令起一个别名。和 bash 函数不同的是 alias 没有自己的独立进程。在有些情况下,和函数是不能相互替代的。
比如

alias shift2="shift 2"

之后就可以在脚本或函数中使用,踢掉两个参数。这样的东西用函数是不管用的。但是,alias 如果写在脚本里,运行时候会说找不到命令。因为对 bash 脚本,默认没有展开 alias 。需要先设置一下才行:

shopt -s expand_aliases

bash 中定义的变量可以用 export 导出,这样在子子孙孙的进程中都可以访问到。实际上,函数也是可以导出的。用 export -f 即可。这样在子进程的 bash 中也可以使用这些函数了。

出于调试目的或者要批量导出,可能需要列出所有函数。可以用

declare -F

,不过这个结果里,每行前面都有 “declare -f” 。用

compgen -A function [prefix]

结果就很干净。另外,这个函数还可以指定函数的前缀。

如果要列出指定前缀的变量,则可以用

echo ${!prefix*}

bash 中有效建立锁

有时候需要防止一段代码在被同时执行,就需要使用锁来防止代码重入。常常见到这样的代码:

if [ -f /var/lock/mylock ]; then
  touch /var/lock/mylock
  ...
  rm -f /var/lock/mylock
fi

但实际上,这样是有问题的。如果两个进程在 test ( [ ) 和 touch 之间,另一个进程同时执行,就会出现竞争问题,最后就可能出现同时运行的情况。要避免这种情况出现,就得改一下加锁的方式。可以用 mkdir 代替 touch,这样在锁目录以及存在的时候,会直接出错;

if mkdir /var/lock/mylock 2>/dev/null; then
  ...
  rm -rf /var/lock/mylock
fi

或者先用 set -C ,让 > 重定向在文件已经存在时出错,然后用 echo … > 来生成锁文件

if ( set -C; echo $$> /var/lock/mylock 2>/dev/null); then
  ...
  rm -f /var/lock/mylock
fi

这两种方法可以保证加锁和检测锁是一个原子操作,避免竞争问题。

在 bash 中分割字符串

比如,要分割 test=”aaa,bbb,cc cc,dd dd”,可以这样

arr=$(echo $test|tr "," "\n")

还可以这样

OLD_IFS=$IFS
IFS=','
arr=$test
IFS=$OLD_IFS

然后用

for x in $arr; do
  echo $x
done

看看效果

或者更直接一点

IFS=',' arr=($test)

这样直接就变成了 bash 数组。可以这样遍历:

for x in ${arr[@]}; do
  echo $x
done

或者直接通过下标访问:

echo ${arr[0]}
echo ${arr[1]}

向上跳目录的小脚本

在很深的目录里的时候,要往上跳n级,就要敲很多键盘。敲多了烦了,就写了一个小脚本,加在了.bashrc里

up(){
  i=$1
  if [ -z $i ]; then
    i=1
  fi
  p=”
  while (($i>0)); do
    p=”../$p”
    (( i– ))
  done
  cd $p
}

用法: up [levels]。比如 up 2 就会向上跳两级目录。不加参数,就往上一级,相当于 cd .. 。

写得可能还不够简练。哪位有什么改进意见么

 

看看自己常用的命令

想不想知道自己平时用的最多的linux命令是哪些吗?

history | perl -n -e ‘split(/s+/,$_,5); print $_[4],”n”‘ | tr “|” “n” | awk ‘{print $1}’ | grep -E “^w+$” | sort | uniq -c | sort -nr | head -n 20

这行脚本可以统计出来。还包括了用管道执行的命令。比如tail xxxx|grep xxx里的grep。