shell基础收集
一.脚本基础1.非交互启动SHELL
$ /bin/sh filename --文件中记录命令
2.终端培植最通用的标准: TERM=vt100
3.设置PATH
PATH=/bin:/usr/bin:/usr/sbin:/sbin:/u1/topprod/tiptop/ds4gl2/bin:/tmp:/u1/genero.dev/bin:/u1/genero.run/bin
/u1/genero.run/bin
/u1/genero.as/bin
/u1/genero.run/bin
.
/u1/topprod/tiptop/ora/bin
/u1/topprod/tiptop/work
/u1/topprod/topcust/bin
/u1/topprod/tiptop/bin
/u1/topprod/tiptop/ds4gl2/bin
/u1/topprod/tiptop/script
/u2/oracle/9i/bin
/u1/usr/tiptop
/usr/kerberos/bin
/usr/local/bin
/bin
/usr/bin
/usr/X11R6/bin
4.设置MANPATH
MANPATH=/usr/man:/usr/share/man
5.使SHELL脚本可执行
chmod a+x ./logins
6.使用正确的SHELL运行脚本,要在脚本开头增加一行代码,必须是第一行:
#!/bin/sh
二.文件操作
ls -F 显示是目录或者文件
ls -laF
浏览文件concatenate
cat files
cat -n -b hosts -n表明行号
wc [option] files
wc -l -m -c files 统计行数,单词数,字符数
cp source destination
cp -i test_results test_results.orig 如果目标文件存在,提示是否覆盖
cp test_results work/
cp res.01 res.02 res.03 work/
mv source destination
mv -i ch07 ch07.bak --如果存在,提示是否覆盖
mv /home/ranga/names /tmp 把ranga下的文件names移到目录/tmp下
mv docs/ work/
mv work/docs
mv work/ docs/ .profile pub/
不同分区情况
cp -r /tmp/ch01 /home/ranga
rm -r /tmp/ch01
删除目录
rmdir 删除空目录
rm -r
rm files
rm -i hw1 hw2 hw3 --提示是否删除
rm -r ch01/ 删除目录ch01和文件
cd 回到用户起始目录
pwd 查询当前目录
ls /usr/local
ls ../../usr/local
ls /u1 /usr 列出两个目录清单
ls .profile /usr /bin/sh
ls -d /home/ranga 只显示目录名
mkdir drectory
mkdir docs pub
mkdir -p /tmp/ch04/test 创建所有需要的父目录
拷贝目录
cp -r source destination
cp -r docs/book /mmt/zip 包括创建文件夹book
cp -r docs/book docs/school work/src /mnt/zip
cp -r .profile /docs/book /mnt/jaz
三.文件属性操作
-普通文件
l符号链
c字符特殊文件
b块特殊文件
p命名管道
s Socket
d目录文件
ls -ld /home/ranga
file filename 查询文件类型
ls -al /u1/usr/1000810
创建符号链
ln -s source destination
ls -s ../httpd/html/users/ranga ./public_html
文件权限
r 读 w 写 x执行
改变文件和目录权限
符号方式
who action permissions
u 所有者 g 组 o 其他 a 所有
+ 增加权限 - 删除权限 = 显示地设置文件权限 rwxs
chmod a=r* chmod guo=r* 所有用户读权限
chmod go-w .profile
chmod go-rwx*
chmod guo+rx*
chmod go-w,a+x a.out
chmal -R o+r pub
八进制方式
读权限4 写权限2 执行权限1 SUID用4 SGID用2
八进制方式是设置而不是修改文件的权限
chmod 0600 .profile
改变文件的所有者chown
改变文件的组chgrp
chown option user:group files
chown efrioo:/home/httpd/html/users/ranga -改目录所有者
chown -R efrioo:/home/httpd/html/users/ranga --所有文件和子目录
chgrp options group files
chgrp authors /home/ranga/docs/ch5.doc
chgrp :authors /home/ranga/docs/ch5.doc
chgrp -R group files
四.进程
后台进程:&
ls cho*.doc &
使监测可用:set -o monitor
取消监测信息: set +o monitor
检查所有的shell选项:set -o
悬挂键:ctrl+z
用stty命令来识别哪个键执行哪个功能 stty -a
ctrl+z后,进程已停止并悬挂,输入 bg 进程在后台恢复工作
如果多个悬挂进程
bg %1
bg %2恢复在后台工作
从后台移到前台
fg %1
使后台进程持续运行
nohup ls &
等待后台进程结束
wait %1
jobs命令显示出悬挂的以及正在后台运行的进程
ps
ps -f
ps -ef
ps -f -u user
ps -a 所有用户信息
ps -x没有终端的进程信息
ps -ux
杀死进程
kill %1
强迫结束进程
kill -9 或kill -KILL
五.变量
定义变量
fruit=peach
fruit ="apple orange plum"
访问值$
$echo $fruit
$$fruit=efrioo 用改变量的值替换该变量
数组变量:数组下标必须为0-1023的整数
fruit[0]=apple
一次设置多个元素
set -A name value1 value2 ....
set -A band derri terry mike gene
$echo ${fruit[0]}
$echo ${fruit[*]}
$echo ${fruit[@]} 有空格时使用
只读变量
readonly fruit
删除变量:只读的无法使用该变量删除
unset name
unset fruit
局部变量.环境变量.SHELL变量
导出环境变量:将变量放入环境中
PATH=/sbin:/bin ;export PATH
导出多个变量:export PATH HOME UID
export name=value
export PTH=/sbin:/bin
export FMHOME=/usr/frame CLEARHOME=/usr/atria PATH
shell变量
PWD UID SHLVL REPLY RANDOM SECONDS IFS PATH HOME
六.替换
文件名替换
ls * 所有文件和目录
ls ch1*
ls *doc
ls back*doc
ls cgi*st*java
ls ch??.doc
ls cho[0123456789].doc
ls cho[0-9].doc
ls [a-z]*
ls [A-Z]*
ls [a-z A-Z]*
ls *[a-Z A-Z 0-9]
ls [!a]* 不包含a
变量替换
${parameter:-word}替换缺省值,只当变量未设置时执行替换
PSI=${parameter:-localhost} "$";export PSI;
${parameter:=word}赋予一个缺省值
PSI=${HOST:='uname -n'} "$"; export PSI HOST;
${parameter:?message} 检查是否已定义,否则提示
:${HOME:? "your home directory is undefined"}
${parameter:+word}已设置时将替换的值
echo ${DEBUG:+ "debug is active "}
命令替换
grep `id -un` /ect/passwd 使用后引号`,不是单引号
up=`date; uptime`
user=`who | wc -l`
算术替换
$((expression))
s1:$((((5+3*2)-4)/2)) 整数运算,取整
七.引用
用反斜线引用(单个)
echo hello \; world
echo you owe \$1250
使用单撇号(一串)
echo ']?)'
echo it\'s friday
使用双撇号:保证了$和后引号`的替换功能
echo "$user owes;[as of(`date+%m/%d`)]"
echo "the dos directory is \"windows
[url=file://\\temp\]\\temp\[/url]
""
引用规则和环境
引用忽略了单词边界
echo hel"lo;w"orld
命令中的组合引用
echo the '$user' variable contains this value \>"|$user|"
在单个参数中嵌入空格
echo "name address"
mail -s 'meetion tomorrow' fred jane line2 '
访问包含特殊字符的文件名而引用
rm 'ch1*'
引用正规表达式通配符
grep '[0-9][0-9]*$' report2 report7
引用反斜线开启echo转义序列
echo -e "line 1\nline 2"
-e使得shell将echo转义序列解释成特殊字符而非文本字符
cpio是一个存储和恢复文件的命令
cpio -icvdum 'usr2/*'
find / -name 'ch*.doc' -print 匹配所有目录
十.流控制
使用test
文件测试
test option file
if [-d /home/ranga/bin ]; then PATH="$ PATH:/home/ranga/bin"; fi 目录是否存在
if [-f $home/.bash_aliai]; then $home/.bash_aliai; fi 测试该文件是否有内容
-b 块特殊文件 -c字符特殊文件 -d目录 -e文件存在 -f规则文件
-g是否设置SGID位的值 -h符号连接 -k是否设置"sticky"位的值 -p为已命名管道
-r文件存在且可读 -s文件存在且大于0 -u设置了SUID位的值 -x文件存在且可执行 -o文件存在且被有效用户ID拥有
字符串比较
-z string长度为0
-n string长度不为0
string1=string2 字符串相等
string1!=string2字符串不等
if [-z "$fruit_basket"]; then
echo "your fruit basket is empty";
else
echo "your fruit basket has following fruit:$fruit_basket"
fi
if [ "$fruit" = apple ] ; then
echo "it is empty"
else
echo "it is $fruit"
fi
数字比较
if [ $? -eq 0 ] ; then
echo "successful";
else
echo "an error was encountered"
fi
-eq 等于 -ne不等于
-lt小于 -le小于等于
-gt大于 -ge大于等于
符合表达式
使用操作符:&& || !
! expr 取反
expr1 -a expr2 都为真则为真
expr1 -o expr2 一个为真则为真
if [ -z "$DTHOME"] && [-d /usr/dt]; then dthmoe=/usr/dt; fi
if [ -z "$dthmoe"] -a -d /usr/dt] ; then dthmoe=/usr/dt; fi
否定一个表达式
if [!-d $home/bin]; then mkdir $home/bin; fi
case语句
fruit=kiwi
case "$fruit" in
apple) echo "apple pie is quite tasty";;
banana) echo " i like banana";;
kiwi) echo "new zealand is famous for kiwi";;
esac
if ["$fruit"=apple]; then
echo "apple pie is quite tasty"
elif ["$fruit"=banana]; then
echo "i like anana nut bread"
elif ["$fruit"=kiwi]; then
echo "new zealand is famous for kiwi"
fi
case "$term" in
*term) term=xterm;;
network|dialup|unknown|vt[0-9][0-9][0-9]) term=vt100;;
esac
十一.循环
while循环
x=0
while [$x -lt 10]
do
echo $x
x=`echo "$x+1"|bc` --bc为X加1
done
while循环嵌套
x=0
while ["$x" -lt 10 ];
do
y="$x"
while [ "$y" -ge 0 ] ;
do
echo "$y \c"
y=`echo "$y-1"|bc`
done
echo
x=`echo "$x+1"|bc`
done
RESPONSE=
while [-z "$RESPONSE" ] ;
do
echo "enter the name of a directory where your files are -locate:\c"
read ESPONSE
if [! -d "$RESPONSE" ] ; then
echo "ERROR:please enter a directory pathname"
RESPONSE=
fi
done
until循环
x=1
while [ ! $x -ge 10]
do
echo $x
x=`echo "$x+a"|bc
done
x=1
until [$x -ge 10]
do
echo $x
x=`echo "$x+1"|bc
done
for循环
for i in 0 1 2 3 4 5 7 9 8
do
echo $i
done
for file in $home/.bash*
do
cp $file ${home}/public_html
chmod a+r ${home}/public_html/${file}
done
select循环
select component in comp1 comp2 comp3 all none
do
case $component in
comp1|comp2|comp3) compconf $component ;;
all) compconf comp1
compconf comp2
compcomf comp3
;;
none) break;;
*) echo "error:invalid selection,$reply.";;
esac
done
$PS3="please make aselection=>";export PS3
循环控制
while
do
read CMD
case $CMD in
[qQ]|[qQ][uU][iI][tT]) break;;
*) process $CMD;;
esac
done
for i in 1 2 3 4 5
do
mkdir -p /mnt/backup/docs/ch0${i}
if [ $? -eq 0] ; then
for j in doc c h m pl sh
do
cp $home/docs/ch0${i}/*.${j} /mnt/backup/docs/ch0${i}
if [ $? -ne 0] ; then break 2 ; --从两个循环中跳出
fi
done
else
echo "could not make backup directory"
fi
done
continue命令 只跳出当前迭代而不是整个循环
for file in $files
do
if [! -f "$file" ] ; then
echo "error:$file is not a file"
continue
fi
#process the file
done
十二.参数
特殊shell变量
$0 正在被执行命令的名字
$n 与脚本被激活时所带的参数相对应
$#提供给脚本的参数号
$*所有参数都被双引号引住$*=$1$2
$@所有参数都被双引号引住
[email=$@=$1$2]$@=$1$2[/email]
$?前一命令执行后的退出状态
$$当前shell的进程号
$!前一个后台命令的进程号
使用$0
#!/bin/sh
case $0 in
*listtar) targs="-tvf $1";;
*maketar) targs="-cvf $1.tar $1";;
esac
tar $targs
usage语句
case $0 in
*listtar) targs="-tvf $1";;
*maketar) targs="-cvf $1.tar $1";;
*) echo "usage:$0[file|directory]"
exit 0
;;
esac
处理参数
USAGE="usage:$0 [ -c|-t] [file|directory]"
case "$1" in
-t) targs="-tvf $2";;
-c) targs="-cvf $2.tar $2";;
*) echo "$suage"
exit 0
;;
esac
使用basename命令
接受一个绝对或相对路径并返回相应的文件或目录名
$ basename /usr/bin/sh
usage:"usage:'basename $0' [-c|-t][file|directory]"
case `basename $0` in
listtar) targs="-tvf $1";;
maketar) targs="-cvf $1.tar $1";;
esac
tar $targs
公用参数处理
usage="usage:`basename $0` [-c|-t][file|directory]"
if [ $# -lt 2]; then
echo "$usage"
exit 1
fi
case "$1" in
-t) targs="-tvf $2";;
-c) targs="-cvf $2.tar $2";;
*) echo "$usage"
exit 0
;;
esac
tar $targs
处理附加文件
case "$1" in
-t) targs="-tvf"
for i in "$@"; do
if [-f "$i"]; then tar $targs "$i"; fi;
done
;;
-c) targs="-cvf $2.tar $2";
tar $targs
;;
*) echo "$usage";
exit 0
;;
esac
usage="usage:`basename $0` [-c|-t][files|directoryies]"
if [$# -lt 2] ; then
echo "$usage";
exit 1;
fi
case "$1" in
-t) shift; targs="-tvf";
for i in "$@" ; do
if [-f "4i"] ; then
files=`tar $targs "$i" 2>/dev/null`
if [$? -eq 0] ; then
echo ; echo "$i"; echo "#files"
else
echo "error:$i not a tar file"
fi
else
echo "error: $i not a file"
fi
done
;;
-c) shift; targs="-cvf";
tar $targs archive.tar "$@"
;;
*) echo "$usage"
exit 0
;;
$?
shell脚本中的选项分析:getopts
verbose=false
while getopts f:o:v option;
do
case "$option" in
f) infile="$optarg";;
o) outfile="$optarg";;
v) verbose=true;;
\?) echo "$usage";
exit 1
;;
esac
done
shift `echo "$optind -1"|bc`
if [-z "$1" -a -z "$infile" ] ; then
echo "error:input file was not specified"
exit 1
fi
if [-z "$infile"] ; then infile="$1" ; fi
shift命令放弃通过减号提供给脚本的参数
if [-f "$infile"]; then uuencode $infile $infile>$outfile; fi
完整脚本:
#!/bin/sh
usage="usage:`basename $0` [-v][-f][filename][-o][filename]";
verbose=false
while getopts f:o:v option ; do
case "$option" in
f) infile="$optarg";;
o) outfile="$optarg";;
v) verbose=true;;
\?) echo "$usage";
exit 1
;;
esac
done
shift `echo "$optind -1"|bc`
if [-z "$1" -a -z "$infile"]; then
echo "error:input file was not specified"
exit 1
fi
if [-z "$infile"] ; then infile="$1"; fi
:${outfile:=${infile}.uu}
if [-f "$infile"]; then
if [["$verbose"="true"];then
echo "uuencoding $infile to $outfile...\c"
fi
uuencode $infile $infile > $outfile;ret=$?
if ["$verbose"="true"] ; then
msg="failed" ; if [$ret -eq 0] ; then msg="done"; fi
fi
fi
exit 0
十三.输入/输出
向终端输出stdout
echo printf
echo eliza,where the devil are my slippers?!?
echo you r home directory is $home
echo的转义序列
\n 换行符 \t 跳格键tab \c 字符串不带换行符
echo "your fruit basket contains:\n$fruit_basket"
echo "name \tuser name\nsriranga\tranga\nsrivathsa\tvathsa"
echo "making directories,please wait ...\t\c"
for i ${dirs_to_make} ; do mkdir -p $I;done
echo "done"
printf
格式化序列类型
s 字符串 c字符 d十进制整数 x十六进制 o八进制 e指数型浮点数 f固定浮点数 g压缩浮点数
printf "%32s %s\n" "file name" "file type"
#printf "%-32s %s\n" "file name" "file type"
for i in *;
do
printf "%32s" "$i"
if [-d "$i"]; then
echo "directory"
elif [-h "$i"]; then
echo "symolic link"
elif [-f "$i"]; then
echo "file"
else
echo "unknown"
fi;
done
输出重定向:将输出重定向到一个文件 >
date>now
{date;uptime;who;}>mylog
向一个文件增加内容
{date;uptime;who;}>>mylog
向文件和屏幕重定向输出
date|tee now
if ["$logging"!="true"] ; then
logging="true";export logging;
exec $0|tee $logfile
fi
输入
输入重定向
[email=efrioo@soda.berkeley.edu]efrioo@soda.berkeley.edu[/email]
lpr
本地文档
cat >urls
读取用户输入
read name
YN=yes
printf "do you want to play a game [$YN]?"
read YN
:${YN:=yes}
case $YN in
[yY]|[yY][eE][sS]) exec xblast;;
*) echo "maybe later" ;;
esac
while read line
do
case $line in
*root*) echo $line;;
esac
done
管道 pipeline
使用一个程序操纵另一个程序的输出
tail -f /var/adm/messages |more tail的标准输出被管道输送到MORE的标准输入中
ps -ael |grep "$uid" |more ps的标准输出被连到grep的标准输入中,而grep的标准输出被连到more的标准输入中
文件描述符
标准输入stdin 0
标准输出stdout 1
标准错误stderr 2
使用exec可将任何文件和文件描述符关联起来
当需要多次将输入输出重定向到一个文件但又不想多次重复文件名时.
exec n>file
exec n>>file n为文件描述符
输入/输出重定向通用的格式
将stdout 1和stderr 2重定向到不同的独立文件
command 1>file1 2>file2
command >>file1 2>>file2
for file in $files
do
ln -s $file ./docs>>/tmp/ln.log 2>/dev/null --删除输出的一个特殊文件
done
将stdout和stderr重定向到同一个文件
command >file 2>&1
command >>file 2>&1
rm -rf /tmp/my_dir > /dev/null 2>&1;mkdir /tmp/my_dir
rdate -s ntp.nasa.gov>>/var/log/rdate.log 2>&1 --rdate与时间服务器同步时间
向STDOUT打印消息
echo string 1>&2
printf format args 1>&2
echo string>&2
printf format args>&2
if [!-f $file] ; then echo "error:$file is not a file" > &2; fi 是否为文件
重定向两个文件描述符
n>&m
if [-f "$1" ]; then
i=0
while read line
do
i=`echo "$i+1" |bc`
done
if [$# -ge 1] ; then
for file in $@
do
exec 5
十四.函数
创建和使用函数
name() {list;}
cd() {chdir${l:-$home};psi=`pwd`$";export psi;}
函数举例
列出路径
OLDIFS="$IFS"
IFS=:
for DIR in $PATH; do echo $DIR ; done
IFS="$OLDIFS"
IFS是shell的内部域分割符internal field separator
lspath() {
OLDIFS="$IFS"
IFS=:
for DIR in $PATH; do echo $DIR ; done
IFS="$OLDIFS"
}
lspath|grep "/usr/dt/bin"
制作用户自己的路径
path=
for dir in /bin /sbin /usr/bin /usr/sbin /usr/ccs/bin /usr/ucb ;
do
if [-d "$dir"]; path = "$path:$dir"; fi
done
export path
SetPath() {
for _dir in "$@"
do
if [-d "$_dir"] ; then path = "$path";"$_dir"; fi
done
export path
unset _dilr
}
使用变量替换进行检查
SetPath() {
path=${path:"/sbin:/bin"};
for _dir in "$@"
do
if [-d "$_dir"] ; then path = "$path";"$_dir"; fi
done
export path
unset _dilr
}
在函数间共享数据
在文件系统间移动
三个在文件系统移动的命令:popd pushd dirs
dirs(){
oldifs="ifs"
ifs=:
for i in $_dir_stack
do
echo "$i \c"
done
echo
ifs="$lodifs"
}
实现pushd
pushd() {
req="$1";
if [-z "$req"] ; then req=.; fi
if [-d "$req"]; then
cd "$seq"> /dev/null 2>$1
if [$? -eq 0] ; then
_dir_stack="`pwd`:$_dir_stack"; export _dir_stack;
dirs
else
echo "error:connot change to directory $req.">$2
fi
else
echo "error:$req is not a directory." >$2
fi
unset req
}
实现popd
帮助函数
_popd_helper() {
popd="$1"
if [-z "$popd" ] ; then
echo "error:the directory stack is empty" >$2
return 1
fi
shift
if [-n "$1"]; then
_dir_stack="$1";
shift;
fo i in $@; do _dir_stack="$_dir_stack:#i"; done
else
_dir_stac=
fi
if [-d "$pdpd"] ; then
cd "$popd" >/dev/null 2> &1
if [$? -ne 0] ;then
echo "error:could not cd to $popd" >&2
fi
pwd
else
echo "error:$popd is not a directory ." >&2
fi
export _dir_stack
unset popd
}
打包函数
popd() {
oldifs="$ifs"
ifs=:
_popd_helper $_dir_stack
ifs="$oldifs"
}
十五.文本过滤器
head tail grep sort uniq tr
head命令
head [-n lines] files 没有选项就显示前10行,否则显示前N行
按最近访问时间将清单排序 -ut
ls -lut /home/ranga/public_html
检索前5个
ls -lut /home/ranga/public_html|head -5
ps -ef|grep udm7|head -5
tail命令
tail [-n lines] files 没有选项,显示标准输入的后10行
ls -t按上次改动时间排序
ls -lt /var/spool/mail
ls -lt /var/spool/mail|tail -5
ps -ef|grep udm7|tail -5
从最老到最新排序 -r
ls -lrt /var/spool/mail|tail -5
tail -f file (follow)当程序正在向文件写入时也可以查看该文件
tail -f /var/log/httpd/access_log
使用grep 在文件中找到包含某个特殊单词或词组的行
grep word file
grep pipe ch14.doc
grep pipe ch14.doc ch15.01.doc 同时查找多文件
grep -i unix ch16.doc 同时匹配大小写
从stdin(标准输入)中读入
who |grep ranga
grep -v home /etc/passwd 不包含单词home
所有运行在一个系统上的bash例程
/bin/ps -ef|grep bash
/bin/ps -ef|grep bash|grep -v grep
行号
grep -n pipe ch15.doc ch15-01.doc
只列出文件名
grep -l delete *
统计单词的总数
tr命令 将一个集合中的所有字符改变成另一个集合中的字符,也可用于删除字符集
sort命令为输入文件中的行进行分类
uniq命令打印出文件中所有的唯一行,并列出某个特定行重复的次数
tr命令
tr `set1` `set2` 将set1转变成set2
tr `!?":;\[\]{}(),.\t\n` ` `
压缩输出空格
tr -s `set1`
遇到字符多次连续出现时,只使用一次这个字符
echo "feed me"|tr -s `e` fed me
echo "shell programming"|tr -s `ln`
tr `!?":;\[\]{}(),.\t\n` ` `
sort命令:统计一个单词使用了多少次,要使用sort命令将文件中的单词排序
先将文件变成每个单词占用一行的格式,要把所有空格变成换行符
tr `!?":;\[\]{}(),.\t\n` ` `
uniq命令
通过sort使用-u选项删除所有重复的单词
uniq统计单词出现的次数使用-c
tr `!?":;\[\]{}(),.\t\n` ` `
为数字排序
sort按数字值排序代替按字符串排序-n,最大的数字首先先打-r,缺省时,最后打印出最大的数字
tr `!?":;\[\]{}(),.\t\n` ` `
sort命令关键字从何处开始到何处结束,以列为单位
sort -k start,end files
sort -rn -k 2,2 switched.txt
sort -rn -k 2 switched.txt
tr命令中字符分类的使用
alnum 字符和数字 alpha 字母 blank 白色空格 cntrl 控制字符 digit 数字 graph 可打印字符,不包括空格 lower 小写字母
print 打印字符,包括空格 punct 标点符号 space 水平或垂直空格 upper 大写字母 xdigit 16进制数字
tr `[:classname:]` `set2`
去掉标点符号和空格
tr `[:punct:]` ` `
十六.使用正规表达式过滤文本
awk sed激活语法: command 'script' filenames
正规表达式
基本构造块包括:普通字符.元字符(通配符)
用于正规表达式的元字符
. 匹配任何除换行符外的单个字符
* 匹配恰处于*字符前的0个或多个所给字符
[chars] 匹配在chars中给出的任一个字符,用 - 指出字符范围,^若为第一个字符,则匹配在char2中未指定的字符
^匹配一行的开始
$匹配一行的结尾
\将紧随在\字符后的字符作为文字字符处理
匹配字符
/a.c/ 匹配包含a+c,a-c,abc行
/a*c/ 包含0个或有a且以c结尾的字符串
/ch.*doc/ 以ch开头,doc结尾
指定字符集
在正规表达式中指定具体的字符集,使用中括号[ ] /[chars]/
常用字符集
[a-z]匹配单个小写字母
[A-Z]匹配单个大写字母
[a-z A-Z]匹配单个字母
[0-9]匹配单个数字
[a-z A-Z 0-9]匹配单个字母或数字
[^T]匹配任何在集合中未给出的字符
/cho[0-9]*doc/
锚定模式anchoring
只匹配单字the ,以there这类单词开头的行不匹配,只需在后面加一个空格
/the /
字符串the在行的开始位置,使用^元字符
/^the/
使用$元字符将表达式锚定在一行的末尾
/friend$/
/^chapter[1-9]*[0-9]$/
匹配空格行
/^$/
转义元字符
/\$[0-9]*\.[0-9][0-9]\/[a-z A-Z]*/
一些有用的正规表达式
空行: /^$/
整行:/^.*$/
一个或多个空格:/*/
html或xml标记:/][^>]*>/
有效的
[url=/[a-z]url:/[a-z[/url]
A-Z][a-z A-Z]*:\/\/[a-Z A-Z 0-9][a-z A-z 0-9\.]*.*/
美元数量格式:/\$[0-9]*\.[0-9][0-9]/
使用sed:sed是一个可用作过滤器的流编辑器
sed `script` files
script是一个或多个按如下格式写出的命令:/pattern/action
在sed中可以利用的某些动作
p 打出该行
d 删除该行
s/pattern1/pattern2 用pattern2替代pattern1
打印行
打出价格低于1元的清单
sed `/0\.[0-9][0-9]$/p` fruit_prices.txt 会出现都打印,符合条件的打印两次,使用-n
sed -n `/0\.[0-9][0-9]$/p` fruit_prices.txt
删除行
以单字mango或Mango开头的行都删除
sed '/^[Mn]ango/d' fruit_prices.txt
执行替换
/pattern/s/pattern1/pattern2/ pattern2代替pattern1
s/pattern1/pattern2
sed 's/paech/Peach/' fruit_prices.txt 一次只执行一次替换
执行全局替换
s/pattern1/pattern2/g
sed 's/eqal/equal/g' nash.txt
重用表达式的值
s命令为我们提供了&操作符,使得pattern2中重用匹配pattern1的字符串 $1 代替 1
sed 's/*[0-9][0-9]*\.[0-9][0-9]$/\$&/' fruit_prices.txt
使用多个sed命令
sed -e 'command1' -e 'command2' ....... files
sed -e 's/Paech/Peach/' -e 's/'*[0-9][0-9]*\.[0-9][0-9]$/\$&/' fruit_prices.txt
mu fruit_pieces.txt fruit_pieces.txt.$$
sed -e 's/Paech/Peach/' -e 's/*[0-9][0-9]*\.[0-9][0-9]$/\$&/' fruit_prices.txt.$$ > fruit_prices.txt
在管道中使用sed
如果sed没有收到文件清单,那么作用于STDIN,可在管道中使用sed
删除第一个圆括号后的所有字符
$/usr/bin/id | sed 's/(.*$//'
删除该行开始处的字符串uid
$/usr/bin/id |sed -e 's/(.*$//' -e 's/^uid=//'
十七.使用awk过滤文本
基本语法: awk 'script' files
显示从一个文件来的所有输入行
awk '{print;}' fruit_prices.txt
域编辑:自动将输入行分割成域,域缺省分割字符是跳格(tab空格)和空格
当输入一行时,awk将已经分析过的域放入变量1作为第一个域.为了访问一个域,使用域操作符$,第一个为$1
awk中,只有当访问一个域变量的值才使用$,访问其他变量的值时,不要求使用$
打印水果名和数量
awk '{print$1 $3;}' fruit_prices.txt
列之间有空格,使用,
awk '{print$, $3;}' fruit_prices.txt
通过使用printf命令代替print将输出格式化
awk '{printf "%-15s %s\n",$1,$3;}' fruit_prices.txt
执行 模式-特定 行为
大于1美元的加*,低于的不变
awk '/*\$[1-9][0-9]*\.[0-9][0-9]*/ {print $1,$2,$3,"*";} /*\$0\.[0-9][0-9]*/{print ;}' fruit_prices.txt
格式化问题
awk '/*\$[1-9][0-9]*\.[0-9][0-9]*/ {print $0,"*";} /*\$0\.[0-9][0-9]*/{print ;}' fruit_prices.txt
比较操作符
expression {actions;}
= == !=
value~/pattern/ value匹配pattern则为真 value!~/pattern/ value不匹配pattern则为真
awk '$375 {print $0;} ' fruit_prices.txt
复合表达式
(expr1)&&(expr2)
(expr1)||(expr2)
单价高于1美元且数量少于75
awk '($2-/^\$[1-9][0-9]*\.[0-9][0-9]$/)&&($3
next命令
awk '$375 {print $0}' fruit_prices.txt
awk '$375 {print $0}' fruit_prices.txt
使用STDIN作为输入
/bin/ls -l | awk '$1 !-/total/{printf "%-32s %s\n",$9,$5;}'
使用数字表达式
awk中的数字操作符
+ - * / %求余 ^求幂
for i in $@
do
if [-f $i] ; then
echo $i
awk '/^*$/{x=x+1;print x;}' $i
else
echo "error:$i not a file" > &2
fi
done
赋值操作符
x=x+1 x+=1
awk中的赋值操作符
+= 加 -=减 *=乘 /=除 %=求余 ^=求幂
特殊模式:begin end
awk '/^&$/{x=x+1;print x;}' $i
awk '
begin {actions} 读取任何输入前执行
/pattern/ {actions}
/pattern/ {actions}
end {actions} 退出前执行
' files
for i in $@
do
if [-f "$i"]; then
echo "$i\c"
awk '
/^*$/{x+=1;}
end {printf "%s\n",x;}
' "$i"
else
echo "error:$i not a file" > &2
fi
done
内置变量
awk中的内置变量
FILENAME 当前输入文件的文件名,不应改变
NR 输入文件中当前输入行或记录的编号,不应改变
NF 当前行或记录中域的编号,不应改变
OFS 输入域分割符(缺省为空)
FS 输入域分割符(缺省为空或TAB键)
ORS 输出记录分割符(缺省为换行符)
RS 输入记录分割符(缺省为换行符)
FILENAME
for i in $@
do
if [-f "$i"] ; then
awk 'begin {printf "%s\t", FILENAME;}
/^*$/ {x+=1;}
end {ave=100*(x/NR); printf "%s\t%3.1f\n",x,ave;}
' "$i"
else
echo "error:$i not a file" >&2
fi
done
改变输入域分割符
在BEGIN模式配置FS
awk指定-F选项 使用SHELL变量动态指定域分割符
awk 'begin {FS=":";} {print $i,$6;}' /etc/passwd
awk -F: '{print $1,$6;}' /etc/passwd
允许awk使用shell变量
awk 'script' awkvar1=value awkvar2=value.......files
NUMFRUIT="$1"
if [-z "$NUMBRUIT"] ;then NUMBRUIT=75 ; fi
awk '$3
$./reorder.sh 25
流控制
if语句
awk '{printf "%s\t",$0; if ($2-/\$[1-9][0-9]*\.[0-9][0-9]/) {printf "*"; if ($3
while语句
awk 'begin {x=0 ; while (x
do语句:执行至少一次
awk 'BEGIN {'BEGIN {x=0 ; do {x+=1; print x; } while (x
nawk gawk awk 建议使用nawk gawk
for语句
for (initalize_counter; test_counter; increment_counter) { action }
awk '{for (x=1;x
十八.各种工具
eval命令 第二次重新处理命令
output=">out.file"
eval echo hello $output
: 命令 是一个完整的shell命令 ,只返回一个完成代码 0 ,指示命令成功完成 .也可作一个空操作no-op
if [-x $cmd]
then :
else
echo error:$cmd is not executable >&2
fi
作为无限循环
while :
do
echo "enter some input: \c"
read INPUT
["$INPUT" = stop ] && break
done
命令估计参数值
:${lines:=24}${term:? "term not set"} lines为空或为定义,则被设为24,term为空或未定义,则发出错误信息
type命令:告诉用户一个指定命令的全路径
type command1 command2...
type s1 ls mv
sleep命令:用于暂停一段时间(给定秒数)
sleep n
echo -e "a value must be input!\a"
sleep 1
echo -ne "\a" \a音频信号 为听到声音信号需要为\a加上-e选项
sleep 1
echo -ne "\a"
用户清单5分钟增加到文件一次
while :
do
date
who
sleep 300
done >>logfile
find命令
find start-dir options actions
find / -name efrioo -print 寻找efrioo并在屏幕上显示他们的全路径
find /reports/1998 -name efrioo -type f -print -exec lp {} \;
/reports/1998 开始目录,只在该目录或子目录寻找
绝对路径: find /u1 -name efrioo -print
相对路径: find ./tmp -name efrioo -print
全目录: find / -name efrioo -print
搜索多目录:find dir1 dir2 -name efrioo print
-name efrioo 只找文件名,不检查目录部分
find / -name '*efrioo*' -print
-type f 只寻找文件类型为f的文件 (f为规则或普通文件,而不是目录,设备文件)
find命令可使用的文件类型
f 规则或普通文件 d 目录 b 块特殊设备 c 字符特殊设备raw l 符号链接 p 有名管道
find -mtime 定位最后一次修改的文件或定位在很久一段时间内都未改变过的文件,该参数以天为单位
find / -mtime -5 -print
+n只寻找改变日期在n天之前的
n 只寻找改变日期在n天前当天的
-n只寻找改变日期在n天之内的
find / -mtime +90 -print
-mtime 找到上次改变时间大于.刚好.或少于n天前
-atime 找到上次访问时间大于.刚好.或少于n天前
-ctime 找到其inode上次改变时间大于,刚好.或少于n天前的文件 inode是磁盘表中一项,包含有关文件属性.大小.最后访问时间等
find -size 基于文件的块大小定位文件
find / -size +2000 -print 所有大小超过2000块的文件的文件名
+n 只寻找文件大小超过n块的文件
n 只寻找文件大小等于0块的文件
-n 只寻找文件大小小于n块的文件
find 组合选项
find / -name efrioo -size +50 -mtime -3 -print
使用-o 指定一个逻辑条件 或
find / \( -size +50 - o -mtime -3 \) -print
find 否定选项 !
find /dev !\(-type b -o -type c -o -type d\) -print
-print 一个动作,向标准输出显示文件的全路径
若在其他选项之前,则后面的被忽略
-exec lp{}\; 一个动作,使用lp命令打印任何匹配规则的文件的硬拷贝,可指定多个动作
-exec使用户指定一个unix命令
find / -name efrioo -exec chmod a+r {}\;
find / -name efrioo -exec rm -f {}\ 知道文件后执行rm命令删除它们 -f不要求用户确认
xargs命令:从标准输入接收一系列单词并将这些单词提供给一个给定命令做为参数
cat filelist|xargs rm
使用 -n选项,可以指定每次从标准输入取来多少个参数用语构造命令行
cat filelist|xargs -n 20 rm
ls |xargs -n 2 echo ----- 指定每行显示的文件数
ls|grep '^acb'|xargs -n 20 rm
expr命令:执行简单的整数算术运算
expr操作符:+ - \* / %
expr 3\*5 3乘5
expr 19%7 求余mod 5
expr要求参数都要被分割开,参数之间都由一个空格分开
cnt=`expr $cnt+1` 后引号完成命令替换
匹配一个正规表达式
expr $abc :'.*' .*是一个可以指出所有字符的正规表达式 ,变量$abc中的所有字符都被计算内
expr $abc :'[0-9]*' 计算在字符串开始部分的数字的个数
expr abcdef :'..\(..|)..' 每个点都是一个正规表达式通配符,代表给定字符串中的一个字符,括号中两个字符被输出
bc命令:是一个不局限于整数的算术工具
bc进入 quit退出
bc可精确计算任何大小的数字 + - * / % ^
bc可为一个变量赋一个计算出的值
average = `echo "scale=4;$price/$units"|bc`
设置obase=16 输出进制是16进制
ibase=8 输入进制是8进制
远程shell
remsh/rsh/rcmd/remote
rmesh remote-sys unix-command
向远程系统拷贝整个目录树
find . -print | cpio -ocva | remsh remote_sys \(cd /destdir \; cpio -icdum \)
高级主题
十九.信号处理
shell脚本中的重要信号
SIGHUP 1 检测控制终端的挂起或控制进程的死亡
SIGINT 2 键盘中断
SINGQUIT 3 从键盘退出
SIGKILL 9 杀死信号
SIGALRM 14 报警时钟信号(为计时器使用)
SIGTERM 15 终止信号
信号列表 signal.h
man 7 signal
man -s 5 signal
man 5 signal
可以理解的信号列表
kill -l
缺省动作
终止进程 忽略信号 内核转储(创建一个名为core的文件,包含接受到信号时进程的内存镜像) 停止进程 继续一个停止的进程
传递信号
最常用的是在脚本执行时按 control-c或interrupt键
其他传递信号的常用方法:kill -signal pid
TERM
缺省条件下,kill向进程ID为pid的进程发送一个TREM或终止信号
kill pid = kill -s SIGTERM pid
HUP
kill -s sighup 1001 发送挂起信号
kill -1 1001
如果kill命令不能终止一个进程,用户可向进程发送quit或int(interrupt)信号
kill -s SIGQUIT 1001
kill -s SIGINT 1001
信号处理函数
kill -9 1001
信号处理
trap命令:设置或取消收到一个信号时的动作
trap name signals 当信号被接收时,就执行name中列出的命令或shell函数
三种trap的常见用法:清除临时文件.一直忽略信号.只在关键操作期间忽略信号
清除临时文件
trap "rm -f $TMPF; exit 2 " 1 2 3 15
当接收到一个HUP,INT,QUIT,TERM信号时,就删除在$TMPF中存储的文件并返回代码2指出退出非正常,正常退出返回0
cleanup() {
if [-f "$outfile" ] ; then
printf "cleaning up...";
rm -f "$outfile" 2>/dev/null;
echo "done.";
fi
}
trap cleanup 1 2 3 15
维持一个进程活着的脚本在接收不同信号时行为就不同:
#!/bin/sh
if [$# -lt 1 ] ; then
echo "usage: `basename $0` command."
exit 0
fi
init() { ---负责停止任何正在运行的程序,然后再次启动它
printf "info: initializing..."
kill -0 $! 2>/dev/null;
if [$? -eq 0] ; then
kill $!>/dev/null 2>&1
if [$? -ne 0] ; then
echo "error:already running as pid $!.exiting."
exit 1
fi
fi
$PROG &
printf "done.\n"
}
cleanup() { ---负责杀死正在运行的程序并退出
kill -9 $!;exit 2;
}
#main() --搜索程序并等待它结束,再往下查,接收到一个INT,QUIT,TERM信号时退出
trap cleanup 2 3 15
trap init 1
PROG=$1
init
while : ;
do
wait $!
$PROG &
done
忽略信号
trap '' signals
trap : signals
脚本要忽略所有信号,则应该在脚本开头增加
trap '' 1 2 3 15
给出该命令时,脚本将忽略所有信号,一直到它退出
在关键操作期间忽略信号
trap ' ' 1 2 3 15
doimportantstuff --函数
trap 1 2 3 15
建立一个计时器
使用ALARM信号和一个信号处理函数来设置一个计时器
#main()
trap alarmhandler 14
settimer 15
$PROG &
CHPROCIDS="$CHPROCIDS $!" --用来维护脚本所有的子进程列表
wait $!
umsettimer
echo "all done"
exit 0
为ALARM信号建立一个处理函数
alarmhandler() {
echo "got sigalarm,cmd took too loog."
killsubprocs
exit 14
}
杀死脚本保存在变量CHPROCIDS中的所有子进程
killsubprocs() {
kill ${CHPROCIDS:-$!}
if [$? -eq 0 ] ; then
echo "sub-processes killed";
fi
}
settimer
使用settimer函数设置计时器
settimer() {
def_tout=${1:-10};
if [$def_tout -ne 0]; then
sleep $def_tout && kill -s 14 $$ &
CHPROCIDS="CHPROCIDS $!"
TIMERPROC=$!
fi
}
计时器是一个后台进程,要用进程ID 来提交子进程列表,还要在变量TIMER PROC中保存计时器的进程ID,便于以后能取消计时器
unsettimer() {
kill $TIMERPROC
}
二十.调试
启动调试--调用激活(invocation activated)调试方式
执行shell脚本的一个常用方法
script arg1 arg2 ...argn
/bin/sh script arg1 arg2 ... argn
通过为shell提供参数来启动一个调试模式:
/bin/sh option script arg1 arg2 ... argn
改变脚本的第一行
#!/bin/sh #!/bin/sh option
shell脚本的调试选项
-n 读所有的命令,但不执行它们
-v 在读时显示所有的行
-x 在执行时显示所有命令和他们的参数(为shell跟踪选项)
使用set命令,用户可以在shell脚本的任何地方启动或取消调试
使用set启动调试
set option
#!/bin/sh
set -x
if [-z "$1" ] ; then
echo "error:insufficient args"
exit 1
fi
使用set取消调试
set +option
set +x 取消shell跟踪调试模式
所有为一个脚本启动的调试模式都可以用下面命令来取消
set -
为一个函数启动调试
set -x; buggyfunction; set +x 保证了函数的实现不被改动
语法检查
/bin/sh -n script arg1 arg2 ...argn
使用verbose模式
shell提供-v(verbose)调试模式来检查语法错误发生的上下文,脚本的每一行都打印出来
用户只运行-v选项,则脚本的每一行都要执行
用户要检查语法错误,应组合-n和-v选项:
/bin/sh -nv script arg1 arg2 ... argn
#!/bin/sh
failed() {
if [$1 -ne 0 ] ; then
echo "failed exiting." ; exit 1;
fi
echo "don."
}
echo "deleting old backups,please wait ...\c"
rm -r backup> /dev/null 2>&1
failed $?
echo "make backup(y/n)?\c"
read RESPONSE
case $RESPONSE in
[yY]|[Yy][Ee][Ss])
echo "making backup,please wait...\c"
cp -r docs backup
failed ;;
[nN]|[Nn][Oo]
echo "backup skipped";;
esac
shell跟踪
-x (execution)
set -x; ls *.sh; set +x
#!/bin/sh
failed() {
if [$1 -ne 0] ; then
echo "failed exiting " ; exit 1;
fi
echo "done"
}
yesno() {
echo "$1 (y/n)? \c"
read RESPONSE
case $RESPONSE in
[yY]|[Yy][Ee][Ss]) RESPONSE=y ;;
[nN]|[Nn][Oo] RESPONSE=n;;
*) RESPONSE=n;;
esac
}
yesno "make backup"
if [$RESPONSE ="y"] ; then
echo "deleting old backups ,please wait...\c"
rm -fr backup >/dev/null 2>&1
failed $?
echo "making new backups ,please wait...\c"
cp -r docs backup
failed $?
fi
使用shell跟踪找出逻辑缺陷
使用调试陷阱
调试陷阱是在函数或关键代码部分启动shell跟踪的函数
脚本用一个特定的命令行选项或把环境变量置为true(DEBUG=true或TRACE=true)
debug() {
if ["$debug"="true"] ; then
if ["$1"="on" -o "$1"= "ON"]; then
set -x
else
set +x
fi
fi
}
激活调试:debug on
取消调试:debug 或 debug off
应用在程序中:debug on
.........
debug off
脚本运行:DEBUG=true /bin/sh ./s1.sh
二十一.使用函数解决问题
创建一个函数库:一个shell函数库一般不包含任何主代码,而只包含函数
从一个库中包含函数
.file
#!/bin/sh
.$HOME/lib/sh/messages.sh
MSG="hello"
echo_error $MSG
命名习惯
库命名
$HOME/lib/sh/libTYSP.sh
使用改库:.$HOME/lib/sh/libTYSP.sh
./usr/local/lib/sh/libTYSP.sh
函数命名
print prompt get
有用的函数
printERROR
printWARNING
printUSAGE
promptYESNO
promptRESPONSE
getSpaceFree
getPID
getUID
询问一个问题
询问yes或no问题
promptYESNO() {
if [$# -lt 1] ; then
printERRPR "insufficient arguments"
return 1
fi
DEF_ARG=""
YESNO=""
case "$2" in
[yY]|[yY][eE][sS]) DEF_ARG=y;;
[nN]|[nN][oO]) DEF_ARG=n;;
esac
while :
do
printf "$1 (y/n)?"
if [-n "$DEF_ARG" ] ; then
printf "[$DEF_ARG]"
fi
read YESNO
if [-z "$YESNO"] ; then
YESNO="$DEF_ARG"
fi
case "$YESNO" in
[yY]|[yY][eE][sS]) YESNO=y; break;;
[nN]|[nN][oO]) YESNO=n; break;;
*) YESNO="";;
esac
done
export YESNO
unset DEF_ARG
return 0
}
函数应用
promptYESNO "make backup"
if ["$YESNO" = "y" ] ; then
cp -r docs backup
fi
提示要求一个回答
promptRESPONSE() {
if [$# -lt 1] ; then
printERROR "insuficient arguments"
return 1
fi
RESPONSE =""
DEF_ARG=""
while :
do
printf "$1 ?"
if [-n "$DEF_ARG"] ; then
printf "[$DEF_ARG]"
fi
read RESPONSE
if [ -n "$RESPONSE"] ; then
break
elif [-z "$RESPONSE" -a -n "$DEF_ARG" ] ; then
response = "$DEF_ARG"
break
fi
done
export RESPONSE
unset DEF_ARG
return 0
}
函数应用:
promptRESPONSE "in which idrectory do you want to install"
if [! -d "$RESPONSE" ] ; then
echo "the directory $RESPONSE does not exist"
promptYESNO "create it " "y"
if ["$YESON" = "y" ] ; then
mkdir "$RESPONSE"
else
exit
fi
fi
检查磁盘空间
getSpaceFree
getSpaceUsed
计算剩余空间
使用df -k (k指KB)命令
df -k /home/ranga
getSpaceFree() {
if [$# -lt 1] ; then
printERROR "insufficient arguments"
return 1
fi
df -k "$1" | awk 'NR!=1 {print $4 ; }'
if ["`getSpaceFree /usr/local`" -gt 20000] ; then
echo "enough space"
fi
}
计算已用空间
计算一个目录使用了多少磁盘空间du
-s 要输出整个目录和它的内容
du -sk
du -sk /home/rang/pub
getSpaceUsed() {
if [$# -lt 1] ; then
printERROR "insufficient arguments"
return 1
fi
if [! -d "$1" ] ; then
printERROR "$1 is not a directory"
return 1
fi
du -sk "$1"|awk '{print $1;}'
if ["`getSpaceSpace /home/ranga/pub`" -gt 10000] ; then
printWARNING "your're using to much space!"
fi
}
通过名字获得进程ID
getPID() {
if [$# -lt 1] ; then
printERROR "INSUFFICIENT ARGUMENTS"
return 1
fi
PSOPTS = "-ef"
/bin/ps $PSOPTS |grep "$1" | grep -v grep |awk '{print $2;}'
}
ps -ef |grep sshd
getPID httpd
ps -auwx (linux freebsd)
获得一个用户ID
id tiptop
id
getID() {
id $1 |sed -e 's/(.*$//' -e 's/^uid=//'
if ["`getUID`" -gt 100] ; then --是否大于100
printERROR "you do not have sufficient privileges"
exit 1
}
getID
getID tiptop
完整函数库
echo "error:"
[email=$@>&2]$@>&2[/email]
echo "warning:"
[email=$@>&2]$@>&2[/email]
echo "usage:" $@
df -k "$1" | awk 'NR!=1 {print $4;}'
du -sk "$1"| awk '{print $1;}'
ps -ef |grep "$1" |grep -v grep |awk '{print $2;}'
二十二.使用shell脚本解决问题
移动目录
rm -rf a cp -r b a rm -rfb
使用tar (内容没有压缩,存储了文件权限和文件所包含的分组和拥有者信息)
创建一个tar文件
tar -cpf - source (- 指出创建的tar文件应被写到STDOUT)
从STDIN解开一个tar文件命令
tar -xpf - (- 指出tar文件应从STDIN读入)
最终使用的命令:
tar -cpf - source | (cd destination ; tar -xpf -) source,destination都是目录
mudir.sh
#!/bin/sh
path = /bin:/usr/bin;export path
printERROR() {
echo "error:$@">&2 ; exit 1;
}
printusage() {
echo "usage:`/bin/basename $@`
[email=$@">&2]$@">&2[/email]
; exit 1;
}
if [$# -lt 2]; then printusage "[src] [dest] " ; fi
if [! -d "$1" ] ; then
printERROR "the source $1 is not a directory ,or does not exist"
fi
SRCDIR_PARENT="`/usr/bin/dirname $1`"
ARCDIR_CHILD="`/bin/basename $1`"
SRCDIR_PARENT="`(cd $SRCDIR_PARENT ; pwd;)`"
if [-d "$2"] ; then
DESTDIR=`(cd "$2" ; pwd;)`
else
DESTDIR="`/usr/bin/dirname $2`"
NEWNAME="`/BIN/BASENAME $2`"
DESTDIR=`(cd $DESTDIR; pwd;)`
if [! -d "$DESTDIR"] ; then
printERROR "a parent of the destination directory $2 does not exist"
fi
fi
cd "$SRCDIR_PARENT">/dev/null 2>&1
if [$? -ne 0]; then
printERROR "could not cd to $SRCDIR_PARENT"
fi
/bin/tar -cpf - "$SRCDIR_CHILD" |(cd "$DESTDIR"; /bin/tar -xpf -)
if [$? -ne 0]; then
printERROR "unable to successfully move $1 to $2"
fi
if [-n "$NEWNAME"] ; then
cd "$DESTDIR">/cev/null 2>&1
if [$? -ne 0] ; then
printERROR "could not cd to $DESTDIR"
fi
/bin/mu "$SRCDIR_CHILD" "$NEWNAME" > /dev/null 2>&1
if [$? -ne 0] ; then
printERROR "could not rename $1 to $2"
fi
cd "$SRCDIR_PARENT" >/dev/null 2>&1
if [$? -ne 0] ; then
printERROR "could not cd to $SRCDIR_PARENT"
fi
fi
if [-d "$SRCDIR_CHILD"] ; then
/bin/rm -r "$SRCDIR_CHILD">/dev/null 2>&1
if [$? -ne 0] ; then
printERROR " could not remove $1"
fi
fi
exit 0
示例
./mvdir.sh /tmp/ch22 /home/ranga/docs/book
ls /tmp /home/ranga/docs/book
维护一个地址簿
显示信息
awk -F:'{ printf "name:%s\nemail:%s\naddress:%s\nphone:%s\n\n",$1,$2,$3,$4;}'
showperson脚本程序
#!/bin/sh
path=/bin:/usr/bin
if [$# -lt 1]; then
echo "usage: `basename $0` name"
exit 1
fi
MYADDRESSBOOK = "$HOME/addressbook"
if [! -f "$MYADDRESSBOOK"] ; then
echo "ERROR: $MYADDRESSBOOK does not exist ,or is not a file " >&2
exit 1
fi
grep "$1" "$MYADDRESSBOOK"|
awk -F:'{ printf "%-10s %s\n%-10s %s\n%-10s %s\n%-10s %s\n\n",\"name:",$1,"email:",$2,"address:",$3,"phone:",$4;}'
exit $?应用:
./showperson ranga
增加信息
addperson脚本的完整程序清单
#!/bin/sh
path =/bin:/usr/bin
MYADDRESSBOOK=$HOME/addressbook
NAME=""
EMAIL=""
ADDR=""
PHONE=""
remove_colon() {
echo "$@" |tr ':' ' ' ; --删除字符
}
if [$# -lt 1 ] ; then
stty erase '^?'
printf "%-10s " "Name:" ; read NAME
printf "%-10s " "Email:" ; read EMAIL
printf "%-10s " "Address:" ; read ADDR
printf "%-10s " "Phone:" ; read PHONE
else
usage= "`basename $0` [ -n name] [ -e email] [ -a address] [-p phone]"
while getopts n:e:a:p:h OPTION
do
case $OPTION in
n) NAME="$OPTARG" ;;
e) EAMIL="$OPTARG";;
a) ADDR="$OPTARG";;
p) PHONE="$OPTARG";;
\?|h) echo "usage:$usage" >&2 ; exit 1 ;;
esac
done
fi
NAME="`remove_colon $NAME`"
EMAIL="`remove_colon $EMAIL`"
ADDR="`remove_colon $ADDR`"
PHONE="`remove_colon $PHONE`"
echo "$NAME:$EMAIL:$ADDR:$PHONE">>"$MYADDRESSBOOK"
exit $?
应用:
./addperson
./addperson -n "james" -e
[email=123@126.com]123@126.com[/email]
-a "1701 main street"
删除信息
delperson脚本程序清单
#!/bin/sh
.$HOME/lib/sh/libTYSP.sh
path=/bin:/usr/bin
if [$# -lt 1] ; then
printUSAGE "`basename $0` name"
exit 1
fi
MYADDRESSBOOK="$HOME/addressbook"
if [! -f "$MYADDRESSBOOK"] ; then
printERROR "$MYADDRESSBOOK does not exists,or is not a file"
exit 1
fi
TMPF1=/tmp/apupdate.$$
TMPF2=/tmp/abdelets.$$
docleanup() {
rm "$TMPF1" "$TMPF1.new" "$TMPF2" 2>/dev/null;}
failed() {
if ["$1" -ne 0 ] ; then
shift
printERROR $0
docleanup
exit 1
fi
}
cp "$MYADDRESSBOOK" "$TMPF1" 2>/dev/null
failed $? "could not make a backup of the address book"
grep "$1" "$TEMPF1" >"$TMPEF2" 2>/dev/null
failed $? "no matches found"
exec 5 "$TMPF1.new" 2>/dev/null
failed $? "unable to update the address book"
mv "$TMPF1.new" "$TMPF1" 2>/dev/null
failed $? "unable to update the address book"
fi
done
exec 5/dev/null
failed $? "unable to update the address book"
mv "$TMPF1" "$MYADDRESSBOOK" 2> /dev/null
failed $? "unable to update the address book"
docleanup
exit $?
}
二十三.脚本可移植性
判断UNIX版本
BSD berkeley software distribution
openbsd netbsd freebsd sunos
system v
solaris sunos hp-ux
BSD SYSTEM V
/bin /usr/bin
/sbin /usr/sbin
/usr/adm /var/adm
/usr/mail /var/mail /var/spool/mail
/usr/tmp /var/tmp
linux的命令和网络都是基于BSD
caldera red hat
使用uname
-a 打印所有信息
-m 打印当前硬件类型
-n 打印系统主机名
-r 打印操作系统发行版本号
-s 打印操作系统的名字
sun microsystems --solaris(新 system v) sunos(BSD)
uname -rs
判断硬件类型
uname -m
判断一个系统的主机名
hostname
uname -n
使用函数判断UNIX版本
getosname() {
case '`uname -s` in
*BSD) echo bsd ;;
SunOS)
case `uname -r` in
5.*) echo solaris ;;
*) echo sunos;;
esac
;;
Linux) echo linux ;;
HP-UX) echo hpux ;;
AIX) echo aix ;;
*) echo unknown ;;
esac
}
isos() {
if [$# -lt 1] ; then
echo "ERROR: insufficient arugments " >&2
return 1
fi
REQ=`echo $1 |tr '[A-Z]' '[a-z]'`
if ["$REQ" = "`getosname`"] ; then return 0 ; fi
return 1
}
提高可移植性的技巧
条件执行
执行远程命令 rsh remote shell
if SystemIS HPUX ; then
RCMD = remsh
else
RCMD=rsh
fi
设置了变量$RCMD后,可执行远程命令
"$RCMD" host command
抽取
getfreespace()
if isos HPUX ; then
df -b "$DIR" |awk '{print $5;}'
else
df -k "$DIR" |awk 'NR!=1 {print $4;}'
fi
getpid()
case `getosname` in
bsd|sunos|linux) PSOPTS="-auwx" ;;
*) PSOPTS="-ef" ;;
esac
/bin/ps $PSOPTS 2>/dev/null | grep "$1" |grep -v grep |awk '{print $2;}'
二十四.shell编程疑难解答
判断shell是否交互式
test -t
tty -s
如何去掉一条命令的输出
command >/dev/null
去掉一条命令的输出和错误输出,使用标准重定向把STDERR重定向到STDOUT
command >/dev/null 2>&1
怎样把信息显示到STDERR
echo message 1>&2
怎样判断shell能否找到一条特定的命令 whence -v
type name >/dev/null 2>&1; if [$? -ne 0]; then list ; fi
怎样判断任务控制是否在shell中可以使用 jobs jsh
if type jobs >/dev/null 2>&1 ; then
echo "wo have job control"
fi
一次一个的考虑一个shell脚本的每个参数
for arg in "$@"
do
list
done
把一个脚本的所有参数赋给另一条命令
command "$@"
在一条sed命令使用一个shell变量的值
sed "/$DEL/d" file1>file2
检查一个变量是否有一个值
if [-z "$VAR"]; then list ; fi
初始化变量
:${VAR:=default}
:${VAR:=`default`}
确定一个目录的全路径名
fullpath='(cd dir; pwd)'
确定一个文件的全路径名
CURDIR=`pwd`
cd `dirname file`
FULLPATH="`pwd`/`basename file`"
cd $CURDIR
定位一个特定文件
find dir -name file -print
find dir -name "*txt" -print
把find命令和xargs命令组合在一起目录及其子目录包含的每一个文件中查找一个特定字符串:
find dir -type f -print | xargs grep "string"
find s -type f -print | xargs grep "/bin"
在一个目录中删除匹配一个特定名字的所有文件
LODIFS="$IFS"
IFS='
' ---为了使for循环在每次循环时把FILE置为正确的值,IFS需要被置成换行符
for FILE in `find . -type f -name "*string*" -print`
do
rm "$FILE"
done
IFS="$OLDIFS"
把所有*.aaa文件重命名为*.bbb文件
dos:
rename *.aaa *.bbb
unix:
OLDSUFFIX=aaa
NEWSUFFIX=bbb
for FILE in *."$OLDSUFFIX"
do
NEWNAME=`echo "$FILE" |sed -e "s/${OLDSUFFIX}\$/$NEWSUFFIX/"`
mv "$FILE" "$NEWNAME"
done
mv -i 更改前提示
把所有aaa*文件重命名为bbb*文件
OLDSUFFIX=aaa
NEWSUFFIX=bbb
for FILE in "$OLDSUFFIX"*
do
NEWNAME=`echo "$FILE" |sed -e "s/^${OLDSUFFIX}/$NEWSUFFIX/"` s/替换
mv "$FILE" "$NEWNAME"
done
把文件名置为小写字母
for FILE in *
do
mv -i "$FILE" `echo "$FILE"|tr '[A-Z]' '[a-z]'` 2>/dev/null
done
消除文件中的回车
\r回车 \n换行
tr -d '\015' newfile \015八进制表示的回车
命令快速参考
.在当前
!!重复执行前次命令
alias 为命令创建一个短名字
eval使shell重新解释后面的命令
exit n 用状态码n来结束shell脚本
fc 显示或编辑历史列表中的一条命令
fg把一个后台或悬挂任务放到前台
function 此关键字定义了一个使用局部变量的函数
getopts在循环中重复调用一个函数来处理命令行参数
history显示该用户最近运行的命令
integer整数变量
jobs列出后台和悬挂任务
let执行整数运算
newgrp改变用户的基本分组,影响用户在分组中创建的所有新文件和目录
print 与echo相同
pwd打印出现在正在工作的或当前目录
r 再次执行上一条命令
read 等待一行标准输入并把每个单词保存在后面的变量中
readonly标记后面的变量为只读
select提供一个菜单并使用户可以选择
set显示或改变shell选项
shift丢弃$1并把所有参数提前一个位置来代替它
test提供许多选项来检测文件.字符串和数值,通常用[表示
trap指明当接收到一个特定信号时执行的代码
type显示后面命令的路径名或指出它是一个内部命令还是别名
typeset设置变量类型及其初值
ulimit显示或设置最大文件或资源的限制
umask显示或设置一个屏蔽来影响用户创建的新文件或目录的权限
unalias删除一个别名
unset取消后面所跟变量的定义
until一直循环到测试命令为真
wait一直等待到所有后台任务完成
whence类似于type命令
while循环到一个测试命令为真
文件测试
-a file file存在则为真
-b file file是一个块特殊设备则为真
-c是一个字符特殊设备则为真
-d是一个目录则为真
-f是一个普通文件则为真
-g设置了SGID权限位则为真
-G file的组与用户组相匹配则为真
-k设置了粘位则为真
-L是一个符号链则为真
-O运行该命令的用户拥有该file则为真
-p是一个命名管道或fifo则为真
-r是可读的则为真
-s大小大于0则为真
-S是一个socket则为真
-t文件描述符与一个终端相关联则为真
-u设置了SUID权限位则为真
-w是可写的则为真
-x是可执行的则为真
字符串测试
-z string string为空时为真
-n string string大小不为0时为真
整数比较
n1 -eq n2 等于为真
n1 -ne n2 不等于为真
n1 -gt n2 大于为真
n1 -ge n2 大于等于为真
n1 -lt n2 小于为真
n1 -le n2 小于等于为真
!expr expr为假时为真
-a and
&& and
-o or
|| or
算术表达式
let "variable=integer_expression" 变量赋值
- 一元减(为后面的值取反)
!~ 逻辑not,二进制补码
*/% 乘 除 求余 + -
>>>2)) =8
=小于等于 大于等于
>>=
参数和变量
uservar=value 赋值
$uservar 用uservar的内容替换
${uservar} 替换为uservar的内容
uservar[index]=value
${uservar[index]} 把一个值替换到命令行
${uservar[*]} 用所有的数组元素替换
${uservar[@]} 用所有的数组元素替换并把每个用双引号扩起来
korn数组的初始化
set -A uservar value1 value2 ...
bash数组的初始化
uservar=(value1 value2 value3...)
内置shell变量
$0 执行的命令或脚本的名字
$n 定位的参数
$# 命令行中所给变元的数目
$* 所有命令行参数的列表
$@ 所有命令行参数的列表每个用双引号括起
$? 最后一条命令的退出状态值
$$ 当前的shell的PID值(进程ID)
$! 最后一个后台命令的PID(进程ID)值
直接影响到变量的内置命令
getopts export read readonly unset
shell变量
CDPATH 包含以冒号分开的目录列表
HOME 用户的起始目录
IFS 内部域分割符
OPTARG 由getopts处理的最后一个命令行参数
OPTIND 由getopts处理的最后一个命令行参数的索引值
PATH 包含一个冒号隔开的目录列表来搜索不包含任何斜杠的命令
PS1基本shell提示字符串
PS2续行时的从属提示字符串
PWD 当前目录
RANDOM每次调用时返回一个不同的随机数(0-32767)
REPLY 通过select命令读入的最后一行输入
SECONDS自从激活shell后所经过的秒数
SHLVL当前激活的shell的数目
UID用户数字ID号
参数替换
${parameter} 替换parameter的内容
${parameter:-word} 替换parameter的内容,但如果它是空的或未定义,则替换word,它可能包含一个未加引号的空格
${parameter:=word}替换parameter的内容,但如果它是空的或未定义,则把word赋给parameter并替换word
${parameter:?message}替换parameter的内容,但如果是空的或未定义,则放弃脚本并给出message做为一个最终错误消息
${parameter:+word}如果parameter不是空的,则替换word,否则不替换
仅在korn/bash中使用的参数替换
${#parameter}替换parameter中包含的字符数
${#array[*]}替换array中元素数目
${parameter#pattern}如果在parameter的开头找到了给出的正规表达式pattern,则删除匹配的字符并替换其余字符.
${parameter##pattern}和上面同,但在parameter开头的最大可能匹配被删除.
${parameter%pattern}和上面同,但删除parameter末端的最小匹配
${parameter%%pattern}和上面同,但删除parameter末端的最大匹配
在bourne/korn/bash中使用的模式通配符
* 匹配0个或更多的任意字符
? 精确匹配一个任意字符
[list] 精确匹配list中任意一个字符
[!list] 精确匹配任意一个不在list中的字符
仅在KORN中使用的通配符
?(pattern1|pattern2...)匹配模式中的任何一个
*(pattern1|pattern2...)匹配模式0次或多次的出现
+(pattern1|pattern2...)匹配模式一次或多次的出现
@(pattern1|pattern2...)仅匹配模式中的一个
!(pattern1|pattern2...)匹配除了模式以外任何字符串
I/O
STDIN 标准输入 0
STDOUT标准输出 1
STDERR标准错误 2
cmd >file
cmd 1>file STDOUT保存到file
cmd >>file
cmd 1>>file STDOUT附加到file
cmd 2>file STDERR
cmd 2>>file
cmd
cmd1 |cmd2 把cmd1的STDOUT作为cmd2的STDIN
cmd |tee file 把unix命令的STDOUT保存到file中,同时把该文本当作STDOUT
exec n>file 把文件描述符n的输出重定向到(覆盖)file ,它用在紧接着的unix命令中
exec n>>file 同上,但是附加到file
cmd 2>&1 把STDERR重定向到STDOUT当前所指向的地方,把输出和错误保存在一个文件中或把他们一起送到另一条命令时有用
cmd>file 2>&1 STDERR和STDOUT都保存到FILE中
cmd>&2 把STDOUT重定向做STDERR.在ECHO显示一个错误信息时有用
cmd 1>&2 同上
cmd n>&m 把文件描述符n重定向到文件描述符m当前所指向的地方,大于2的n和m可以用来保存一个I/O目标并在以后提取它
常用命令汇总
echo
\b backspace
\c 抑制跟踪新行
\f 换页
\n 换行
\r 回车
\t Tab
\\ 反斜杠
\0nm ASCII码为八进制nm的字符
grep
-i 忽略大小写
-l 仅显示包含匹配的文件名,而不是匹配的行
-n 显示文件行号和每个匹配的行
-r 与测试相反,意味着忽略包含模式的行
printf
printf "text %[-]m.nx" arguments
- 左对齐(可选)
m 最小域长度
n 字符串的最大长度,浮点数的小数部分位数
x参数类型 s 字符串 c字符值 d十进制整数值 x十六进制值 o八进制值 e指数浮点值 f固定浮点值 g普通浮点值
sort ---显示排序后的行
-b 忽略开头的空格
-d 忽略开头的标点
-f 不分大小写
-n 按开头数字大小排序
-r 以相反顺序排序
+n 排序时忽略前n个域
gerp fgrep egrep sed vi perl awk都允许在模式搜索中使用正规表达式通配符
所有的正规表达式都可以包含下面的通配符
^pattern 仅匹配以pattern开始的行
pattern$ 仅匹配以pattern结尾的行
. 恰好匹配一个任意字符
[list] 恰好匹配list中的一个任意字符
[^list] 恰好匹配一个在list中的任意字符
* 匹配前面元素的0次或多次重复
.* 匹配0个或多个任意字符
扩充正规表达式通配符
\{n\} 匹配前面元素的n次重复
\{n,\} 匹配前面元素的n次或更多次重复
\{n,m\}匹配前面元素的至少n次至多m次重复
? 匹配前面元素的0次或1次出现
+匹配前面元素的1次或更多次出现
[b]本文来自ChinaUnix博客,如果查看原文请点:[/b][url]http://blog.chinaunix.net/u2/84135/showart_1806253.html[/url]
页:
[1]