记一次shell脚本的history问题分析


最近才开始买VPS搭建个人blog玩玩,虽说时间不是很长,但我个人觉得在这段折腾的时间里,个人还是学到了很多东西的,比方说很多shell脚本的问题,当你没有遇到这类问题的时候,即便你很聪明,在第一次碰到这个问题时,也很难下手,比方说这次记录的:”/bin/sh: 1: history: not found”
因为之前碰到过几次VPS不稳定{其实也因为我喜欢重装、网络连接不稳定、分析问题不够冷静彻底等原因}而导致了数据经常性的丢失,有些数据or内容因为有备份丢了还不算什么,但是有些内容,特别是某些只在VPS上修改过了的脚本丢了那我就非常心痛了,一来是有些修改只有在当时的环境中才会存在,其次这些修改真的是一个解决问题/难题的过程记录,丢了的话就很难找回来了,在痛定思痛之后我决定每天凌晨的时候进行自动备份{将任务写入crontab就行},备份的除了脚本还有我使用putty远程登陆过程中执行过的命令,修改了HISTTIMEFORMAT格式(HISTTIMEFORMAT=”@%F_%T “;export HISTTIMEFORMAT)之后还是显得非常直观的,可以清楚的看到我在什么时候执行了什么命令,不论是事后发现问题还是安全审计,这都是不错的一个改进,但是问题也就随之而来:
第一次:
直接将命令写入crontab(输入命令:crontab -e之后,编辑内容,添加一行内容:59 23 * * * history | mutt [email protected] -s “history_bak”,但是第二天在查看邮件内容的时候发现是空的,而且在”/var/mail/root”中有一行提示:/bin/sh: 1: history: not found
第二次:
想着先将history的内容导出到一个txt文件,然后以邮件附件的形式发送出去。但是,依旧报错:/bin/sh: 1: history: not found 附件中的文本文件内容为空。
第三次:
决心把问题抓出来,于是开始搜索:
  • site:stackoverflow.com /bin/sh: 1: history: not found
  • ubuntu bash history no output site:stackoverflow.com
终于在以下内容中找到了问题所在:
http://stackoverflow.com/questions/10957664/ssh-host-history-tail-produces-no-output
History is not loaded for a non-interactive shell.(history命令在非交互式shell的情况下是没有加载的) You can either tail the history file (~/.bash_history) or turn on history and load it.
set -o history
history -r

The full commands needed to do this from the remote host is as follows:

ssh host 'HISTFILE=~/.bash_history; history -r; history' | tail
即,history命令无法在一个非交互式的shell中执行,所以,如果想要达到我的目的,有两种方法
  1. 直接使用~/.bash_history文件内容,但因为我添加了命令执行时间戳的功能,文件中显示的时间是Unix格式的,不直观,所以暂时不推荐;
  2. 在非交互式shell中加载/打开history命令,使用”set -o history”命令(如果经常性需要,可以将它添加到/etc/rc.local或~/.bashrc中)。
第二种方法的一个示例:
history_in_nonloginShell
在脚本内部添加了”set -o history”命令
如果全局添加”set -o history”命令的话,估计安全性是个很大的问题(默认情况下history命令记录的都是交互式shell中执行的命令列表,而shell脚本中的命令执行属于非交互式的,所以,执行shell脚本时,脚本里面执行的命令不会出现在history命令显示的结果中),所以,想想我还是算了吧,使用~/.bash_history文件也可以的。
================================================
在找到答案之前走的一些弯路:
关于-/bin/sh:xx(命令) not found 的几种原因:
  • $PATH下没有这个命令;
  • $PATH下有这个命令,只是执行权限不够,或者程序执行权限不够;
  • 程序需要的静态库或者动态库没有。
上面的内容一看其实是非常有道理的,但是,刚开始我也就这么被带进沟里面了o(╯□╰)o
查找history文件:
# find / -name history #没有任何输出{在我的Ubuntu12.04 x32系统上
# man history  #没有-r选项
# man bash  #这里面的内容比较多,但是也确实太多了……
后来在搜索的过程中,才想到:因为history命令属于Linux的内建(built-in)命令,用find查找不到也就很正常了(关于如何知道Linux有哪些内建命令,请自行搜索or翻至文章最底部)
一些参考链接:
====
linux中的set命令:”set -e”与”set -o pipefail”
工作中经常在shell脚本中看到set的这两个用法,但就像生活中的很多事情,习惯导致忽视,直到出现问题才引起关注
1. set -e
set命令的-e参数,Linux中的说明如下:
“Exit immediately if a simple command exits with a non-zero status.”
也就是说,在”set -e”之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出。有的人喜欢使用这个参数,是出于保证代码安全性的考虑。但有的时候,这种美好的初衷,也会导致严重的问题。
2. set -o pipefail
对于set命令-o参数的pipefail选项,Linux里是这样解释的:
“If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a  non-zero  status,or zero if all commands in the pipeline exit successfully.  This option is disabled by default.”
设置了这个选项以后,包含管道命令的语句的返回值,会变成最后一个返回非零的管道命令的返回值。听起来比较绕,其实也很简单:
$ vim set_o_pipefail.sh
$ cat set_o_pipefail.sh
#!/bin/bash
set -o pipefail
ls ./a.txt |echo "hi" >/dev/null
echo $?
$
$ bash set_o_pipefail.sh
ls: cannot access ./a.txt: No such file or directory
2
$
$ vim set_o_pipefail.sh
$ cat set_o_pipefail.sh
#!/bin/bash
#set -o pipefail
ls ./a.txt |echo "hi" >/dev/null
echo $?
$
$ bash set_o_pipefail.sh
ls: cannot access ./a.txt: No such file or directory
0
$
参考文章Bash Reference Manual: The Set Builtin
一个关于history命令使用方法的超赞文章
15 Examples To Master Linux Command Line History
In the example below, the !^ next to the vi command gets the first argument from the previous command (i.e cp command) to the current command (i.e vi command).
# cp anaconda-ks.cfg anaconda-ks.cfg.bak
anaconda-ks.cfg
# vi  !^
vi anaconda-ks.cfg
这个获取上个命令的第一个参数的方法太有用了,梦里寻他千百度,蓦然回首,那人却在灯火阑珊处!
history__firstArgument
如何判断一个命令是否为Linux的内建命令(即,如何获取Linux系统的bash的内建命令列表):
Linux系统查看命令是不是内建命令“type”
使用type命令进行判断
# type history
history is a shell builtin
在解决问题的过程中浏览过的网页、搜索过的关键字真的是一个不可多得的经验财富!
@2014年6月23日 12:57:02

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注