Shell高效工作指南

作者:夏清然

优化配置shell环境

Bourne shell(sh) and Bourne-Again shell(bash)

sh(bash)的profile样例,将其放入~/.profile,或/etc/profile,然后source /etc/profile or source ~/.profile使之立即生效。
# Begin /etc/profile
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}


# Set the initial path
export PATH=/bin:/usr/bin

if [ $EUID -eq 0 ] ; then
        pathappend /sbin:/usr/sbin
        unset HISTFILE
fi

# Setup some environment variables.
export HISTSIZE=1000
export HISTIGNORE="&:[bf]g:exit"
#export PS1="[\u@\h \w]\\$ "
export PS1='\u@\h:\w\$ '
alias ls='ls -G'
alias ll='ls -Glrt'

for script in /etc/profile.d/*.sh ; do
        if [ -r $script ] ; then
                . $script
        fi
done


unset pathremove pathprepend pathappend

# End /etc/profile

csh

来自FreeBSD.org cshrc文件配置样例,将其放入/etc/csh.cshrc或~/.cshrc,然后执行source /etc/csh.cshrc or source ~/.cshrc使之立即生效。
# System-wide .cshrc file for csh(1).

set ostype = `uname -s`         # sad, no?

# pretty path
set path = ( ~/bin /bin /usr/local/bin /usr/local/sbin /usr/bin /sbin \
             /usr/sbin /usr/X11R6/bin /usr/local/jdk1.3.1/bin \
           )

if ( -d "/usr/games" ) set path = ( $path /usr/games )
if ( -d "/usr/ports" ) set path = ( $path /usr/ports/Tools/scripts )
if ( -d "/var/qmail" ) set path = ( $path /var/qmail/bin )
if ( -d "/home/des/bin" ) set path = ( $path /home/des/bin )

# settings
set autocorrect                 # fix my mistakes.
set autolist = ambiguous        #
set cdpath = ( ~ )              # lazy
set complete = enhance          # vi f.b completes to foo.bar!
set correct = cmd               # correct what i type.
set filec                       # file completion
set prompt = '[%B%n@%m%b] %B%~%b%# '
set history = 100               # history buffer
set notify                      # don't wait for activity; instant job status
set watch=(0 any any)           # who's here?
unset autologout                # idle.
unset noglob                    #

# environment setup
setenv PAGER            "less"
setenv LSCOLORS ExGxFxdxCxegedabagExEx 

if ( -d "~/tmp" ) setenv TMPDIR "$HOME/tmp"     # secure.

if (! $?term) exit              # if we don't have a terminal, bail.

# nifty prompt.  xterm title if we're in an xterm...
switch ($term)
case "aterm":
case "rxvt":
case "screen":
case "xterm":
case "xterm-color":
    setenv TERM xterm
    set xterm="%{\033]2;%n@%m:%~\007%}%{\033]1;%m\007%}"
    breaksw
default:
    set xterm=""
endsw

unset xterm

# aliases because I am lazy
alias   \!              'h'
alias   c               'clear'
alias   dist.cshrc      'xapply "scp .cshrc %1:." `cat .hosts`'
alias   dosort          'sort -o \!* \!*'
alias   eg              'egrep'
alias   f               'find . -name \!* -print'
alias   ff              'find . -name \!* -exec ls -l {} \;'
alias   g               'grep'
alias   h               'history'
alias   j               'jobs -l'
alias   l               'less'
alias   ll              'ls -la'
alias   lld             'ls -ald'
alias   ls              'ls -Ga'
alias   m               'make'
alias   mq              'mailq'
alias   mqg             'mailq | grep \!*'
alias   newhost         'xapply "scp %1 \!*\:." .ssh/authorized_keys .cshrc'
alias   r               'rehash'
alias   res             'source ~/.cshrc'
alias   z               'suspend'

# bah.
switch ($ostype)
case "SunOS":
    set psargs = "-ef"
    set psargs2 = "-fu $USER"
    breaksw
default:
    set psargs = "-auwx"
    set psargs2 = "-ux"
endsw

alias   psa             'ps $psargs'
alias   psx             'ps $psargs2'
alias   psg             'ps $psargs | grep \!* | grep -v grep'

# cool autocomplete goodness

# this has a tab completion for hosts. yay!
set hosts
set noglob
foreach f ($HOME/.hosts $HOME/.rhosts)
  if ( -r $f ) then
    set hosts = ($hosts `grep -v "+" $f | tr -s " " "   " | cut -f 1`)
  endif
end

# more complete loving.
complete -%*            c/%/j/
complete {alias,unalias}        p/1/a/
complete {bg,fg,stop}   c/%/j/ p/1/"(%)"//
complete cat            n/*/f/
complete cd             p/1/d/
complete chgrp          c/-/"(c f h R v -)"/ n/-/g/ p/1/g/ n/*/f/
complete chown          c/-/"(c f h R v -)"/ C@[./\$~]@f@ c/*[.:]/g/ \
                        n/-/u/. p/1/u/. n/*/f/
complete exec           p/1/c/
complete ftp            c/-/"(d i g n v)"/ n/-/\$hosts/ p/1/\$hosts/ n/*/n/
complete finger         c/*@/\$hosts/ n/*/u/@ 
complete kill           'c/-/S/' 'c/%/j/' \
                        'n/*/`ps -xu $LOGNAME | awk '"'"'{print $2}'"'"'`/'
complete {killall,pkill}        c/-/S/ n/*/c/
complete make           'n/-f/f/' 'c/*=/f/' \
                        'n@*@`cat -s GNUmakefile Makefile makefile |& sed -n -e "/No such file/d" -e "/^[^     #].*:/s/:.*//p"`@'
complete mutt           c@=@F:$HOME/Mail/@
complete ping           p/1/\$hosts/
complete {portupgrade,pkg_delete,pkg_info}      c/-/"(f x)"/ p@*@D:/var/db/pkg@@
complete rmdir          n/*/d/
complete set            'c/*=/f/' 'p/1/s/=' 'n/=/f/'
complete ssh            p/*/\$hosts/ c/-/t/ n/-l/u/
complete sudo           n/-l/u/ p/1/c/
complete talk           p/1/'`users | tr " " "\012" | uniq`'/ \
                        n/*/\`who\ \|\ grep\ \$:1\ \|\ awk\ \'\{\ print\ \$2\ \}\'\`/
complete telnet         p/1/\$hosts/ p/2/x:''/ n/*/n/
complete traceroute     p/1/\$hosts/
complete unset          n/*/s/
complete which          n/*/c/
complete xhost          c/[+-]/\$hosts/ n/*/\$hosts/
complete xpdf           n/*/f:*.pdf/
unset noglob

bindkey -k up history-search-backward
bindkey -k down history-search-forward
bindkey "^W" backward-delete-word

常用命令

find和xargs

命令简介
find基本命令语法:find path -options [-print -exec -ok]

pathname: find命令所查找的目录路径。例如用"."来表示当前目录,用".."表示上级目录,用"/"来表示系统根目录。
-print: find命令将匹配的文件输出到标准输出。
-exec: find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为' command' {} \;,注意{ }和\;之间的空格。但是在一些的系统中只允许执行ls等操作,且-exec的执行速度较慢,建议使用xargs来完成相同的功能。
-ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。

-options详细介绍:

-name:按照文件名查找文件。
-prem:按照文件权限查找文件。
-user:按照文件属主查找。
-group:按照文件所属的组查找。
-amin:查找最后n分钟被访问过的文件。
-atime:查找最后n*24小时被访问过的文件。
-mmin:查找最后n分钟被修改过的文件。
-mtime -n(+n):按照文件的修改时间查找。- n表示文件更改时间距现在n天以内,+ n表示文件更改时间距现在n天以前。
-cmin:查找最后n分钟被改变状态的文件。
-ctime:查找最后n*24小时被改变状态的文件。
-nouser:查找无有效属主的文件,即文件的属主在/etc/password中不存在。
-nogroup:查找无有效属组的文件,即文件的属组在/etc/group中不存在。
-newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件。
-type 查找某一类型的文件:包括:

b 块设备文件
d 目录
c 字符设备文件
p 命名管道文件(FIFO)
l 符号链接文件
s socket文件
f 普通文件
-size n[c]: 查找文件长度为n块的文件,带有c时表示文件长度以字节计。
-maxdepth(-mindepth) levels:查找最大(最小)几层的目录。
-fstype:查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/ e t c / f s t a b中找到,该配置文件中包含了本系统中有关文件系统的信息。

xargs是用于接受stdin的输入,并把起重整为一个命令行来执行。

应用实例
查找当前目录下所有的普通文件,并以长列表显示
find ./ -type f -exec ls -l {} \;
find ./ -type f | xargs ls -l
查找当前目录下所有的普通文件,属主具有读写权限,并且文件所属组的用户和其他用户具有读权限的文件,并以长列表显示:
find ./ -type f -perm 644 -exec ls -l {} \;
find ./ -type f -perm 644 | xargs ls -l
忽略某个目录。如果在查找文件时希望忽略某个目录,那么可以使用-prune选项来指出需要忽略的目录。在使用此选项时要当心,因为如果你同时使用了-depth选项,那么-prune选项就会被find命令忽略。
find /home -path "/home/tom" -prune -o -print #希望在/home下查找文件,但是不想在/home/tom的目录下查找
find /home/sam \( -path /home/sam/dir1 -o -path /home/sam/dir2 \) -prune -o -print #在/home/sam下查找文件,但是避开/home/sam/dir1和/home/sam/dir2
其他实例
find / -user sam #查找系统中所有属主为sam的文件
find / -amin -10 #查找系统中下所有的最后10min被访问过的文件
find / -mmin -5 # 查找在系统中最后5分钟里修改过的文件
find / -atime -2 # 查找在系统中最后48小时访问的文件
find / -mtime -2 # 查找在系统中最后48小时访问的文件
find / -empty # 查找在系统中为空的文件或者文件夹
find / -name '*.core' | xargs echo -n 1 "" >/tmp/core.log #在整个系统中查找内存信息转储文件(core dump) ,然后把结果保存到/tmp/core.log。
find / -type f | xargs grep "hostname" #在所有文件中查找含有hostname这个词的文件
find / -size +100c #查找大于100Bytes的所有文件
find / -size +500kc -and -size -1000kc #查找大于500KB且小于1000KB的文件

grep

命令简介
grep基本语法:grep -options regex files

-options详细介绍:

-A n 输出匹配行和其后n行。
-B n 输出匹配行和其前n行。
-c 只输出匹配行的计数。
-i 不区分大小写(只适用于单字符)。
-h 查询多文件时不显示文件名。
-l 查询多文件时只输出包含匹配字符的文件名。
-n 显示匹配行及行号。
-s 不显示不存在或无匹配文本的错误信息。
-v 显示不包含匹配文本的所有行。
-r 对目录进行递归搜索。注意,此选项并非 POSIX。
-o 只输出符合 RE 的字符串. (gnu 新版独有, 不见得所有版本都支持.)
-w 整词比对, 类似 \ .
-q 静默模式, 不输出任何结果(stderr 除外. 常用以获取 return value(或存储在环境变量$?中), 符合为 true, 否则为 false .)
-E 切换为 egrep .

regex请见“正则表达式”

应用实例
grep "tom" /etc/passwd #查找本机中用户名为tom的用户信息
ps waux | grep httpd #查找系统中的所有httpd进程的相关信息
grep -E '(begin|start)' * #查找含有begin或start字符的所有文件
grep -nr "sys_open\b" fs/ #在fs目录中递归查找字符串"sys_open"
grep -nr "foo\b" ./ | grep -v "bar\b" #在当前目录中搜索含有"foo"但没有"bar"的文件

sort、uniq、cut

sort命令简介
sort命令格式:sort -cmu -o output_file [other options] +pos1 +pos2 input_files

参数介绍:
-c 测试文件是否已经分类。
-m 合并两个分类文件。
-u 删除所有复制行。
-o 存储sort结果的输出文件名。

-b 使用域进行分类时,忽略第一个空格。
-n 指定分类是域上按照数字大小排序(默认是根据字母的字典排序)。
-t 域分隔符;用非空格或tab键分隔域。
-r 对分类次序或比较求逆。
+n n为域号。使用此域号开始分类。
-k 指定第几列,一般和-t连用。
post1 传递到m,n。m为域号,n为开始分类字符数;例如4,6意即以第5域分类,从第7个字符开始。

uniq命令简介
uniq用来从一个文本文件中去除或禁止重复行。一般uniq假定文件已排序,并且结果正确(所以uniq经常与sort连用)。
uniq命令格式:uniq -options INPUT[OUTPUT]

-options:

-c 显示行数,即每个重复行数目
-d 显示重复出现的数据行
-u 只显示不重复行
-f n为数字,前n个域被忽略
cut命令简介
cut用来从标准输入或文本文件中剪切列或域。剪切文本可以将之粘贴到一个文本文件。
cut命令格式: cut -options file1 file2

-options详细介绍: -b bytes 选定指定的字节数; -c list 指定剪切字符数;
-f field 指定剪切域数;
-d 指定与空格和tab键不同的域分隔符;
- c用来指定剪切范围,如下所示:
- c 1,5-7 剪切第1个字符,然后是第5到第7个字符;
-c1-50 剪切前50个字符;
-f 格式与- c相同;
-f 1,5 剪切第1域,第5域;
- f 1,10-12 剪切第1域,第10域到第12域;
-s 对不包含分割符的行不打印输出。

应用实例
sort -u file1 -o file2 #去掉文件file1中的重复行,存入file2
sort file1 | uniq >file2 #去掉文件file1中的重复行,存入file2
sort file1 | uniq -c | sort -nk1 #把文件file1中的相同行计算出现的次数,并根据出现的次数从小到大排序
sort -k: -t 2 file #把文件按照“:”分割的第2列排序
sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n ipaddresses.txt #对IP地址进行排序
cut -d. -f1-4 file #把文件按照“.”分割的第一到第四列取出
cut -s -d: -f2,5-7 file #把文件按照“:”分割的第二列,第五到第七列取出,并不输出不包含“:”的行

awk

awk所包含的内容太多,所以这里仅仅从事例着手去讲解。
应用实例
  • 取域数据(awk默认使用"空格"和"tab"作为域的分割符,如果两者同时出现那么就按照“空格”分割;同时还能使用-F参数手工指定分割符)
  • awk '{print $0}' file #输出整篇文章
    awk '{print $1}' file #输出第一列
    awk -F "\t" '{print $2}' #取出根据tab分割的第2列
    awk -F ":" '{print $NF}' file #取出根据“:”分割的最后一列
    awk -F " " '{print NF}' file #输出根据空格分割的每行的列数
    awk -F "\t" '{print NF}' file #输出根据tab分割的每行的列数
    awk '{print $0"\n"}' file #把文件的每行之间插入一个空行
    awk -F ":" '{if(NF>6)print $0;}' #把根据“:”分割多于6列的行取出
    
  • 按域的范围取值
  • awk '{if(($2~/^begin/)&&($3<10))print $NF;}' filename #把第2列以“begin”开头,并且第3列小于10的行的最后一列取出
    awk -F "\t" '{if($3>5)print $0;}' filename #把以tab分割的第3列大于5的那些行取出
    
  • 其他
  • awk '{$NF="";print $0}' filename #把除最后一列的其他列按原文输出
    awk '{print NR"\t"$0}' file1  | sort -nk1 -r | awk -F "\t" '{print $2}' #把一个文件按行反序输出(第一行是最后一行,最后一行是第一行)
    awk -F "\t" '{SUM+=$2}END{print SUM}' filename #把以tab分割的第2列进行加和,并输出和
    ps a | awk '{print $5}' | sort | uniq -c | sort -nk1 #查看系统中所有进程,并按照进程名运行次数从小到大排序
    
  • 把如下所示的文件按照其总计属性从小到大排列
  • $cat sample
    John 5
    Jam 10
    Tom 20
    Jam 34
    Tom 10
    John 6
    Jerry 2
    $awk '{array[$1]+=$2}END{for(i in array) print i" "array[i]}' sample |sort -n -k 2
    

其他应用技巧

日志关键字的着色监控

在监控应用服务器滚动的日志的时候经常需要对日志的关键字进行着色显示,方便我们监控服务器的各种错误。例如监控邮件服务器的maillog日志的showlist脚本:

每天发送邮件服务器Postfix的运行报告

使用pflogsumm监控Postfix昨天的运行状况(需要安装pflogsumm),然后在/etc/crontab中加入自动执行:
0 2 * * * root zcat /var/log/maillog.0.bz2 | pflogsumm -d yesterday | mail -s "Mail Report from `hostname` on `data`" user@domain.com

参考资料

对话Unix