Shell脚本:Linux Shell脚本学习指南(第二部分Shell编程)二

第二部分:Shell编程(二)

十一、Shell数组:Shell数组定义以及获取数组元素

和其他编程语言一样,Shell 也支持数组。数组(Array)是若干数据的集合,其中的每一份数据都称为元素(Element)。

Shell 并且没有限制数组的大小,理论上可以存放无限量的数据。和C++、Java、C# 等类似,Shell 数组元素的下标也是从 0 开始计数。

获取数组中的元素要使用下标[ ],下标可以是一个整数,也可以是一个结果为整数的表达式;当然,下标必须大于等于 0。

遗憾的是,常用的 Bash Shell 只支持一维数组,不支持多维数组。

1、Shell 数组的定义

在 Shell 中,用括号( )来表示数组,数组元素之间用空格来分隔。由此,定义数组的一般形式为:

array_name=(ele1  ele2  ele3 ... elen)

注意,赋值号=两边不能有空格,必须紧挨着数组名和数组元素。

下面是一个定义数组的实例:

nums=(29 100 13 8 91 44)

Shell 是弱类型的,它并不要求所有数组元素的类型必须相同,例如:

arr=(20 56 "http://c.biancheng.net/shell/")

第三个元素就是一个“异类”,前面两个元素都是整数,而第三个元素是字符串。

Shell 数组的长度不是固定的,定义之后还可以增加元素。例如,对于上面的 nums 数组,它的长度是 6,使用下面的代码会在最后增加一个元素,使其长度扩展到 7:

nums[6]=88

此外,你也无需逐个元素地给数组赋值,下面的代码就是只给特定元素赋值:

ages=([3]=24 [5]=19 [10]=12)

以上代码就只给第 3、5、10 个元素赋值,所以数组长度是 3。

2、获取数组元素

获取数组元素的值,一般使用下面的格式:

${array_name[index]}

其中,array_name 是数组名,index 是下标。例如:

n=${nums[2]}

表示获取 nums 数组的第二个元素,然后赋值给变量 n。再如:

echo ${nums[3]}

表示输出 nums 数组的第 3 个元素。

使用@*可以获取数组中的所有元素,例如:

${nums[*]}

${nums[@]}

两者都可以得到 nums 数组的所有元素

完整的演示:

#!/bin/bash

nums=(29 100 13 8 91 44)

echo ${nums[@]}     #输出所有数组元素

nums[10]=66            #给第10个元素赋值(此时会增加数组长度)

echo ${nums[*]}       #输出所有数组元素

echo ${nums[4]}      #输出第4个元素

运行结果:

29 100 13 8 91 44
29 100 13 8 91 44 66
91

十二、Shell获取数组长度

所谓数组长度,就是数组元素的个数。

利用@*可以将数组扩展成列表然后使用#来获取数组元素的个数,格式如下:

${#array_name[@]}
${#array_name[*]}

其中 array_name 表示数组名。两种形式是等价的,选择其一即可。

如果某个元素是字符串,还可以通过指定下标的方式获得该元素的长度,如下所示:

${#arr[2]}

获取 arr 数组的第 2 个元素(假设它是字符串)的长度。

1、回忆字符串长度的获取

回想一下 Shell 是如何获取字符串长度的呢?其实和获取数组长度如出一辙,它的格式如下:

${#string_name}

string_name 是字符串名。

2、实例演示

下面我们通过实际代码来演示一下如何获取数组长度。

#!/bin/bash


nums=(29 100 13)
echo ${#nums[*]}

#向数组中添加元素

nums[10]="http://c.biancheng.net/shell/"

echo ${#nums[@]}

echo ${#nums[10]}

#删除数组元素

unset nums[1]

echo ${#nums[*]}

运行结果:

3
4
29
3

十三、Shell数组拼接,Shell数组合并

所谓 Shell 数组拼接(数组合并),就是将两个数组连接成一个数组。

拼接数组的思路是:先利用@*,将数组扩展成列表,然后再合并到一起。具体格式如下:

array_new=(${array1[@]}  ${array2[@]})
array_new=(${array1[*]}  ${array2[*]})

两种方式是等价的,选择其一即可。其中,array1 和 array2 是需要拼接的数组,array_new 是拼接后形成的新数组。

下面是完整的演示代码:

#!/bin/bash

array1=(23 56)

array2=(99 "http://c.biancheng.net/shell/")

array_new=(${array1[@]} ${array2[*]})

echo ${array_new[@]} #也可以写作 ${array_new[*]}

运行结果:

23 56 99 http://c.biancheng.net/shell/

十四、Shell删除数组元素(也可以删除整个数组)

在 Shell 中,使用 unset 关键字来删除数组元素,具体格式如下:

unset array_name[index]

其中,array_name 表示数组名,index 表示数组下标。

如果不写下标,而是写成下面的形式:

unset array_name

那么就是删除整个数组,所有元素都会消失

下面我们通过具体的代码来演示:

#!/bin/bash

arr=(23 56 99 "http://c.biancheng.net/shell/")

unset arr[1]

echo ${arr[@]}

unset arr

echo ${arr[*]}

运行结果:

23 99 http://c.biancheng.net/shell/
 

注意最后的空行,它表示什么也没输出,因为数组被删除了,所以输出为空。

十五、Shell关联数组(下标是字符串的数组)

现在最新的 Bash Shell 已经支持关联数组了。关联数组使用字符串作为下标,而不是整数,这样可以做到见名知意。

关联数组也称为“键值对(key-value)”数组,键(key)也即字符串形式的数组下标,值(value)也即元素值。

例如,我们可以创建一个叫做 color 的关联数组,并用颜色名字作为下标。

declare -A color

color["red"]="#ff0000"

color["green"]="#00ff00"

color["blue"]="#0000ff"

也可以在定义的同时赋值:

declare -A color=(["red"]="#ff0000", ["green"]="#00ff00", ["blue"]="#0000ff")

不同于普通数组,关联数组必须使用带有-A选项的 declare 命令创建。

1、访问关联数组元素

访问关联数组元素的方式几乎与普通数组相同,具体形式为:

array_name["index"]

例如:

color["white"]="#ffffff"

color["black"]="#000000"

加上$()即可获取数组元素的值:

$(array_name["index"])

例如:

echo $(color["white"])

white=$(color["black"])

2、获取所有元素的下标和值

使用下面的形式可以获得关联数组的所有元素值

${array_name[@]}
${array_name[*]}

使用下面的形式可以获取关联数组的所有下标值

${!array_name[@]}
${!array_name[*]}

3、获取关联数组长度

使用下面的形式可以获得关联数组的长度:

${#array_name[*]}
${#array_name[@]}

关联数组实例演示:

#!/bin/bash

declare -A color

color["red"]="#ff0000"

color["green"]="#00ff00"

color["blue"]="#0000ff"

color["white"]="#ffffff"

color["black"]="#000000"

#获取所有元素值

for value in ${color[*]}

do

        echo $value

done

echo "****************"

#获取所有元素下标(键)

for key in ${!color[*]}

do

        echo $key

done

echo "****************"

#列出所有键值对

for key in ${!color[@]}

do

        echo "${key} -> ${color[$key]}"

done

运行结果:

#ff0000
#0000ff
#ffffff
#000000
#00ff00
****************
red
blue
white
black
green
****************
red -> #ff0000
blue -> #0000ff
white -> #ffffff
black -> #000000
green -> #00ff00

十六、Shell内建命令(内置命令)

所谓 Shell 内建命令,就是由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件。

例如,用于进入或者切换目录的 cd 命令,虽然我们一直在使用它,但如果不加以注意很难意识到它与普通命令的性质是不一样的:该命令并不是某个外部文件,只要在 Shell 中你就一定可以运行这个命令。

可以使用 type 来确定一个命令是否是内建命令

[root@localhost ~]# type cd
cd is a Shell builtin
[root@localhost ~]# type ifconfig
ifconfig is /sbin/ifconfig

由此可见,cd 是一个 Shell 内建命令,而 ifconfig 是一个外部文件,它的位置是/sbin/ifconfig

还记得系统变量$PATH 吗?$PATH 变量包含的目录中几乎聚集了系统中绝大多数的可执行命令,它们都是外部命令。

通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘 I/O,还需要 fork 出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前 Shell 进程的一个函数。

下表列出了 Bash Shell 中直接可用的内建命令。

Bash Shell 内建命令

命令说明
:扩展参数列表,执行重定向操作
.读取并执行指定文件中的命令(在当前 shell 环境中)
alias为指定命令定义一个别名
bg将作业以后台模式运行
bind将键盘序列绑定到一个 readline 函数或宏
break退出 for、while、select 或 until 循环
builtin执行指定的 shell 内建命令
caller返回活动子函数调用的上下文
cd将当前目录切换为指定的目录
command执行指定的命令,无需进行通常的 shell 查找
compgen为指定单词生成可能的补全匹配
complete显示指定的单词是如何补全的
compopt修改指定单词的补全选项
continue继续执行 for、while、select 或 until 循环的下一次迭代
declare声明一个变量或变量类型。
dirs显示当前存储目录的列表
disown从进程作业表中刪除指定的作业
echo将指定字符串输出到 STDOUT
enable启用或禁用指定的内建shell命令
eval将指定的参数拼接成一个命令,然后执行该命令
exec用指定命令替换 shell 进程
exit强制 shell 以指定的退出状态码退出
export设置子 shell 进程可用的变量
fc从历史记录中选择命令列表
fg将作业以前台模式运行
getopts分析指定的位置参数
hash查找并记住指定命令的全路径名
help显示帮助文件
history显示命令历史记录
jobs列出活动作业
kill向指定的进程 ID(PID) 发送一个系统信号
let计算一个数学表达式中的每个参数
local在函数中创建一个作用域受限的变量
logout退出登录 shell
mapfile从 STDIN 读取数据行,并将其加入索引数组
popd从目录栈中删除记录
printf使用格式化字符串显示文本
pushd向目录栈添加一个目录
pwd显示当前工作目录的路径名
read从 STDIN 读取一行数据并将其赋给一个变量
readarray从 STDIN 读取数据行并将其放入索引数组
readonly从 STDIN 读取一行数据并将其赋给一个不可修改的变量
return强制函数以某个值退出,这个值可以被调用脚本提取
set设置并显示环境变量的值和 shell 属性
shift将位置参数依次向下降一个位置
shopt打开/关闭控制 shell 可选行为的变量值
source读取并执行指定文件中的命令(在当前 shell 环境中)
suspend暂停 Shell 的执行,直到收到一个 SIGCONT 信号
test基于指定条件返回退出状态码 0 或 1
times显示累计的用户和系统时间
trap如果收到了指定的系统信号,执行指定的命令
type显示指定的单词如果作为命令将会如何被解释
typeset声明一个变量或变量类型。
ulimit为系统用户设置指定的资源的上限
umask为新建的文件和目录设置默认权限
unalias刪除指定的别名
unset刪除指定的环境变量或 shell 属性
wait等待指定的进程完成,并返回退出状态码

接下来的几节我们将重点讲解几个常用的 Shell 内置命令。

十七、Shell alias:给命令创建别名

alisa 用来给命令创建一个别名。若直接输入该命令且不带任何参数,则列出当前 Shell 进程中使用了哪些别名。现在你应该能理解类似ll这样的命令为什么与ls -l的效果是一样的吧。

下面让我们来看一下有哪些命令被默认创建了别名:

[mozhiyan@localhost ~]$ alias
alias cp='cp -i'
alias l.='ls -d .* --color=tty'
alias ll='ls -l --color=tty'
alias ls='ls --color=tty'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

你看,为了让我们使用方便,Shell 会给某些命令默认创建别名。

1、使用 alias 命令自定义别名

使用 alias 命令自定义别名的语法格式为:

alias new_name='command'

比如,一般的关机命令是shutdown-h now,写起来比较长,这时可以重新定义一个关机命令,以后就方便多了。

alias myShutdown='shutdown -h now'

再如,通过 date 命令可以获得当前的 UNIX 时间戳,具体写法为date +%s,如果你嫌弃它太长或者不容易记住,那可以给它定义一个别名。

alias timestamp='date +%s'

《三、Shell命令替换》一节中,我们使用date +%s计算脚本的运行时间,现在学了 alias,就可以简化代码了。

#!/bin/bash

alias timestamp='date +%s'

begin=`timestamp`

sleep 20s

finish=$(timestamp)

difference=$((finish - begin))

echo "run time: ${difference}s"

运行脚本,20 秒后看到输出结果:

run time: 20s

别名只是临时的

在代码中使用 alias 命令定义的别名只能在当前 Shell 进程中使用,在子进程和其它进程中都不能使用。当前 Shell 进程结束后,别名也随之消失。

要想让别名对所有的 Shell 进程都有效,就得把别名写入 Shell 配置文件。Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,将别名放在配置文件中,那么每次启动进程都会定义这个别名。

2、使用 unalias 命令删除别名

使用 unalias 内建命令可以删除当前 Shell 进程中的别名。unalias 有两种使用方法:

  • 第一种用法是在命令后跟上某个命令的别名,用于删除指定的别名。
  • 第二种用法是在命令后接-a参数,删除当前 Shell 进程中所有的别名。

同样,这两种方法都是在当前 Shell 进程中生效的。要想永久删除配置文件中定义的别名,只能进入该文件手动删除。

# 删除 ll 别名
[mozhiyan@localhost ~]$ unalias ll
# 再次运行该命令时,报“找不到该命令”的错误,说明该别名被删除了
[mozhiyan@localhost ~]$ ll
-bash: ll: command not found

十八、Shell echo命令:输出字符串

echo 是一个Shell内建明命令 ,用来在终端输出字符串,并在最后默认加上换行符。请看下面的例子:

#!/bin/bash

name="Shell教程"

url="http://c.biancheng.net/shell/"

echo "读者,你好! "                             #直接输出字符串

echo $url #输出变量

echo "${name}的网址是:${url}"        #双引号包围的字符串中可以解析变量

echo '${name}的网址是:${url}'         #单引号包围的字符串中不能解析变量

运行结果:

读者,你好!
http://c.biancheng.net/shell/
Shell教程的网址是:http://c.biancheng.net/shell/
${name}的网址是:${url}

1、不换行

echo 命令输出结束后默认会换行,如果不希望换行,可以加上-n参数,如下所示:

#!/bin/bash

name="Tom"

age=20

height=175

weight=62

echo -n "${name} is ${age} years old, "

echo -n "${height}cm in height "

echo "and ${weight}kg in weight."

echo "Thank you!"

运行结果:

Tom is 20 years old, 175cm in height and 62kg in weight.
Thank you!

2、输出转义字符

默认情况下,echo 不会解析以反斜杠\开头的转义字符。比如,\n表示换行,echo 默认会将它作为普通字符对待。请看下面的例子:

[root@localhost ~]# echo "hello \nworld"
hello \nworld

我们可以添加-e参数来让 echo 命令解析转义字符。例如:

[root@localhost ~]# echo -e "hello \nworld"
hello
world

\c 转义字符

有了-e参数,我们也可以使用转义字符\c来强制 echo 命令不换行了。请看下面的例子:

#!/bin/bash

name="Tom"

age=20

height=175

weight=62

echo -e "${name} is ${age} years old, \c"

echo -e "${height}cm in height \c"

echo "and ${weight}kg in weight."

echo "Thank you!"

运行结果:

Tom is 20 years old, 175cm in height and 62kg in weight.
Thank you!

十九、Shell read命令:读取从键盘输入的数据

read 是Shell内置明命令用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据。

后续我们会在《Linux Shell重定向》一节中深入讲解重定向的概念,不了解的读者可以不用理会,暂时就认为:read 命令就是从键盘读取数据。

read 命令的用法为:

read [-options] [variables]

options表示选项,如下表所示;variables表示用来存储数据的变量,可以有一个,也可以有多个。

optionsvariables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY 中。

Shell read 命令支持的选项

选项说明
-a array把读取的数据赋值给数组 array,从下标 0 开始。
-d delimiter用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。
-e在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。
-n num读取 num 个字符,而不是整行字符。
-p prompt显示提示信息,提示内容为 prompt。
-r原样读取(Raw mode),不把反斜杠字符解释为转义字符。
-s静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。
设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败。
-u fd使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。

【实例1】使用 read 命令给多个变量赋值。

#!/bin/bash

read -p "Enter some information > " name url age

echo "网站名字:$name"

echo "网址:$url"

echo "年龄:$age"

运行结果:

Enter some information > C语言中文网 http://c.biancheng.net 7↙
网站名字:C语言中文网
网址:http://c.biancheng.net
年龄:7

注意,必须在一行内输入所有的值,不能换行,否则只能给第一个变量赋值,后续变量都会赋值失败。

本例还使用了-p选项该选项会用一段文本来提示用户输入。

【示例2】只读取一个字符。

#!/bin/bash

read -n 1 -p "Enter a char > " char

printf "\n" #换行

echo $char

运行结果:

Enter a char > 1
1

-n 1表示只读取一个字符。运行脚本后,只要用户输入一个字符,立即读取结束,不用等待用户按下回车键。

printf "\n"语句用来达到换行的效果,否则 echo 的输出结果会和用户输入的内容位于同一行,不容易区分。

【实例3】在指定时间内输入密码。

#!/bin/bash

if

        read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" && #第一次输入密码

        read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" && #第二次输入密码

        [ $pass1 == $pass2 ] #判断两次输入的密码是否相等

then

        echo "Valid password"

else

        echo "Invalid password"

fi

这段代码中,我们使用&&组合了多个命令,这些命令会依次执行,并且从整体上作为 if 语句的判断条件,只要其中一个命令执行失败(退出状态为非 0 值),整个判断条件就失败了,后续的命令也就没有必要执行了。

如果两次输入密码相同,运行结果为:
Enter password in 20 seconds(once) >
Enter password in 20 seconds(again)>
Valid password

如果两次输入密码不同,运行结果为:
Enter password in 20 seconds(once) >
Enter password in 20 seconds(again)>
Invalid password

如果第一次输入超时,运行结果为:
Enter password in 20 seconds(once) > Invalid password

如果第二次输入超时,运行结果为:
Enter password in 20 seconds(once) >
Enter password in 20 seconds(again)> Invalid password

二十、Shell exit命令:退出当前进程

exit 是一个Shell内置明命令 ,用来退出当前 Shell 进程,并返回一个退出状态;使用$?可以接收这个退出状态,这一点已在《七、Shell $?:获取函数返回值或者上一个命令的退出状态》一节中进行了讲解。

exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。

一般情况下,退出状态为 0 表示成功,退出状态为非 0 表示执行失败(出错)了。

exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。

Shell 进程执行出错时,可以根据退出状态来判断具体出现了什么错误,比如打开一个文件时,我们可以指定 1 表示文件不存在,2 表示文件没有读取权限,3 表示文件类型不对。

编写下面的脚本,并命名为 test.sh:

#!/bin/bash

echo "befor exit"

exit 8

echo "after exit"

运行该脚本:

[mozhiyan@localhost ~]$ bash ./test.sh
befor exit

可以看到,"after exit"并没有输出,这说明遇到 exit 命令后,test.sh 执行就结束了。

注意, exit 表示退出当前 Shell 进程,我们必须在新进程中运行 test.sh,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法看到输出结果了。

我们可以紧接着使用$?来获取 test.sh 的退出状态:

[mozhiyan@localhost ~]$ echo $?

8

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值