1. shell
对于一台计算机而言,其硬件受系统内核的控制,使用者想要控制计算机,就必须有与系统内核进行通讯的手段。而shell就是使用者与计算机进行通讯的手段之一。从命名上看,shell其实是相对于kernel(内核)而言,指系统与外界(使用者)进行接触的部分,一个提供系统功能给用户使用的软件,它接受来自用户的指令,然后调用相应的应用程序。
为了满足不同的需求,shell提供了两种执行命令方式:
- a. 交互式:解释并执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令。
- b. 程序设计式:作为一种脚本语言,提供变量、控制结构和函数,再通过解释器解释并执行。
Linux上常见的shell有sh、bash、ksh、tcsh等,不同解释器在某些语法的执行方面可能有些不同。通过查看/etc/shells文件就可以知道本系统所支持的shell解释器类型。如shells的文件内容如下:
而linux默认是用的解释器是bash。在脚本头可以声明本脚本所使用的解释器,ryeshen@~$ cat /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin /bin/tcsh /bin/csh /bin/ksh /bin/zsh
声明方式: #!/bin/bash
2. 变量
-
a. 赋值
- 赋值方式:variable_name = variable_value
- 等号两边不能有空格符;
- 增加变量内容:PATH=”$PATH”:/home/bin
- 取消变量:unset variable_name
- 变量类型:可以使用declare [[-/+]aixr] [name[=value] …],其中-表示赋予变量属性,+表示去除变量属性,a-数组,i-整数,r-只读,x-环境变量
-
b. 自定义变量与环境变量
- 使用“=”赋值得到的自定义变量,这个变量的作用域为当前shell进程。
- 使用export命令可以声明一个环境变量,一个环境变量的作用域为当前shell进程及
其shell子进程(subshell)。 - 在启动shell进程的时候,shell会从文件中加载一些默认的环境变量到shell进程中,
如HOME、PATH等。 - 用env可以查看当前所有的环境变量,set则可以看所有的环境变量和自定义变量。
-
p.s. 关于子shell进程
- i. 创建一个shell子进程的方式包括但不限于:
- 使用&提交后台作业。后台作业于subshell中运行;
- 使用管道。|左右的命令均在独立的subshell中运行;
- 括号操作符。()中的命令在subshell中运行;
- 执行外部脚本或程序。
- ii. subshell可以继承shell父进程的属性包括:当前的工作目录、环境变量、标准输入输出和错误输出、所有已打开的文件描述符、忽略的信号。
- iii. subshell对环境变量的修改对shell父进程不可见。
- i. 创建一个shell子进程的方式包括但不限于:
-
c. shell中的单引号、双引号和反引号
- 双引号:保留$的变量扩展功能,使用变量时会$后的变量转变为变量的值。在双引号中可以使用转义符将$转变为纯文本符号。使用双引号后不支持正则匹配。
-
单引号:不保留特殊符号的功能,括号内内容仅作为纯文本进行使用。
如:ryeshen@~$ echo "I am $name" I am someone ryeshen@~$ echo 'I am $name' I am $name
-
反引号:引号内内容会被作为命令先被执行,命令的结果作为引号的输出。$()的效果与之相同
如:ryeshen@~$ echo I am `pwd` I am /home/asl ryeshen@~$ echo I am $(pwd) I am /home/asl
-
p.s. 反引号与$()的一点区别
— 参考自http://km.oa.com/articles/show/310289
先来看几个例子:
参考的文章的解释是“反引号齐本身就对进行了转义,保留了齐本身意思,如果我们想在反引号中起到的特殊意义,我们必须使用2个来进行表示”。ryeshen@~$ echo `echo $SHELL` /bin/bash ryeshen@~$ echo $(echo $SHELL) $SHELL ryeshen@~$ echo `echo \$SHELL` $SHELL ryeshen@~$ echo $(echo \$SHELL) /bin/bash ryeshen@~$ echo `echo \` > ryeshen@~$ echo $(echo \)
对此我的理解是,在反引号中,表示续行(见倒数第二个例子),才表示转义符。而在$()中,本身就起到转义符的效果。3. 数组
-
a. 赋值
#shell脚本中#表示行注释 array=(0 1 2 3 4) array[101]=101 #直接通过 数组名[下标] 就可以对其进行引用赋值。 #如果下标不存在,自动添加新一个数组元素 unset array[101] #使用unset清除指定值 unset array #使用unset清除整个数组
-
b. 取值
echo ${array[2]} #使用下标获取值 echo ${a[@]} #使用@或*获取数组所有值 echo ${a[*]}
输出结果:
2 0 1 2 3 4 0 1 2 3 4
-
c. 截取
#${数组名[@或*]:起始位置:长度} newArray=(${array[@]:1:3}) #先截取数组"1 2 3",再将截取到的数组赋给newArray
-
d. 遍历
for a in ${arr[@]} do echo "$a" done
-
e. 拆分字符串到数组
a="one,two,three,four" OLD_IFS="$IFS" #使用临时变量保存环境变量IFS的值 IFS="," #给环境变量IFS赋值,这个值用来切割字符串 arr=($a) #字符串切分 IFS="$OLD_IFS" #还原IFS
4. 语法
-
a. if
if语句的格式如下:
注意格式中分号;的使用。if …; then … elif …; then … else … fi
if的判断语句中,方括号和逻辑运算符两边都必须有空格(“]”的右边除外)。 -
p.s. []必须有空格的原因
“[“ 是linux系统中的指(试试whereis [),用法与test相同,但最后一个参数必须是 “]”。这也是为什么then前面要加分号”;”的原因。相似地可以推出,if后面可以接其他指令,利用其返回值进行判断,当返回值为0时if才判断为true
如:ryeshen@~$ if echo "123"; then > echo "123" > fi 123 123
-
p.s. (),(()),[]和[[]]
( ): 1. 用于数组的初始化
2. 指令群组(command group),即用括号将一组命令包括起
来,这组命令共用一个shell子进程,因此可以分享自定义变量等。
(( )): 相当于命令let,用于算数运算。举个例子:ryeshen@~$ ((a=1+1)); echo $a 2 ryeshen@~$ ((a=1+1)); echo $a 2
[ ]和[[ ]]的区别:
如上所说,[其实是一个指令,因此使用判断中字符串时最好用双引号括住,
且>、<必须改写成>和<(>,<是重定向符)。
另外,在使用&&和||时,必须写成 [ cond1 ] && [ cond2 ] 的形式。
[[ ]]则是bash的关键字。可以直接使用>、<、&&、||,如[[ a>1 && b>2 ]]。
[[ ]]中字符串未双引号括住的话,能进行正则表达式匹配。如:ryeshen@~$ [[ ab == a* ]] && echo "ok" ok
if语句支持的运算符/操作符:(可以理解为是[指令的参数,就像rm -rf一样)
-
b. for
for var in …; do do something done for (( cond1; cond2; cond3 )) do do something done
应用示例:
#输出1 2 3 4 …… 10
for i in $(seq 10); do #这里是用seq指令生成了"1 2 3 …… 10"的字符串
echo $i;
done;
#判断输入的字符串是不是文件名
for file in $*; do # $*指从命令行读入的参数,如输入"test.sh a.xml b.txt c.pdf",则$*=(a.xml b.txt c.pdf)
if [ -f "$file" ]; then
echo "INFO: $file exists"
else
echo "ERROR: $file not exists"
fi
done;
-
p.s. 关于脚本的传入参数
-
c. while和until
while [ cond1 ] && { || } [ cond2 ] …; do do something done
应用示例:until [ cond1 ] && { || } [ cond2 ] …; do do somethingdone
# 逐行输出/etc/hosts文件内容 while read line; do echo $line; done < /etc/hosts; # 这里能读入/etc/hosts,是因为<将文件重定向到read指令
-
d. case
case var in pattern 1 ) … ;; pattern 2 ) … ;; *) … ;; esac
- case行尾必须为单词“in”,每一个模式必须以右括号“)”结束。
- ;; 表示命令序列结束。
- pattern可以使用匹配符,如
- a|b: a或者b
- *:匹配任意长度的任意字符;
- ?:匹配任意单个字符;
- [-]:范围匹配
-
另外提一下shfit,它可以将入参左移,即进行赋值:$1=$2;$2=$3;$3=$4……
case可以与shfit结合使用,用于根据入参进行处理,举例如下:
shell脚本test.sh:#!/bin/bash while [ $# -gt 0 ]; do case $1 in -a) shift; echo a-$1; shift; ;; -b|-d) shift; echo bd-$1; shift; ;; -c) shift; echo c-$1; shift; ;; *) echo unkown; shift; esac done
输出:
ryeshen@~$ ./test.sh -a 1 -c 2 -b 3 -d 3 a-1 c-2 bd-3 bd-3
-
e. function
- 函数定义
functionname() { do something } function func() { do something }
- 函数调用
函数必须先定义后使用。func param1 param2 ... # 函数内使用$#,$1等方式获取参数属性和参数,与脚本获取入参类似
- 函数定义
- 函数返回值
func ret=$? #使用$?获取函数的执行结果 ret=`func` #使用反单引号执行函数并将返回值赋值给变量
- 函数变量
+ 脚本中定义的变量可在函数中使用 + 函数中可用local关键字声明属于自己的,不被外部可见的局部变量
后记
学习过shell的基础知识后,最大的感受是原以为是 shell脚本的特殊用法事实上是有规律可推理的。在此之前,虽然知道[]两边要加空格、使用while read $line;do……done < a.txt可以逐行读入文件等零碎的知识点,但只是知其然却不知其所以然。了解了shell的特殊符号的含义后,才发现这些东西并非只能靠死记硬背,而是有规则可循的。