Shell
Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。
当前用户用的shell
vim /etc/passwd
其中最后一列显示了用户对应的shell类型
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
itcast:x:1000:1000:itcast,,,:/home/itcast:/bin/bash
ftp:x:115:125:ftp daemon,,,:/srv/ftp:/bin/false
root:x:0:0:root:/root:/bin/bash
解释:
root 用户名
x 表示密码通过加密放到了shadow
0 用户id
0 组id
root 用户描述
/root home目录地址
/bin/bash 用户登录之后执行的第一条命令
如果用户启动的第一条命令不是shell,就执行完命令就退出了。
确定当前用户用什么shell
echo $SHELL
修改当前用户的shell
chsh
用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cd、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令,内建命令没有单独的man手册,要在man手册中查看内建命令,应该执行man bash-builtins 或者help
编写一个简单的脚本test.sh:
#!/bin/sh
echo HelloWorld
cd ..
ls
1、Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。如果把这个脚本文件加上可执行权限然后执行: 默认权限为0664:-rw-rw-r-- 1 kimi kimi 35 3月 15 10:25 test.sh
chmod a+x test.sh
./test.sh
虽然执行了,但当前目录不会变,因为是启动一个子进程,父进程的当前目录不会更改。要提前说明使用什么解释器,在脚本中的第一句指定:#!/bin/sh
2、当我们没办法调整脚本权限时,其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序 /bin/sh ./test.sh
3、以这种方式执行不需要test.sh文件具有可执行权限。 如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如: (cd ..;ls -l)
和上面两种方法执行Shell脚本的效果是相同的,cd …命令改变的是子Shell的PWD,而不会影响到交互式Shell。命令:cd ..;ls -l 则有不同的效果,cd …命令是直接在交互式Shell下执行的,改变交互式Shell的PWD。
4、这种方式相当于这样执行Shell脚本:source ./test.sh 或者 . ./test.sh ,利用 source或者.(同个意思)命令是Shell的内建命令,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令,source可能会改变当前目录。
基本语法
Shell变量通常由字母加下划线开头,由任意长度的字母、数字、下划线组成。
在Shell中定义或赋值一个变量:VARNAME=value
注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。 变量的使用,用$ 符号跟上变量名表示对某个变量取值,变量名可以加上{} 花括号来表示变量名的范围:
echo $VARNAME
echo ${VARNAME}_suffix
shell变量
全局变量:shell中不适用任何修饰符修饰的变量都是全局变量,不管函数内还是函数外都一样,从声明周期开始到脚本结束,都是其生命周期
局部变量:用local修饰,只能声明在函数内,从声明语句调用开始一直到函数结束
shell变量只能在当前shell中传递,跨进程不能使用,除非是用环境变量
环境变量 是操作系统自带的,每一个进程都会有,当启动一个子进程时,环境变量时从父进程拷贝到子进程,环境变量是单向传递,只能父进程->子进程。
export VARNAME=value
子进程
#!/bin/bash
echo "this is in sub script"
echo $globalVar1
echo "sub script end"
echo $environVar
export environVar="2222222"
父进程
#!/bin/bash
globalVar1="hello"
function test()
{
globalVar2="world"
local localVar="itcast"
echo $localVar
}
test
echo $globalVar1 $globalVar2 $localVar
export environVar="this is in environ"
./subScript.sh
echo $environVar
unset globalVar1
echo "globalVar1=" $globalVar1
结果程序 删除变量
用来删除已经定义的变量,包括本地和环境变量:unset VARNAME
文件名代换 这些用于匹配的字符称为通配符(Wildcard),如:* ? [ ] 具体如下:
* 匹配0个或多个任意字符? 匹配一个任意字符[若干字符] 匹配方括号中任意一个字符的一次出现
参数扩展:touch {1,2,3,4}.txt 参数扩展文件不一定要存在
命令代换
由“`”反引号括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。
算术代换
$(()) 中只能用±*/和()运算符,并且只能做整数运算。 $[base#n] ,其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。
转义字符
和C语言类似,\ 在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值
DATE=`date`
echo $DATE
DATE=$(date)
#!/bin/bash
dateTime=$(date)
echo "dateTime is " $dateTime
curPath=$(cd `dirname $0`;pwd)
touch $curPath/1.txt
var=45
var2=2
echo $[var+3]
echo $((var+3))
echo $((var*var2))
echo $(($var*$var2))
//八进制的10 再加上11
echo $[8
$SHELL
\$SHELL
var="a b"
./a.out $var
./a.out "$var"
touch $var
touch "$var"
注意
各种UNIX命令都把-号开头的命令行参数当作命令的选项,而不会当作文件名。如果非要处理以-号开头的文件名,可以有两种办法: touch ./-hello 或者 touch -- -hello
\还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符>,等待用户继续输入,最后把所有的续行接到一起当作一个命令执行。例如:
ls \
> -l
(ls -l命令的输出)
单引号
和C语言不同,Shell脚本中的单引号和双引号都是字符串的界定符,而不是字符的界定符。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。
kimi$ echo '$SHELL'
$SHELL
kimi$ echo 'ABC\(回车)
> DE'(再按一次回车结束命令)
ABC\
DE
双引号
被双引号括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引号的处理方式不同。支持变量的扩展。使用变量之前,如果变量是作为参数才传递的,要习惯性地对变量添加双引号。
kimi$ DATE=$(date)
kimi$ echo "$DATE"
kimi$ echo '$DATE'
kimi$ VAR=200
kimi$ echo $VAR
200
kimi$ echo '$VAR'
$VAR
kimi$ echo "$VAR"
200
Shell表示真假
直接使用某条命令的返回状态来确定 main 返回 0 为真, 返回 非0 为假
条件测试
命令test 或 [ 可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1(注意与C语言的逻辑表示正好相反)。
[ -d DIR ] 如果DIR存在并且是一个目录则为真
[ -f FILE ] 如果FILE存在且是一个普通文件则为真
[ -z STRING ] 如果STRING的长度为零则为真
[ -n STRING ] 如果STRING的长度非零则为真
[ STRING1 = STRING2 ] 如果两个字符串相同则为真
[ 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表示“逻辑或”
分支
和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。这种流程控制语句本质上也是由若干条Shell命令组成的
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
其实是三条命令,if [ -f ~/.bashrc ] 是第一条,then . ~/.bashrc 是第二条,fi 是第三条。如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。
#!/bin/bash
if [ -f /bin/bash ]
then
echo "/bin/bash is a file"
else
echo "/bin/bash is not a file"
fi
if :
then
echo "always true"
fi
if false
then
:
else
echo "always false"
fi
var=1
var2=2
if [ $var -eq 1 ] && [ $var2 -eq 2 ]
then
echo "yes"
fi
#!/bin/bash
echo "Is it morning? Please answer yes or no!"
read YES_OR_NO
if [ "$YES_OR_NO" = "yes" ]
then
echo "Good morning"
elif [ "$YES_OR_NO" = "no" ] ; then
echo "Good afternoon"
else
echo "Not recognized"
:
fi
“:”是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。
Shell还提供了&&和||语法,和C语言类似,具有Short-circuit特性,很多Shell脚本喜欢写成这样:
test "$(whoami)" != 'root' && (echo you are using a non-privileged account;)
&&相当于“if…then…”,而||相当于“if not…then…”。 &&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别,例如:
test "$VAR" -gt 1 -a "$VAR" -lt 3
和以下写法是等价的
test "$VAR" -gt 1 && test "$VAR" -lt 3
case/esac
case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;; 结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。
#! /bin/sh
echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in
yes|y|Yes|YES)
echo "Good Morning!";;
[nN][Oo])
echo "Good Afternoon!";;
*)
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
return 1;;
esac
使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):
case "$1" in
start)
...
;;
stop)
...
;;
reload | force-reload)
...
;;
restart)
...
*)
log_success_msg"Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"
;;
esac
启动nfs-kernel-server服务的命令是
$ sudo /etc/init.d/nfs-kernel-server start
$1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数,也就是start,所以进入start)分支执行相关的命令。同理,命令行参数指定为stop、reload或restart可以进入其它分支执行停止服务、重新加载配置文件或重新启动服务的相关命令。
循环
for/do/done
#!/bin/bash
for FRUIT in apple banana pear
do
echo "I like $FRUIT"
done
sum=0
for i in {1..100}
do
sum=$[$sum+$i]
done
echo $sum
for f in $(ls)
do
if [ -f "$f" ]
then
echo "$f is a regular file"
elif [ -d "$f" ]
then
echo "$f is a directory"
else
echo "$f is not recognized"
fi
done
sum=0
count=1
while [ $count -le 100 ]
do
sum=$[$sum+$count]
count=$[$count+1]
done
echo $sum
while/do/done
#!/bin/bash
echo "Please input paswd"
read try
errCount=1
while [ "$try" != "secret" ]
do
if [ "$errCount" -ge 5 ]
then
echo "Error 5 times , exit"
break
fi
echo "Sorry , try again!"
read try
errCount=$[$errCount+1]
done
|