shell编程追加2

这时我们可以看到这并不是一件很难的事情,不是这样吗?我们必须转义圆括号,这样他就不会被Shell所保护,同时用引号引用*,这样他就可以直接传递给find命令了.
既然我们现在能够可靠的查找文件,下面我们就来看一下当我们查找指定的文件时我们可以进行的一些协作.我们要再一次强调,我们在这里所列出的只是一些最常用的选项,我们可以查看手册页得到全部的集合.
-exec command    执行一个命令.这是我们最常执行的动作.
-ok command    与-exec相类似,所不同的只是他会提示用户在执行将要执行的命令之前进行命令的确认.
-print        打印出文件名
-ls        使用ls命令列出当前的文件
-exec和-ok命令会同一行的参数子序列作为他的参数的一部分,直到遇到一个终结符\;序列.对于-exec和-ok来说字符串{}是珍上特殊的类型,而且会为当前文件的绝对路径所替换.
这样的解释也许并不是太认人容易理解,但是一个例子也许可以很好的来说明这些.
如下面的一个简单的例子:
$ find . -newer while2 -type f -exec ls -l {} \;
-rwxr-xr-x    1 rick     rick          275 Feb 8 17:07 ./elif3
-rwxr-xr-x    1 rick     rick          336 Feb 8 16:52 ./words.txt
-rwxr-xr-x    1 rick     rick         1274 Feb 8 16:52 ./words2.txt
-rwxr-xr-x    1 rick     rick          504 Feb 8 18:43 ./_trap
$
正如我们现在所看到的,find命令是相当有用的.要用好这个命令只需要一些简单的练习.然而这样的练习也许要付一定的代价,所以我们应做一些find命令的实验.
grep命令
我们将要看到的第二个非常有用的命令为grep命令,这是一个并不常见的名字,他是通用正则表达式解析器的简称(General Regular Expression Parser).我们使用find命令在我们的系统是查找所需的文件,但是我们却要使用grep命令在文件中查找指定的字符串.而事实上,最常用的做法就 是当我们在使用find命令时将grep作为一个命令传递给-exec.
grep命令可以带选项,匹配的模式以及我们要在其中查找的文件:
grep [options] PATTERN [FILES]
如果并没有指定文件名,他就会搜索标准输入.
让我们从grep命令的主要的选项开始.我们在这里列出的只是一些主要的选项,我们可以从手册中得到更为详细的内容说明.
-c    打印出匹配行的总数,而不是打印出匹配的行
-E    打开扩展表达式
-h    禁止将在其中查找到匹配内容的文件名作为输出行的前缀
-i    忽略大小写
-l    列出带用匹配行的文件名,而不是输出实际的匹配行
-v    将匹配类型转换为选择不匹配的行而不是匹配的行
如下面的一些例子:
$ grep in words.txt
When shall we three meet again. In thunder, lightning, or in rain?
I come, Graymalkin!
$ grep -c in words.txt words2.txt
words.txt:2
words2.txt:14
$ grep -c -v in words.txt words2.txt
words.txt:9
words2.txt:16
$
工作原理:
第一个例子中并没有指定选项,grep命令只是简单在的words.txt文件中查找字符串in,并且打印出所匹配的行.在这里并没有打印出文件名,这是因为在这里我们只是使用了一个文件.
在第二个例子中打印出在两个不同的中匹配行的总数,在这种情况就要打印出文件名.
在最后的一个例子中我们使用了-v选项来转换查找的条件并且打印出在两个文件中不匹配的总行数.
正则表达式
正是我们所看到的,grep命令的基本用法是比较容易掌握的.现在我们要来看一下基本的正则表达式,这会允许我们做一些更为复杂的匹配.正如我们在前面所提到的,正则表达式是用在Linux或是共他的一些开源中的语言.我们可以在vi或是在编写Perl脚本时使用.
在正则表达式的使用过程中,一些字符会被以不同的方式进行处理.最常见的一些用法如下:
^    在一行的开头
$    在一行的结尾
.    任意一个单一字符
[]    方括号中所包含是字母的范围,其中的任何一个都可以进行匹配,例如a-e的字母范围,或者是我们可以使用^来进行反义.
如果我们要将他们作为普通的字符来使用就要在这些字符前面加上\.所以如果我们要查找一个$字符,我们就要使用\$来进行查找.
下面的是一些可以在方括号中使用的比较有用的特殊匹配:
[:alnum:]    字母数字字符
[:alpha:]    字母
[:ascii:]    ASCII字符
[:blank:]    空格或是Tab
[:cntrl:]    ASCII码控制字符
[:digit:]    数字
[:graph:]    非控制,非空格字符
[:lower:]    小写字母
[:print:]    可打印字符
[:punct:]    标点字符
[:space:]    空白字符,包括垂直Tab
[:upper:]    大写字符
[:xdigit:]    十六进制数字
另外,如果同时使用-E选项指定了扩展匹配,在正则表达式的后面也许会跟一些其他的控制匹配类型组合的字符.如果我们只是想将他们作为普通的字符进行使用,我们也要在其前面加上转义符\.
?    可选的匹配,但是最多匹配一次
*    必须匹配0个或是多个项目
+    必须匹配1个或是多个项目
{n}    必须匹配n次
{n,}    必须匹配n次或是更多次
{n,m}    匹配范围为n次到m次,包括m次
这些内容看起来有一些复杂,但是如果我们循序渐进,我们就会发现事实上这些内容并不如我们在第一眼看到时那样的复杂.最简单的掌握正则表达式的方法就是简单的试一些例子:
如果我们要查找以字符e结尾的行我们可以用下面的命令:
$ grep e$ words2.txt
Art thou not, fatal vision, sensible
I see thee yet, in form as palpable
Nature seems dead, and wicked dreams abuse
$
正如我们所看到的,这个命令会搜索出以e结尾的匹配行.
现在假设我们要查找以字母a结尾的单词.要达到这个目的,我们在方括号中使用特殊的匹配.在这样的情况下,我们要使用[[:blank:]],这会测试一个空格或是一个Tab:
$ grep a[[:blank:]] words2.txt
Is this a dagger which I see before me,
A dagger of the mind, a false creation,
Moves like a ghost. Thou sure and firm-set earth,
$
现在假设我们要查找一个以Th开头的三个字母的单词.在这种情况下,我们需要同时使用[[:space:]]来决定一个单词的结尾并使用.来匹配另外的一个字母:
$ grep Th.[[:space:]] words2.txt
The handle toward my hand? Come, let me clutch thee.
The curtain’d sleep; witchcraft celebrates
Thy very stones prate of my whereabout,
$
最后我们要使用扩展的grep命令来查找10个字符长的小写字母的单词.在这里我们要指定一个字符的范围的来匹配a到z,同时指定字符的10次重复:
$ grep -E [a-z]\{10\} words2.txt
Proceeding from the heat-oppressed brain?
And such an instrument I was to use.
The curtain’d sleep; witchcraft celebrates
Thy very stones prate of my whereabout,
$
我们在这里只是接触正则表达式一些相对来说更为重要的一部分.正如在Linux中的其他的大多数的内容,在这之外会许多的文档来帮助我们要发现更为详细的内容,但是学习正则表达式的最好的方法就是要实验这些表达式.
命令执行:
当我们编写脚本时,我们常常需要在Shell脚本中取得命令执行结果的结果来使用.也就说我们需要执行一个命令并将这个命令的输出结果放在一个变量中.这 时我们可以使用我们在前面的set命令的例子中所介绍的$(command)语法.这也是一个相对较老的格式,而最常使用的用法是`command`格 式.
所有新的脚本应使用$(...)的格式,这可以用来避免一些相当复杂的在反引号命令中使用$,`,\所造成的转换规则.如果在`...`结构中使用了反引 号,我们就需要使用\进行转义.这些相对模糊的字符会使得程序感到迷惑,有时甚至是一些经验丰富的程序也不得不进行一些试验以使得在反引号命令中的引号可 以正确的进行工作.
$(command)命令的结果只是简单的命令的输出.在这里我们要注意的是这并不是这个命令的返回状态,而是输出的字符串.如下面的例子:
#!/bin/sh
echo The current directory is $PWD
echo The current users are $(who)
exit 0
因为当前的目录是一个Shell环境变量,所以第一行并不需要使用这种命令执行结构.然而,who命令的执行结果,如果希望他在这个脚本中可见,我们就要使用这种命令结构.
如果我们希望将他们的结果放在一个变量中,我们可以像平常一样将他们赋值给一个变量:
whoisthere=$(who)
echo $whoisthere
将一个命令的执行结果放在一个脚本变量中的能力是相当强大的,因为这样就可以很容易的在脚本中使用现在的命令并取得他们的输出.如果你发现在你正在试着转 换一个标准命令在标准输出上的输出结果的参数集合并将他们作为一个程序的参数,你就会发现命令xargs会帮助你完成这一切.可以查看手册页得到更深更详 细的内容.
有时会出现的一个问题就是我们要调用的命令会在我们所希望的文本出现之前输出了一些空白符,或者是比我们所希望的更多的内容.在这样的情况下,我们可以使用我们在前面所说到的set命令.
算术扩展
我们已经使用了expr命令,这可以允许处理简单的算术命令,但是他的执行是相当的慢的,因为在处理expr命令时需要调用一个新的Shell.
一个新的更好的替换就是$((...))扩展.通过将我们所希望的表达式包在括号里以便在$((...))中进行赋值,我们可以进行更为有效的简单算术.
如下面的例子:
#!/bin/sh
x=0
while [ “$x” -ne 10 ]; do
    echo $x
    x=$(($x+1))
done
exit 0
参数扩展
我们在前面已经看到了参数分配与扩展的最简单形式,在那里我们是这样写的:
foo=fred
echo $foo
当我们要在一个变量的结尾处加上另外的一个字符时却会发生问题.假设我们要写一个简短的脚本来处理名为1_tmp和2_tmp的文件,我们可以试着用下面的脚本来处理:
#!/bin/sh
for i in 1 2
do
    my_secret_process $i_tmp
done
但是在每一个循环中,我们会得到下面的信息:
my_secret_process: too few arguments
发生了什么错误呢?
问题就在于Shell会试着将变量$i_tmp用他的变量值进行替换,但是却并不存在这个变量.而Shell并不会认为这是一个错误,而只是用空值来进行 替换,所以并没有参数传递给my_secret_process.要将$i的扩展保护为变量的一部分,我们需要将i放在一对花括号中:
#!/bin/sh
for i in 1 2
do
    my_secret_process ${i}_tmp
done
这样以后在第一个循环中,i的值会用${i}进行替换,从而给出一个实际的文件名.这样我们就已经将一个参数的值替换为一个字符串了.
我们可以在Shell中进行许多的替换.常常这样的方法会为参数的处理问题提供一个优雅的解决方法.
常用到的一些如下表:
${parm:-default}    如果一个参数为空,则将他设定为一个默认值.
${#parm}        给出参数的长度.
${parm%word}        从末尾开始,移除与word相匹配的最小部分并返回其余的部分.
${parm%%word}        从末尾开始,移除与word相匹配的最长部分并返回其余的部分.
${parm#word}        从开头开始,移除与word相匹配的最小部分并返回其余的部分.
${parm##word}        从开头开始,移除与word相匹配的最长部分并返回其余的部分.
这些替换对于我们要处理字符串来说是相当有用的.而最后的四个可以用来移除字符串中的部分内容,而这对于处理文件名和路径是更为有用的.如下面的一些例子中所示的:
#!/bin/sh
unset foo
echo ${foo:-bar}
foo=fud
echo ${foo:-bar}
foo=/usr/bin/X11/startx
echo ${foo#*/}
echo ${foo##*/}
bar=/usr/local/etc/local/networks
echo ${bar%local*}
echo ${bar%%local*}
exit 0
如果我们运行这个脚本我们会得到下面的输出结果:
bar
fud
usr/bin/X11/startx
startx
/usr/local/etc
/usr
工作原理:
第一个句子,${foo:-bar},会为foo的值指定为bar,因为当这个语句开始执行时并没有为foo指定任何值.foo的值会保持不变直到他遇到unset语句.
在这里我们有一些需要我们注意的内容:
${foo:=bar}将会设置变量$foo.这个字符串运算符会检测foo存在并且不为空值.如果他不为空,则会返回他的值,但是如果是相反的情况,就会将foo的值设为bar并且会返回替换的结果值.
${foo:?bar}会打印出foo: bar,而如果foo并不存在或是他被设为空值则会退出命令.
最后,${foo:+bar},如果foo存在并且不为空则会返回bar.
{foo#*/}语句进行匹配并且只是移除左面的内容(在这里我们要记住*匹配0个或是多个字符).{foo##*/}进行匹配并会移除尽可能多的内容,所以他会移除了最右面的/以及他前面的所有字符.
{bar%local*}语句匹配从右面开始直到第一次出现local的字符,而{bar%%local*}会从右面开始匹配尽可能多的字符,直到第一次发现local.
因为Unix和Linux都比较强的依赖于过滤的概念,所以我们常常要将一个操作的执行结果进行手工重定向.假设我们要使用cjpeg命令将一个GIF的文件转换为JPEG的文件:
$ cjpeg image.gif > image.jpg
也许有时我们会在大量的文件上进行这样的操作.这时我们如何自动重定向?我们可以很容易的这样来做:
#!/bin/sh
for image in *.gif
do
   cjpeg $image > ${image%%gif}jpg
done
这个脚本可以将当前目录下的每一个GIF文件转换成为JPEG文件.

Here Documents
从一个Shell脚本传递给一个命令的一个比较特殊的方法就是使用here document.这个文档可以使得执行的命令就像是由文件或是键盘读入的,而事实上,这是由这个脚本读入的.
一个here document是以<<开头的,后面所跟的是要在文档的结尾处重复出现的字符序列.<<是Shell的重定向标签,在这样的情 况下,他会强制将命令输入给here document.这个特殊的序列的作用就像是一个标记,来告诉Shell here document要在哪里结束.这个标记充列不可以出现在要传递给命令的行上,所以最好是将他们标记为最不常用的或是难以忘记的内容.
如下面的这个例子:
#!/bin/sh
cat <<!FUNKY!
hello
this is a here
document
!FUNKY!
而这个脚本的输出如下:
hello
this is a here
document
here document看上去也许是一个古怪的特征,但是他们却是非常强大的,因为他可以允许我们调用一个交互的程序,如编辑器,而提供给他们一些预定义的输 入.然而他们最常用到的地方却是从一个脚本的内部输出大量的文本,正如我们在前面所看到的一样,从而可以避免在每一行使用echo命令.我们使用了!来标 记每一行的标记符以保证不会造成迷惑.
如果我们希望以一个预先定义好的方式来处理一个文件中的一些行,我们可以使用ed编辑器并且可以在一个Shell脚本中使用here document传递编辑的命令.
假如有一个名为a_text_file的文件,他包含下面的一些行:
That is line 1
That is line 2
That is line 3
That is line 4
我们可以用下面的这个脚本来进行处理:
#!/bin/sh
ed a_text_file <<!FunkyStuff!
3
d
.,\$s/is/was/
w
q
!FunkyStuff!
exit 0
在我们运行这个脚本之后,这个文件的内容如下:
That is line 1
That is line 2
That was line 4
工作原理:
这个Shell脚本只是简单的调用了ed编辑,然后将一些要执行的动作命令传递给ed编辑器,这些命令分别是移动到第三行,删除此行,然后在当前行使用 what was进行替换.ed命令是由当前脚本中的here document处取得的,也就是在!FunkyStuff!之间的部分.
在这里我们要注意的一点就我们使用了\进行了$的转义.
调试脚本
调试脚本是一件非常容易的事情,但是却并没有其他的特殊的工具来帮助我们进行调试.在这里我们会概要的说明一些常用到的方法.
当发生错误时,Shell通常会打印出包含错误内容的行号.如果错误并没有立即是显示出来,我们可以添加另外的echo语句来显示变量的内容而且可以简单的输入交互Shell中进行代码片段的测试.
因为脚本是被解释的,所以修改和重试一个Shell时并没有编译的动作.跟踪更为复杂的错误的主要方法就是设置各种Shell选项.要这样做,我们可以在调用脚本后使用命令行选项或者是使用set命令.我们在这里列出一些常用到的选项:
命令行选项        set选项            描述
sh -n <script>        set -o noexec        只检查语法错误,并不执行命令
            set -n       
sh -v <script>        set -o verbose        在运行命令之前将命令打印出来
            set -v           
sh -x <script>        set -o xtrace        在命令执行之后将命令打印出来
            set -x       
            set -o nounset        当使用未定义的变量时给出错误信息
            set -u
我们可以设置打开标记选项,使用-o,或是关闭,使用+o,这样就如同在使用缩写版本.我们可以使用xtrace选项进行简单的跟踪执行.作为最初的检 查,我们可以使用命令选项,但是在最后的测试时,我们可以在脚本中会出现的问题的代码中加入xtrace标记.执行跟踪会使得Shell在执行Shell 脚本中的每一行命令之前将变量的扩展等内容打印出来.
我们可以使用下面的命令来打开xtrace:
set -o xtrace
我们也可以用下面的命令来关闭xtrace:
set +o xtrace
扩展的层次在由每一行的开始的+的个数来表示的(默认情况下).我们可以将+改为一些更有意义的内容,方法是在我们的Shell配置文件中设置Shell变量PS4的值.
在Shell中,我们还会发现我们可以通过取得EXIT的信号来得到一个程序的状态.
trap ‘echo Exiting: critical variable = $critical_variable’ EXIT
进入图形---对话实用程序
在结束我们的Shell脚本的讨论之前,我们还要讨论一个特征,虽然严格来说这并不是Shell的一部分,但是对于Shell程序来说却是相当有用的,所 以我们要在这里讨论一下.如果我们知道我们的脚本程序只在Linux文本下运行,我们可以有一个简洁的办法来加亮我们脚本的特色,那就是我们可以使用 dialog命令.这个命令会使用文本的模式和颜色,但是他看起来还是有着图形界面的色彩.
dialog是相当简单的,也只是一个有着各种参数及变量的单一程序并且允许我们显示各种类型的图形框,所示的范围也只是有着Yes/No的来进行输入的 方框甚至是菜单选择.这个程序通常会在用户进行了输入分类时返回,返回的结果可以由返回的状态得到或者是通过取回标准错误流输入的文本.
在我们在开始更为详细的说明以前,我们先来看一个非常简单的dialog操作.我们可以直接在命令行使用dialog,这可以很好的来展示原型,所以我们先创建一个简单的消息框来显示传统的第一个程序:
dialog --msgbox “Hello World” 9 18
这样就会在屏幕上显示一个图形信息框,仅是一个OK对话框.
现在我们可以看到dialog是一件多么简单的事情.下面我们可以来看一些更为详细的选项:
下表列出我们可以创建的主要的对话框.
选择对话框    --checklist    允许我们显示一个列表项,其中的每一个都可以单独选择
信息对话框    --infobox    可以立即返回的简单对话框,返回后并不会清除屏幕
输入对话框    --inputbox    允许用户输入文本
菜单对话框    --menu        允许用户从列表项中进行单一选择
信息对话框    --msgbox    向用户显示一个有着Ok按钮的信息框
单选对话框    --raidolist    允许用户从列表中选择一项
文本对话框    --textbox    允计我们在一个滚动框中显示一个文件
Yes/No对话框    --yesno        允许我们询问一个问题,可以允许用户选择yes或是no
另外还有一些其他的对话框类型可以用.如果我们想要知道更多的一些不常用的对话框,如以前一样,我们可以从手册页可得到更为详细的内容说明.
要得到任何一个允许我们进行文本输入的对话框的输出或是选择,我们需要得到错误流,通常情况下我们是将他们重定向一个临时文件,这样我们就可以在稍后进行 处理了.要得到Yes/No类型的结果,我们只需要查看一下返回代码就可以了,与其他的程序相类似,成功则返回0(例如yes选择),失败则返回1.
--checklist    text height width list-height [tag text status] ...
--infobox    text height width
--inputbox    text height width [initial string]
--menu        text height width menu-height [tag item ] ...
--msgbox    text height width
--radiolist    text height width list-height [tag text status] ...
--textbox    filename height width
--yesno        text height width
另外,所有的对话框都会带一些选项.我们在这里不会列出所有的选项,但是我们却要注意其听两个:--title,允许我们为对话框指定一个标题,--clear,用来清屏.我们可以通地查看手册页得到更为详细的内容.
下面我们开始一个简单的完整的例子.一旦我们懂得了这个例子,那么所有其他的也只就是一件简单的事了.这个例子子会创建一个选择列表类型的对话框,标题为 Check me,而说明为Pick Numbers.这个列表对话将会是15个字符高和25个字符宽,而每一个选项为3个字符高.最后我们显示出这些内容,并且在设置了默认情况下的开关选 项.
dialog --title “Check me” --checklist “Pick Numbers” 15 25 3 1 “one” “off” 2 “two”“on” 3 “three” “off”
工作原理:
在这个例子中,--checklist的参数告诉我们要创建一个checklist类型的对话框.我们使用--title选项将这个对话框的标题设置为Check me,而下一个参数则是提示的信息,在这里我们将其设为Pick Numbers.
我们接下来的操作是来设置对话框的大小.我们在这里将其设成15行高和25行宽,另外有将有3行用来显示菜单.这并不是一个很好的尺寸,但是在这里却很好的向我们显示了如何来进行布局.
也许这样的选项看上去实在是有一些太奇怪了,但是我们所要记住的只是每一个菜单选项有三个值:
标号,文本以及状态.
所以第一个项目的标号我们设为1,而显示的文本为"one"并且状态为"on".我们可以依此设置以后的各个选项.
很容易是不是?我们只要在命令行上试验一些命令我们就可以看到这个命令是如何容易的来使用了.
为了将这样的对话框放在一个程序中,我们需要能够访问用户输入的结果.这也是相当容易的,我们只要简单的文本输入重定向错误流或者是检查环境变量$?,这也就是我们在前面的命令行中所调用的返回状态.
如下面的这个例子:
#!/bin/sh
# Ask some questions and collect the answer
dialog --title “Questionnaire” --msgbox “Welcome to my simple survey” 9 18
在这里我们只是通过一个简单的对话框显示来告诉用户发生了什么.而我们并不需要得到用户输入的结果,所以这是相当简单的.
我们可以将这个程序加以改进:
dialog --title “Confirm” --yesno “Are you willing to take part?” 9 18
if [ $? != 0 ]; then
   dialog --infobox “Thank you anyway” 5 20
   sleep 2
   dialog --clear
   exit 0
fi
现在我们通过使用一个简单的yes/no对话框来询问用户是否要进行处理.我们使用环境变量$?来检测用户是否选择了yes或是no.如果用户不希望进行处理,我们就会在退出之前使用一个简单的不需要用户有输入的信息对话框.
dialog --title “Questionnaire” --inputbox “Please enter your name” 9 30 2>_1.txt
Q_NAME=$(cat _1.txt)
现在我们使用一个输入对话框来询问用户的姓名.我们重定向标准错误流2到一个临时文件,_1.txt,这样我们就可以用变量Q_NAME来进行处理了.
dialog --menu “$Q_NAME, what music do you like best?” 15 30 4 1 “Classical” 2
“Jazz” 3 “Country” 4 “Other” 2>_1.txt
Q_MUSIC=$(cat _1.txt)
现在我们显示了一个有着四个不同选项的菜单.在这里我们也将重定向标准错误流到一个临时文件并装入到一个变量中.
if [ “$Q_MUSIC” == “1” ]; then
    dialog --msgbox “Good choice!” 12 25
fi
如果用户选择了菜单选项1,这就会被存放在一个临时文件_1.txt中,这样我们可以将结果取出放在一个变量Q_MUSIC中,从而我们可以检测结果.
sleep 5
dialog --clear
exit 0
最后我们清除最后一个对话框并退出程序.
现在如果我们只需要使用Linux文本界面,从一个Shell脚本编写简单的GUI程序,我们就可以使用这样的方法.
组合
现在我们已经看到将Shell做为一门编程语言所具有的主要特征,现在我们可以将我们已经学到的这些内容组合在一起来编写一个简单的程序.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值