今天小弟在重构代码中的一个脚本模块,其中涉及到日志功能。上午花了点儿时间想出了个在 shell 打日志的技巧,觉得值得写一下。
希望要实现的效果是:实现一个 write_log 命令,给一条出错消息作为输入,write_log 记录日志时自动加上 时间戳、脚本文件名和行号。形如:
2010-12-17 19:13:44 [work.sh:24] FATAL: mkdir -p /x.
时间戳、脚本文件名都好获得,但是行号就没那么容易实现了。shell 中的 $LINENO 变量只能展开成当前行的行号,如果把 write_log 实现成函数的话,势必在函数中无法使用 $LINENO。
开始我想了好大一会儿,觉得 eval 能干这个事情。但是如果用 eval 的话,还不如直接把 $LINENO 传给 write_log 函数呢,与我的初衷不是太相符。我拉来同事讨论了一把,也没解决问题。正当我准备放弃了,计划每次传 $LINENO 参数时,忽然想起来,怎么把 alias 给忘了呢?
于是,write_log 的实现就是这个样子了:
function _write_log()
{
if [ $# -eq 2 ]; then
if [ -z $LOGFILE ]; then
echo "$(date "+%Y-%m-%d %H:%M:%S") [$0:$1] $2"
else
echo "$(date "+%Y-%m-%d %H:%M:%S") [$0:$1] $2" >> $LOGFILE
fi
elif [ $# -eq 1 ]; then
if [ -z $LOGFILE ]; then
echo "$(date "+%Y-%m-%d %H:%M:%S") [$0] $1"
else
echo "$(date "+%Y-%m-%d %H:%M:%S") [$0] $1" >> $LOGFILE
fi
else
return 1
fi
}
alias write_log='_write_log $LINENO' # 这里必须使用单引号
存在的问题是:上面这段代码在 bash 里是不工作的,但是用 sh 可以——即使 sh 也是链接到 bash 的。问题出在 alias 上,可以把问题简化成这样,有一个脚本 a.sh:
$ cat a.sh alias lss='ls -l' lss /tmp
这个脚本用 /bin/sh 执行是这样的:
$ sh a.sh total 8 drwx------ 2 gdm gdm 4096 2010-12-17 19:34 orbit-gdm drwx------ 2 gdm gdm 4096 2010-12-17 11:04 pulse-PKdhtXMmr18n
用 /bin/bash 执行是这样的:
$ bash a.sh a.sh: line 2: lss: command not found
把 bash 随便 link 成一个叫 sh 的链接文件,再执行是类似这样的:
$ ln -s /bin/bash ~/sh $ ~/sh a.sh total 8 drwx------ 2 gdm gdm 4096 2010-12-17 19:34 orbit-gdm drwx------ 2 gdm gdm 4096 2010-12-17 11:04 pulse-PKdhtXMmr18n
这个问题肯定是有原因的,我不愿意去翻 bash 源代码,也不知道哪里去找答案,所以我放弃了,直接在文件头加上
#!/bin/sh
如果哪位兄台知道这种“奇怪”现象的原因所在,请不吝赐教 :)
shopt -s expand_aliases
Aliases are not expanded when the shell is not interactive, unless the
expand_aliases shell option is set using shopt.
@依云 : 恩,多谢指点!
这个可能和sh,bash解析处理alias顺序有关,跟了下,在查找命令时,前者是找ls;后者还是找的lss
sh = bash --posix