shell脚本编程 linux shell编程实例
|您好,欢迎来到“C语言中文网-->Shell专题”,您将享受到免费的Shell教程和学习资料!
Shell 诞生于 Unix,是与 Unix/Linux 交互的工具,单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Lunix 基础。
近几年来,Shell一直被忽略,是一个不受重视的脚本语言。Shell虽然是Unix的第一个脚本语言,但它是相当优秀的。它结合了延展性与效率,持续保有独具的特色,并不断的被改良,使它多年来能与那些花招很多的脚本语言保持抗衡。
Shell需要依赖其他程序才能完成大部分的工作,这或许是它的缺陷,但它不容置疑的长处是:简洁的脚本语言标记方式,而且比C语言编写的程序执行更快、更有效率。
C语言中文网以通俗易懂的语言向您讲解Shell编程,让您在最短的时间内快速掌握Shell,编写出实用的程序和代码。
为了让您尽快体验最新技术,我们的教程将不断更新,保持与时俱进,请及时关注。您的支持是我们前进的动力!
说明:C语言中文网的文章由团队成员翻译、编辑和整理,难免有错误和纰漏,请在文章底部留言向我们指出,谢谢。
Shell脚本编程的常识
现在我们使用的操作系统(Windows、Mac OS、Android、iOS 等)都是带图形界面的,简单直观,容易上手,对专业用户(程序员、网管等)和普通用户(家庭主妇、老年人等)都非常适用;计算机的普及离不开图形界面。
然而在计算机的早期并没有图形界面,我们只能通过一个一个地命令来控制计算机,这些命令有成百上千之多,且不说记住这些命令非常困难,每天面对没有任何色彩的“黑屏”本身就是一件枯燥的事情;这个时候的计算机还远远谈不上炫酷和普及,只有专业人员才能使用。
猛击《带你逛西雅图活电脑博物馆》可以欣赏更多早期的计算机。
对于图形界面,用户点击某个图标就能启动某个程序;对于命令行,用户输入某个程序的名字(可以看做一个命令)就能启动某个程序。这两者的基本过程都是类似的,都需要查找程序在硬盘上的安装位置,然后将它们加载到内存运行。
关于程序的运行原理,请猛击《载入内存,让程序运行起来》。
换句话说,图形界面和命令行要达到的目的是一样的,都是让用户控制计算机。
然而,真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁。
由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核。如此一来,用户和内核之间就多了一层“代理”,这层“代理”既简化了用户的操作,也保护了内核。
用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell。
Shell 除了能解释用户输入的命令,将它传递给内核,还可以:
- 调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果;
- 在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入;
- Shell 本身也可以被其他程序调用。
由此可见,Shell 是将内核、程序和用户连接了起来。
Shell 本身支持的命令并不多,但是它可以调用其他的程序,每个程序就是一个命令,这使得 Shell 命令的数量可以无限扩展,其结果就是 Shell 的功能非常强大,完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。
Shell 并不是简单的堆砌命令,我们还可以在 Shell 中编程,这和使用 C/C++、Java、Python 等常见的编程语言并没有什么两样。
Shell 虽然没有 C/C++、Java、Python 等强大,但也支持了基本的编程元素,例如:
- if...else 选择结构,switch...case 开关语句,for、while、until 循环;
- 变量、数组、字符串、注释、加减乘除、逻辑运算等概念;
- 函数,包括用户自定义的函数和内置函数(例如 printf、export、eval 等)。
站在这个角度讲,Shell 也是一种编程语言,它的编译器(解释器)是 Shell 这个程序。我们平时所说的 Shell,有时候是指连接用户和内核的这个程序,有时候又是指 Shell 编程。
Shell 主要用来开发一些实用的、自动化的小工具,而不是用来开发具有复杂业务逻辑的中大型软件,例如检测计算机的硬件参数、一键搭建Web开发环境、日志分析等,Shell 都非常合适。
使用 Shell 的熟练程度反映了用户对 Linux 的掌握程度,运维工程师、网络管理员、程序员都应该学习 Shell。
尤其是 Linux 运维工程师,Shell 更是必不可少的,是必须掌握的技能,它使得我们能够自动化地管理服务器集群,否则你就得一个一个地登录所有的服务器,对每一台服务器都进行相同的设置,而这些服务器可能有成百上千之多,会浪费大量的时间在重复性的工作上。
Shell 是一种脚本语言
任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。
有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
而有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器。
编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合Web开发以及小工具的制作。
Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
(这些往往是经常用到,但是各种网络上的材料都语焉不详的东西,个人认为比较有用)
七种文件类型
d 目录 l 符号链接
s 套接字文件 b 块设备文件
c 字符设备文件 p 命名管道文件
- 普通文件
正则表达式
从一个文件或命令输出中抽取或过滤文本时。可使用正则表达式(RE),正则表达式是一些特殊或不很特殊的字符串模式的集合。
基本的元字符集:
^ 只匹配行首。
$ 只匹配行尾。
* 一个单字符后紧跟*,匹配0个或多个此单字符。
[] 匹配[]内字符,可以是一个单字符,也可以是字符序列。可以使
用-来表示[]内范围,如[1-5]等价于[1,2,3,4,5]。
\ 屏蔽一个元字符的特殊含义,如\$表示字符$,而不表示匹配行
尾。
. 匹配任意单字符。
pattern\{n\} 匹配pattern出现的次数n
pattern\{n,\}m匹配pattern出现的次数,但表示次数最少为n
pattern\{n,m\} 匹配pattern出现的次数在n与m之间(n,m为0-255)
几个常见的例子:
显示可执行的文件:ls –l | grep …x...x..x
只显示文件夹:ls –l | grep ^d
匹配所有的空行:^$
匹配所有的单词:[A-Z a-z]*
匹配任一非字母型字符:[^A-Z a-z]
包含八个字符的行:^……..$(8个.)
字符类描述
以下是可用字符类的相当完整的列表:
[:alnum:] 字母数字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或制表键
[:cntrl:] 任何控制字符
[:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格)
[:lower:] 小写 [a-z]
[:print:] 非控制字符
[:punct:] 标点字符
[:space:] 空格
[:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]
尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).
shell的引号类型
shell共有四种引用类型:
“ ” 双引号
‘ ’ 单引号
` ` 反引号
\ 反斜线
l “ ” 可引用除$、` 、\ 、外的任意字符或字符串,“ ”中的变量能够正常显示变量值。
l ‘ ’与“ ”类似,不同在于shell会忽略任何的引用值。
例如: GIRL=‘girl’
echo “The ‘$GIRL’ did well”
则打印:The ‘girl’ did well
l ` `用于设置系统命令的输出到变量,shell会将` `中的内容作为一个系统命令并执行质。
例如:echo `date` 则打印当前的系统时间。
l \ 用来屏蔽特殊含义的字符:& * + ^ $ ` “ | ?
例如:expr 12 \* 12 将输出144
变量设置时的不同模式:
valiable_name=value 设置实际值到 variable_name中
valiable_name+value 如果设置了variable_name,则重设其值
valiable_name:?value 如果未设置variable_name,则先显示未定义用户错误信息
valiable_name?value 如果未设置variable_name,则显示系统错误信息
valiable_name:=value 如果未设置variable_name,则设置其值
valiable_name-value 同上,但取值并不设置到variable_name
条件测试
test命令用于测试字符串、文件状态和数字,expr测试和执行数值输出。
Test格式:test condition 或 [ condition ](需要特别注意的是condition的两边都要有一个空格,否则会报错),test命令返回0表示成功。
l 下面将分别描述test的三种测试:
n 文件状态测试(常用的)
-d 测试是否文件夹
-f 测试是否一般文件
-L 测试是否链接文件
-r 测试文件是否可读
-w 测试文件是否可写
-x 测试文件是否可执行
-s 测试文件是否非空
n 字符串测试
五种格式: test “string”
test string_operator “string”
test “string” string_operator “string”
[ string_operator “string” ]
[ “string” string_operator “string” ]
其中string_operator可以为: = 两字符串相等
!= 两字符串不等
-z 空串
-n 非空串
n 数值测试
两种格式: “number” number_operator “number”
[ “number” number_operator “number” ]
其中:number_operator 可以为:-eq 、-ne、-gt、-lt、-ge
例如: NUMBER=130
[ “990” –le “995” –a “NUMBER” -gt “133” ]
(其中-a表示前后结果相“与”)
l expr命令一般用于整数值,但也可以用于字符串。
n 格式: expr srgument operator operator argument
例如: expr 10 + 10
expr 10 ^ 2 (10的平方)
expr $value + 10
n 增量计数――expr在循环中最基本的用法
例如: LOOP=0
LOOP=`expr $LOOP + 1`
n 模式匹配:通过指定的冒号选项计算字符串中的字符数
例如: value=account.doc
expr $value : `\(.*\).doc`
输出 account
每一个在UNIX/Linux上工作的程序员可能都擅长shell脚本编程。但大家解决问题的方式却不尽相同,这要取决于对专业知识的掌握程度、使用命令的种类、看待问题的方式等等。对于那些处在shell脚本编程初级阶段的程序员来说,遵循一些恰当的做法可以帮助你更快、更好的学习这些编程技巧。下面,我们就来讨论这些能帮助你学习shell脚本编程的方法吧。
0、多动手
你想学习shell脚本编程,这很不错。于是你拿了一本书开始学习。一些人会首先通读整本教材后再上机练习。这种方法可能适用于一些人,但我却不太看好它。我的建议是,仅仅学一些最基础的能够让你开始编码的知识就可以了。之后,动手写一些简单的程序吧。一旦你由于知识上的欠缺而不得不停止时,再回到书本上去读你想要了解的那部分,然后继续做你的项目。如此周而复始,不断提高你的水平。这种边学边做的方法曾让我受益良多。
1、善用命令提示符
有时候,我们写的脚本中有一些错误。我们修改错误,运行脚本,但系统再次报错。并且这个改错报错的过程可能会发生很多次。碰到这些情况,首先需要找到有问题的行或命令,这可以通过一些调试语句来轻松做到。一旦发现这条语句,尝试在命令提示符下执行相同的语句。如果它在命令提示符下开始正常运行,你就可以容易的推断出它不能正常运行的原因了。可能是由于某些错误输入的命令,或者是某些环境变量不匹配,或者是从不同的地方引用了某个二进制文件等等。这种方法会让调试变得简单易行。
2、考虑问题要全面
现在我们来看个问题。你想到了关于某个问题的解决方案,但这个解决方案只适用于处理小型文件。可是当处理比较大的文件时,你该怎么办?举个例子,我们想要得到一个文件的第一行内容:
1
|
sed -n '1p' file
|
这条语句当然会给出你想要的第一行内容。可是如果处理的文件包含上百万条记录呢?尽管上面的那条sed命令可以输出文件的第一行内容,但是想要处理大型文件一定会带来性能上的问题。
解决办法:
1
|
sed -n '1p;1q' file
|
这条命令将只输出第一行,同时退出程序。
3、经常尝试不同的方法
你在写脚本时碰到一个问题,然后你找到了一种独特的解决方法。下一次你偶然又碰到类似的问题,这时,不要再用以前你用过的方法来解决。试试另外一种方法吧。如果某一天再次遇到这种情况,再试试其它方法。
例如:
1
2
3
4
|
if [ $? -eq 0 ]
then
echo "Success"
fi
|
另一种方法:
1
|
[ $? -eq 0 ] && echo "Success"
|
现在你可能会明白这个博客里会有那么多以“……的不同解决方法”为题的文章了吧。所有这些文章的目的都是用来帮助订阅这个博客的开发者开阔视野,打开思路。
4、快速编码
脚本可以节省我们的时间,提高生产力。可是,难道我们花在写脚本和测试上的时间还少吗?我们想写一个脚本,于是打开一个文件,写下代码,保存文件,之后运行脚本,系统报错,我们再打开文件修改、保存、运行……在这个过程中会花费很多时间。在此前的一篇题为《如何快速写shell脚本》的文章里,你可以学会如何编写脚本和测试正在运行中的脚本,而不用再回顾命令提示符。这些方法可以加快编码的速度。当我写脚本的时候,我总是使用这些方法。而且我可以很肯定的说,它们帮我节约了不少时间。
5、经常使用内部命令
无论碰到哪种情况,请尽量考虑使用内部命令而不是外部命令。在此前的一篇题为《内部命令和外部命令》的文章里,我们可以看到二者间的差异。用内部命令对你永远都有好处。根据正在处理的输入文件的大小,内部命令可以在性能方面为你节省很多。虽然你并不总是有这样选择内部命令抑或外部命令的机会,但在某些情况下,你一定能做出正确的选择。
6、没有必要使用cat命令
这是我们经常在论坛里讨论的话题之一。没有必要使用cat命令指的是在有些时候,我们会发现根本没有必要使用cat命令。有时候,使用了多余的cat命令会让你的代码看起来很丑陋,而且还会带来性能上的问题。
例如:
1
|
$ cat /etc/passwd | grep guru
|
正确的方法应该是:
1
|
$ grep guru /etc/passwd
|
7、仔细阅读错误信息
程序员常犯的一个错误是:当我们敲入的命令报错后,我们中的大多数人只是对错误信息一瞥而过,而不会去认真的读一读。很多时候,错误信息里就包含了解决办法。更重要的是,有时候我们修改了某个错误并再次运行后,系统依旧会报错。然后我们再次修改,但系统再次报错。这可能会持续很长时间。但实际上,旧的错误可能已经被纠正,只是由于出现了其它一些新错误才导致系统再次报错。而我们依旧在怀疑为什么修改好的代码依然不能正常运行。因此,请你养成仔细阅读错误信息的习惯。
8、尽量避免臃肿的命令
你正在尝试去从一个大的文件中筛选某条信息。接下来你可能写一大堆命令来实现这一功能。可是,尽管你将得到正确的结果,你写的命令却不够好,且晦涩难懂。因此,我们应该尽量避免这种情况发生。下面这个例子就是代码优化的好例子。
从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善的今天,在系统管理等领域,Shell编程仍然起着不可忽视的作用。深入地了解和熟练地掌握Shell编程,是每一个Linux用户的必修 功课之一。
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),等等。不同的Shell语言的语法有所不同,所以不能交换使用。每种Shell都有其特色之处,基本上,掌握其中任何一种 就足够了。在本文中,我们关注的重点是Bash,也就是Bourne Again Shell,由于易用和免费,Bash在日常工作中被广泛使用;同时,Bash也是大多数Linux系统默认的Shell。在一般情况下,人们并不区分 Bourne Shell和Bourne Again Shell,所以,在下面的文字中,我们可以看到#!/bin/sh,它同样也可以改为#!/bin/bash。
利用vi等文本编辑器编写Shell脚本的格式是固定的,如下:
1
2
3
|
#!/bin/sh #comments Your commands go here |
首行中的符号#!告诉系统其后路径所指定的程序即是解释此脚本文件的Shell程 序。如果首行没有这句话,在执行脚本文件的时候,将会出现错误。后续的部分就是主程序,Shell脚本像高级语言一样,也有变量赋值,也有控制语句。除第 一行外,以#开头的行就是注释行,直到此行的结束。如果一行未完成,可以在行尾加上",这个符号表明下一行与此行会合并为同一行。
编辑完毕,将脚本存盘为filename.sh,文件名后缀sh表明这是一个Bash脚本文件。执行脚本的时候,要先将脚本文件的属性改为可执行的:
1
|
chmod +x filename.sh |
执行脚本的方法是:
1
|
. /filename .sh |
下面我们从经典的“hello world”入手,看一看最简单的Shell脚本的模样。
1
2
3
4
|
#!/bin/sh #print hello world in the console window a = "hello world" echo $a |
Shell Script是一种弱类型语言,使用变量的时候无需首先声明其类型。新的变量会在本地数据区分配内存进行存储,这个变量归当前的Shell所有,任何子进 程都不能访问本地变量。这些变量与环境变量不同,环境变量被存储在另一内存区,叫做用户环境区,这块内存中的变量可以被子进程访问。变量赋值的方式是:
1
|
variable_name = variable_value |
如果对一个已经有值的变量赋值,新值将取代旧值。取值的时候要在变量名前加$,$variable_name可以在引号中使用,这一点和其他高级语言是明显不同的。如果出现混淆的情况,可以使用花括号来区分,例如:
echo "Hi, $as"
就不会输出“Hi, hello worlds”,而是输出“Hi,”。这是因为Shell把$as当成一个变量,而$as未被赋值,其值为空。正确的方法是:
echo "Hi, ${a}s"
单引号中的变量不会进行变量替换操作。
关于变量,还需要知道几个与其相关的Linux命令。
env用于显示用户环境区中的变量及其取值;set用于显示本地数据区和用户环境区中的变量及其取值;unset用于删除指定变量当前的取值,该值将被指定为NULL;export命令用于将本地数据区中的变量转移到用户环境区。
0 Comments.