Linux shell高级编程(上)

1. 概述

在前面的章节中,我们讲解了shell脚本的基本编程,包括结构化命令,读取用户输入和测试命令。而这篇文章主要是描述shell脚本的高级编程部分,包括创建函数,流式编辑器sed和gawk的基本用法。

 

 

2. 函数部分

 

(1)基本格式:

function name() {

 

commands

 

}

 

示例:

!/bin/bash
#using a function in a script
#格式:function name(){ command}
function func(){
echo "this is an example of a function"

}
count=1
while [ $count -le 5 ]
do
func
count=$[ $count+1]
done
echo "this is the end of the loop"

 

(2)使用变量接收函数输出

当使用变量接收函数输出,必须用反引号,而且在函数中要用echo语句生成消息。

result=`functionname`

function functionname() {

 

echo "message"

}

 

 

#!/bin/bash
#using the echo to return a value
function db1() {
read -p "Enter a value:" value
echo  $[ $value*2 ]  #使用echo生成消息,然后用变量接收函数值
}
result=`db1`
echo "the new value is $result

 

(3)向函数传递参数环境变量

使用标准的参数环境变量表示命令行给函数传递的参数,$0表示函数名,$1表示函数命令行的第一个参数,$2表示函数命令行的第二个参数。

由于函数调用是在脚本内部的,所以只能在脚本内部向函数传递参数。在脚本外部,如在命令行中,那么传递的$1,$2是脚本参数变量,是向脚本传递的参数,此时,在函数内部是不能直接用的,因为函数有自己的特有的参数环境变量。必须手动的通过调用函数时传递到函数内部才可以使用。

脚本的参数环境变量虽然与函数的参数环境变量形式相同,但有着本质的区别。脚本的参数环境变量是脚本使用的,而在函数内部时,函数的参数环境变量是函数特有的。一个相当于全局变量,一个相当于局部变量,如果在函数中使用与脚本中相同的参数环境变量$1,那么函数中的$1当然会覆盖脚本中的$1,如果不向函数中手动传值的话,这个变量就没有值了。所以,必须向函数中传递$1,而不能直接用脚本中的$1.

 

直接使用脚本中的参数环境变量:

#!/bin/bash
#trying to access script parameters inside a function
#函数使用的变量$1和$2,不同于主脚本的$1和变量$2,如果想在函数>中使用这些值,那么必须在调用该函数时手动传入这些值
function func7(){
echo $[ $1*$2 ]

}
if [ $# -eq 2 ]
then
func7
echo "the result is $value"
else
echo "Usage:badtest a b"
fi

 

 

结果: [root@localhost chapter14]# ./test7 1 3
./test7: line 5: * : syntax error: operand expected (error token is "* ")

 

手动传递脚本中的参数环境变量

#!/bin/bash
#trying to access script parameters inside a function
#函数使用的变量$1和$2,不同于主脚本的$1和变量$2,如果想在函数>中使用这些值,那么必须在调用该函数时手动传入这些值
function func7(){
echo $[ $1*$2 ]

}
if [ $# -eq 2 ]
then
value=`func7 $1 $2`
echo "the result is $value"
else
echo "Usage:badtest a b"
fi

分析: fun7 $1 $2 把脚本中的参数环境变量手动传递到函数中去了。

 

 

(4)函数中的局部变量与全局变量

全局变量指的是在shell脚本内处处有效的变量。如果在脚本的主代码中定义了全局变量,那么在函数内部就可以获得这个变量。

而局部变量的定义可以用 local temp

#!/bin/bash
#demonstrating the local keyword
#全局变量就是在shell脚本内处处有效的如果脚本的主代码中定义了>全局变量,那么函数内部可以获得这个值,同样要在函数内部定义了>全局变量,那么脚本的主代码也可以获得这个值。而局部变量只在函>数的内部有效 local是用来定义局部变量


function fun(){
local temp=$[ $value+5 ]
result=$[ $temp*2 ]

}

temp=4;
value=6;
fun
echo "the result is $result"
if [ $temp -gt $value  ]
then
echo "temp is larger"
else
echo "temp is smaller"
fi

 

(5)向函数传递数组

向函数传递数组,必须将数组变量拆分为单个元素,然后使用这此元素值作为函数参数。函数内部可以将这些参数重组为新的数组变量。

 

传递数组示例1:

#!/bin/bash
#array variable to function set
#向函数传递数组,首先将数组用$@取出来,然后在函数内部重新构建
数组,$@用来获得所有的命令行参数
function test(){
local newarray
newarray=(`echo "$@"`) //$@代表传递过来的所有参数,然后重新构造数组
echo "the new array value is:${newarray[*]}"

}
myarray=(1,2,3,4,5)
echo "the original array is ${myarray[*]}"
test ${myarray[*]}  //传递数组元素

结果:

[root@localhost chapter14]# ./test11
the original array is 1,2,3,4,5
the new array value is:1,2,3,4,5

 

传递数组示例2:

#!/bin/bash
function add() {
local sum=0
local newarray
newarray=(`echo "$@"`)
for value in ${newarray[*]}
do
sum=$[ $sum+$value ]
done
echo $sum
}
myarray=(1 2 3 4 5)
echo "the original array is: ${myarray[*]}"
arg1=`echo ${myarray[*]}`
result=`add $arg1`
echo "the result is $result"

 

运行结果:

[root@localhost chapter11]# ./test111
the original array is: 1 2 3 4 5
the result is 15

 

返回数组:

 

#!/bin/bash
function add() {
local originarray
local newarray
local elements
local i
originarray=(`echo "$@"`)
newarray=(`echo "$@"`)
elements=$[ $#-1 ]
for((i=0;i<=$elements;i++)){
 newarray[$i]=$[ ${originarray[$i]}*2 ]

}
echo ${newarray[*]}

}
myarray=(1 2 3 4 5)
echo "the original array is:${myarray[*]}"
arg1=`echo ${myarray[*]}`
result=(`add $arg1`)
echo "the new array is:${result[*]}"

 

运行结果:

[root@localhost chapter11]# ./test112
the original array is:1 2 3 4 5
the new array is:2 4 6 8 10

 

 

(6)创建库

可以用bash shell创建函数库文件,然后可以在不同的脚本中引用该库文件。

 

函数库文件:

#!/bin/bash
#my script functions
#创建库文件在脚本内部用点操作符来运行库文件脚本,这样脚本可以
使用这些函数
function addem(){
echo $[ $1+$2 ]

}
function multem(){
echo $[ $1*$2 ]

}
function divem(){
if [ $2 -ne 0 ]
then
echo $[ $1/$2 ]
else
echo -1
fi


}

 

在脚本中调用库文件,用source命令,source命令在shell脚本内部运行库文件脚本。source有一个短小的别名,即点操作符。

下面在脚本中调用该函数库:

 

#!/bin/bash
#using functions defined in a library file
. ./myfuncs    //点操作符运行库文件脚本
value1=10
value2=5
result1=`addem $value1 $value2`
result2=`multem $value1 $value2`
result3=`divem $value1 $value2`
echo "the result of adding them is:$result1"
echo "the result of multiplying them is $result2"
echo "the result of dividing them is:$result3"

结果:

[root@localhost chapter14]# ./test14
the result of adding them is:15
the result of multiplying them is 50
the result of dividing them is:2

 

 

将库文件放到.bashrc,在root用户中就放到/root/.bashrc中去。由于每次无论bash shell是交互式启动还是从已有的shell启动新的shell,都会在主目录下查找这个文件。这样就可以在新的shell中直接使用这个库函数。

 

在 /root/.bashrc 中加入:

 

. /home/shell/myfuncs

 

然后在命令行中运行: source /root/.bashrc

[root@localhost chapter14]# addem 1 2
3
即看到,可以在shell命令行中任意调用函数库中的函数了。

 

 

 

3. sed基础知识

sed也称为流编辑器,它每次读取一行数据,将该数据与所提供的编辑器命令进行匹配,根据命令修改数据流中的数据,然后将数据输出到stdout中去。在流编辑器将全部命令和一行数据匹配完之后,它将读取下一行数据,并重复上面这个过程。处理完数据流中的全部数据之后,流编辑器停止。 gawk比sed更先进的地方是提供了一种编程语言而不仅仅是编辑器命令行。

 

 

(1)在命令行中定义编辑器命令

替换命令: s. s命令用第二个字符串去替换与文本匹配的两斜杠之间的第一个字符串。

 

例子:

[root@localhost chapter14]# echo "this is a test"|sed 's/test/big test/'
this is a big test

 

分析: 管道的输出作为流编辑器sed的输入。

 

如果从sed命令执行多个命令,只需要用-e选项,并且两个命令之间用;隔开。

 

[root@localhost chapter14]# cat <data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

 

[root@localhost chapter14]# sed -e 's/brown/green/; s/dog/cat/' data1
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.

 

(2)从文件读取编辑器命令

-f从指定文件读取命令。

script1:

 

s/brown/green/
s/fox/cat/

[root@localhost chapter14]# sed -f script1 data1
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.
The quick green cat jumps over the lazy dog.

 

 

(3)替换选项

a.替换标记

格式:

s/pattern/replacement/flags
替换标记flags:

数字: 表示新文本的替换模式

g: 表示用新文本替换现有文本的全部实例

p: 表示打印原始行的内容

w file:将替换结果写入文件

 

 

示例:

 

[root@localhost chapter14]# cat <data4
This is a test of the test script
This is the second test of the test script

 

[root@localhost chapter14]# sed 's/test/trial/2' data4
This is a test of the trial script
This is the second test of the trial script

即将第2个test替换成trial.

 

[root@localhost chapter14]# sed 's/test/trial/g' data4
This is a trial of the trial script
This is the second trial of the trial script

替换所有的test成trial.

 

p命令是打印原始行的内容。

-n选项是禁止编辑器的输出。

那么p命令与-n选项使用就表示打印生成替换命令已经修改的那些行。

 

[root@localhost chapter14]# cat <data6
this is line1
this is line2
this is line3
this is line4

 

[root@localhost chapter14]# sed -n 's/line1/line/p' data6
this is line

 

-n禁止编辑器输出

p打印已经修改的行。

所有 -n与p只打印已经替换的行。如果没有-n,将会打印已经修改的行和编辑器的输出。

 

[root@localhost chapter14]# sed  's/line1/line/p' data6
this is line
this is line
this is line2
this is line3
this is line4

 

 

b.替换字符:

正斜杠的替换。

 

sed 's///bin//bash///bin//csh/' /etc/passwd

 

必须使用反斜杠对正斜杠进行转义。由于替换标记中有正斜杠。

也可以使用一个不同的字符串定界符。如!

sed 's!/bin/bash!/bin/csh!'  /etc/passwd

 

c.使用地址

如只替换第二行。

sed '2s/dog/cat/' data1

2表示只替换第二行。

 

sed '2,3s/dog/cat/' data1
2,3表示起始行与结束行。替换第2行与第3行。

 

 sed '2,$s/dog/cat/' data1

替换第2行到结束行。 2,$

 

 

d. 使用文本模式筛选器

/pattern/command

 

 sed -n  '/root/s/bash/csh/p' /etc/passwd

 

表示匹配/root/的替换。

 

e. 组合命令

如果在单独一行上执行多个命令,用大括号将命令括在一起。

 

[root@localhost chapter14]# sed '2{
> s/fox/elep/
> s/dog/cat/
> }' data1

 

表示在2行上执行如下两个命令。

 

 

(4)删除行

删除命令用d来完成。

sed '3d' data6

表示删除第3行。

sed '2,3d' data6

表示删除第2行与第3行。

 

(5)插入与附加文本

 

插入命令(i):在指定行之前添加一个新行

附加命令(a):在指定行之后添加一个新行

 

格式:

sed '[address]command/ new line'

 

[root@localhost chapter14]# echo "testing"|sed 'i/this is a test'
this is a test
testing

 

[root@localhost chapter14]# sed '3i/this is a test' data6
this is line1
this is line2
this is a test
this is line3
this is line4

 

 

[root@localhost chapter14]# sed '3a/this is a test' data6
this is line1
this is line2
this is line3
this is a test
this is line4

 

(6)更改行 c命令

[root@localhost chapter14]# sed '3c/this is a test' data6
this is line1
this is line2
this is a test
this is line4

更改第3行。

 

[root@localhost chapter14]# sed '/line3/c/this is a test' data6
this is line1
this is line2
this is a test
this is line4

查找到line3的行更改。

 

 

(7)打印命令

p用于打印文本行。

=用于打印行号

 

[root@localhost chapter14]# echo "this is a test"|sed 'p'
this is a test
this is a test
[root@localhost chapter14]# echo "this is a test"|sed -n 'p'
this is a test

 

第一个例子的第一行是sed输出,第二行是p输出的。

 

 

[root@localhost chapter14]# sed '=' data6
1
this is line1
2
this is line2
3
this is line3
4
this is line4

 

打印行号。

sed ''

(8)读写文件

[address] w filename

 

[root@localhost chapter14]# sed -n '1,2w test' data6

[root@localhost chapter14]# cat <test
this is line1
this is line2

将第1行与第2行写入到test.

 

[address] r filename

 

 

[root@localhost chapter14]# sed '$r data11' data6
this is line1
this is line2
this is line3
this is line4
this is an added line.
this is the second added line.

从data11中读出两行加入到data6的最后一行。

 

4. gawk基础知识

gawk程序的基本格式:

gawk options program file

-F fs 字段文件分隔符

-f file 指定读取程序的文件名

-v var=value 定义gawk程序中使用的变量和默认值

-mf N 数据文件中要处理的字段的最大数目

-mr N 指定数据文件中的最大记录大小

-W keyword 指定gawk兼容模式或警告级别

 

gawk的威力在于读取一行数据,处理这些数据可以生成任意类型的报告。

 

(1)从命令行读取程序脚本

gawk程序脚本必须由{}来定义。而且脚本必须在单引号之内。

 

[root@localhost chapter14]# gawk '{print "hello"}'
hello
hello
world
hello

 

输入值时,始终打印出hello.

 

(2)从文件中读取程序脚本

自动变量: $0,$1,$2,....

$0表示整个文本行

$1表示文本行的第一个数据字段

$2表示文本行的第二个数据字段

各数据字段依据字段分割符进行分割。gawk默认的分割符是空白字符如空格符或者是制表符。

 

 

如:

gawk -F: '{print $1}' /etc/passwd

 

-F: 指定字段分割符为冒号。

 

script3:

 

{test="'s userid is"
 print $5 test $1
}

 

gawk -F: -f script3 /etc/passwd

 

-f指定读取的程序名。

 

 

(3)BEGIN与END

BEGIN表示在处理数据之前运行的脚本。

END在读取数据之后运行的脚本。

 

 

BEGIN {
print "the latest list of users and shells"
print " USERid          Shell"
PRINT "--------         ------"
FS=":"

}

{
print $1 "      " $7

}

END {

print "This concludes the listing"

}

 

运行结果:

awk -f script4 /etc/passwd
the latest list of users and shells
 USERid         Shell
root    /bin/bash
bin     /sbin/nologin
daemon  /sbin/nologin
adm     /sbin/nologin
lp      /sbin/nologin
sync    /bin/sync
shutdown        /sbin/shutdown
halt    /sbin/halt
mail    /sbin/nologin
news
uucp    /sbin/nologin
operator        /sbin/nologin
games   /sbin/nologin
gopher  /sbin/nologin
ftp     /sbin/nologin
nobody  /sbin/nologin
nscd    /sbin/nologin
vcsa    /sbin/nologin
rpc     /sbin/nologin
mailnull        /sbin/nologin
smmsp   /sbin/nologin
pcap    /sbin/nologin
ntp     /sbin/nologin
dbus    /sbin/nologin
avahi   /sbin/nologin
sshd    /sbin/nologin
rpcuser /sbin/nologin
nfsnobody       /sbin/nologin
haldaemon       /sbin/nologin
avahi-autoipd   /sbin/nologin
distcache       /sbin/nologin
apache  /sbin/nologin
postgres        /bin/bash
oprofile        /sbin/nologin
webalizer       /sbin/nologin
dovecot /sbin/nologin
squid   /sbin/nologin
mysql   /bin/bash
xfs     /sbin/nologin
named   /sbin/nologin

 

关于awk就简单介绍到这了。

 

总结:主要介绍了shell高级编程中的函数部分,sed,与awk的简单用法。要分清函数中的参数环境变量与脚本中的参数环境变量。还有awk中的自动变量,虽然写法都是一样,但是表示的意义完全不相同。关于sed,awk的高级用法下一篇文章中继续介绍。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Shell脚本高级编程教程,希望对你有所帮助。 Example 10-23. Using continue N in an actual task: 1 # Albert Reiner gives an example of how to use "continue N": 2 # --------------------------------------------------------- 3 4 # Suppose I have a large number of jobs that need to be run, with 5 #+ any data that is to be treated in files of a given name pattern in a 6 #+ directory. There are several machines that access this directory, and 7 #+ I want to distribute the work over these different boxen. Then I 8 #+ usually nohup something like the following on every box: 9 10 while true 11 do 12 for n in .iso.* 13 do 14 [ "$n" = ".iso.opts" ] && continue 15 beta=${n#.iso.} 16 [ -r .Iso.$beta ] && continue 17 [ -r .lock.$beta ] && sleep 10 && continue 18 lockfile -r0 .lock.$beta || continue 19 echo -n "$beta: " `date` 20 run-isotherm $beta 21 date 22 ls -alF .Iso.$beta 23 [ -r .Iso.$beta ] && rm -f .lock.$beta 24 continue 2 25 done 26 break 27 done 28 29 # The details, in particular the sleep N, are particular to my 30 #+ application, but the general pattern is: 31 32 while true 33 do 34 for job in {pattern} 35 do 36 {job already done or running} && continue 37 {mark job as running, do job, mark job as done} 38 continue 2 39 done 40 break # Or something like `sleep 600' to avoid termination. 41 done 42 43 # This way the script will stop only when there are no more jobs to do 44 #+ (including jobs that were added during runtime). Through the use 45 #+ of appropriate lockfiles it can be run on several machines 46 #+ concurrently without duplication of calculations [which run a couple 47 #+ of hours in my case, so I really want to avoid this]. Also, as search 48 #+ always starts again from the beginning, one can encode priorities in 49 #+ the file names. Of course, one could also do this without `continue 2', 50 #+ but then one would have to actually check whether or not some job 51 #+ was done (so that we should immediately look for the next job) or not 52 #+ (in which case we terminate or sleep for a long time before checking 53 #+ for a new job).
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值