1、什么是shell?

现在我们使用的操作系统(Windows、Mac OS、Android、iOS 等)都是带图形界面的,简单直观,容易上手,对专业用户(程序员、网管等)和普通用户(家庭主妇、老年人等)都非常适用;计算机的普及离不开图形界面。

然而在计算机的早期并没有图形界面,我们只能通过一个一个地命令来控制计算机,这些命令有成百上千之多,且不说记住这些命令非常困难,每天面对没有任何色彩的“黑屏”本身就是一件枯燥的事情;这个时候的计算机还远远谈不上炫酷和普及,只有专业人员才能使用。

对于图形界面,用户点击某个图标就能启动某个程序;对于命令行,用户输入某个程序的名字(可以看做一个命令)就能启动某个程序。这两者的基本过程都是类似的,都需要查找程序在硬盘上的安装位置,然后将它们加载到内存运行。

换句话说,图形界面和命令行要达到的目的是一样的,都是让用户控制计算机。

然而,真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁。

由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,这样用户就能间接地使用操作系统内核了。你看,在用户和内核之间增加一层“代理”,既能简化用户的操作,又能保障内核的安全,何乐不为呢?

用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell。

Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。

Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox 等其它软件没有什么区别。然而 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux。

我们前面学过了很多命令,这些命令的解释都是通过shell来执行的,centos的默认shell程序是bash,也就是说,我们的命令是通过bash的接收、处理,然后把结果输出给我们的。

Shell 能够接收用户输入的命令,并对命令进行处理,处理完毕后再将结果反馈给用户,比如输出到显示器、写入到文件等,这就是大部分读者对 Shell 的认知。你看,我一直都在使用 Shell,哪有使用内核哦?我也没有看到 Shell 将我和内核连接起来呀?!

其实,Shell 程序本身的功能是很弱的,比如文件操作、输入输出、进程管理等都得依赖内核。我们运行一个命令,大部分情况下 Shell 都会去调用内核暴露出来的接口,这就是在使用内核,只是这个过程被 Shell 隐藏了起来,它自己在背后默默进行,我们看不到而已。

接口其实就是一个一个的函数,使用内核就是调用这些函数。这就是使用内核的全部内容了吗?嗯,是的!除了函数,你没有别的途径使用内核。

比如,我们都知道在 Shell 中输入cat log.txt命令就可以查看 log.txt 文件中的内容,然而,log.txt 放在磁盘的哪个位置?分成了几个数据块?在哪里开始?在哪里终止?如何操作探头读取它?这些底层细节 Shell 统统不知道的,它只能去调用内核提供的 open() 和 read() 函数,告诉内核我要读取 log.txt 文件,请帮助我,然后内核就乖乖地按照 Shell 的吩咐去读取文件了,并将读取到的文件内容交给 Shell,最后再由 Shell 呈现给用户(其实呈现到显示器上还得依赖内核)。整个过程中 Shell 就是一个“中间商”,它在用户和内核之间“倒卖”数据,只是用户不知道罢了。

在 Shell 中输入的命令,有一部分是 Shell 本身自带的,这叫做内置命令;有一部分是其它的应用程序(一个程序就是一个命令),这叫做外部命令。

Shell 本身支持的命令并不多,功能也有限,但是 Shell 可以调用其他的程序,每个程序就是一个命令,这使得 Shell 命令的数量可以无限扩展,其结果就是 Shell 的功能非常强大,完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。

更加惊讶的是,Shell 还可以让多个外部程序发生连接,在它们之间很方便地传递数据,也就是把一个程序的输出结果传递给另一个程序作为输入。

大家所说的 Shell 强大,并不是 Shell 本身功能丰富,而是它擅长使用和组织其他的程序。Shell 就是一个领导者,这正是 Shell 的魅力所在。

可以将 Shell 在整个 Linux 系统中的地位描述成下图所示的样子。注意“用户”和“其它应用程序”是通过虚线连接的,因为用户启动 Linux 后直接面对的是 Shell,通过 Shell 才能运行其它的应用程序。

2、什么是shell脚本(shell script)?

shell编程其实就是写shell脚本,shell脚本可以把多个命令集合在一起执行,可以操作文件的读写、系统管理、加入各种你想要的逻辑。

shell 脚本是在一个文件中写入一起执行的命令集。对于使用像 dos 操作系统的 DOS,windows 操作系统的 bat,linux 操作系统的 bash 的人来说,这几乎都是相同的概念。你只需要把一串命令写入一个 text 文件然后一起运行。区别在于 bash 脚本比批处理文件能做的更多。

使用 shell script 可以完成系统上的任何任务。当然,您也可以使用其他编程语言,诸如,Ruby,Python 或 Go 完成一些更多是单一或其编程语言擅长完成的小任务。无论如何,您将发现一定会用到 Shell Scripts 。

Shell script经常用于系统运维自动化任务,装配复杂系统配置,优化系统性能等系统级配置场景。它突出的能力在于让多个 shell 命令有逻辑性地组合在一起,将需要多个命令才能完成的复杂的任务逻辑封装到一个脚本文件里形成一个命令。因此,提升了系统的灵活性、特殊性及使用价值。更进一步,shell 命令行和图形桌面运行环境的结合,不是矛盾,而是双赢。

  • 自动化系统每日常规任务
  • 创建自己系统独特的 shell 命令,可选创建与用户交互的命令。
  • 体量小,易传播。mac 苹果系统和 Linux 系统基本通用。

3、第一个shell script

对于学计算机的人来说,不管是什么语言,第一个程序一般都是hello world,我们的shell script也不例外,首先来写个输出hello world的shell script。

  1. 用vi新建一个名叫my_first_shell.sh的文件(.sh是bash程序的扩展名)

    vi  my_first_shell.sh
  2. 按i键向文件里面输入echo “hello world”

    echo "hello world" # echo是bash自带的命令,用于向屏幕显示后面的字符串,这里是向屏幕显示Hello world
  3. 输入:wq保存文件

    :wq 
  4. 给my_first_shell.sh加上可执行权限,要在linux运行程序,程序必须得有执行权限,不然没法运行

    chmod a+x my_first_shell.sh # a+x表示在拥有者、属组、其他人的可执行位上都加上可执行权限
  5. 运行脚本

    $ bash my_script.sh
    $ ./my_script.sh
    Hello world

    现在我们已经完成了将 Hello world 打印到屏幕上的非常基本的 shell 脚本。

在我们深入研究 shell 脚本的一些语言结构之前,您应该对 Linux 命令有一些基本的了解。

4、深入学习

现在我们已经了解了如何编写一个基本的 Hello World 示例,接下来让我们看看您在编写 shell 脚本时经常使用的一些语言结构。

4.1 变量

为了处理数据,数据必须保存在计算机的内存中。内存被划分为小的单元,每个单元都有一个唯一的编号,称为内存地址,用于存储数据
程序员可以为这个称为变量的内存地址提供唯一的名称。命名为存储位置的变量区域,该区域可以取不同的值,但每次只能取一个值。

在 Linux Shell 脚本中,有两种类型的变量:

系统变量 —— 由 Linux 本身创建和维护。这种类型的变量用大写字母定义。
用户定义的变量 —— 由用户创建和维护。这种类型的变量用小写字母定义。
可以在脚本中使用系统变量来显示这些变量包含的任何信息。像一些重要的系统变量:

BASH — 保存 shell 名称
BASH_VERSION — 保存 shell 版本名称
HOME — 保存主目录路径
OSTYPE — 保存操作系统类型
USERNAME – 保存当前登录到计算机的用户名
注意 — 上面的一些系统变量在不同的环境中可能有不同的值.

用户自定义的变量与其他编程语言中的变量一样简单,但是变量可以存储任何类型的数据,如下面的示例所示:

# 定义变量的语法
name=abc

要访问用户自定义的变量,请使用以下语法:

#访问变量的语法
$name

屏幕输出:

#打印变量的语法
echo $name #输出
abc

 

在字符串中使用上面的变量:

#打印变量的语法
echo "My name is $name" #在双引号里面用$表示的变量会被替换为实际的值,这里$name会被替换为abc
My name is abc

4.2 引号

下面是 Shell 脚本中可用的三种引号。

双引号(”): 除了 \ 和 $ 外,双引号内的任何内容都是字符串。看下面例子:

#定义字符串变量双引号的语法
str="Shell scripting article"
echo $str#输出
Shell scripting article#使用 \ 来转义字符
str="Shell scripting \"article\""
echo $str#输出
Shell scripting "article"#在字符串中使用变量
user="ABC"
str="Shell scripting \"article\" by $user"
echo $str#输出
Shell scripting "article" by ABC

单引号(’):单引号内的任何内容都是字符串。 看下面例子:

#使用单引号定义字符串变量的语法
str='Shell scripting article'
echo $str#输出
Shell scripting article #尝试在单引号中使用 \ 转义字符
str='Shell scripting \"article\"'
echo $str#输出
Shell scripting \"article\"

左引号(`): 左引号内的任何内容都将被视为可执行命令,和~在同一个键。 看下面例子:

#定义字符串变量的语法
str='Current date is `date`'
echo $str#输出
Current date is Wed Apr 4 10:57:12 +04 2018

4.3 条件 [if/else]

if/else和我们的如果是这样则怎么做,否则就那样做的意思是一样的。
if 语句最基本的形式是:

# 简单的 if then 语句的语法
if [ 35 -gt 0 ] # 如果35大于0
then # 则执行下面的命令
    echo "Greater"
fi # if倒着写,这是规定,表示if语句结束了

你注意到 fi 的拼写是 if 倒过来的吗?参见下面包含 else 语句的示例:

#简单的 if then 语句的语法
if [ 35 -gt 45 ] # 如果35大于45
then # 则执行下面的命令
 echo "Greater"
else # 否则,执行下面的命令
 echo "Lesser"
fi

如果有多个条件判断,则使用 elif 命令添加 else-if 语句结构。

# 简单 if then else-if 语句的语法
if [ 35 -gt 55 ] # 如果35大于55
then # 则执行下面的命令
 echo "Greater"
elif [ 35 -gt 45 ] # 如果35大于45
then # 执行下面的命令
 echo "Greater"
else # 否则执行下面的命令
 echo "Lesser"
fi

条件语句在 Shell 脚本中有许多不同的使用方法。下表详细说明了如何添加一些重要的比较:

字符串比较
+------------------+-----------------------------------------------+
|   条件            |                    描述                |
+------------------+-----------------------------------------------+
| Str1 = Str2      | 如果字符串相等,则为真                 |                            
| Str1 != Str2     | 如果字符串不相等,则为真            |
| -n Str1          | 如果字符串不为空,则为真                | 
| -z Str1          | 如果字符串为空,则为真                    |                             
+------------------+-----------------------------------------------+
数值比较
+------------------+-----------------------------------------------+
|   条件            |                    描述                |
+------------------+-----------------------------------------------+
| expr1 -eq expr2  | 如果表达式相等,则为真             |
| expr1 -ne expr2  | 如果表达式不相等,则为真         |
| expr1 -gt expr2  | 如果表达式1大于表达式2,则为真           |
| expr1 -ge expr2  | 如果表达式1大于等于表达式2,则为真  |
| expr1 -lt expr2  | 如果表达式1小于表达式2,则为真            | 
| expr1 -le expr2  | 如果表达式1小于等于表达式2,则为真  |
| !expr1           | 否定表达式的结果          |            
+------------------+-----------------------------------------------+

4.4 循环

几乎所有的语言都有循环的概念,如果我们想要重复一个任务十次,我们不想要输入代码十次,每次可能会有一点变化。
因此,我们在 shell 脚本中有 for 和 while 循环。这比其他语言的特性要少一些。

for 循环:

# 简单的 for 循环语句
for i in 1 2 3 4 5 # i会依次代表1 2 3 4 5来执行下面的语句
do
    echo "Hello world $i"
done

#输出
Hello world 1
Hello world 2
Hello world 3
Hello world 4
Hello world 5

上面的 for 循环首先创建变量 i,并从 1 到 5 的数字列表中给它分配一个数字,shell 对 i 的每次分配执行 echo 语句,在每次 迭代 中,它将回显输出中所示的语句。这个过程将持续到最后一项。

while循环:
while 循环将一直执行,直到条件为真。看下面的例子:

# 简单的 While 循环语句
i = 1
while [ $i -le 5 ] # 如果i小于等于5,则会一直执行,直到i大于5
do
 echo "Hello world $i"
 i=`expr $i + 1` # expr 对后面的表达式$i + 1求值,然后赋值给i,如果i是2,则2+1=3,此时i就会为3
done

# 输出
Hello world 1
Hello world 2
Hello world 3
Hello world 4
Hello world 5

上面的脚本首先创建一个值为 1 的变量 i。然后循环迭代直到 i 的值小于等于 5。语句
i=expr $i + 1 负责 i 的增值。

如果这个语句被删除,上面所说的循环将是一个 无限循环。

4.5 函数

函数( Function)是一种程序或处理过程类型。它封装了一个任务(逻辑组合了多个指令去完成确定的工作)并形成一条指令(一个供调用的函数名)。许多程序( programming) 语言本身提供许多内建的 函数(functions)。若没有这些函数,程序中要需要许多指令语句去完成某项任务。例如,计算一个数的平方。

说的通俗点,函数就是把多条语句放在一起,方便多次重复运行这些语句。

在 shell 脚本中,有两种方式定义函数。

在本脚本文件里定义函数并调用。
创建单独的脚本文件包含供其他脚本调用的函数定义。例如, library.sh 文件里包含所有用到的函数。
在脚本文件中定义和使用函数,见下面的示例:

#声明和定义简单函数的语法
print_date()
{
echo "Today is `date +"%A %d %B %Y (%r)"`"
return
}
#调用上面定义的函数
print_date

#程序输出
Today is Thursday 05 April 2018 (12:11:23 PM)

 

4.5.1 脚本退出状态

exit 指令终止脚本的执行。类似 C 语言程序的 exit 指令语句。它返回一个数值到创建脚本子进程的父进程。

shell 中每执行完成一个命令,这条命令都返回一个表示 退出状态(exit status) 的数值(常常也被称做 ‘return status’ 或 ‘exit code’ )。一条正确执行完成的命令返回 0 值,否则,返回非零值,这个值被称作 error code 。它可能对应到一个简单的文字解释说明错误原因。设计优良的 UNIX 命令,程序和工具软件,在出现一些非致命异常的情况下也能完成执行并返回 0。

当一个 shell 脚本内部执行无参数的 exit 指令时,脚本的退出状态是脚本里执行 exit 指令前最后一个执行的命令的退出状态。

#exit 语法
LAST_COMMAND
# 将会以 LAST_COMMAND 执行的退出状态为脚本的退出状态返回

exit 指令与 exit $? 完全相同,只写成 exit 即可。

LAST_COMMAND
# LAST_COMMAND 执行完成后,返回值会保存在 $? 变量中,脚本以这个值返回

$? 在 shell 里是个特殊的变量,每当一个命令完成后,它返回的退出状态值都保存在变量 $? 中。当前 shell 状态下,$? 里保存的是最后完成命令的返回状态值。

总结

  • shell脚本就是把多条命令集合在一起执行,用于自动化一些系统任务,比如文件处理、系统管理等。
  • if/else/elif相当于中文中的如果这样则这样做,如果那样则那样做,如果都不是上面的情况(否则)则这样做
  • 循环for和while,表示一直执行循环下的命令,直到循环的条件为假。
  • 函数是把多条命令组织在一起,方便在多个地方运行同样的命令。
  • linux中每执行一条命令,都会有一个退出状态(以数字表示),默认情况下都是返回0,表示执行成功,如果有错误则返回大于0的数字,表示一个错误码。

原创文章,转载请注明出处:http://b.nwumba.cn/article/109/