SHELL编程基础
shell脚本, 必须在开始的第一行输入:
1
|
#!/bin/bash
|
>#! 会告诉系统执行该脚本的程序, 例如bash;
Note 最好使用"!/bin/bash"而不是"!/bin/sh", 如果使用tc shell改为tcsh,其他类似.
保存文件后, 想要执行脚本, 必须先使它可执行:
1
|
chmod
+x filename
|
>输入 ./filename就可以执行
1 变量赋值和引用
变量无需事先声明, 变量的命名规则:
1) 首字符必须为字母(a-z, A-Z)或者下划线_; 2) 中间不能有空格, 可以有下划线; 3) 不能使用其他标点符号;
赋值表达式: 变量名=值; [没空格]
取得变量的值, 需要在变量前加'$'
1
2
3
4
5
|
#!/bin/bash
# 对变量赋值:
a=
"hello world"
#等号两边均不能有空格存在
# 打印变量a的值:
echo
"A is:"
$a
|
>取得变量的值, 前面加 $;
Note 注意空格; 给变量赋值, 等号两边不能有空格;
区分文字和变量:
1
2
|
num=2
echo
"this is the ${num}nd"
|
>输出: 'this is the 2nd'num
>不加花括号的话, shell会搜索 '&numnd' 变量, 发现没有值; 输出: 'this is the '
注意花括号的位置:
1
2
|
num=2
echo
"this is the {$num}nd"
|
>输出: 'this is the {2}nd'
Note shell的默认赋值是字符串赋值;
1
2
3
|
var=1
var=$var+1
echo
$var
|
>输出不是2而是 1+1;
为了达到数字效果:
1
2
3
4
5
6
|
let
"var+=1"
var=
"$[$var+1]"
((var++))
var=$(($var+1))
var=
"$(expr "
$var
" + 1)"
#不建议使用
var=
"`expr "
$var
" + 1`"
#强烈不建议使用,注意加号两边的空格,否则还是按照字符串的方式赋值,`为Esc下方的`,而不是单引号'。
|
Note 前2种方式在bash下有效, 在sh下会出错;
>let表示数学运算, expr用于整数值运算, 每一项用空格分开, $[]将中括号内的表达式作为数学运算先计算结果再输出;
shell脚本有许多便利是系统自动设定的, 除了只在脚本内有效的普通shell便另外, 还有环境变量, 即由export关键字处理过的变量, 一般只在登录脚本中用到;
2 Shell里的流程控制
if语句
if 表达式为真, 则执行 then 后的部分
1
2
3
4
5
6
7
|
if
....;
then
....
elif
....;
then
....
else
....
fi
|
多数情况下, 可以使用测试命令来对条件进行测试, 比如可以比较字符串, 判断文件是否存在以及是否可读等等...通常用" [ ] "来表示条件测试;
Note 要注意中括号前后的空格, 确保存在;
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
Note 执行 man test 可以查看所有测试表的式可以比较和判断的类型;
e.g.
1
2
3
4
5
|
if
[ ${SHELL} =
"/bin/bash"
];
then
echo
"your login shell is the bash (bourne again shell)"
else
echo
"your login shell is not bash but ${SHELL}"
fi
|
>变量$SHELL包含有登录shell的名称, 和/bin/hash比较来判断当前shell是否是bash;
&&和||操作符
类似C语言:
1
|
[ -f
"/etc/shadow"
] &&
echo
"This computer uses shadow passwords"
|
&&是一个快捷操作符, 如果左边的表达式为真则执行右边的语句, 可以看作逻辑运算中的与操作;
>如果/etc/shadow文件存在, 则打印文字;
或操作 || 类似:
1
2
3
4
|
mailfolder=
/var/spool/mail/james
[ -r
"$mailfolder"
] || {
echo
"Can not read $mailfolder"
;
exit
1; }
echo
"$mailfolder has mail from:"
grep
"^From "
$mailfolder
|
>先判断mailfolder是否可读, 可读则打印文件中的 From 行; [可读为true则不执行后面的条件]; 不可读则或操作生效, 打印错误信息, 脚本退出;
使用花括号以匿名函数的形式将两个命令放到一起作为一个命令, 以 ; 分割; 使用与, 或操作符会使脚本更便利;
case语句
case表达式可以用来匹配一个给定字符串, 而不是数字; (和C语言的switch...case不同)
1
2
3
4
|
case
...
in
...)
do
something here
;;
esac
|
file命令 可以辨别一个给定文件的文件类型; e.g. file lf.gz --> lf.gz: gzip compressed data, ...
利用file命令写smartzip脚本, 自动解压bzip2, gzip, zip不同类项的压缩文件:
1
2
3
4
5
6
7
8
9
10
|
ftype=
"$(file "
$1
")"
case
"$ftype"
in
"$1: Zip archive"
*)
unzip
"$1"
;;
"$1: gzip compressed"
*)
gunzip
"$1"
;;
"$1: bzip2 compressed"
*)
bunzip2
"$1"
;;
*)
echo
"File $1 can not be uncompressed with smartzip"
;;
esac
|
Note 特殊变量$1, 包含传递给脚本的第一个参数值; e.g. 运行 smartzip sample.zip 时, $1就是字符串sample.zip;
select语句
select表达式是bash的扩展应用, 用于交互式场合;
1
2
3
4
|
select
var
in
... ;
do
break
;
done
.... now $var can be used ....
|
e.g.
1
2
3
4
5
|
echo
"What is your favourite OS?"
select
var
in
"Linux"
"Gnu Hurd"
"Free BSD"
"Other"
;
do
break
;
done
echo
"You have selected $var"
|
>自动按照1)...2)...的方式输出选项;
while/for 循环
只要测试条件为真, while循环将一直运行; 关键字break跳出循环, continue跳过一个循环;
1
2
3
|
while
...;
do
....
done
|
for循环会查看字符串列表(字符串用空格分隔), 将其赋给一个变量:
1
2
3
|
for
var
in
....;
do
....
done
|
e.g.
1
2
3
|
for
var
in
A B C ;
do
echo
"var is $var"
done
|
e.g. 脚本打印RPM包统计信息: showrpm
1
2
3
4
5
6
7
8
9
10
11
|
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for
rpmpackage
in
"$@"
;
do
if
[ -r
"$rpmpackage"
];
then
echo
"=============== $rpmpackage =============="
rpm -qi -p $rpmpackage
else
echo
"ERROR: cannot read file $rpmpackage"
fi
done
|
Note 特殊变量$@, 包含输入的所有命令行参数值; e.g. showrpm ssh.rpm web.rpm, 那么"$@"(必须有引号)就会出现2个字符串: ssh.rpm, web.rpm;
$* 有类似功能, 但是只有一个字符串, 如果不加引号, 带空格的参数会被截断;
3 Shell里的特殊符号
引号
在向程序传递任何参数之前, 程序会扩展通配符和变量; 这里的扩展是指程序会把通配符(比如*)替换成适当的文件名, 变量替换成变量值; 使用引号可以防止这种扩展;
e.g. 假设目录下有两个jpg, mail.jpg和tux.jpg;
1
|
echo
*.jpg
|
>输出两个文件名;
单引号和双引号可以防止通配符的扩展:
1
2
|
echo
"*.jpg"
echo
'*.jpg'
|
>输出都是 *.jpg ;
单引号更严格, 可以防止任何变量扩展; 双引号则是防止通配符扩展但允许变量扩展:
1
2
3
|
echo
$SHELL
echo
"$SHELL"
echo
'$SHELL'
|
>输出 /bin/hash 和 /bin/bash 和 $SHELL;
还有一种防止扩展的方法, 使用转义字符--反斜杠 \ ;
1
2
|
echo
\*.jpg
echo
\$SHELL
|
>输出 *.jpg 和 $SHELL ;
4 Here Document
需要将几行文字传递给一个命令时, here document是不错的选择; 对每个脚本写一段帮助性的文字, 如果使用here document就不必用echo函数一行行输出; here document用 << 开头, 后面接一个字符串, 字符串必须出现在here document的末尾;
e.g. 对多个文件重命名, 使用here document打印帮助:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# we have less than 3 arguments. Print the help text:
if
[ $
# -lt 3 ] ; then
cat
<< HELP
ren -- renames a number of files using
sed
regular expressions USAGE: ren
'regexp'
'replacement'
files...
EXAMPLE: rename all *.HTM files
in
*.html:
ren
'HTM$'
'html'
*.HTM
HELP
#这里HELP要顶格写,前面不能有空格或者TAB制表符。如果cat一行写成cat << -HELP,前边可以带TAB.
exit
0
fi
OLD=
"$1"
NEW=
"$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $@ contains now all the files:
for
file
in
"$@"
;
do
if
[ -f
"$file"
] ;
then
newfile=`
echo
"$file"
|
sed
"s/${OLD}/${NEW}/g"
`
if
[ -f
"$newfile"
];
then
echo
"ERROR: $newfile exists already"
else
echo
"renaming $file to $newfile ..."
mv
"$file"
"$newfile"
fi
fi
done
|
>第一个if表达式判断输入命令行参数是否小于3个; 如果小于, 则将帮助文字传递给cat命令, 打印到屏幕上, 然后退出;
Note 特殊变量 $# 表示包含参数的个数; $2, $3 ...代表第二, 第三个参数;
>如果输入参数>=3个, 将第一个参数赋值给变量OLD, 第二个赋值给变量NEW; shift命令将第一个和第二个参数从参数列表删除, 原来的第三个参数变成参数列表$*的第一个参数;
>进入循环, 命令行参数列表被赋给$file, 判断文件是否存在, 存在则通过sed命令搜索和替换来产生新文件名; 然后将反斜线内命令结果赋值给newfile; 最后得到了旧文件名和新文件名, mv命令进行重命名;
[HELP不是关键字, 可以被其他字符替换(EOF), 加上单引号/双引号防止扩展]
refer to http://www.serverwatch.com/columns/article.php/3860446/Shell-Scripts-and-Here-Documents.htm
1
2
3
4
5
|
cat
<<-
'EOF'
test
word
text will be printed
with out tabs
EOF
|