加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

万字总结 | 完全掌握Shell编程

04/05 11:25
1190
阅读需 33 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

对于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上直接运行即可。通常,我们在编写脚本时,在第一行需要用#!来指定解释器来运行Shell脚本,比如,#!/bin/sh。

下面我们将为大家从如下几个方面全面系统的为大家梳理Shell编程的相关知识点。


Shell编程

输入输出

shell中有两种输出命令:echo和printf。学习程序,生硬的文字始终没有代码来的直接易懂。我们就直接通过例子来学习吧。

echo "hello world"
printf "%s %s" "hello" "world"
printf "!!!n"


output

从上面的例子很容易发现,echo命令默认带有换行符的,而printf则不是。与C语言中类似,printf是可以进行格式化输出的,其中,%s就是格式控制符,还有%c %d %f等控制符。另外,还可以通过在控制符中添加数字来制定字符的长度,比如,%3s表示三个字符长度;%4.3f表示4位整数,3位小数。

#!/bin/sh
printf "%6s %3s %4s %5sn" 公司名 评级 销售额 市场占比
printf "%6s %3c %4d %5fn" 公司A A 5001 0.5001
printf "%6s %3c %4d %5fn" 公司B C 1999 0.1999
printf "%6s %3c %4d %5fn" 公司B B 3000 0.3


printf

如果需要字符对齐,还可以使用-进行字符左对齐,省略不加默认右对齐。

在shell中是使用read命令作为输入,它可以接受标准键盘的输入;除此之外,也可以作为文件描述符的输入,对文件进行操作。

read命名的格式如下:

read [选项名] [变量名]

read命令在执行时,会将输入的数据放到变量中,通常,我们会指定一个自定义的变量,如果不指定变量的话,则就会把输入的数据保存到REPLY变量中。关于变量的一些具体信息可以在下面的变量章节了解。

read命令的选项名有下面几种参数,我们可以选择一种或几种进行设置。

  • -p:设置提示信息,屏幕会输出提示信息;
  • -t:设置等待时间(秒),等待用户输入的时间;
  • -n:设置接收指定的字符数;
  • -s:隐藏输入的数据,用于比较隐私机密信息的输入。

具体的使用方法大家可以参考下面的例子:

#!/bin/sh
read -p "input a name:" name
read -p "input a password:" -s passwd

echo ""

echo $name
echo $passwd


read

注释

每种语言都少不了注释,对于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}

大家觉得最后应该输出多少呢?


example

答案是不是超出了大家的预料?这样大家应该理解了为什么说Shell定义变量时默认是字符串类型。

那么问题来了,该怎么表示数字呢?其实,这里我们稍微进行特殊处理一下就可以了,在数据运算的时候我们用$[运算表达式]形式就可以了。

a=1
b=2
echo $[${a}+${b}]


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}


let命令

通过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}


expr

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}


declare命令

通过运算表达式实现数字运算的方式,主要有$((运算表达式))和$[运算表达式],我们依次来看看他们的使用方法。

#!/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}


表达式方式1

#!/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}


表达式方式2

除了这些运算之外,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


case语句

上面的这些只是一些简单的语句结构,大家只要掌握了这几种分支语句的用法,就可以组成更加复杂的分支语句,比如,多个判断条件,多个分支嵌套等。

循环语句

用于流程控制的另一种方式是循环语句,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


for循环

我们通过定义一个递增变量i,依次访问数组各个元素。有没有更简单的变量方法呢?我们再看下面一个例子。

a=("hello" "world" "hello" "shell")

for v in ${a[*]}
do
echo $v
done


for...in

上面的例子用的是第二种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

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

对于until循环语句来说,一般没有上面的几种循环语句较为常用。

最后,还有一种较为特殊的循环select,我们先看一下它的格式:

select var in list
do
statements
done

为什么说它是一种特殊的循环?我们看下面这个例子:

a=("hello" "world" "hello" "shell")
i=0

select v in ${a[*]}
do
echo $v
done


select

从这里例子上,我们可以发现在每行打印前面都有一个序号,我们还可以选择其中一个序号,会输出对应需要的结果。它是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


select in 和 case in

函数

在一个复杂的功能的脚本程序中,会有很多重复或相似的功能,为了避免大量的重复代码,这个时候函数的作用体现出来了。一般的我们会将一些相似的或重复的操作抽象成一个函数,并根据参数的不同返回相应的结果。这样程序将更具模块化,逻辑也更加清楚,便于开发和维护。

当然,在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 #调用函数

函数1

上面函数没有带参数的,那如果有参数怎么办呢?我们继续看例子:

function test2() {
echo "parameter1 : $1"
echo "parameter2 : $2"

return 3
}

test2 1 2 #调用函数
echo $? #获取函数返回值


函数2

实际上,函数中是不要定义形参的,在调用时在函数后面加上实参就可以了。而函数体中可以通过$加参数编号访问参数,比如,第一个参数$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知识体系。

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
KSZ8895MQXCA 1 Microchip Technology Inc DATACOM, ETHERNET TRANSCEIVER, PQFP128

ECAD模型

下载ECAD模型
$5.9 查看
NCV7344D10R2G 1 onsemi CAN FD Transceiver, High Speed, Low Power with NC, long filter time, 3000-REEL
$0.82 查看
AT86RF215-ZUR 1 Microchip Technology Inc IC RF TXRX 802.15.4/ISM 48-VFQFN
$6.36 查看

相关推荐

电子产业图谱