Shell脚本
宗旨:技术的学习是有限的,分享的精神的无限的。
一、shell简介
Shell的作用是解释执行用户的命令,用户输入一条命令, Shell就解释执行一条,这种方式称为交 互式( Interactive),Shell还有一种执行命令的方式称为批处理( Batch),用户事先写一 个Shell脚本( Script) ,其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲 命令。 Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需 要编译, Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。
默认的shell是/bin/bash 可以用【echo $SHELL】查看。
$ cat /etc/shells
/bin/sh
/bin/ash
/bin/bash
/bin/dash
/bin/mksh
/bin/pdksh
/bin/posh
/bin/tcsh
/bin/zsh
/usr/bin/sh
/usr/bin/ash
/usr/bin/bash
/usr/bin/dash
/usr/bin/mksh
/usr/bin/pdksh
/usr/bin/posh
/usr/bin/tcsh
/usr/bin/zsh
二、shell执行命令
1、执行交互式命令
用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。
man bash-builtins可以查看有哪些内建命令,type可以看该命令是内部、外部命令。
2、执行脚本
先编写一个脚本名为hello.sh
#! /bin/bash cd .. ls
Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/bash解释执行。
Shell会fork一个子进程并调用exec执行./script.sh这个程序,exec系统调用应该把子进程的代码段替换成./ hello.sh程序的代码段,并从它的_start开始执行。然而hello.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。
执行前加权限chmod+x hello.sh
两种执行Shell脚本的方法:./script.sh 或 sh./script.sh
执行过程:
(1). 交互Shell( bash)fork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止。
(2).sh读取脚本中的cd..命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。
(3).sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件, sh等待ls终止。
(4).ls终止后,sh继续执行,读到脚本文件末尾,sh终止。
(5).sh终止后,bash继续执行,打印提示符等待用户输入。
三、Shell的基本语法
1、变量
环境变量和本地变量。
export命令可以把本地变量导出为环境变量:$ exportVARNAME=value
2、文件名代换【: * ? []】——通配符
* —— 匹配一个或多个任意字符
? —— 匹配任意一个字符
[] —— 匹配方括号中任意一个字符的一次出现
3、命令代换:`或$()
如:
DATE=`date`
echo $DATE
4、算术代换:$(())——转换成整数
$(())中只能用+-*/和()运算符,并且只能做整数运算。
VAR = 99
echo $(($VAR + 3))
5、转义字符\——除紧跟其后的单个字符的特殊意义
6、单引号——保持引号内所有字符的字面值,即使引号内的\和回车也不例外
7、双引号——双引号用于保持引号内所有字符的字面值(回车也不例外)
下面例外:
$加变量名可以取变量的值 反引号仍表示命令替换 \$表示$的字面值
\`表示`的字面值 \"表示"的字面值 \\表示\的字面值
四、bash启动脚本
1、交互登录Shell
交互Shell是指用户在提示符下输命令的Shell而非执行脚本的Shell,登录Shell就是在输入用户名和密码登录后得到的Shell,比如从字符终端登录或者用telnet/ssh从远程登录,但是从图形界面的窗口管理器登录之后会显示桌面而不会产生登录Shell(也不会执行启动脚本),在图形界面下打开终端窗口得到的Shell也不是登录Shell。
这样启动bash会自动执行以下脚本:
(1).首先执行/etc/profile,系统中每个用户登录时都要执行这个脚本,如果系统管理员希望某个设置对所有用户都生效,可以写在这个脚本里
(2).然后依次查找当前用户主目录的~/.bash_profile、~/.bash_login和~/.profile三个文件,找到第一个存在并且可读的文件来执行,如果希望某个设置只对当前用户生效,可以写在这个脚本里,由于这个脚本在/etc/profile之后执行,/etc/profile设置的一些环境变量的值在这个脚本中可以修改,也就是说,当前用户的设置可以覆盖(Override)系统中全局的设置。~/.profile这个启动脚本是sh规定的,bash规定首先查找以~/.bash_开头的启动脚本,如果没有则执行~/.profile,是为了和sh保持一致。
(3).顺便一提,在退出登录时会执行~/.bash_logout脚本(如果它存在的话)。
2、交互非登录Shell
比如在图形界面下开一个终端窗口,或者在登录Shell提示符下再输入bash命令,就得到一个交互非登录的Shell,这种Shell在启动时自动执行~/.bashrc脚本。
如果要在启动脚本中做某些设置,使它在图形终端窗口和字符终端的Shell中都起作用,最好就是在~/.bashrc中设置。
3、非交互启动
为执行脚本而fork出来的子Shell是非交互Shell,启动时执行的脚本文件由环境变量BASH_ENV定义,相当于自动执行以下命令:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
如果环境变量BASH_ENV的值不是空字符串,则把它的值当作启动脚本的文件名,source这个脚本。
五、Shell脚本语法
1、条件测试:test
命令test或[可以测试一个条件是否成立,如果测试结果为真,则该命令的ExitStatus为0,如果测试结果为假,则命令的ExitStatus为1(注意与C语言的逻辑表示正好相反)。
$ var=2 $ test $var -gt 1 $ echo $? 0 $ test $var -gt 3 $ echo $?
[ -d DIR ] |
如果DIR存在并且是一个目录则为真 |
[ -f FILE] |
如果FILE存在且是一个普通文件则为真 |
[ -z STRING ] |
如果STRING的长度为零则为真 |
[ -n STRING ] |
如果STRING的长度非零则为真 |
[ STRING1= STRING2] |
如果两个字符串相同则为真 |
[ STRING1!= STRING2] |
如果字符串不相同则为真 |
[ ARG1 OP ARG2 ] |
ARG1和ARG2应该是整数或者取值为整数的变量, OP是-eq(等于) -ne(不等于) -lt(小于) -le(小于等于) -gt(大于) -ge(大于等 于)之中的一个 |
[ ! EXPR ] |
EXPR可以是上表中的任意一种测试条件, !表示逻辑反 |
[ EXPR1 -a EXPR2]
|
EXPR1和EXPR2可以是上表中的任意一种测试条件, -a表示逻辑与 |
[ EXPR1 -o EXPR2] |
EXPR1和EXPR2可以是上表中的任意一种测试条件, -o表示逻辑或 |
2、if/then/elif/else/fi case/esac
3、for/do/done while/do/done
4、位置参数和特殊变量
$0相当于C语言main函数的argv[0]
$1、 $2... 这些称为位置参数( Positional Parameter),相当于C语言main函数的argv[1]、argv[2]...
$# 相当于C语言main函数的argc - 1,注意这里的#后面不表示注释
$@ 表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。
$? 上一条命令的Exit Status
$$ 当前Shell的进程号
5、函数 —— 没有返回值也没有参数列表
#! /bin/sh
foo(){ echo "Function foo is called";}
echo "-=start=-"
foo
echo "-=end=-"