ShellProgramming

这个文档用于记录自己在学习shell编程时的一些心得,还有一个示例以及对其的解读,以便日后的查阅。

文件基本结构

一个完整的shell文件中可能有的部分为:

  • #!/bin/sh,第一行的这个命令告诉系统应该用哪个程序来运行这个shell文件。
  • 代码,shell程序将要执行的代码部分
  • 注释,注释以#开头,#的后面皆为注释内容

shell中的变量

在shell中定义变量的格式为:

var1="Hello"
var2=13

代码第一行定义了一个var1变量,并将其赋值为Hello,第二行则定义了一个var2变量,定义为13

注意:在代码中=两边不能加空格,(像这样var1 = Hello),这样会使shell认为var1是一个命令,而不是你定义的变量的名字

如果要使用变量,使用下面的语法:

echo $var1

echo是一个命令,他会将后面的内容输出到终端上。$表示取值,这行命令的含义是将变量var1中的值输出到终端上,如果不加$echo var1),则表示将字符串var1输出到终端上。

在表示一个字符串的时候,我们通常用""''将字符串包围起来,而单引号跟双引号是有区别的。下面的程序展示了这种区别:

var="Hello"

echo "$var"
echo '$var'

# 输出为
Hello
$var

使用双引号时,碰到$时,会将其作为一个取值符号,并对其进行处理,将变量中的值取出来。而使用单引号时,只是将$作为一个字符,并不代表取值操作。

下面介绍另一个命令,read表示从终端中读取一个值并赋值给一个变量,这个被赋值的变量可以是之前没有被声明过的变量。例如:

read x
echo $x

控制流语句

shell中也有一些判断和循环语句用来控制程序的执行。

if语句

下面是一个简单的if语句示例:

if test -f first
then	#part of the if control segment
echo file \"first\" exist.
else
echo file \"first\" doesn\'t exist.
fi	#suggest end of if statement

这个程序的作用是判断当前文件夹中是否存在名为first的文件。其中if控制语句的框架为:

if test ARGUMENT
then	
	#code segment
else
	#code segment
fi

其中test是一个命令,他根据后面的参数ARGUMENT返回truefalseif语句根据test命令的返回值来决定执行哪一个代码块。可以在shell中使用man test命令来查看更多test的用法。这里test后面的-f first表示判读当前目录是否存在名为first的文件。

if后面要跟上一个then表示分支语句块的开始,对应的在语句块结束的时候要加上fi。在shell中,一行表示一个语句,所以语句之间不需要分隔符。但是也可以将多个语句写在同一行中,但是这样的话就需要用分号;隔开。

elif语句

elif语句的框架如下:

read num	

if test EXPRESSION1
then
	CODE
elif test EXPRESSION2
then
	CODE
elif test EXPRESSION3
then
	CODE
else
	
fi

注意:如果判断语句中出现了if,则在判断语句的后面必须要加上then与其配套,而对于else则不需要。

下面是一个简单的elif语句示例:

if test $num = 1	#use $ to get content from the variable num
then
	echo You get one apple.

elif test $num = 2
then
	echo You get two apples.

elif test $num = 3
then
	echo You get three apples.
else
	echo You input is $num
	echo Please enter a number between 1 and 3.
fi

for循环

下面是一个简单的for循环例子:

for foo in bar fud 43	#take three strings (bar, fud, 43) as input
do
	echo $foo
done

这段代码中,for循环的框架为:

for VAR in LIST
do
	LOOP
done

跟其他的代码快一样,for循环的循环体也有一个dodone表示代码快的开始和结尾。VAR表示LIST中的其中一个元素,LIST中可以是字符串,变量(使用变量的时候记得加$),或者数字。

while循环

下面是一个简单的while循环的例子:

num=1	#don't use num = 1, space makes shell think num is a command
while test $num != 0
do
	read num
	echo You entered $num, enter 0 to exit.
done

该程序从终端中读取数字,如果数字是0,则跳出循环。其中while循环的框架为:

while test EXPRESSION
do
	CODE
done

其中test命令的用法跟if判断语句中的用法一致,并且while循环也有表示循环体开始和结束的关键字dodone

&& 和 ||

跟C语言一样,shell中也有表示条件与和或的运算符,并且其写法和用法都跟C语言中的一致,下面是一个简单示例:

echo Please enter num1:
read num1
echo Please enter num2:
read num2
echo num1 = $num1, num2 = $num2
if test $num1 = 0 && $num2 = 0	#each sub segment requires a test command
then
	echo Double zeros
elif test $num1 = 0 || test $num2 = 0
then
	echo One zero
else
	echo No zero
fi

函数

在shell中也可以定义函数,但是调用的时候跟其他语言有很多不一样的地方,特别是传递参数的方式。下面看一个例子:

set "Hello"

#定义函数
fun(){
	echo $0
	echo $1
	echo $2
}

#调用函数

fun Hello var2
###################输出########################
/bin/bash
Hello
var2
###############################################

跟很多编程语言不一样,shell中的函数如果要接受参数,不需要在函数签名中声明参数列表。只需要在函数中使用$1,$2…就行,注意这里是从1开始的,而$0是shell中的$0的值。

同样如果函数需要返回一个值,也不需要做任何声明,而只需要在函数体中直接用return返回即可。

常用命令

set

在shell运行的时候,在其运行环境中存在一系列变量,他们用$NUM表示,例如$0,$1,$2,…。他们可以在运行一个shell文件的时候给定,也可以在shell中用set命令进行设置。在默认的情况下,$0的值是/bin/bash,也就是shell终端的路径。

在使用set设置参数的时候,是从$1开始设置的,如果想设置$2,则需要提供两个参数,以此类推,想要设置$3则需要提供3个参数。例如:

echo $0 $1 $2
###################输出########################
/bin/bash


###############################################

set "Again" "Variable2"
echo $0 $1 $2
###################输出########################
/bin/bash
Again
Variable2
###############################################

set "Hello"
echo $0 $1 $2
###################输出########################
/bin/bash
Hello

###############################################

在默认状态下,只有$0参数有值为/bin/bash,后面的参数都是空的。可以通过set命令为shell设置参数,设置了参数之后

shift

该命令使当前shell中的参数向左移动一位,而最左端的参数将被丢弃,但是$0不受影响。下面看一个例子:

echo '$0' = $0, '$1' = $1, '$2' = $2, '$3', = $3
set var1 var2 var3
echo '$0' = $0, '$1' = $1, '$2' = $2, '$3', = $3
shift
echo '$0' = $0, '$1' = $1, '$2' = $2, '$3', = $3
#############################Output###################################
$0 = /bin/bash, $1 = , $2 = , $3 =
$0 = /bin/bash, $1 = var1, $2 = var2, $3 = var3
$0 = /bin/bash, $1 = var2, $2 = var3, $3 =

break

break命令跟C语言中的命令含义跟用法一样,这里不再赘述。

:

在shell中,:运算符表示一个空命令,跟C语言中的continue类似。但这只是:的其中一个用法,还有一个常见的用法就是用来构建无限循环。例如:

echo Please enter a num \(0 to break\)
while :
do
	read x
	if test $x = 0
	then
		echo You entered a 0, break out of the loop
		break;
	fi
done

.

.命令会在当前shell程序下执行另一个shell文件,其运行原理跟C语言中的#include类似,它会将另一个shell文件中的文字内容复制到当前shell程序中,并执行其内容。也就是说,执行这个命令相当于你手动从另一个shell文件中复制里面的内容,并粘贴到当前命令行进行运行,这样的运行方式可以使另一个shell文件中的操作改变当前shell程序的环境。下面是一个例子:

##############################Main.sh#################################
var="main"
echo $var
. ./Second.sh
echo
##############################Second.sh###############################
var="second"
##############################Output##################################
main
second

在上面的程序中,文件Main.sh中定义了一个变量var,并且在使用.调用的Second.sh文件中可以访问并改变Main.shvar的值。

eval

eval命令将一个字符串作为一条命令并执行,这条命令在需要动态地根据程序运行情况来决定执行什么命令的时候特别管用,下面是一个例子:

foo=10
x=foo	
y='$'$x	
echo $y

eval res=$y

echo $res

这段程序首先定义了一个foo变量并将其初始化为10,接着定义了一个x变量,并将字符串foo赋值给他。注意这里并不是将变量foo赋值给x,将变量foo赋值给x的语法为x=$foo。进一步,程序在x的值(也就是字符串foo)前面加上了一个字符$,将其变成一个字符串$foo。最后使用eval命令将变量y中的值(字符串$foo)作为一条命令执行,执行的结果就是变量foo中的之被取出,进一步被赋值给变量res

exit

exit命令将使当前shell程序退出,并返回一个状态码,其中0表示执行成功,1表示失败,这里跟平时判断布尔值的方式不一样需要注意。下面是语法:

exit 0	#程序执行成功,返回成功状态码0
exit 1	#程序执行失败,返回失败状态码1

exec

这个指令会结束当前运行的shell程序,并在推出之后运行一个命令。例如:

##############################exec.sh#################################
 
exec ls

##############################Terminal################################
./exec.sh

上面的代码在一个终端中调用exec.sh文件,该文件中有一个exec ls指令,该指令导致exec.sh立即推出,并且在退出之后立即执行命令ls

export

该命令会让当前shell环境中的一个变量对于其子shell可见,子shell就是从当前shell文件中打开的shell程序。例如:

##############################main.sh#################################
var="Hello"
echo var in main bash is $var

export var

./sub.sh
##############################sub.sh##################################
echo var in sub bash is $var

##############################Output##################################
var in main bash is Hello
var in sub bash is Hello

可以看到,在子shellsub.sh中即使没有定义变量var也可以访问到var中的值,这就是以为在父shell中用export命令将已经定义好的var变量传递到了子shell中。

expr

该命令接受一个参数作为表达式,它会将该表达式的值打印到终端上。这里的表达式跟eval可以接受的不一样,eval接受的是命令,而expr可以接受的表达式是有要求的,只能接受固定的一些表达式。至于expr命令具体可以接受哪些表达式,可以使用man expr命令查看,下面是一个例子:

x=1
expr x + 1
#############################Output###################################
2

unset

该命令用于从环境中删除变量或函数,但是不能删除shell本身的定义的只读变量。例如:

foo=Hello
echo $foo

unset foo

echo $foo
#############################Output###################################
Hello

上面程序使用unset将自己定义的foo变量删除,所以在第二次echo的时候,没有输出任何值。

trap

该命令使得当程序接到系统发送的给定命令时,执行相应的操作。通常用于处理系统中断,异常等。其中系统会发送的信号有:

信号说明
HUP(1)挂起,通常因终端掉线或用户退出引发
INT(2)中断,通常因按下Ctrl+C组合键而引发
QUIT(3)退出,通常因按下Ctrl+\组合键而引发
ABRT(6)中止,通常因某些严重的执行错误而引发
ALRM(14)报警,通常用来处理超时
TERM(15)终止,通常在系统关机时发送

trap命令使用时的通常格式为trap 'command' signal
下面是一个例子:

trap "rm -f *.tmp" INT

命令的执行

$()

这个命令会将括号里的命令执行的结果作为返回值返回,可以使用这个命令将另一个命令的返回值存储到一个变量里。例如

ls
############################Output####################################
first.sh	second.sh	third.sh

上面的ls命令将当前目录下所有文件名字打印在终端上,我们可以使用$()ls命令的输出存储在一个变量里,而不是直接打印在终端上。

x=$(ls)
echo $x
############################Output####################################
first.sh second.sh third.sh

$(())

算术扩展$(())跟之前的eval命令效果一样,但是eval会额外打开一个shell程序来执行给定的算术内容,而$(())的执行会更加高效。

为什么需要这样一个算术扩展呢?,在shell中,所有的变量都是以字符串的形式存储的,例如你定义一个变量x=1,实质上你是定义了一个字符串1而不是一个整数1。所以如果直接使用算术表达式进行运算,例如x=$x+1x不会像我们预想的那样等于2,而是等于1+1。因为在上面的表达式中,shell进行的只是字符串的拼接操作,注意x是一个字符串1,而不是整数1。所以需要使用算术扩展使字符串1变成整数1。像这样,x=$(($x + 1))

${}

参数扩展,参数扩展用于对给定参数进行说明。通过{}中的不同值,可以对变量的值做各种处理。其中不同的格式对应不同的处理如下:

注意:花括号里面的变量名不需要再用$进行取值操作。对于shell环境变量(使用$1$2等访问的),我们只需要通过${1}这样的格式访问即可,不需要加上$

格式处理
${param:-default}如果param为空,就把他设置为字符串default
${#param}返回param的长度
${param%word}从param尾部开始删除与word匹配的最小部分,然后返回剩余部分
${param%%word}从param尾部开始删除与word匹配的最大部分,然后返回剩余部分
${param#word}从param头部开始删除与word匹配的最小部分,然后返回剩余部分
${param##word}从param头部开始删除与word匹配的最大部分,然后返回剩余部分

参数扩展还有另外一种使用方式,假设要对一系列已经编号的文件(1_file, 2_file, 3_file)进行处理。下面的程序

for i in 1 2 3
do
	process ${i}_file
done

如果这里不使用参数扩展${}而使用$i_file,shell会认为整个后面的字符串i_file表示一个变量,便会寻找一个名为i_file的变量,但是这样的变量并不存在,这样就会引发程序错误。

Here 文档

该指令的作用是截取shell中的一部分连续的字符串作为一个临时文档,并将该文档输入到命令当中,这样刻意使得命令像是从一个文档中读取内容。例如对于cat命令,该命令接收一个文件名作为参数,并将文件中的内容打印到控制台上。但是如果我们要在shell中动态输入内容给cat,让其将内容打印到控制台上,就可以使用Here文档

cat << MARK
first line of the content
second line of the content
.....
last line of the content
MARK

############################Output####################################
first line of the content
second line of the content
.....
last line of the content

上面的<<表示下面的内容是一个Here文档MARK表示一个标志,表示HERE文档的开始,并且该文档也已该标志结束。所以实际中使用Here文档的时候要选取一个与正文内容不会冲突的字符串作为开始和结束的标志。使用这个命令,就可以用手动输入字符串的方式输入内容给那些接受文件作为输入的命令。

Here文档还有另一个用法,就是将一系列操作写在一个Here文档中,并将该文档输入到程序中,这样做的效果就像是程序在执行的过程中从命令行连续接受了一系列输入作为指令。例如,下面一个程序使用vim编辑器删除给定文件中的第一行,并保存退出:

vim FILE << MARK
ggdd:wq
MARK

上面的例子可以看到,通过Here文档输入的指令跟用键盘输入的指令一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值