1、shell编程语言必知必会
shell命令解释器:bash编程
常用命令解释器:
命令解释器 | 说明 |
---|---|
bash | 目前应用最广泛的一款命令解释器,红帽系列、debian,Ubuntu等。BASH全称:Boume-Again Shell |
dash | 一般debian、Ubuntu系统默认的,运行脚本推荐使用bash 脚本名字 |
csh、tcsh | 一些Unix系统使用 |
zsh | 功能多,支持更多的插件,可以更好看 |
注意:、
shell脚本(bash .sh)运行在Ubuntu中的时候,不推荐使用sh运行,推荐使用bash运行
2、编程语言分类
安装执行方式分类:
解析型:直接解析类,例如,shell、Python、php,书写成文件后,可以通过对应的解释器直接运行。
编译型:需要编译后运行,例如:C、C++、java、go等,下载好源代码,必须要进行编译成可以运行的命令
解释型:
[root@m01 ~]#vim /server/script/show.py
print("Hello World!!!")
[root@m01 ~]#python /server/script/show.py
Hello World!!!
[root@m01 ~]#
编译型:
[root@m01 ~]#vim /server/script/show.c
#include "stdio.h"
void main() {
printf("hello world!!!");
}
#编译代码
[root@m01 ~]#gcc /server/script/show.c -o /server/script/show.bin
#生成的命令为:/server/script/show.bin
#执行命令:
[root@m01 ~]#/server/script/show.bin
hello world!!!
[root@m01 ~]#
3、编程环境准备
环境 | 说明 | 备注 |
---|---|---|
主机 | 主机名:m01 | 192.168.10.61、172.16.1.61 |
代码目录 | /server/script/devops-shell/ | |
编程环境 | vim、sublime等编程环境IDE | 这里选择vim或sublime即可 |
vim配置 | 自动添加说明信息 | 创建.sh或.bash文件自动添加信息 |
修改vimrc文件,达到控制vim创建,编辑文件的动作。
1、当前用户家目录下~/.vimrc,当前用户生效
2、放在/etc/vimrc文件末尾,所有用户生效
设置如下,在/etc/vimrc文件末尾添加:
set ignorecase
autocmd BufNewFile *.py,.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") =~ 'sh\|bash'
call setline(1, "#!/bin/bash")
call setline(2, "#####################################")
call setline(3, "# File Name:".expand("%"))
call setline(4, "# Version:v1.0")
call setline(5, "# Author:xzm")
call setline(6, "# Organization:logmm.org")
call setline(7, "# Desc:")
call setline(8, "#####################################")
endif
endfunc
解释:
set ignorecase #搜索的时候忽略大小写
#设置创建.py、.sh文件的时候,使用SetTitle()函数
autocmd BufNewFile *.py,.cc,*.sh,*.java exec ":call SetTitle()"
#函数内容
func SetTitle()
if expand("%:e") =~ 'sh\|bash' #判断结尾是.sh或.bash
#setline写入行,1表示行号
call setline(1, "#!/bin/bash")
call setline(2, "#####################################")
call setline(3, "# File Name:".expand("%"))
call setline(4, "# Version:v1.0)
call setline(5, "# Author:xzm")
call setline(6, "# Organization:logmm.org")
call setline(7, "# Desc:")
call setline(8, "#####################################")
endif #判断结束
endfunc #函数结束
4、shell脚本执行方式
4.1 概述
应用及场景
应用场景 | 说明 |
---|---|
通过sh或bash | 书写脚本后,最常用的方式。 在其他非红帽系统中,建议使用bash运行脚本 |
通过.(点)或source | 加载/生效配置文件(环境变量,别名) 常用:可以用来实现include功能,把其他脚本引入当前脚本中 |
通过相对或绝对路径 | 一般不推荐使用这种,一般都是系统脚本/调用的脚本,脚本需要加上可执行权限才能用 |
输入重定向符号 | 不推荐使用 |
sh或bash
[root@m01 ~]#cd /server/script/devops-shell/
[root@m01 /server/script/devops-shell]#vim 01.show.sh
#!/bin/bash
####################################################
# File Name:01.show.sh
# Version:v1.0
# Author:xzm
# Organization:logmm.org
# Desc:
####################################################
whoami
ls
#执行脚本
[root@m01 /server/script/devops-shell]#sh 01.show.sh
root
01.show.sh
[root@m01 /server/script/devops-shell]#bash 01.show.sh
root
01.show.sh
[root@m01 /server/script/devops-shell]#sh < 01.show.sh
root
01.show.sh
4.2 #!符号含义
并非注释的意思,写在脚本开头,用于指定脚本默认的命令解释器。
命令解释器 | 写法 |
---|---|
bash | #!/bin/bash |
python | #!/usr/bin/python2或#!/usr/bin/dev python2 |
5、变量
5.1 变量
变量:variable
变量的本质:
变量:内存中的一块空间,变量名字===>内存空间地址
变量的定义:
变量名称=值
5.2 变量命名
变量命名要求
1、不能以数字开头
2、不能以特殊符号开头,但可以以_开头
3、建议使用字母开头
4、不能与系统中已有的变量或命令名称相同
命名方式:
1、驼峰方式:第一个单词小写,从第二个单词开始,每个单词的首字母大写,其他小写,如:personOfName
2、单词(小写)+“_”,如:person_of_name
3、要见名知义
5.3 变量分类
分类 | 说明 |
---|---|
普通变量(局部变量) | 在脚本中定义,通过变量名=值这种形式创建的 |
环境变量(全局变量) | 一般都是系统创建的,如:PATH、PS1…. |
特殊变量 | $+特殊符号的变量,shell脚本、命令、各方面 |
1)普通变量
取出变量的内容:$变量名
温馨提示:
$与${}一致,可以用于取值
例如:
age=32
$age与${age}一样,都是取出age的值
例如:
[root@m01 ~]#day=6
[root@m01 ~]#echo $day
6
[root@m01 ~]#echo ${day}
6
[root@m01 ~]#echo ${day}天
6天
[root@m01 ~]#echo $day'天'
6天
[root@m01 ~]#echo $day"天"
6天
[root@m01 ~]#
2)环境变量
全局变量:一处定义,处处使用
大部分都是系统定义的,我们一般就是修改
使用env命令可以查看系统的环境变量
环境变量名字 | 含义 | 应用场景 |
---|---|---|
PATH | 记录命令位置的环境变量,运行命令的时候,bash会在PATH的路径中查找 | 通过二进制包或编译安装的软件,需要增加新的命令路径 |
LANG | LANG,language,系统语言与字符集。中文:LANG=zh_CN.UTF8,英文:LANG=en_US.UTF8 | 修改语言字符 |
PS1 | 命令行格式 | 修改命令行格式 |
UID、EUID | 用户的uid | 可以在脚本中用于判断用户是否是root,root用户uid为0 |
HOSTNAME | 主机名 | |
历史四人组 | history命令相关 | |
HISTSIZE | history命令记录最多多少条命令,生产环境中尽量少 | |
HISTFILESIZE | history历史记录文件的大小,~/.bash_history。生产环境尽量少 | |
HISTCONTROL | 控制历史命令记录或不记录哪些内容。生产环境ignorespace以空格开头的命令,不记录 | |
HISTFILE | 指定历史命令的记录文件的名字和位置,默认,当前用户的家目录的.bash_history文件 | |
IFS | 类似于awk -F,指定分隔符(指定每一列的分隔符) | 一般与循环配合读取文件内容 |
TMOUT | 超时自动退出时间 | |
PROMPT_COMMAND | 存放命令,命令行执行命令后会运行这个变量的内容,用于实现行为审计(记录用户操作) | 手写跳板机、行为审计 |
修改环境变量:
export 创建或修改全局变量(环境变量)
案例1:把当前系统语言修改为中文utf8
[root@m01 ~]#export LANG=zh_CN.UTF-8
#永久修改:
[root@m01 ~]#echo "LANG=zh_CN.UTF-8" >> /etc/profile
[root@m01 ~]#source /etc/profile
3)环境变量相关文件及目录
文件或目录 | 说明 | 应用场景 |
---|---|---|
/etc/profile | 全局生效,存放函数、环境变量、别名 | – |
/etc/bashrc | 全局生效,存放别名 | – |
~/.bashrc | 家目录下,局部生效 | – |
~/.bash_profile | 家目录下,局部生效 | – |
/etc/profile.d/ | 目录,每个用户登录的时候(远程连接与su功能),加载目录下的.sh结尾的文件 | 设置一些登录后的提示信息 |
案例01:书写脚本,每次用户登录后显示系统信息
显示系统信息如下:
主机名: xxx
ip地址:xxx xxxx
总计内存:xxx
可用内存:xxx
系统负载:xxx xxx xxx
脚本如下:
[root@m01 ~]#vim /etc/profile.d//show_info.sh
#1、vars
hostname=`hostname`
ip_addr=`hostname -I`
mem_total=`free -h | awk 'NR==2{print $2}'`
mem_available=`free -h | awk 'NR==2{print $NF}'`
sys_loadavg=`top -bn1 | awk -F: 'NR==1{print $NF}'`
#2、echo
echo "主机名:$hostname"
echo "ip地址:$ip_addr"
echo "内存总大小:$mem_total"
echo "可用内存:$mem_available"
echo "系统负载:$sys_loadavg"
登录系统,显示如下:

另外,脚本的中的输出部分可以写成:
#2、echo
cat << EOF
操作需谨慎,删根弹指间
不要使用rm命令
主机名:${hostname}
ip地址:${ip_addr}
总内存:${mem_total}
可用内存:${mem_available}
系统负载:${sys_loadavg}
EOF
效果如下:

这个脚本的小bug,希望只有登录系统后显示,切换用户的时候不显示
判断两个变量是否存在即可,存在表示远程登录,不存在就切换用户
SSH_CLIENT=192.168.10.1 3399 22
SSH-TTY=/dev/pts/0
环境变量的加载顺序:

4)特殊变量
Linux shell编程中有各种各样的特殊变量:
位置相关的特殊变量
状态相关的特殊变量
变量子串
变量扩展
4.1)位置变量
位置的特殊变量 | 含义 | 应用场景 |
---|---|---|
$n(n:数字,n>=1) | 脚本的第n个参数 | 命令行与脚本内部桥梁 |
$0 | 脚本的名字 | 用于输出脚本的格式或版主的时候,用于错误提示输出帮助 |
$# | 脚本参数的个数 | 一般与判断结合,检查脚本参数个数 |
$@ | 取出脚本所有参数($1,$2,$3…$n) | 数组或循环中 |
$* | 取出脚本所有参数($1,$2,$3…$n) | 数组或循环中 |
案例1:$n、$0、$#参数:
[root@m01 ~]#cd /server/script/devops-shell/
[root@m01 /server/script/devops-shell]#vim 02.test_params.sh
echo '第1个参数 $1:' $1
echo '第2个参数 $2:' $2
echo '脚本的名称 $0:' $0
echo '脚本参数的个数 $#:' $#
#执行脚本:
[root@m01 /server/script/devops-shell]#sh 02.test_params.sh aa bb
第1个参数 $1: aa
第2个参数 $2: bb
脚本的名称 $0: 02.test_params.sh
脚本参数的个数 $#: 2
[root@m01 /server/script/devops-shell]#
$?:上一个命令执行的结果,0表示执行成功,非0表示执行失败
案例2:执行脚本的时候输入用户名,判断用户名是否存在
[root@m01 /server/script/devops-shell]#vim 03.check_user.sh
name=$1
id $name &> /dev/null
if [ $? -eq 0 ]
then
echo "$1用户存在。"
else
echo "$1用户不存在!"
fi
#执行效果
[root@m01 /server/script/devops-shell]#sh 03.check_user.sh root
root用户存在。
[root@m01 /server/script/devops-shell]#
案例3:$n,n大于9会有什么问题?
$10
大于9后,会表示为$1+0,即第1个参数+0
${10}才表示第10个参数
例如:
[root@m01 /server/script/devops-shell]#vim 03.params-10.sh
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11}
#测试
[root@m01 /server/script/devops-shell]#sh 03.params-10.sh {a..k}
a b c d e f g h i a0 a1
a b c d e f g h i j k
[root@m01 /server/script/devops-shell]#
因此:
$10:$1+0,所以是a0
$11:$1+1,所以是a1
${10}:表示第10个参数
${11}:表示第11个参数
案例04:$0,如果脚本执行错了,需要输出使用帮助:脚本名+{内容}
先略过如何检查是否出错。
echo "xxx.sh{start|stop|restart}"
脚本如下:
[root@m01 /server/script/devops-shell]#vim 04.print_usage.sh
echo "Usage:$0 start|stop|restart"
[root@m01 /server/script/devops-shell]#sh 04.print_usage.sh
Usage:04.print_usage.sh start|stop|restart
[root@m01 /server/script/devops-shell]#
案例05:$# 脚本参数个数
[root@m01 /server/script/devops-shell]#vim 03.check_user_v2.sh
name=$1
#脚本参数个数不是1
#提示并退出
if [ $# -ne 1 ];then
echo "请输入:$0 用户名"
exit 1 #退出
fi
id $name &> /dev/null
if [ $? -eq 0 ]
then
echo "$1用户存在。"
else
echo "$1用户不存在!"
fi
案例06:$@与$*
[root@m01 /server/script/devops-shell]#vim 03.params-10-v2.sh
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11}
echo $@
echo $*
[root@m01 /server/script/devops-shell]#sh 03.params-10-v2.sh {1..12}
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
[root@m01 /server/script/devops-shell]#
案例07:书写一个回收站脚本,替代rm命令
执行脚本,传入参数:被删除的文件或目录
如何替代rm?删除—>mv
创建回收站目录:/recyle/年-月-日_周几-时-分-秒
脚本:
[root@m01 /server/script/devops-shell]#vim 06.recyle_rm.sh
#1、vars
time=`date +%F_%w_%H%M%S`
recyle_dir=/recyle/$time
#2、判断
if [ $# -eq 0 ];then
echo "提示:$0 files dirs"
exit 1
fi
#3、创建目录及移动
mkdir -p $recyle_dir
mv $* $recyle_dir
#4、测试
[root@m01 /server/script/devops-shell]#touch a{1..3}.txt
[root@m01 /server/script/devops-shell]#sh 06.recyle_rm.sh *.txt
[root@m01 /server/script/devops-shell]#ll /recyle/2023-07-12_3_225939/
总用量 0
-rw-r--r-- 1 root root 0 7月 12 22:59 a1.txt
-rw-r--r-- 1 root root 0 7月 12 22:59 a2.txt
-rw-r--r-- 1 root root 0 7月 12 22:59 a3.txt
[root@m01 /server/script/devops-shell]#
#5、给rm起别名
[root@m01 /server/script/devops-shell]#alias rm='sh /server/script/devops-shell/06.recyle_rm.sh $*'
#这样rm命令就被替换了,这是临时生效,如果要永久,得在/etc/profile文件中定义别名
$@、$*的区别
符号 | 共同点 | 区别 |
---|---|---|
$@ | 取出脚本所有参数($1,$2,$3…$n) | 加上双引号,会识别合并在一起的参数,每个都是独立参数 |
$* | 取出脚本所有参数($1,$2,$3…$n) | 加上双引号,所有参数会合并为1个参数 |
例如:
[root@m01 /server/script/devops-shell]#vim 07.sh
set "I am old" 1 2 3
for i in "$*"
do
echo $i
done
echo "#############"
for j in "$@"
do
echo $j
done
#执行结果:
[root@m01 /server/script/devops-shell]#sh 07.sh
I am old 1 2 3
#############
I am old
1
2
3
[root@m01 /server/script/devops-shell]#
4.2)状态变量
状态变量 | 含义 | 应用场景 |
---|---|---|
$? | 上一个命令、脚本的返回值,0表示正确,非0表示错误 | 一般与判断检查命令结果 |
$$ | 当前脚本的pid | 一般写在脚本中获取脚本pid |
$! | 上一个脚本、命令(持续运行的)pid | |
$_ | 上一个命令的最后一个参数,其实下划线是个环境变量,记录了上一个命令、脚本的最后一个参数。使用esc+.(点) |
案例07:输入任何一个命令并执行,检查这个命令的执行结果是否正确
分析:
1、输入命令(参数)
2、执行命令
3、执行结果$?
if判断,如果是0,则表示成功
如果不是0,则表示失败
具体脚本:
[root@m01 /server/script/devops-shell]#vim 07.check_cmd.sh
source /etc/init.d/functions
#1.检查
#脚本参数个数 大于等于1
if [ $# -eq 0 ];then
echo "Usage: $0 命令"
exit 1
fi
#检查当前用户是否为root
if [ $UID -ne 0 ];then
echo "请使用root用户运行脚本"
exit 2
fi
#2、运行传入进来的命令
$* &> /dev/null
#3、判断
if [ $? -eq 0 ];then
echo "命令执行成功"
action "命令执行成功" /bin/true
else
echo "命令执行失败"
action "命令执行失败" /bin/false
fi
执行脚本:

输出颜色:
1、source /etc/init.d/functions
2、action "XXXX" /bin/true 或 /bin/false
例如:

4.3)变量子串
变量子串:parameter expension,变量扩展
格式:${变量},写成”$变量“形式无法识别
作用:用于对变量处理。(统计变量中字符串数量,对变量内容进行替换、删除等)
应用:可以不用
变量子串 parameter表示变量名字 | 含义 |
---|---|
基础 | |
${parameter} $para | 变量取值 |
${#parameter} | 统计字符长度(变量中有多少个字符) |
删除(开头、结尾) | word表示要删除的内容 |
${parameter#word} | 从变量左边开始删除,按照最短匹配删除 |
${parameter##word} | 从变量左边开始删除,按照最长匹配删除 |
${parameter%word} | 从变量右边开始删除,按照最短匹配删除 |
${parameter%%word} | 从变量右边开始删除,按照最长匹配删除 |
截取(切片)类似于cut -c | |
${var:5} | |
${var:5:2} | 从下标是5字符开始向后截取2个字符(注意:下标是从0开始的,0、1、2、3…) |
替换 | |
${para/word/replace} | 把word替换为replace,仅替换第1个 |
${para//word/replace} | 把word替换为replace,替换全部 |
1、统计字符长度
[root@m01 ~]#name="linux"
[root@m01 ~]#echo ${#name}
5
[root@m01 ~]#
案例:I am oldboy teacher welcome to linux class.
打印出这句话中字母数不大于6的单词。
思路:
1、循环取出这句话中每个单词(处理掉非单词的内容,比如点)
2、判断单词的字符数
3、输出
具体脚本
[root@m01 /server/script/devops-shell]#vim 08.print_word.sh
string="I am oldboy teacher welcome to linux class."
string_del=`echo $string | sed 's#\.##g'`
for i in $string_del
do
if [ ${#i} -le 6 ];then
echo $i
fi
done
或者:
#1、vars
string="I am oldboy teacher welcome to linux class."
#2、for
for i in ${string%.}
do
if [ ${#i} -le 6 ];then
echo $i
fi
done
使用awk:
echo 'I am oldboy teacher welcome to linux class.' | xargs -n1 | awk -F "[ .]" 'length()<=6'
2、删除变量中的内容(仅影响输出,不会影响变量本身)
[root@m01 ~]#var=oldboylida996
[root@m01 ~]#echo ${var#oldboy}
lida996
[root@m01 ~]#echo ${var#o}
ldboylida996
[root@m01 ~]#echo ${var##*o}
ylida996
[root@m01 ~]#echo ${var#*o}
ldboylida996
[root@m01 ~]#
取出路径中的文件名:
[root@m01 ~]#dir="/etc/sysconfig/network-scripts/ifcfg-eth0"
[root@m01 ~]#echo ${dir##*/}
ifcfg-eth0
%:使用
[root@m01 ~]#echo ${dir%/*}
/etc/sysconfig/network-scripts
[root@m01 ~]#
3、切片
[root@m01 ~]#a=123456
[root@m01 ~]#echo ${a:2}
3456
[root@m01 ~]#echo ${a:2:2}
34
[root@m01 ~]#
cut的切片:
[root@m01 ~]#echo 12345 | cut -c1
1
[root@m01 ~]#echo 12345 | cut -c1-3
123
[root@m01 ~]#echo 12345 | cut -c2-4
234
[root@m01 ~]#
4、替换
[root@m01 ~]#a=121
[root@m01 ~]#echo ${a/1/a}
a21
[root@m01 ~]#echo ${a//1/a}
a2a
[root@m01 ~]#echo ${a//[1-3]/a}
aaa
[root@m01 ~]#
统计脚本运行多久:time sh 脚本名称
[root@m01 /server/script/devops-shell]#time sh 08.print_word.sh
I
am
oldboy
to
linux
class
real 0m0.008s
user 0m0.002s
sys 0m0.005s
统计字符串个数:
[root@m01 ~]#echo 12345 | wc -L
5
[root@m01 ~]#
4.4)变量扩展
给变量设置默认值
格式 | 含义 |
---|---|
${parameter:-word} | 变量parameter没定义或为空,把word作为默认值,不修改变量内容(输出) |
${parameter:=word} | 变量parameter没定义或为空,把word作为默认值,修改变量内容 |
${parameter:?word} | 变量parameter没定义或为空,显示word,错误输出 |
${parameter:+word} | 变量parameter没定义或为空,则啥也不做,否则把word替换为变量内容 |
例如:
:-使用:
[root@m01 ~]#echo $name
[root@m01 ~]#echo ${name:-root}
root
[root@m01 ~]#name=haha
[root@m01 ~]#echo ${name:-root}
haha
[root@m01 ~]#
:=使用:
[root@m01 ~]#unset name
[root@m01 ~]#name=haha
[root@m01 ~]#echo ${name:=root}
haha
[root@m01 ~]#unset name
[root@m01 ~]#echo ${name:=root}
root
[root@m01 ~]#echo $name
root
[root@m01 ~]#
:?使用:
[root@m01 ~]#unset name
[root@m01 ~]#echo ${name:?root}
-bash: name: root
[root@m01 ~]#name=haha
[root@m01 ~]#echo ${name:?root}
haha
:+使用:
[root@m01 ~]#unset name
[root@m01 ~]#echo ${name:+root}
[root@m01 ~]#name=haha
[root@m01 ~]#echo ${name:+root}
root
[root@m01 ~]#echo $name
haha
[root@m01 ~]#