欢迎大家来到IT世界,在知识的湖畔探索吧!
对于Linux平台下的开发者和维护人员来说,Shell编程是必须要掌握的一个知识点。通过Shell脚本能够将十分复杂的操作进行简化,从而大大的提高我们工作效率。
什么是Shell?
实际上,Shell是一个比较宽泛的概念,它可以有多种含义。比如,一个终端或命令行软件我们可以称为Shell,实际上它就是一个应用程序,是人与系统进行交互的一个操作界面;它也是一种程序语言或者命令语言,可以通过它编译一系列的脚本。
作为Shell终端软件来说,它实际上也是属于泛指。之所以这么说是因为Shell终端软件也有多种。不过,基本上所有的Linux和大多数的Mac OS X里默认用的都是Bourne Again Shell,也就是平时我们说的bash。它早在1987年由Brian Fox开发。
除bash之外,还有其他的Shell应用程序:
然而,今天我们要学习的实际上是在Shell上执行的脚本语言,所以我们说Shell脚本编程。由于它是一种解释性语言,Shell脚本编程不需要额外的安装编译器,它可以直接用编辑器直接编辑,然后直接在Shell上直接运行即可。通常,我们在编写脚本时,在第一行需要用#!来指定解释器来运行Shell脚本,比如,#!/bin/sh。
下面我们将为大家从如下几个方面全面系统的为大家梳理Shell编程的相关知识点。
输入输出
shell中有两种输出命令:echo和printf。学习程序,生硬的文字始终没有代码来的直接易懂。我们就直接通过例子来学习吧。
echo "hello world"
printf "%s %s" "hello" "world"
printf "!!!\n"
欢迎大家来到IT世界,在知识的湖畔探索吧!
从上面的例子很容易发现,echo命令默认带有换行符的,而printf则不是。与C语言中类似,printf是可以进行格式化输出的,其中,%s就是格式控制符,还有%c %d %f等控制符。另外,还可以通过在控制符中添加数字来制定字符的长度,比如,%3s表示三个字符长度;%4.3f表示4位整数,3位小数。
欢迎大家来到IT世界,在知识的湖畔探索吧!#!/bin/sh
printf "%6s %3s %4s %5s\n" 公司名 评级 销售额 市场占比
printf "%6s %3c %4d %5f\n" 公司A A 5001 0.5001
printf "%6s %3c %4d %5f\n" 公司B C 1999 0.1999
printf "%6s %3c %4d %5f\n" 公司B B 3000 0.3
如果需要字符对齐,还可以使用–进行字符左对齐,省略不加默认右对齐。
在shell中是使用read命令作为输入,它可以接受标准键盘的输入;除此之外,也可以作为文件描述符的输入,对文件进行操作。
read命名的格式如下:
read [选项名] [变量名]
read命令在执行时,会将输入的数据放到变量中,通常,我们会指定一个自定义的变量,如果不指定变量的话,则就会把输入的数据保存到REPLY变量中。关于变量的一些具体信息可以在下面的变量章节了解。
read命令的选项名有下面几种参数,我们可以选择一种或几种进行设置。
- -p:设置提示信息,屏幕会输出提示信息;
- -t:设置等待时间(秒),等待用户输入的时间;
- -n:设置接收指定的字符数;
- -s:隐藏输入的数据,用于比较隐私机密信息的输入。
具体的使用方法大家可以参考下面的例子:
欢迎大家来到IT世界,在知识的湖畔探索吧!#!/bin/sh
read -p "input a name:" name
read -p "input a password:" -s passwd
echo ""
echo $name
echo $passwd
注释
每种语言都少不了注释,对于Shell也是一样。一个好的注释可以让代码更容易阅读和维护。shell脚本里也可以使用两种注释:单行注释和多行注释。单行注释可以直接在所在行使用#,多行注释就需要:<<!和!。
# 这是一行内容
# 这是一行内容
:<<!
这是一行内容
这是一行内容
!
对于多行注释除了用!符号外,还可以用EOF ‘等符号。
除了这两种方法之外,还可以通过条件语句来实现。
变量
Shell可以定义变量,并通过=给变量赋值,与其他语言不同的是,在=和变量及被赋的值之间不能有空格。习惯了其他语言的同学可能会有些不适应,不过要注意这一点。
对于命名的规则,其实是与其他语言是类似的:
- 使用英文字母,数字和下划线,但不能以数字开头。
- shell的保留关键字不可以使用
v1=1234 #正确
v2="hello" #正确
v3_1="world" #正确
v4_1 = "world" #错误,‘=’符前后不能有空格
除此之外,在访问变量时需要在变量前$符来访问,如果需要区分变量的边界,还需要在变量前后加上{}用来区分变量名的边界,建议在使用变量时加上{}。
a="hell world:"
b="一个敲代码的厨子"
echo ${a}${b}
Shell的数据类型比较简单,变量的默认数据类型是字符串类型。我们可以使用单引号或双引号,因此,也是可以不用引号的。
我们再来看一个例子:
a=1
b=2
echo ${a}+${b}
大家觉得最后应该输出多少呢?
答案是不是超出了大家的预料?这样大家应该理解了为什么说Shell定义变量时默认是字符串类型。
那么问题来了,该怎么表示数字呢?其实,这里我们稍微进行特殊处理一下就可以了,在数据运算的时候我们用$[运算表达式]形式就可以了。
a=1
b=2
echo $[${a}+${b}]
除了这种方法,还有其他的方法可以进行数据运算,我们在后面的数据运算章节在详细展开,我们接着说变量。
我们可以将变量分成局部变量和环境变量:
- 局部变量:是在Shell命令或脚本中定义的变量,只能在当前Shell命令或脚本中有效。
- 环境变量:创建它们的shell及其派生出来的任意子进程中使用等。它可以保证一些程序的正常运行。
在一些特殊的场景,我们不希望我们定义的变量数值被改变,这时,我们可以使用readonly命令将变量设置成只读。
a="hello world"
readonly a
echo ${a}
a="hahah"
还有一些场景需要清除一个变量,我们可以使用unset命令将变量删除,需要注意的是对于只读变量是不能删除的。
a="hello world"
b="一个敲代码的厨子"
readonly b
unset a
unset b
echo ${a}
echo ${b}
字符串变量操作
了解了上面变量的内容之后,我们知道变量模式是字符串类型的。那字符串的操作有哪些呢?
一般我们可以会对字符串变量进行如下操作:
- 获取字符串的长度:可以变量前加上#符号,${#变量};
- 截取字符串:可以在变量后使用:截取,${变量:x:y};
- 替换字符串中子字符串:可以在变量后使用/符号,${变量/子字符串/替换的字符串};
- 删除字符串中的子字符串:实际上可以通过替换子字符串的方法实现;
- 字符串大小写替换:可以使用^^转换成大写,使用,,转换成小写;
下面,我们可以从下面的例子更直观的了解这些操作。
a="Hello World"
echo "${#a}" #获取字符串长度
echo "${a:6:3}" #从下标6开始截取3字符
echo "${a/ll/hh}" #将字符串中的ll替换为hh
echo "${a/or/}" #删除子字符串or
echo "${a^^}" #全部转化成大写
echo "${a,,}" #全部转化成小写
数字运算
在上面的变量章节,我们学习到了可以使用$[运算表达式]形式,使数字变量进行运算。这一章节,我们将会详细了解数字运算。实际上Shell可以使用命令和运算表达式的方式进行数字运算,它们可以支持+ – * / %等算术运算。
命令方式主要有let declare expr等命令,下面我们一一通过例子来学习他们的使用。
通过let命令进行数字运算,let命名后直接跟上运算表达式即可。
#!/bin/sh
a="4"
b="2"
let c1=${a}+${b}
let c2=${a}-${b}
let c3=${a}*${b}
let c4=${a}/${b}
let c5=${a}%${b}
echo "a + b =" ${c1}
echo "a - b =" ${c2}
echo "a * b =" ${c3}
echo "a / b =" ${c4}
echo "a % b =" ${c5}
通过expr命令进行数字运算,可以查看下面的示例代码,但是需要注意两点:
- 运算符和两边的变量需要使用空格隔开;
- 需要注意需要将*转义为\*。
#!/bin/sh
a=4
b=2
c1=$(expr ${a} + ${b})
echo "a + b =" ${c1}
c2=$(expr ${a} - ${b})
echo "a - b =" ${c2}
c3=$(expr ${a} \* ${b})
echo "a * b =" ${c3}
c4=$(expr ${a} / ${b})
echo "a / b =" ${c4}
c5=$(expr ${a} % ${b})
echo "a % b =" ${c5}
declare命令也可以进行数字运算,它的参数选项中有一个-i选项,它可以将变量声明为整数型,因此,我们也可以通过declare实现数字的运算。
#!/bin/sh
a="4"
b="2"
declare -i c1=${a}+${b}
declare -i c2=${a}-${b}
declare -i c3=${a}*${b}
declare -i c4=${a}/${b}
declare -i c5=${a}%${b}
echo "a + b =" ${c1}
echo "a - b =" ${c2}
echo "a * b =" ${c3}
echo "a / b =" ${c4}
echo "a % b =" ${c5}
通过运算表达式实现数字运算的方式,主要有$((运算表达式))和$[运算表达式],我们依次来看看他们的使用方法。
#!/bin/sh
a=4
b=2
c1=$((${a}+${b}))
c2=$((${a}-${b}))
c3=$((${a}*${b}))
c4=$((${a}/${b}))
c5=$((${a}%${b}))
echo "a + b =" ${c1}
echo "a - b =" ${c2}
echo "a * b =" ${c3}
echo "a / b =" ${c4}
echo "a % b =" ${c5}
#!/bin/sh
a=4
b=2
c1=$[${a}+${b}]
c2=$[${a}-${b}]
c3=$[${a}*${b}]
c4=$[${a}/${b}]
c5=$[${a}%${b}]
echo "a + b =" ${c1}
echo "a - b =" ${c2}
echo "a * b =" ${c3}
echo "a / b =" ${c4}
echo "a % b =" ${c5}
除了这些运算之外,shell也支持自增和自减运算,这里以let命令为例:
#!/bin/sh
c1=2
c2=2
c3=2
let c1++
let c2--
let c3+=1
echo "c1 =" ${c1}
echo "c2 =" ${c2}
echo "c3 =" ${c3}
上面的这些运算方法都有各自的要求,在使用的时候我们一定要清楚它们的使用方法。另外,在默认情况下shell是不支持小数运算,大家可以发现上面的运算都是整数运算,怎么进行小数运算呢?
我们可以借助Linux平台下的bc计算器进行小数运算。
#!/bin/sh
a=1.411
b=1.332
c1=`echo "$a+$b"|bc`
c2=`echo "$a-$b"|bc`
c3=`echo "$a*$b"|bc`
c4=`echo "scale=3;$a/$b"|bc` #scale用来指定小数的位数
echo $c1
echo $c2
echo $c3
echo $c4
数组
Shell可以定义数组用来存放多个数据,格式如下,数组中的各个元素需要使用空格隔开,数组在定义时可以不用指定数组的大小。
array=(value1 value2 value3 …)
在访问数组时,可以使用中括号和下标([下标])访问各个元素,它的下标也是从0开始的。
#!/bin/sh
a=(hello world code)
echo ${a[0]}
echo ${a[1]}
echo ${a[2]}
除了用下标访问单个元素之外,是否有其他方法获取所有元素呢?我们可以使用*和@符号获取数组的所有元素。这两个符号是不是很熟悉?我们在字符串变量操作章节用到过这两个符号。
#!/bin/sh
a=(hello world code)
echo ${a[*]}
echo ${a[@]}
同样的,与获取字符变量长度类似,我们也可以使用#符号来获取数组的长度。
#!/bin/sh
a=(hello world code)
echo ${#a[*]}
关系运算
关系运算也就是比较运算,因为在shell里都是字符串类型,我们怎么比较数字的大小呢?shell中专门提供了一些专门用来关系运算的运算符。如下:
- -eq:可以判断两个数是否相等,相等则为ture,格式为[ $a -eq $b ];
- -ne:可以判断两个数是否不相等,不相等则为true,格式为[ $a -ne $b ];
- -gt:可以左边的数是否大于右边的,如果是则为true,格式为[ $a -gt $b ];
- -lt:可以判断左边的数是否小于右边的,如果是则为true,格式为[ $a -lt $b ];
- -ge:可以判断左边的数是否大于等于右边的,如果是则为true,格式为[ $a -ge $b ];
- -le:可以判断左边的数是否小于等于右边的,如果是则为true,格式为[ $a -le $b ]。
这里要注意变量和中括号两边是有空格隔开,运算符和变量之间也有空格隔开,具体我们可以通过一个例子来进一步了解它们的使用。
#!/bin/sh
a=1
b=2
if [ $a -eq $b ];then
echo "yes"
else
echo "no"
fi
if [ $a -ne $b ];then
echo "yes"
else
echo "no"
fi
if [ $a -gt $b ];then
echo "yes"
else
echo "no"
fi
if [ $a -lt $b ];then
echo "yes"
else
echo "no"
fi
这里用到了判断语句if…else,详细内容可以在条件语句章节再深入了解。
上面的是数值关系运算,当然,对于字符串同样也有类似的元素符。
- =:判断两个字符串是否相等,相等则为真,[ $a = $b ] ;
- != 判断两个字符串是否不相等,不相则为真,[ $a != $b ];
- -z 判断字符串长度是否为0,如果是则为真,[ -z $a ];
- -n 判断字符串长度是否不为0,如果是则为真,[ -n “$a” ];
- $ 判断字符串是否为空,如果不为空则为真,[ $a ];
#!/bin/sh
a="hello"
b="hello"
c="world"
d=""
echo "a = b ?"
if [ $a = $b ];then
echo "yes"
else
echo "no"
fi
echo "a != b ?"
if [ $a != $b ];then
echo "yes"
else
echo "no"
fi
echo "a != c ?"
if [ $a != $c ];then
echo "yes"
else
echo "no"
fi
echo "len(c) = 0 ?"
if [ -z $c ];then
echo "yes"
else
echo "no"
fi
echo "len(d) != 0 ?"
if [ -n "$d" ];then
echo "yes"
else
echo "no"
fi
echo "len(a) = 0 ?"
if [ $a ];then
echo "yes"
else
echo "no"
fi
逻辑运算
除了我们上面介绍的算术运算和关系运算,Shell还有逻辑运算。逻辑运算主要有逻辑与和逻辑或运算。
- 逻辑与运算使用&&表示,运算符两边都为真则结果为真;
- 逻辑或运算使用||表示,运算符两边只要有一个为真则结果为真。
我们通过一个例子来对逻辑运算进一步了解。
#!/bin/sh
a=1
b=2
c=1
echo "a = c && a != b ?"
if [[ $a -eq $c && $a -ne $b ]];then
echo "yes"
else
echo "no"
fi
echo "a = b || a = c ?"
if [[ $a -eq $b || $a -eq $c ]];then
echo "yes"
else
echo "no"
fi
条件语句
与其他语言一样,shell脚本编程也可以进行流程控制,比如,条件语句,循环语句等,这一章节我们学习条件语句。
条件语句中主要通过if else then elif fi等关键字组成,主要可以组成下面几种情况:
- 单分支
- 双分支
- 多分支:多个条件,多个执行分支
单分支这种情况,结构比较简单,一个条件一个执行分支。
a=1
if [ $a == 1 ];then
echo "a = 1"
fi
双分支的情况,比单分支多一个执行分治。
a=1
if [ $a == 2 ];then
echo "a = 1"
else
echo "a != 1"
fi
多分支结构比较适合多种条件,多个执行分支的情况。
a=2
if [ $a == 1 ];then
echo "a = 1"
elif [ $a == 2 ];then
echo "a = 2"
else
echo $a
fi
对于if的分支语句大家要注意格式问题,在[]里的条件表达式一定要和两边中括号符号用空格隔开。
此外,还有一种多分支语句case语句,格式为:
case $变量 in
"value1")
执行语句1
;;
"value2")
执行语句2
;;
*)
执行其他语句
;;
esac
我们看一个case语句的示例:
#!/bin/sh
read -p "please in put a number:" num
case $num in
"1")
echo "Start 1th flow"
;;
"2")
echo "Start 2th flow"
;;
*)
echo "Do nothing"
esac
上面的这些只是一些简单的语句结构,大家只要掌握了这几种分支语句的用法,就可以组成更加复杂的分支语句,比如,多个判断条件,多个分支嵌套等。
循环语句
用于流程控制的另一种方式是循环语句,Shell中常见有for while until select这四种循环语句。下面我们依次来了解这四种循环方式。
for循环for(())和for…in这两种形式,我们可以根据自己的需要进行选择。
先来看看for(())这种循环格式:
for((ex1;exp2;exp3))
do
循环体
done
这里我们可以结合的数组的知识来举个循环的栗子。
a=("hello" "world" "hello" "shell")
for((i=0;i<4;i++))
do
echo ${a[i]}
done
我们通过定义一个递增变量i,依次访问数组各个元素。有没有更简单的变量方法呢?我们再看下面一个例子。
a=("hello" "world" "hello" "shell")
for v in ${a[*]}
do
echo $v
done
上面的例子用的是第二种for…in的循环格式,其格式如下,它可以方便的遍历一个列表或数组,而不需再定义递增/递减变量。
for var in list
do
循环体
done
接下来我们看while循环,while循环的格式如下,
while [ 条件表达式 ]
do
循环体
done
再看一个例子:
a=("hello" "world" "hello" "shell")
i=0
while [ $i -lt 4 ]
do
echo ${a[i]}
let i++
done
while循环需要注意条件表达的写法,相信看了上面的关于条件语句的同学已经很清楚了。
在shell中有一个与while循环恰好相反的循环until循环;在while循环中条件表达式成立就会进入循环体,而在until循环中条件表达式不成立才会进入循环,until循环的格式如下:
until [ 条件表达式 ]
do
循环体
done
我们将上面while循环例子的条件表达式稍加改动。
a=("hello" "world" "hello" "shell")
i=0
until [ $i -ge 4 ]
do
echo ${a[i]}
let i++
done
对于until循环语句来说,一般没有上面的几种循环语句较为常用。
最后,还有一种较为特殊的循环select,我们先看一下它的格式:
select var in list
do
statements
done
为什么说它是一种特殊的循环?我们看下面这个例子:
a=("hello" "world" "hello" "shell")
i=0
select v in ${a[*]}
do
echo $v
done
从这里例子上,我们可以发现在每行打印前面都有一个序号,我们还可以选择其中一个序号,会输出对应需要的结果。它是shell中特有的一种结构,通常和case…in语句一起使用,通过根据选择的序号不同,可以选择执行case…in里不同的动作。
a=("公司A" "公司B" "公司C" "公司D")
select v in ${a[*]}
do
case $v in
"公司A")
echo "恭喜您,你选择了公司A !"
break
;;
"公司B")
echo "恭喜您,你选择了公司B !"
break
;;
"公司C")
echo "恭喜您,你选择了公司C !"
break
;;
"公司D")
echo "恭喜您,你选择了公司D !"
break
;;
*)
echo "您没有选择任何一个公司,请重新选择!"
esac
done
函数
在一个复杂的功能的脚本程序中,会有很多重复或相似的功能,为了避免大量的重复代码,这个时候函数的作用体现出来了。一般的我们会将一些相似的或重复的操作抽象成一个函数,并根据参数的不同返回相应的结果。这样程序将更具模块化,逻辑也更加清楚,便于开发和维护。
当然,在shell中也可以使用函数。其格式如下:
function name() {
# 函数体
return value
}
这里function是用来定义函数的关键字,name是需要我们自定义的函数名,return value是函数的返回值。现在我们来定义一个函数:
function test1() {
echo "this is a function"
return 0
}
该怎么调用函数呢?直接写函数名即可,我们看一个完整版程序:
function test1() {
echo "this is a function"
return 0
}
test1 #调用函数
上面函数没有带参数的,那如果有参数怎么办呢?我们继续看例子:
function test2() {
echo "parameter1 : $1"
echo "parameter2 : $2"
return 3
}
test2 1 2 #调用函数
echo $? #获取函数返回值
实际上,函数中是不要定义形参的,在调用时在函数后面加上实参就可以了。而函数体中可以通过$加参数编号访问参数,比如,第一个参数$1,到第十个参数以上就需要加上{}符号,比如${10},而函数返回值需要在调用该函数后通过$?获得。
文件包含
有的时候某个功能可能被多个脚本使用,这时就需要在将这个功能放到一个公共的文件中,需要这个功能的脚本直接包含这个公共的文件即可,这就是文件的包含,同样的Shell也是支持文件包含的。
在Shell中可以使用.或source加上文件名来包含文件。直接来看例子:
先建一个文件test_one.sh:
#test_one.sh
var1=1
再建一个文件test_two.sh:
#test_two.sh
var2=2
下面我们在建一个文件包含test_one.sh和test_two.sh这两个文件。
. ./test_one.sh
source ./test_two.sh
echo "file one var1=${var1}"
echo "file two var2=${var2}"
这里需要注意.和./test_one.sh文件之间是有一个空格。
最后
至此,我们已经学会了shell编程的一些基本知识。文中使用的都是一些简单的例子,在实际的shell脚本中往往都是比较复杂的逻辑。不过,再复杂的代码也是有这些简单的结构组成。因此,大家一定要有一个扎实基础和掌握一个完整的shell知识体系。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/36787.html