如何编写shell脚本

如何编写shell脚本

本文用大量的例子阐述了如何编写shell脚本。

为什么要shell编程?

在Linux系统中,虽然有各种图形界面工具,但sell仍然是一个非常灵活的工具。Shell不仅是命令的集合,也是一种伟大的编程语言。使用shell可以自动化很多任务,shell特别擅长系统管理任务,尤其是那些易用性、可维护性、可移植性比效率更重要的任务。

让我们来看看shell是如何工作的:

创建脚本

Linux中有许多不同的shell,但通常我们使用bash (bourne又是shell)进行shell编程,因为bash是免费的并且易于使用。因此,在本文中,作者提供的脚本都使用了bash(但大多数情况下,这些脚本也可以运行在bash的大姐——bourne shell中)。

就像其他语言一样,我们可以使用任何文本编辑器,比如nedit、kedit、emacs、vi。

等着写我们的shell程序。

程序必须以下面一行开始(它必须在文件的第一行):

#!/bin/sh

符号#!用来告诉系统,后面的参数用来执行文件的程序。在这个例子中,我们使用/bin/sh来执行程序。

编辑脚本时,如果要执行它,必须使它可执行。

要使脚本可执行:

chmod +x文件名

然后,您可以通过输入:。/文件名。

给…作注解

在shell编程中,以#开头的句子表示注释,直到本行结束。我们真诚地建议您在程序中使用注释。如果使用注释,即使很久没有使用脚本,也能在短时间内理解脚本的功能和工作原理。

可变的

您必须在其他编程语言中使用变量。在shell编程中,所有的变量都是由字符串组成的,不需要声明变量。要给变量赋值,可以写:

变量名=值

取出变量值时,可以在变量前放一个美元符号($):

#!/bin/sh

#为变量赋值:

a="hello world "

#现在打印变量a的内容:

回声“A是:”

echo $a

在编辑器中输入上述内容,并首先将其保存为文件。然后先执行chmod +x。

使之可执行,最后进入。/首先执行脚本。

该脚本将输出:

一个是:

你好世界

有时变量名很容易与其他单词混淆,例如:

数量=2

echo“这是$numnd”

这样就不会打印出“这是2号”,而只会打印出“这是”,因为shell会搜索变量numnd的值,但是这个变量没有值。您可以使用花括号告诉shell我们想要打印num变量:

数量=2

echo "这是第$ { num }个"

这将打印:这是第二次

系统自动设置的变量有很多,后面使用这些变量的时候会讨论。

如果需要处理数学表达式,那么就需要使用expr之类的程序(见下文)。

除了只在程序内有效的一般shell变量之外,还有环境变量。由export关键字处理的变量称为环境变量。我们不讨论环境变量,因为它们通常只在登录脚本中使用。

外壳命令和过程控制

shell脚本中可以使用三种类型的命令:

1)Unix命令:

尽管可以在shell脚本中使用任意的unix命令,但是仍然有一些相对更常用的命令。这些命令通常用于文件和文本操作。

常用命令的语法和功能

回显“某些文本”:打印屏幕上的文本内容。

Ls:文件列表

Wc -l filewc -w filewc -c文件:计算文件的行数,计算文件的字数,计算文件的字符数。

Cp源文件destfile:文件副本

重命名或移动一个文件。

Rm文件:删除文件

Grep 'pattern' file:在文件中搜索字符串,例如grep 'searchstring' file.txt

Cut -b colnum file:指定要显示的文件内容范围,并输出到标准输出设备,例如输出每行的第5到第9个字符。cut -b5-9 file.txt一定不能和cat命令混淆,这是两个完全不同的命令。

Cat file.txt:将文件内容输出到标准输出设备(屏幕)。

File somefile:获取文件类型

读取var:提示用户输入并将输入赋给变量。

排序file.txt:对file.txt文件中的行进行排序。

Uniq:删除文本文件中出现的行和列,比如sort file.txt | uniq。

Expr:执行数学运算示例:将2和3 expr 2“+”3相加。

查找:搜索文件,比如根据文件查找搜索查找。-名称文件名-打印。

Tee:将数据输出到标准输出设备(屏幕)和文件,如somecommand | tee outfile。

Basename file:返回不带路径的文件名,例如basename /bin/tux将返回tux。

Dirname file:返回文件所在的路径。比如dirname /bin/tux会返回/bin。

头文件:打印文本文件的前几行。

尾文件:打印文本文件的最后几行。

Sed: Sed是一个基本的查找和替换程序。您可以从标准输入(如命令管道)读取文本,并将结果输出到标准输出(屏幕)。该命令使用正则表达式(参见参考)进行搜索。不要将其与shell中的通配符混淆。例如,将linuxfocus替换为LinuxFocus: cattext.file | sed的/Linux focus/Linux focus/' > new text . file

Awk: awk用于从文本文件中提取字段。默认情况下,字段分隔符是一个空格,您可以使用-F指定另一个分隔符。Catfile。txt | awk-f,' {print $1 ',' $3} '在这里用作字段分隔符,以同时打印第一个和第三个字段。如果文件内容如下:Adam Bor,34,IndiaKerry Miller,22,USA command的输出结果是Adam Bor,India Kerry Miller,USA。

2)概念:管道、重定向和backlog。

这些不是系统命令,但是它们真的很重要。

管道(|)将一个命令的输出作为另一个命令的输入。

grep "hello" file.txt | wc -l

在file.txt中搜索包含“hello”的行,统计行数。

这里,grep命令的输出被用作wc命令的输入。当然,您可以使用多个命令。

重定向:将命令的结果输出到一个文件,而不是标准输出(屏幕)。

& gt写入文件并覆盖旧文件。

& gt& gt添加到文件的末尾,并保留旧文件的内容。

反斜杠

使用反斜杠将一个命令的输出用作另一个命令的命令行参数。

命令:

找到。-mtime -1型f -print

用于查找在过去24小时内修改过的文件(-mtime–2表示过去48小时)。如果您想打包所有找到的文件,您可以使用以下脚本:

#!/bin/sh

#记号是反记号(`),而不是正常的引号('):

塔尔-zcvf lastmod.tar.gz `发现。-mtime -1型f -print `型

3)过程控制

“if”表达式如果条件为真,则执行以下部分:

如果....;然后

....

否则如果....;然后

....

其他

....

船方不负担装货费用

在大多数情况下,您可以使用测试命令来测试条件。比如可以比较字符串,判断文件是否存在,是否可读等等。

通常,“[]”用来表示条件测试。注意这里的空格是很重要的。确保方括号中的空格。

[-f "somefile"]:判断是否是文件。

[-x "/bin/ls"]:判断/bin/ls是否存在,是否有可执行权限。

[-n "$var"]:判断$var变量是否有值。

["$a" = "$b"]:判断$a和$b是否相等。

执行man test,查看所有测试表达式可以比较和判断的类型。

直接执行以下脚本:

#!/bin/sh

if[" $ SHELL " = "/bin/bash "];然后

echo“您的登录shell是bash (bourne again shell)”

其他

echo“您的登录shell不是bash而是$SHELL”

船方不负担装货费用

变量$SHELL包含登录SHELL的名称,我们将它与/bin/bash进行了比较。

快捷操作符

熟悉C语言的朋友可能会喜欢下面这个表述:

[-f "/etc/shadow "]& amp;& ampecho“这台计算机使用影子密码”

这里& amp;是快捷操作符,如果左边的表达式为真,则执行右边的语句。你也可以把它想成逻辑运算中的一个AND运算。在上面的示例中,如果/etc/shadow文件存在,将显示“这台计算机使用影子密码”。同样的OR操作(||)也可以在shell编程中使用。这里有一个例子:

#!/bin/sh

mail folder =/var/spool/mail/James

[-r " $ mail folder "]“{ echo”无法读取$ mail folder ";出口1;}

echo "$mailfolder有邮件来自:"

grep " ^from " $邮件文件夹

该脚本首先确定mailfolder是否可读。如果可读,打印文件中的“From”行。如果它不可读,则OR操作生效,脚本在打印错误消息后退出。这里有一个问题,就是我们必须有两个订单:

-打印错误信息

-退出程序

我们使用花括号将两个命令以匿名函数的形式放在一起作为一个命令。下面将提到一般功能。

没有and和or操作符,我们可以对if表达式做任何事情,但是使用AND或OR操作符要方便得多。

Case表达式可用于匹配给定的字符串,而不是数字。

情况...在

...)在这里做点什么;;

environmental systems applications center 环境系统应用程序中心

让我们看一个例子。file命令可以识别给定文件的文件类型,例如:

lf.gz档案

这将返回:

lf.gz: gzip压缩数据,缩小,原文件名,

最后修改时间:2006年8月27日星期一23:09:18

我们用这个写一个名为smartzip的脚本,可以自动提取bzip2、gzip和zip类型的压缩文件:

#!/bin/sh

ftype = ` file " $ 1 " ` 0

案例" $ftype "在

" $1: Zip存档" *)

解压“$ 1”;;

" $1: gzip compressed"*)

gunzip " $ 1 ";;

" $1: bzip2 compressed"*)

bunzip2 " $ 1 ";;

*)错误“文件$1不能用smartzip解压缩”;;

environmental systems applications center 环境系统应用程序中心

您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含传递给程序的第一个参数值。也就是说,当我们运行时:

smartzip articles.zip

$1是字符串articles.zip

Select expression是bash的扩展应用,尤其擅长交互使用。用户可以从一组不同的值中进行选择。

选择变量于...;做

破裂

完成的

....现在可以使用$var....

这里有一个例子:

#!/bin/sh

echo“你最喜欢的操作系统是什么?”

在“Linux”“Gnu Hurd”“Free BSD”“其他”中选择var做

破裂

完成的

回显“您选择了$var”

以下是运行该脚本的结果:

你最喜欢的操作系统是什么?

1) Linux

2) Gnu赫德

3)免费BSD

4)其他

#?1

您选择了Linux

您还可以在shell中使用以下循环表达式:

在…期间...;做

....

完成的

While-loop将一直运行,直到表达式测试为真。当我们测试的表达式为真时将运行。关键字“break”用于跳出循环。关键字“continue”用于跳到下一个循环,而不执行其余部分。

for循环表达式查看字符串列表(由空格分隔的字符串),并将它们赋给一个变量:

为了改变....;做

....

完成的

在下面的例子中,ABC将分别被打印到屏幕上:

#!/bin/sh

用于在一个B C中变化;做

echo“var is $ var”

完成的

下面是一个比较有用的脚本showRPM,它的作用是打印一些RPM包的统计数据:

#!/bin/sh

#列出一些RPM包的内容摘要

#用法:showrpm rpmfile1 rpmfile2...

#示例:showrpm /cdrom/RedHat/RPMS/*。每分钟转数

对于$*中的rpmpackage做

if[-r " $ rpmpackage "];然后

echo " = = = = = = = = = = = = = = = $ rpmpackage = = = = = = = = = = = = = = "

rpm-qi-p $ rpm包

其他

echo "错误:无法读取文件$rpmpackage "

船方不负担装货费用

完成的

第二个特殊变量$ *出现在这里,它包含所有输入命令行参数值。如果运行showrpm OpenSSH。rpmw3m.rpmwebgrep.rpm

此时$ *包含三个字符串,即OpenSSH。rpm,W3M。rpm和Webgrep。转速。

引用

在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓的扩展名,是指程序将通配符(如*)替换为合适的文件名,其他变量替换为变量值。为了防止程序进行这种替换,你可以使用引号:让我们看一个例子,假设在当前目录中有一些文件,两个jpg文件,mail.jpg和tux.jpg。

#!/bin/sh

回声*。使用jpeg文件交换格式存储的编码图像文件扩展名

这将打印出“mail.jpg tux.jpg”的结果。

引号(单引号和双引号)将防止通配符扩展:

#!/bin/sh

回声" *。jpg "

回声*。' jpg '

这将打印“*”。jpg”两次。

单引号更严格。它可以防止任何变量扩大。双引号可以防止通配符扩展,但允许变量扩展。

#!/bin/sh

echo $SHELL

回显" $SHELL "

回显' $SHELL '

运行结果如下:

/bin/bash

/bin/bash

$SHELL

最后,还有一种方法可以防止这种扩展,那就是使用转义符——反对角线:

回声*。使用jpeg文件交换格式存储的编码图像文件扩展名

echo $SHELL

这将输出:

*.使用jpeg文件交换格式存储的编码图像文件扩展名

$SHELL

此处文档

在这里,documents是向命令传递几行文本的好方法。为每个剧本写一段有帮助的文字是非常有用的。这时,如果我们有那个here文档,就不需要用echo函数逐行输出了。“这里的文档”以

#!/bin/sh

#我们只有不到3个论点。打印帮助文本:

if[$ #-lt 3];然后

cat & lt& lt帮助

ren -使用sed正则表达式重命名多个文件

用法:ren 'regexp' 'replacement '文件...

示例:重命名所有*。HTM档案在*。html:

任' HTM$' 'html' *。html文件的后缀

帮助

出口0

船方不负担装货费用

OLD="$1 "

NEW="$2 "

shift命令从列表中删除一个参数

#命令行参数。

变化

变化

# $*现在包含所有文件:

对于$*中的文件;做

if[-f " $ file "];然后

NEW file = ` echo " $ file " | sed " s/$ { OLD }/$ { NEW }/g " ` 1

if[-f " $ new file "];然后

echo“错误:$newfile已经存在”

其他

回显“将$file重命名为$newfile ...”

mv "$file" "$newfile "

船方不负担装货费用

船方不负担装货费用

完成的

这是一个更复杂的例子。下面详细讨论一下。第一个if表达式确定输入命令行参数是否小于3(特殊变量$ #表示参数的数量)。如果输入参数小于3,帮助文本将被传递给cat命令,然后由cat命令打印在屏幕上。程序在打印帮助文本后退出。如果输入参数等于或大于3,我们将把第一个参数赋给变量OLD,把第二个参数赋给变量NEW。接下来,我们使用shift命令从参数列表中删除第一个和第二个参数,这样原来的第三个参数就变成了参数列表$ *中的第一个参数。然后我们开始循环,命令行参数列表被逐个赋给变量$file。然后我们判断文件是否存在,如果存在,搜索并用sed命令替换,生成新的文件名。然后将反斜杠中的命令结果赋给newfile。这样,我们达到了目的:我们得到了旧文件名和新文件名。然后使用mv命令将其重命名。

功能

如果你写一个稍微复杂一点的程序,你会发现在程序的几个地方可能会用到同一个代码,你也会发现如果我们用函数的话会方便很多。一个函数看起来像这样:

函数名()

{

#在函数体$1中,是函数的第一个参数

# $2秒...

身体

}

你需要在每个程序的开始声明这个函数。

下面是一个名为xtitlebar的脚本,使用它可以更改终端窗口的名称。这里使用了一个名为help的函数。如您所见,这个定义的函数使用了两次。

#!/bin/sh

# vim: set sw=4 ts=4 et:

帮助()

{

cat & lt& lt帮助

修改一个xterm,gnome终端或者kde konsole的名字

用法:xtitlebar[-h]" string _ for _ titel bar "

选项:-h帮助文本

例如:xtitlebar“CVS”

帮助

出口0

}

#如果出现错误或给定了-h,我们调用函数help:

[-z " $ 1 "]& amp;& amp帮助

[" $ 1 " = "-h "]& amp;& amp帮助

#发送转义序列以更改xterm titelbar:

echo-e " 33]0;$107"

#

在脚本中提供帮助是一个很好的编程习惯,可以方便其他用户(和您)使用和理解脚本。

命令行参数

我们已经看到了特殊变量,如$ *和$1,$2...$9,这些特殊变量包含用户从命令行输入的参数。到目前为止,我们只学习了一些简单的命令行语法(比如一些强制参数和查看帮助的-h选项)。但是当编写更复杂的程序时,你可能会发现你需要更多的定制选项。通常的做法是在所有可选参数前加一个减号,后面跟参数值(比如文件名)。

有许多方法可以分析输入参数,但是下面使用case表达式的例子是一个很好的例子。

#!/bin/sh

帮助()

{

cat & lt& lt帮助

这是一个通用的命令行解析器演示。

用法示例:cmd parser-l hello-f--some file 1 some file 2

帮助

出口0

}

while[-n " $ 1 "];做

案例$1英寸

帮助;shift 1;;#调用函数帮助

-f)opt _ f = 1;shift 1;;#变量opt_f已设置

-l)opt _ l = $ 2;班次2;;# -l接受一个参数-& gt;移动2

-)移位;打破;;#选项结束

-*) echo”错误:没有这样的选项$1。-h表示帮助";出口1;;

*)破;;

environmental systems applications center 环境系统应用程序中心

完成的

echo“opt _ f是$opt_f”

echo“opt _ l是$opt_l”

echo "第一个参数是$1 "

echo "第二个参数是$2 "

您可以像这样运行脚本:

cmd parser-l hello-f--some file 1 some file 2

返回的结果是:

opt_f是1

opt_l是hello

第一个参数是-somefile1

第二个参数是somefile2

这个脚本是如何工作的?该脚本首先遍历所有输入命令行参数,将输入参数与case表达式进行比较,设置一个变量,如果匹配就删除参数。按照unix系统的约定,应该先输入包含负号的参数。

例子

一般编程步骤

现在我们来讨论一下写剧本的一般步骤。任何优秀的脚本都应该有帮助和输入参数。并且编写一个伪脚本(framework.sh)是一个非常好的想法,它包含了大多数脚本需要的框架结构。这时,在编写新的脚本时,我们只需要执行copy命令:

cp framework.sh myscript

然后插入自己的函数。

让我们再看两个例子:

二进制到十进制的转换

脚本b2d将二进制数(如1101)转换成相应的十进制数。这也是使用expr命令进行数学运算的一个示例:

#!/bin/sh

# vim: set sw=4 ts=4 et:

帮助()

{

cat & lt& lt帮助

b2h -将二进制转换为十进制

用法:b2h [-h] binarynum

选项:-h帮助文本

例如:b2h 111010

将返回58

帮助

出口0

}

错误()

{

#打印错误并退出

echo "$1 "

1号出口

}

lastchar()

{

#返回$rval中字符串的最后一个字符

if[-z " $ 1 "];然后

#空字符串

rval= " "

返回

船方不负担装货费用

# wc在输出后面留出一些空间,这就是我们需要sed的原因:

numofchar = ` echo-n " $ 1 " | WC-c | sed ' s///g ' ` 1

#现在去掉最后一个字符

rval = ' echo-n " $ 1 " | cut-b $ numofchar '

}

斩()

{

#删除字符串中的最后一个字符,并在$rval中返回

if[-z " $ 1 "];然后

#空字符串

rval= " "

返回

船方不负担装货费用

# wc在输出后面留出一些空间,这就是我们需要sed的原因:

numofchar = ` echo-n " $ 1 " | WC-c | sed ' s///g ' ` 1

if[" $ numofchar " = " 1 "];然后

#字符串中只有一个字符

rval= " "

返回

船方不负担装货费用

numofcharminus 1 = ' expr $ numofchar "-" 1 '

#现在除了最后一个字符之外,删除所有字符:

rval = ` echo-n " $ 1 " | cut-b 0-$ { numofcharminus 1 } ` 1

}

while[-n " $ 1 "];做

案例$1英寸

帮助;shift 1;;#调用函数帮助

-)移位;打破;;#选项结束

-*) error”错误:没有这样的选项$1。-h表示帮助";;

*)破;;

environmental systems applications center 环境系统应用程序中心

完成的

#主程序

总和=0

重量=1

#必须给定一个参数:

[-z " $ 1 "]& amp;& amp帮助

binnum="$1 "

binnumorig="$1 "

while[-n " $ binnum "];做

lastchar "$binnum "

if[" $ rval " = " 1 "];然后

sum=`expr "$weight" "+" "$sum " `

船方不负担装货费用

#删除$binnum中的最后一个位置

印章" $binnum "

binnum="$rval "

weight = `expr " $ weight " " * " 2 '

完成的

echo "二进制$binnumorig是十进制$sum "

#

该脚本中使用的算法是使用十进制和二进制权重(1,2,4,8,16,...),例如二进制“10”可以这样转换成十进制:

0 * 1 + 1 * 2 = 2

为了得到一个二进制数,我们使用lastchar函数。这个函数使用WC–C计算字符数,然后使用cut命令取出最后一个字符。Chop函数是删除最后一个字符。

文件循环程序

也许你是想把所有发来的邮件都存到一个文件里的人之一,但是几个月后,文件可能会变得很大,以至于对文件的访问速度会变慢。下面的脚本rotatefile可以解决这个问题。这个脚本可以将邮件保存文件(假设为outmail)重命名为outmail.1,对于outmail.1,则变成outmail.2,以此类推。...

#!/bin/sh

# vim: set sw=4 ts=4 et:

ver="0.1 "

帮助()

{

cat & lt& lt帮助

rotatefile -旋转文件名

用法:rotatefile [-h] filename

选项:-h帮助文本

示例:rotatefile out

例如,这会将out.2重命名为out.3,将out.1重命名为out.2,将out重命名为out.1

并创建一个空的输出文件

最大数量是10

版本$ver

帮助

出口0

}

错误()

{

echo "$1 "

1号出口

}

while[-n " $ 1 "];做

案例$1英寸

帮助;shift 1;;

-)破;;

-*) echo”错误:没有这样的选项$1。-h表示帮助";出口1;;

*)破;;

environmental systems applications center 环境系统应用程序中心

完成的

#输入检查:

if[-z " $ 1 "];然后

错误“错误:您必须指定一个文件,使用-h获取帮助”

船方不负担装货费用

filen="$1 "

#重命名任何. 1、. 2等文件:

对于9 8 7 6 5 4 3 2 1中的n;做

if [ -f "$filen。$ n "];然后

p= '表达式$n + 1 '

回声”mv $filen。$n $filen。$p "

mv $filen。$n $filen。$p

船方不负担装货费用

完成的

#重命名原始文件:

if[-f " $ filen "];然后

echo "mv $filen $filen.1 "

mv $filen $filen.1

船方不负担装货费用

echo touch $filen

触摸$filen

这个脚本是如何工作的?在检测到用户提供了文件名后,我们从9到1进行了一次循环。文件9被命名为10,文件8被重命名为9,依此类推。循环完成后,我们将原始文件命名为file 1,并创建一个与原始文件同名的空文件。

试运行测试/调试

最简单的调试命令当然是使用echo命令。您可以使用echo在您怀疑有问题的地方打印任何变量值。这也是为什么大多数shell程序员花80%的时间调试程序。Shell程序的好处是不需要重新编译,插入一个echo命令也不需要太多时间。

Shell也有真正的调试模式。如果脚本“strangescript”中有错误,可以这样调试:

sh -x奇异脚本

这将执行脚本并显示所有变量的值。

shell还有一种模式,在这种模式下,您不需要执行脚本,只需检查语法。你可以这样使用它:

你的脚本

这将返回所有语法错误。