我们知道计算机的硬件资源比如磁盘,IO,内存都是由软件来统一管理的,这类特殊的软件就是常说的操作系统,windows在底层的资源控制基础上构建了自己的界面,非常适合使用,只需要到处点点就能完成我们需要的功能。这是一种控制资源的方式,同时也可以使用command的方式来操作底层的资源。liunx中最重要的部分是它的内核,内核管理着系统的资源,同时也提供给我们操作内核的接口,就是我们经常用到的shell(壳),主流的shell有以下几种:
sh:
burne shell (sh)
burne again shell (bash)
csh:
c shell (csh)
tc shell (tcsh)
korn shell (ksh)
接触比较多的是bash和ksh,几种不同的shell功能大体相似,当然也各有特点。
一般给非管理员用户显示的是$,管理员显示的是root。linux下的换行符(按下enter键)用CR表示,当遇到CR时,shell解释器会去解释CR前面的command line,解释命令行是先按照IFS去分割命令行,然后对其中的元字符进行处理,比如:echo $A 其中A的值为hello,按照IFS(默认为tab,newline,space)拆解为echo和$A然后对元字符$进行解释,取A变量的值,最后输出hello到屏幕。
一、echo
echo命令是我们经常需要用到的输出命令,可以查看参数的值,其中提供了几个参数,能够实现不同的效果,比如echo -n就是取消末尾的换行符,可以在环境上试试输出echo -n "a" 和echo "a"有什么不同。如果我们想在默认再增加一个换行可能会想到echo "a\n",这是行不通的,echo默认执行的是-E参数,表示不去解释反斜线的字符转换,因此需要开启\功能,需要使用-e参数,如下:echo -e "a\n"。
二、双引号""和单引号‘‘
在shell中双引号和单引号的作用是不同的,shell中的字符可以分为两种,一种是普通字符,一种是元字符,比如$是一种元字符,当执行到元字符的时候shell会去做相应的动作,比如echo $A就是打印变量A的值,但是如果我们想打印“$A”那么应该怎么实现呢,答案就是使用单引号,echo ‘$A‘,单引号中的所有元字符都会被当做普通的字符来处理,那么双引号又有什么不同呢,双引号会关闭大部分的元字符处理,但是有少部分还是会当做元字符来解释,比如$符号,另外还有一种关闭元字符的做法是在元字符前加/反斜线。
三、var=value和export
严格来说在当前shell中定义的的变量均为本地变量,只有export后才会成为环境变量,供后面的命令使用,export命令的详细解释以后会提到。
四、exec和source
执行shell的时候经常用到source *.sh,实际上我们执行*.sh脚本的时候,会产生一个subshell进程来执行,并且该子进程的环境变量和父进程的环境变量不是共享的,因此假如我们定义cd /home/a/b/d命令在test.sh脚本中,并且在/home/a下面执行test.sh,实际上目录并不会切换到/home/a/b/d下,就是这个原因。那么如何才能执行test.sh使得目录切换,shell提供了两种方式来阻止产生subshell进程执行脚本,source和exec,这两个命令会在当前的shell进程中完成脚本的调用。那么这两种方式的调用又有什么区别呢?看下面的一个例子:
1.sh
#!/bin/bash A=B echo "PID of 1.sh before exec/source/fork:$$" export A echo "1.sh :\$A is $A" case $1 in exec) echo "using exec..." exec ./2.sh ;; source) echo "using source" . ./2.sh ;; *) echo "using fork" ./2.sh ;; esac echo "PID of 1.sh after exec/source/fork:$$" echo "1.sh:\$A is $A"
2.sh
#!/bin/bash echo "PID for 2.sh is: $$" echo "2.sh get \$A=$A form 1.sh" A=C export A echo "2.sh:\$A is $A"
执行./1.sh fork后的结果:
PID of 1.sh before exec/source/fork:18885
1.sh :$A is B
using fork
PID for 2.sh is: 18886
2.sh get $A=B form 1.sh
2.sh:$A is C
PID of 1.sh after exec/source/fork:18885
1.sh:$A is B
执行./1.sh exec后的结果:
PID of 1.sh before exec/source/fork:20952
1.sh :$A is B
using exec...
PID for 2.sh is: 20952
2.sh get $A=B form 1.sh
2.sh:$A is C
执行./1.sh source后的结果:a
PID of 1.sh before exec/source/fork:26519
1.sh :$A is B
using source
PID for 2.sh is: 26519
2.sh get $A=B form 1.sh
2.sh:$A is C
PID of 1.sh after exec/source/fork:26519
1.sh:$A is C
下面逐一分析打印的结果:
默认的fork执行,会产生一个子进程来执行shell脚本,其中父进程中的export效果会传递到子进程,但是子进程中的export效果并不会传递给父进程,因此export命令的效果是单向的,从父进程传递子进程。
exec和source执行并不会产生子进程,而是在当前进程执行脚本,不同点是exec会终止当前的脚本执行,当子脚本执行完毕后,整个执行过程就算完毕了。
五、()和{}的区别
这里要引入一个命令组的概念,就像是其他语言中的函数,()和{}的不同是()是在子shell中执行,{}是在本shell中执行,说到这里不得不提出一个概念,函数,在shell中定义函数的方式有两种,一种是function_name{},还有一种是function_name (){}。
六、$(())、${}和$()的区别
在bash中,$()和``都是用作命令替换,命令替换和之前的变量替换的概念有些类似,比如dir=$(pwd),首先pwd的执行结果会赋值给变量dir,${}的作用是变量替换,比如A=B;echo ${A},实际上在这里也可以直接echo $A,但是假如写成echo $AA,则shell是不识别的,因此${}起到一个边界的作用,但是如果你只认为${}只是起到边界的作用,那就太小瞧它了。
假设有变量file=/dir1/dir2/dir3/my.file.txt
我们可以用${}进行不同的变量替换得到不同的值。
${file#*/}:去掉第一个/及其左边的所有字符,结果为dir1/dir2/dir3/my.file.txt
${file##*/}去掉最后一个/及其左边的所有字符,结果为my.file.txt
${file%/*}去掉最后一个/及其右边的所有字符,结果为/dir1/dir2/dir3
${file%%/*}:拿掉第一个 / 及其右边的字串:(空值)
#是去掉左边,%是去掉右边,一个是最短匹配,两个是最长匹配
${file:0:5} 结果为从第0个开始向右取5个字符/dir1
因此${file:offset:length}从offset开始向右取length个字符
${file/dir/path}将第一个dir替换为path,结果为/path1/dir2/dir3/my.file.txt
${file//dir/path}将所有的dir都替换为path。
上面的是字符的替换,还可以根据当前的值是否为空来做替换,如下:
${file-myfile.txt}如果file未定义则输出myfile.txt
${file:-myfile.txt}如果file未定义或者为定义为空则输出myfile.txt
${file+myfile.txt}如果file有定义则不论是否为空,都输出myfile.txt
${file:+myfile.txt}如果file不为空,则输出myfile.txt
${file=myfile.txt}如果file未定义则输出myfille.txt同时赋值file为myfile.txt
${file:=myfile.txt}如果file未定义或者为空,则输出myfile.txt
${#file}输出file变量的长度
顺便再来介绍一下bash中的数组array
数组的定义类似:A=(a b c d e)中间用空格分割
如果要得到这个数组的内容,可以使用${A[@]}或者${A[*]}
最后再来看下$(())的用途,$(())方便我们做整数运算,比如a=1;b=2;echo $((a+b))输出为3。$(())为我们做比较和运算提供了类C语言的风格。
七、[email protected]和$*的差别
比如我们有个sh脚本名称为test.sh,它可以接受参数,如下 test.sh a b c,获得外部参数的方式为$1 $2 $3分别获取a b c,如果要获取脚本身的名称则使用$0。注意脚本中定义的函数也可以接收参数同样可以使用$1...这种方式获取传入的参数。不同的时$0不是函数的名称而是最外面脚本的名称。
$10表示的并不是获取第十个参数,而是获取第一个参数后面再加上字符串0,因此如果要获取第十个参数可以使用${10},或者使用shift命令,shift 1为取消第一个参数($0)排除,shift 5为取消前5个参数。
再介绍一个参数$#表示获取传入的参数总数,比如test.sh one two three 则$#为3。
如果要获取所有的参数则可以使用[email protected],比如test.sh one two three则获取到三个word,而$*是将参数当做一个整体。
八、&&和||的差别
在介绍$$和||之前先来了解一个新的概念,return value,在执行command或者function的时候都会传值会父进程,我们可以使用$?来获取这个最新的返回值,return value只有两种状态,0的话为真,非0的话为假,比如:
假设myfile.txt存在,而a.txt不存在,ls a.txt后再echo $?为非0,如果ls myfile.txt则为0。可以使用test expersion或者[ expersion ]来测试返回值。不过我更习惯使用[]这种语法。
接下来就可以看看$$和||的差别了,command1 && command2表示如果command1命令返回真则执行command2。command1 || command2表示,如果command1返回假则执行command2。
九、<和>
大概存在三种FD(文件描述符),分别为0标准输入,1标准输出,2错误输出,>表示标准输出重定向与1>相同。
2>&1表示把标准错误输出重定向到标准输出。
&>file表示把标准输出和标准错误输出重定向到文件file中。
2>&-表示将标准错误输出关闭
>/dev/null 2>&1表示将标准错误输出绑定到标准输入并且标准输入重定向到/dev/null,也即什么都不会输出
十、case和if
有时候需要根据当前的值来执行不同的逻辑,if在这里就派上了用场,标准的if用法为:
if comd1;then
comd2
elif comd3;then
comd4
else
comd5
fi
如果then后面不想跑任何命令可以使用:这个null command来替代。
如果条件比较多并且为字符串则可以使用另外一种语法,case
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
exit 1
esac
十一、for、while与until
loop是非常基本的一种逻辑控制,所有的语言都提供了自己的循环控制语句
for loop从一个清单列表中读取值,依次做处理:
for var in one two three four five
do
...
done
下面给出一个累计变量的语句:
for ((i=1;i<=10;i++))
do
...
done
如果改为while实现则如下:
num=1
while [ $num -le 10 ];do
...
done
-le表示小于等于
while是在条件为true时进入循环,until则相反,是在条件为false时进入循环。
同样循环中也有break和continue关键字,和java等其他语言的用法是一样的,就不做解释了。
上面就是shell十三问的大体内容,有些内容并没有涉及到,后续会对一些比较常用的命令比如grep,sed,awk等进行简单的讲解。