基本概念
Shell定位
· Shell是一个用C语言编写的程序,是用户使用Linux的桥梁;Shell既是一种命令语言,又是一种程序设计语言。
· Shell是指一种应用程序,一种和内核沟通的外壳应用程序的统称,这个应用程序有时提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Shell & Bash
· Shell如果叫做媒婆,Bash就是王婆,媒婆当中的佼佼者
Shell脚本
· Shell脚本(Shell script)是一种为Shell编写的脚本程序
· 业界所说的Shell通常都是指Shell脚本,但要知道,Shell和Shell Script是两个不同的概念
· 由于习惯的原因,简洁起见,我们统一认为“Shell编程”都是指Shell脚本编程,而不是指开发Shell自身
Shell开发环境
· Shell编程跟java、php、python编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了
· Linux的Shell解释器种类众多,常见的有:
· Bourne Shell(/usr/bin/sh或bin/sh)
· Bourne Again Shell(/bin/bash)
· C Shell(/usr/bin/csh)
· K Shell(usr/bin/ksh)
· Shell For Root(/sbin/sh)等
· 我们关注的是Bash,也就是Bourne Again Shell,由于易用和免费,Bash在日常工作中被广泛使用,同时,Bash也是大多数Linux系统默认的Shell
Shell特点
· 解释非编译型
· 弱类型
· 执行模式:交互式/批处理式
第一个Shell脚本
创建文件first.sh,写入如下内容,保存退出:
· #!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即:使用哪一种Shell;通常,(#!)的名称,叫做“Shebang”或者“Sha-bang”
· echo命令用于向窗口输出文本
执行有两种方式
1.作为可执行程序
注意:一定要写成./first.sh,而不是first.sh,运行其二进制的程序也一样,Linux系统会在PATH里寻找有没有叫first.sh的,而只有/bin,/sbin,/usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成first.sh是会找不到命令的,要用./first.sh告诉系统就在当前目录找
2.作为解释器参数
· 这种运行方式是直接运行解释器,其参数就是Shell脚本中的文件名;这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用
· Shell脚本中用#表示注释,相当于C语言的/注释
· 但如果#位于第一行开头,并且是外侧,它表示该脚本使用后面指定的解释器/bin/bash解释执行
解释执行本质原理
· 第一种执行方式:Shell会fork一个子进程并调用exec执行./first.sh,exec系统调用应该把子进程的代码段替换成./first.sh程序的代码段,并从它的_start开始执行
· 但first.sh是个文本文件,根本没有代码段和_start函数,其实exec还有另外一种机制:如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件件被当做命令行参数传给解释器
· 交互Shell(bash)fork/exec一个子Shell(sh)用于执行脚本,父进程bash等待子进程sh终止
· sh读取脚本中的cd ..命令,调用相应的函数执行命令,改变当前工作目录为上一级目录
· sh读取脚本中的ls命令,fork/exec这个程序,列出当前工作目录下的文件,sh等待ls终止
· ls终止后,sh继续执行,读到脚本文件末尾,sh终止
· sh终止后,bash继续执行,打印提示符等待用户输入
了解了执行本质原理,来个例子:
在shell脚本中执行cd..命令,发现回显消息的当前目录发生改变,但真实目录并未改变,其实也很好理解,毕竟要创建子进程来解释脚本,但是:
直接在命令行执行cd..命令,发现父bash的工作目录也发生了改变,这又如何理解?说好的创建子进程呢?
归根结底,执行命令不一定创建子进程,这些不需要创建子进程的命令,叫做shell内置命令,由父bash直接执行,理解上,将它们当做shell内置函数就好了,可是:
发现加入了source或 . 之后,脚本的执行影响到了父bash,是因为source或 .命令是Shell的内建命令,它们也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。
Shell变量:Shell是弱类型语言,原则上不是特别强调Shell变量,或者Shell变量可以放很多常见内容,Shell变量也不需要提前定义,需要时直接使用即可
赋值和命名规则
变量名的命名必须遵循以下规则:(特别注意:变量名和等号之间不能有空格)
- 首个字符必须为字母(a-z,A-Z);
- 中间不能有空格,可以使用下划线;
- 不能使用标点符号;
- 不能使用bash里的关键字;
可见,Shell变量可以放入很多你想放入的内容,而且所有的变量不需要定义,而是直接使用。
使用变量
如上图所示,使用一个已经赋值过的变量,只需要在变量名前加$符号即可
但是:
显然此处我们想输出的是“hello worldni hao”,但实际上只输出了部分,原因就是Shell将mystring和hello认为是一个新的变量名,而该变量名并未被赋值,故而为空串,那么如何解决这个问题呢?
加 {} 是为了帮助解释器识别变量的边界,推荐给所有变量加上 {} 。
以定义的变量,可以被重新定义:
注意:第二次赋值的时候不能写$my_hobby="read",当变量作为右值时,才需要带$符号。
只读变量:使用readonly命令可以将变量定义为只读变量,只读变量的值不能改变
删除变量:使用unset命令可以删除变量:
被删除的变量,内容会被清空,一般不再使用的变量需要unset,但是,unset命令不能删除只读变量:
变量类型
Shell的变量类型和我们通常所说的C/C++变量类型有点不一样:
- 本地变量(局部变量):在脚本或本命令内定义,仅在当前Shell实例中有效,其他Shell启动的程序不能访问局部变量
- 环境变量:所有的程序,包括Shell启东的程序,都能访问环境变量,有些程序需要环境变量保证其正常运行,必要的时候,Shell脚本也可以定义环境变量
- Shell变量:Shell变量是由Shell程序设置的特殊变量,Shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了Shell的正常运行
来看例子:
我们在父Shell上定义了一个变量,直接在交互式父Shell中,可以直接访问,但是在子Shell中,却没有访问到。
将myval变量导成环境变量,直接就可以显示出来了
本地变量
只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数,上面的myval在没有被export的时候,就是父Shell内的一个本地变量,环境变量是任何进程都有的概念,而本地变量是Shell特有的概念
环境变量
环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程,用printenv命令可以显示当前Shell进程的环境变量
一个变量定义后仅存在与当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成:
export VARNAME=value
也可以分两步完成:
VARNAME=value
export VARNAME
用unset命令可以删除已定义的本地变量或环境变量:
unset VARNAME
拼接字符串
原则上,只要将信息写在一起,就完成了string的拼接,但也有一些特殊情况,我们来看下:
获取字符串长度
提取字符串
如:从字符串的第4个字符开始截取4个字符:
文件名代换: *、?、[]
通配符*:匹配0个或多个任意字符
?:匹配一个任意字符
[若干字符]:匹配方括号中任意一个字符的一次出现
命令代换和算术代换
由反引号``括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令中
命令代换也可以用$()表示:date=$(date +%Y:%m:%d)
(())中的Shell变量取值将转换成整数,常用于算术计算,如:
如果要对运算结果进行赋值或者作为右值,则:
注意:(())中只能用+、-、*、/和()运算符,并且只能做整数运算
转义字符
和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。另外,\还可以使紧跟其后的普通字符取特殊含义
另外,还有一个字符虽然不具有特殊含义,但是要用它做文件名也很麻烦,就是-号,如果要创建一个文件名以-开头的文件,这样是不行的,即使加上\转义也还是报错;因为各种UNIX命令都把-号开头的命令行参数当做命令的选项而不会当做文件名,如果非要处理以-开头的文件,有以下两种办法:
\还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符>,等待用户继续输入,最后把所有的续行接到一起当做一个命令执行,如:
单引号双引号
Shell脚本中的单引号和双引号都是字符串的界定符,而不是字符的界定符,单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号,如果引号没有配对就输入回车,Shell会给出续行提示符,因此我们使用时要给引号配对
双引号用于保持引号内所有字符的字面值(回车也不例外),但以下情况除外:
- $加变量名可以取变量的值
- 反引号仍表示命令替换
- $表示$的字面值
- `(反引号)表示`的字面值
- \"表示"的字面值
- \表示\的字面值,除以上情况之外,在其它字符前面的\无特殊含义,只表示字面值