一、Linux Shell脚本编程入门
1.1 什么是Shell
Shell是Linux上的一个命令解释器。它的作用是解释执行用户输入的命令,然后调用相应的应用程序,命令行中每键入一个命令,Shell就解释执行一条,并将结果展示到命令行窗口中,这种称为交互式的方式。
Shell俗称壳,存在于操作系统的最外层,负责与用户直接对话。把用户的输入解释给操作系统,并将操作系统输出的结果输出给屏幕返回给用户。
1.2 什么是shell脚本
Shell脚本就像早期dos年代的.bat,最简单的功能就是将许多指令汇整在一起,让使用者很容易地 就能够一个操作执行多个命令,主要是方便管理员进行设置或者管理用的。但是它比Windows下的批处 理更强大,它提供了数组、循环、条件以及逻辑判断等重要功能,让使用者可以直接以Shell来写程序, 比用其他编程语言编写的程序效率更高,毕竟它使用了Linux/Unix下的命令。
# cat /etc/shells
#查看当前环境下支持的shell
![](https://i-blog.csdnimg.cn/blog_migrate/78b1c97a33323bc8003a06cb60fe4e22.png)
1.3创建第一个Shell脚本
在 Linux 系统中, Shell 脚本(bash Shell 程序) 通常是在编辑器 vi/vim 中编写的,由 UNIX/Linux 命令、 bash Shell 命令、 程序结构控制语句和注释等内容组成。 这里推荐用 Linux 自带的 功能更强的 vim 编辑器来编写, 可以事先做一个别名 alias vi='vim,,并使其永久生效, 这样以后习惯 输人 vi 的读者也就可以直接调用 vim 编辑器了, 设置方法如下:
# echo "alias vi='vim'" >>/etc/profile
# source /etc/profile
![](https://i-blog.csdnimg.cn/blog_migrate/3a573f9edd3e2280e406956a3c4eb29a.png)
1.3.1、脚本开头(第一行)
脚本文件一般以.sh结尾;一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器) 来执行脚本中的内容,这一行内容在 Linux bash 的编程一般为,:
#!/bin/bash
1.3.2、脚本的注释
在Shell脚本中,除了第一行,跟在#后面的内容均为注释内容,注释部分不会当做程序来执行,仅供开发者和使 用者看,注释可以自成一行也可以跟在代码段后面,在我们学习Shell脚本编程时养成注释的好习 惯,方便别人也为了方便自己,例如:
![](https://i-blog.csdnimg.cn/blog_migrate/78dbcc20f037e6a415d033bfe4f3802f.png)
1.3.3、脚本的执行
Shell脚本的执行方法有一下几种:
1、bash script_name或者sh script_name,当脚本没有执行权限时,建议使用此种方法执行脚本,默认情况下LInux不允许文件具有执行权限(从安全上来说,这是一件非常好的事情),所以推荐大家使 用这种方法;
![](https://i-blog.csdnimg.cn/blog_migrate/1eca4cbfd94f0e8104c74cab09f25765.png)
2、./script_name 或者/path/to/script_name,在当前路径或者指定路径下执行脚本,这种方法要求脚本具有执行权限。若没有需要我们给脚本文件增加执行权限。
![](https://i-blog.csdnimg.cn/blog_migrate/5d6a27a256d3ac92ab9dcd83707e6135.png)
现在文件没有执行权限
![](https://i-blog.csdnimg.cn/blog_migrate/044180744e5138e6d46ec8639a24e81e.png)
执行脚本显示权限拒绝
![](https://i-blog.csdnimg.cn/blog_migrate/7914211d54011b65a2a810bc632998b1.png)
修该权限后就可以运行
![](https://i-blog.csdnimg.cn/blog_migrate/9e5671722063ba3a69b4d21dd7f5678b.png)
3、source script_name或者. script_name,了解一下就好。
![](https://i-blog.csdnimg.cn/blog_migrate/75b6f6490a44ccc2cf89e0f7f371a996.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c7e8a4e1db2b0440440c76cbc99872a4.png)
1.3.4、Shell脚本编写规范
1、指定解释器,通常为
#!/bin/bash
或者
#!/bin/sh
2、增加版本,作者及脚本描述信息等
#Date: 18:00 2018-10-10
#Author: Little Fatty
#Desc: Clean logs
#Version: 1.0
#Email: nebula-tech@163.com
3、脚本注释尽量减少中文
4、Shell脚本的扩展名以.sh结尾
5、成对的符号应该一次写完,然后退格在括号增加内容。如[ ]、{ }、“ ”、``、‘ ’、等等
6、流程控制语句应该先写将格式写完,再添加内容,避免嵌套增多时遗漏。代码缩进让代码内容更
清晰。
if 条件语句
then
内容
fi
for
do
内容
done
二、Shell脚本中的变量
变量为程序或脚本访问内存中的可被修改的一块数据提供 了简单的方式。Linux Shell中的变量可以被指定为任意的数据类型,比如文本字符串或是数值。
# LittleFatty=" I am LittleFatty "
# echo $LittleFatty
![](https://i-blog.csdnimg.cn/blog_migrate/b70b0e36bec1dc4f7465a3e21e8efbaa.png)
2.1变量的类型
变量可分为两类: 环境变量(全局变量) 和普通变量(局部变量)。
环境变量也可称为全局变量, 可以在创建它们的 Shell 及其派生出来的任意子进程Shell 中使用,环 境变量又可分为自定义环境变量和 bash 内置的环境变量。
普通变量也可称为局部变量, 只能在创建它们的 Shell 函数或 Shell 脚本中使用。 普通变量一般由开 发者在开发脚本程序时创建。
2.1.1常见的环境变量
![](https://i-blog.csdnimg.cn/blog_migrate/eb9ec945c56bb37148d70d7673adc162.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1cd09f5e6ec11f7fd791b78c9f9a322c.png)
2.1.2查看环境变量
# set (输出所有的变量)
# env
# declare
# typeset
2.2自定义环境变量
如果想要设置环境变量,就需要给变量赋值之后或在设置变量时使用export命令,除了export之外还可以使用declare带-x选项的方法来定义。 我们在当前shell环境定义的变量只在当前环境有效 ; export和declare命令格式如下:
①export 变量名=value
②变量名=value;export 变量名
③declare -x 变量名=value
注:在赋值操作符“=”的周围,不要有任何空格。本地变量定义也需要遵循。
![](https://i-blog.csdnimg.cn/blog_migrate/88d1d7ad6d0723c7d50faa76a8a1c09a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a373adbed6291b082d02ee750973e7d8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/877f79722462c2721886d8b429474d7a.png)
2.2.1当前shell环境变量永久生效
(1)用户的环境变量配置
.bashrc
.bash_profile
(2)全局的环境变量配置 ,所有用户生效;
/etc/profile
/etc/bashrc
/etc/profile.d/
在这三个文件中优先推荐在/etc/bashrc中设置,如果要在登陆后初始化或者显示加载内容,我们可以将写好的脚本文件放在/etc/profile.d/下即可(无需加执行权限) 。
2.2.2设置登录提示的两种方式
第一种:
# vim /etc/motd
# cat /etc/motd
![](https://i-blog.csdnimg.cn/blog_migrate/e61d87dd4c2b5ffc5dcdf7dd08536bff.png)
![](https://i-blog.csdnimg.cn/blog_migrate/55e538c6795f416bd5bafd4635500e69.png)
第二种:
在/etc/profile.d/下增加脚本
# cd /etc/profile.d/
# echo "echo hello world" > login.sh
![](https://i-blog.csdnimg.cn/blog_migrate/d973f7ef361065723609d7441e8eeaac.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4817f260161bb59c9628025923566b36.png)
2.2.3用unset取消环境变量
格式:unset 变量名
# env |grep bbb
# a=123
# echo $a
123
# unset a
# echo $a
#
![](https://i-blog.csdnimg.cn/blog_migrate/af4e1bebb3e700f83644e718fce305bd.png)
2.2.4环境变量生效顺序
用户登录系统环境变量文件生效顺序
/etc/profile
/etc/profile.d/
.bash_profile
.bashrc
/etc/bashrc
非交互式登录(不用用户登录)环境变量文件生效顺序 ;
只会加载家目录下.bashrc,/etc/bashrc ;
/etc/bashrc是最后加载的,但也是最先生效的;所以一般定义环境变量定义到这个文件中就可以了。
2.3普通变量
普通变量也叫局部变量或者本地变量,顾名思义,本地环境变量只在定义它们的本地进程中可见Linux系统中默认定义了标准的本地环境变量。目前来说还没有那个命令只显示出本地变量,我们可以使用set命令显示出所有的变量,其中包括执行env显示的全局环境变量,剩下的变量即为本地变量。
2.3.1 自定义本地变量
1.本地变量的赋值
变量名=value
变量名='value'
变量名="value"
2.变量名及赋值要求
变量名一般由字母、数字、下划线组成,要能做到见名知义。
变量的值可以用单引号,双引号或者不加引号,三者含义有所不同。
3.变量定义示例
# a=1
# b='2'
# c="3"
# echo $a $b $c
1 2 3
# echo ${a} ${b} ${c}
1 2 3
![](https://i-blog.csdnimg.cn/blog_migrate/0afce320f7117649438debd547e27bed.png)
输出变量时,若加上单引号,如果引号里有变量则该变量不会被替换,例如:
![](https://i-blog.csdnimg.cn/blog_migrate/ec7a58ef33d95c38932324066ad6e219.png)
通过上面例子我们来总结一下,三种不同定义方式的结果
第一种定义变量a的方式是不加任何引号,直接定义内容,当内容为简单的连续数字,字符串等时可 以这样用,例如a=1,a=abc等,不加引号,值里有变量时,会被解析出变量值再一同输出。
第二种定义变量b的方式是加了单引号,这种定义的方式的特点是,输出变量的值的时候,引号内的值是什么就输出什么,不会解析,当值中有变量或者命令(命令用反引号)也只会原样输出,这种方式比较适合定义显示纯字符的情况,不希望有解析的场景。
第三种定义变量c的方式是用双引号,这种方式在输出变量的值的时候,会将值中的变量和命令解析后再输出,而不是原样输出,这种方式比较适合希望将变量值中的变量及命令解析出来的场景。加双引号的方式是最常见的定义变量方式。
2.3.2变量值使用一个命令的结果的赋值方法
变量名=`commond` <---此处使用的反引号,Tab键上的符号
变量名=$(commond) <---把命令使用$()括起来
![](https://i-blog.csdnimg.cn/blog_migrate/440394919b644580e1e92c288b8f74a4.png)
此方式在企业生产场景中很常见,例如以当前日期时间命令的备份压缩等操作。
例如打包备份文件
[root@nebulalinux ~]# echo `date +%F`.tar.gz
2018-11-04.tar.gz
[root@nebulalinux ~]# tar zcf etc_$(date +%F).tar.gz /etc
tar: Removing leading `/' from member names
[root@nebulalinux ~]# ls
etc_2018-11-04.tar.gz
2.4 变量数组
环境变量的一个非常好的特性就是能够当做数组使用,数组是能够保存多个值的变量,数组中的值 既可以分别引用,也可以作为整体引用。
要为某个环境变量设置多个值,只需要将他们列到圆括号内,各个值之间用空格分割
# myvariable=(one two 3 4 5 6 7)
# echo $myvariable
one
![](https://i-blog.csdnimg.cn/blog_migrate/66cfa58a751f3ec7e86f01dd1ed124ac.png)
显示变量值的时候大家发现并没有将所有的变量值都显示出来,而是只显示了第一个值,要想显示数组的中的值,我们需要使用数值索引号;
echo ${myvariable[1]}
![](https://i-blog.csdnimg.cn/blog_migrate/daaee6e8cca08213c93fae3976d6042d.png)
索引的值以0开始,大家要注意,第一个值的索引号是0。
显示整个数值的变量,可以用*号通配
# echo ${myvariable[*]}
one two 3 4 5 6 7
![](https://i-blog.csdnimg.cn/blog_migrate/f43f843d96df47aa9358c074a8b08799.png)
2.5 特殊的Shell变量
2.5.1 位置变量
位置变量 | 作用说明 |
$0 | 获取脚本文件名 |
$n | 获取脚本的第n个参数 如果n>9, ${10} |
$# | 获取当前脚本的参数总个数 |
$* | 获取脚本所有传递的参数,把参数当作一个整体字符串,如果不加引号结果与$@相同 |
$@ | 获取脚本所有传递的参数,把参数当作独立的字符串 |
在Shell中存在一些特殊的环境变量,例如 #,#*,我们称为位置参数变量,要给命令,脚本等传递参数时,就需要在Shell脚本中使用位置参数变量;
在执行脚本的时候,脚本后面的值可以当作脚本的参数来执行;
# vi mytest1.sh
#!/bin/bash
echo "当前脚本文件名:$0"
n=1
echo "使用\$@的参数列表为:$@"
echo "总共有$#个参数"
for i in "$@"
do
echo "第$n个参数的值是:$i"
let n+=1
done
# bash mytest1.sh 1 2 3 4 5
![](https://i-blog.csdnimg.cn/blog_migrate/6823419edd8ef863af4c06a4f3f47523.png)
# vi mytest2.sh
#!/bin/bash
echo "当前脚本文件名:$0"
n=1
echo "使用\$*的参数列表为:$*"
echo "总共有$#个参数"
for i in "$*"
do
echo "第$n个参数的值是:$i"
let n+=1
done
# bash mytest2.sh 1 2 3 4 5
![](https://i-blog.csdnimg.cn/blog_migrate/1e3d92c30679497dc16a10924f75236e.png)
#vi mytest3.sh
#!/bin/bash
echo "当前脚本文件名:$0"
n=1
echo "使用\$*的参数列表为:$*"
echo "总共有$#个参数"
for i in $*
do
echo "第$n个参数的值是:$i"
let n+=1
done
# bash mytest3.sh 1 2 3 4 5
![](https://i-blog.csdnimg.cn/blog_migrate/0c9eb23ffa8388aaac5ffe2c0c638866.png)
总结:$@ "$*" $*的区别:
$@将传递的参数看作一个一个的的单个字符串;
"$*"将传递的参数看作是一个整体;
$*和$@一样,将传递的参数看作一个一个的的单个字符串;
2.5.2 状态变量
![](https://i-blog.csdnimg.cn/blog_migrate/2ceb9ef623719605b97932ef989f441c.png)
变量功能演示 :
[root@nebulalinux ~]# pwd
/root
[root@nebulalinux ~]# echo $?
0 #<---0表示执行成功
[root@nebulalinux ~]# cmd
-bash: cmd: command not found
[root@nebulalinux ~]# echo $?
127 #<---127表示命令执行失败
[root@nebulalinux ~]# echo $$
21457 #<---显示当前shell进程号
[root@nebulalinux ~]# top & #<-----在后台执行
[1] 21475
[root@nebulalinux ~]# echo $!
21475 #<---显示上一个后台工作的进程的进程号
[1]+ Stopped top
[root@nebulalinux ~]# ls -ld /root/
dr-xr-x---. 2 root root 4096 Oct 12 05:36 /root/
[root@nebulalinux ~]# echo $_
/root/ #<---获取上个命令的最后一个参数
[root@nebulalinux ~]#
2.5.3内置命令
Shell中包含了一些内置命令,如echo,printf,export,read,shift等 ;
1.echo命令用于在屏幕上输出数据,常用选项有:
![](https://i-blog.csdnimg.cn/blog_migrate/2cdaee93a97ef82a6c407b311b54d93a.png)
[root@nebulalinux ~]# echo hello;echo world
hello
world
[root@nebulalinux ~]# echo -n hello;echo world
helloworld
[root@nebulalinux ~]# echo "hello\tworld\nhello\tworld"
hello\tworld\nhello\tworld
[root@nebulalinux ~]# echo -e "hello\tworld\nhello\tworld"
hello world
hello world
[root@nebulalinux ~]# echo -e "hello\vworld\vhello\vworld"
hello
world
hello
world
[root@nebulalinux ~]# echo -e "hello \bworld"
helloworld
#printf功能和echo类似
2.exec命令
exec命令可以在不创建子进程的情况下去执行指定命令,并在命令执行结束后进程终止;
[root@nebulalinux ~]# su - nebula
hello world
[nebula@nebulalinux ~]$ exec date
Sun Nov 4 13:16:24 EST 2018
[root@nebulalinux ~]#
3.read命令
从标准输入中读取字符串信息,传给Shell内部定义的变量;
语法格式:read [选项] 变量名
选项:
-t:等待时间,
-p:设置提示信息
[root@nebulalinux ~]# read -t 10 -p "please input one number: " num
please input one number: 1
[root@nebulalinux ~]# echo $num
1
4.shift命令
shift语句会重新命名所有的位置参数;每使用一次shift,所有位置参数依次向左诺一位,并且位置参数$#会减一。
2.6 Shell变量扩展
表达式 | 说明 |
${parameter:-word} | 如果parameter的变量值为空或者未赋值,则会返回word字符串代替变 量值 |
${parameter:=word} | 如果parameter的变量值为空或者未赋值,则设置这个变量值为word, 返回值也是word |
${parameter:?word} | 如果parameter的变量值为空或者未赋值,那么word字符串会被当做标 准错误输出,否则输出变量的值 |
${parameter:+word} | 如果parameter的变量值为空或者未赋值,则输出空值,否则word字 符串将被替代变量的值 |
2.6.1 ${parameter:-word}功能实践
[root@nebulalinux03 ~]# echo $test #变量未设置输出为空
[root@nebulalinux03 ~]# res=${test:-unset} #使用特殊变量
[root@nebulalinux03 ~]# echo $res 打印res,返回unset
unset
[root@nebulalinux03 ~]#
#当变量test没有定义值时,就返回变量结尾设置的unset字符串
#可以用来判断变量是否已定义
2.6.2 ${parameter:=word}功能实践
[root@nebulalinux03 ~]# unset res
[root@nebulalinux03 ~]# echo $res
[root@nebulalinux03 ~]# unset test
[root@nebulalinux03 ~]# echo $test
[root@nebulalinux03 ~]# res=${test:=unset}
[root@nebulalinux03 ~]# echo $res
unset
[root@nebulalinux03 ~]# echo $test
unset
#test原本没有定义,现在已经被赋值unset
[root@nebulalinux03 ~]#
#当变量res值中的变量test值没有定义时,会给变量res赋值“:=”后面的值,同时会把“:=”后面的值赋
值给变量test
#可以用来解决变量没有定义的问题,并确保没有定义的变量的始终有值
2.6.3 ${parameter:?word}功能实践
[root@nebulalinux03 ~]# unset test
[root@nebulalinux03 ~]# echo ${test:? not defined}
-bash: test: not defined
[root@nebulalinux03 ~]# echo ${test? not defined}
-bash: test: not defined
[root@nebulalinux03 ~]# test=hello
[root@nebulalinux03 ~]# echo ${test:? not defined}
hello
[root@nebulalinux03 ~]# echo ${test? not defined}
hello
[root@nebulalinux03 ~]# unset test
[root@nebulalinux03 ~]# echo ${test? not defined}
-bash: test: not defined
[root@nebulalinux03 ~]#
#用来设定由于由于变量未定义而报错的内容
2.6.4 ${parameter:+word}功能实践
[root@nebulalinux03 ~]# res=${test:+replace}
[root@nebulalinux03 ~]# echo $res
[root@nebulalinux03 ~]# test=hello
[root@nebulalinux03 ~]# res=${test:+replace}
[root@nebulalinux03 ~]# echo $res
replace
[root@nebulalinux03 ~]#
#测试变量是否存在,如果变量值为replace,则证明变量test有值