Shell脚本(一)


一、编程语言介绍

编程语言是指计算机能理解的语言,人类通过使用计算机语言可以给计算机批量下达任务,让其按照人类的思想去完成工作。

最常见的语言有:汇编语言、C语言、java语言、php语言、Python语言、golang语言等等。

编程语言分类:

  • 编译型语言 程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、java、Golang等
  • 解释型语言 程序不需要编译,程序在运行时由解释器翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。

二、shell介绍

shell在计算机中起到什么作用呢?为什么要求shell呢,我们可以看看计算机操作系统的组成:

OS分层.png

看图之前问大家个问题,两个人在电话聊天:只会说法语的法国人,只会说汉语的你。如何沟通呢?

请个翻译在你两中间

同理,系统内核只知道二进制

如果你想给计算机内核下任务,让其驱动硬件干活,那么有两种选择

1、你学会二进制

2、找个翻译

1、shell介绍

shell就是我们找来的翻译,通过shell语言将完成一个任务的所有代码写入一个文件,并给执行权限。

shell是一个程序,采用C语言编写,是用户和linux内核沟通的桥梁。它既是一种命令语言,又是一种解释性的编程语言。通过一个图表来查看一下shell的作用。

00_shell.png

为什么要学shell,而不是其他计算机语言

  • 上手快
  • 入门简单
  • 学习周期短

学习这门课程的优势

  • 解放运维人员:7X24小时监控,监控为例,监控帮你干活,你只需要处理问题就好。
  • 提升业务能力:业务初始化,自动备份,日志分析,你的工作脚本来做,效率更高。
  • 提升管理能力:从系统安装到业务部署,再到服务器管理维护,实现自动化运维,批量管理机器与业务。
  • 提升运维薪资:技术能力和工资成正比。

学了能干什么

重点:重复性的工作,全部通过脚本来完成。高效的同时还不出错。

根据企业架构自定义监控系统,量身打造企业级监控系统

  • 业务初始化部署系统,业务初始化全部一键搞定,省去繁琐的安装与排错
  • 一键备份,分分钟搞定备份问题
  • 日志分析,繁琐又复杂的日志分析让机器取做吧。
  • 三方软件模块插件的编写:根据业务定制三方软件的功能,更贴合自己的业务。

学习什么内容

shell学习路线图.png

学习的技巧

好的编程是代码喂出来的。提升自己的代码量,把所有想到的事情都用代码实现出来

成长路径

shell编程掌握等级图.png

1、能看到代码实现的算法、原理
2、能根据自己的脚本应用要求修改脚本
3、能根据业务需求写脚本
4、能根据脚本执行问题优化脚本代码


总结:
什么时候用到脚本?
重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。

①自动化分析处理
②自动化备份
③自动化批量部署安装
④等等…

如何学习shell脚本?

尽可能记忆更多的命令
掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
必须熟悉掌握脚本的基本语法(重点)

学习脚本的秘诀:
多看(看懂)——>多模仿(多练)——>多思考

2、bash shell基本特性

知己知彼方可百战百胜,如何应用Bash shell,我们先看看他的特性,有助于我们快速应用。

2.1、 命令和文件自动补全

Tab只能补全命令和文件

2.2、 常见的快捷键—提升操作熟练度

Ctrl+c    终止前台运行的程序
Ctrl+z    将前台运行的程序挂起到后台
Ctrl+d    退出 等价exit
Ctrl+l    清屏 
Ctrl+a |home   光标移到命令行的最前端
Ctrl+e |end   光标移到命令行的后端
Ctrl+u    删除光标前所有字符
Ctrl+k    删除光标后所有字符
Ctrl+r   搜索历史命令

3、shell脚本介绍

shell脚本是什么

简单来说就是将需要完成某个任务所执行的命令按照执行顺序保存到文本中,并给予执行权限。

  • 按照顺序执行。

  • 它是解释型的,意味着不需要编译。

  • 准确来说是若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本

脚本命令演示
创建一个用户:tom     useradd tom
密码设置为:liangzhi666 echo "liangzhi666"|passwd --stdin tom
该用户创建文件夹/tmp/tom   mkdir /tmp/tom
该用户创建文件/tmp/tom/README  touch /tmp/tom/README
将“hello world“输入到/tmp/tom/README  echo 'hello world' > /tmp/tom/README

实现代码 01_task.sh
#!/bin/bash

#DESC: this is a test script
#AUTHOR: zj1007
#RELEASE: 1.0

#main 

#创建用户tom
useradd tom

#设置用户密码 liangzhi666
echo "liangzhi666"|passwd --stdin tom


#使用tom创建文件夹,文件,输入文件中内容
su - tom -c "mkdir /tmp/tom"
su - tom -c "touch /tmp/tom/README"
su - tom -c "echo 'hello world' > /tmp/tom/README"

4、shell脚本语法

来吧,光说不练嘴把式,我们来看看如何书写一个脚本呢,写好一个脚本有哪些规范呢?

  • shell脚本组成

shell程序组成.png

  • 脚本命名 nginx_install.sh 脚本名称 脚本扩展名 .sh 名字不要太长 26个字节内
  • 代码规范:
1、#!/bin/bash
//脚本第一行, #!魔法字符,指定脚本代码执行的程序。即它告诉系统这个脚本需要什么解释器来执行,也就是使用
哪一种Shell

2、#代表注释,#!特例

3、//以下内容是对脚本的基本信息的描述,大家可以根据实际情况尽可能的写详细一些,方便后续使用者
# Name: 脚本名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间
# Author:作者
# Release: 分发版本

//下面就是脚本的具体内容
commands
...
  • 脚本执行方法: – 标准脚本执行方法(建议):
[root@zuolaoshi shell01]# cat 1.sh 
#!/bin/bash
#xxxx
#xxx
#xxx
hostname
date
[root@zuolaoshi shell01]# chmod +x 1.sh 
[root@zuolaoshi shell01]# ll
total 4
-rwxr-xr-x 1 root root 42 Jul 22 14:40 1.sh
[root@zuolaoshishell01]# /shell/shell01/1.sh
zuolaoshi
Sun Jul 22 14:41:00 CST 2018
[root@zuolaoshi shell01]# ./1.sh
zuolaoshi
Sun Jul 22 14:41:30 CST 2018

– 非标准的执行方法(不建议):

[root@zuolaoshi shell01]# bash 1.sh
zuolaoshi
Sun Jul 22 14:42:51 CST 2018
[root@zuolaoshi shell01]# sh 1.sh
zuolaoshi
Sun Jul 22 14:43:01 CST 2018
[root@zuolaoshi shell01]#
[root@zuolaoshi shell01]# bash -x 1.sh
+ hostname
zuolaoshi
+ date
Sun Jul 22 14:43:20 CST 2018

-x:一般用于排错,查看脚本的执行过程
-n:用来查看脚本的语法是否有问题

注意:如果脚本没有加可执行权限,不能使用标准的执行方法执行,bash 1.sh

其他:
[root@zuolaoshishell01]# source 2.sh
server
Thu Nov 22 15:45:50 CST 2018
[root@zuolaoshi shell01]# . 2.sh
server
Thu Nov 22 15:46:07 CST 2018

source  . 表示读取文件,执行文件里的命令

– 命令式脚本执行方法:

定义命令路径变量  PATH
PATH=$PATH:脚本路径

备注:脚本必须给执行权限

三、变量介绍

在编程中,我们总有一些数据需要临时存放在内存,以待后续使用时快速读出。先了解一下计算机的存储单位吧。

计算机的单位:
1B=8bit

1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
1PB=1024TB
1EB=1024PB
1ZB=1024EB
...
好了,已经够大了!当然还有YB、BB更大的单位,同样进制也是1024.

1G=1024*1024*1024=1073741824B

假如你将一个1B的字符存入内存,如何读出呢?有没有一种大海捞针的感觉啊!我们讨论一下计算机是如何通过让我们人类快速将数据存在内存,如何从内存中读出数据的。我们研究过变量后就明白了。

变量:变量是编程中最常用的一种临时在内存中存取数据的一种方式。

变量存取原理

关于内存的说明
a、系统启动    内存被按照1B一个单位划分成N块     并且以十六进制为每一个空间编号

b、内存跟踪表记录  使用和未使用的内存的地址编号

c、内存申请    系统从未使用的内存中拿出一个或者一段连续空间  给你使用   同时在内存跟踪表中记录
该地址被占用不在分给别人,同时在系统中建立映射机制   
比如:变量名 STRING1=‘ABC’

name<==>0x5

d、释放内存
从内存跟踪表中将记录删除,下次存数据直接覆盖

变量存储.png

CHAR1(0x3)=A
从图片可以看出,当我们在脚本中定义变量存值的时候,可以从以下方面看到变化:
a、内存占用:如果存的是一个字符则占用1个字节,如果存的是字符串则是字符串的长度加1个字节长度(\0是一个
特殊字符,代表字符串结束)。

b、变量名与内存空间关系:计算机中会将对应的内存空间地址和变量名称绑定在一起,此时代表这段内存空间已经被
程序占用,其他程序不可复用;然后将变量名对应的值存在对应内存地址的空间里。

四、变量定义

1、什么时候需要定义变量?

如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值。 在代码运作的过程中,可能会把某些命令的执行结果保存起来,后续代码需要使用这些结果,就可以直接使用这个变量。

2、定义一个变量

变量格式: 变量名=值

在shell编程中的变量名和等号之间不能有空格。

变量名命名规则:
    命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
    中间不能有空格,可以使用下划线(_)。
    不能使用标点符号。
    不能使用bash里的关键字(可用help命令查看保留关键字)。

定义变量举例: VAR1=1 age=18 整形 name=‘zj’ 字符串 score=88.8 浮点

注意:字符串要用单引号或双引号引起来
建议变量名为大写,和命令区分
            _name

定义变量演示:

变量赋值,此种方法设置为本地变量
[root@zuolaoshi ~]# name="zj"
[root@zuolaoshi ~]# school='liangzhi'
[root@zuolaoshi ~]# age=30
[root@zuolaoshi ~]# score=88.8
3、取消变量 unset
取消当前环境中的变量,如果是变量设置是保存在文件中,下次重启又会恢复
[root@zuolaoshi ~]# unset name
[root@zuolaoshi ~]# echo $name
4、 有类型变量 declare

-i 将变量看成整数 -r 使变量只读 readonly,该变量的值无法改变,并且不能为unset -x 标记变量通过环境导出 export -a 指定为索引数组(普通数组);查看普通数组 -A 指定为关联数组;查看关联数组

[root@zuolaoshi ~]# declare -i num='asa'
[root@zuolaoshi ~]# echo $num
0
[root@zuolaoshi ~]# num=100
[root@zuolaoshi ~]# echo $num
100
[root@zuolaoshi ~]# declare -r num
[root@zuolaoshi ~]# echo $num
100
[root@zuolaoshi~]# num=200
-bash: num: 只读变量

[root@zuolaoshi ~]# declare -x
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="Bai_Shuming"
declare -x LANG="zh_CN.UTF-8"
declare -x LESSOPEN="||/usr/bin/lesspipe.sh %s"

五、变量分类

系统中的变量根据作用域及生命周期可以分为四类:本地变量、环境变量、全局变量、内置变量

1、本地变量

用户自定义的变量,定义在脚本或者当前终端中,脚本执行完毕或终端结束变量失效。

2、环境变量

定义在用户家目录下的.bashrc或.bash_profile文件中,用户私有变量,只能本用户使用。

查看当前用户的环境变量 env

查询当前用户的所有变量(临时变量与环境变量) set

3、将当前变量变成环境变量 export
# 定义一个临时变量
[root@zuolaoshi tmp]# export A=hello //临时将一个本地变量(临时变量)变成环境变量
[root@zuolaoshi tmp]# env|grep ^A
A=hello

[root@zuolaoshi tmp]# A=HELLO
[root@zuolaoshi tmp]# export A

# 定义一个永久生效变量:
vim .bash_profile 或者 ~/.bashrc
A=hello


# 关于export说明
用户登录时:
1) 用户登录到Linux系统后,系统将启动一个用户shell。在这个shell中,可以使用shell命令或声明变量,也可以
创建并运行 shell脚本程序。

运行脚本时:
2) 运行shell脚本程序时,系统将创建一个子shell。此时,系统中将有两个shell,一个是登录时系统启动的shell,
另一个是系统为运行脚本程序创建的shell。当一个脚本程序运行完毕,它的脚本shell将终止,可以返回到执行该脚本
之前的shell。

从这种意义上来说,用户可以有许多 shell,每个shell都是由某个shell(称为父shell)派生的。
在子shell中定义的变量只在该子shell内有效。如果在一个shell脚本程序中定义了一个变量,当该脚本程序运行时,
这个定义的变量只是该脚本程序内的一个局部变量,其他的shell不能引用它,要使某个变量的值可以在其他shell中
被改变,可以使用export命令对已定义的变量进行输出。 

export命令将使系统在创建每一个新的shell时定义这个变量的一个拷贝。这个过程称之为变量输出。

父shell与子shell

shell父子关系.png

4、全局变量

使用export命令将本地变量输出为当前shell中的环境变量 所有用户及shell都可以使用,可以在/etc/profile /etc/bashrc下永久定义

打印全局变量 printenv

定义格式
export SCHOOL='liangzhi'


测试方法:
通过不同用户登录测试是否能读取变量
5、内置变量

系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用.

$?:上一条命令执行后返回的状态,当返回状态值为0时表示执行正常,非0值表示执行异常或出错
 若退出状态值为0,表示命令运行成功
 若退出状态值为127,表示command not found
 若退出状态值为126,表示找到了该命令但无法执行(权限不够)
 若退出状态值为1&2,表示没有那个文件或目录

$$:当前所在进程的进程号     echo $$   eg:kill -9 `echo $$`  = exit   退出当前会话

$!:后台运行的最后一个进程号  (当前终端)  # gedit &
!$ 调用最后一条命令历史中的参数
!! 调用最后一条命令历史


$#:脚本后面接的参数的个数
$*:脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@: 脚本后面所有参数,参数是独立的,也是全部输出

$0:当前执行的进程/程序名  echo $0     
$1~$9 位置参数变量
${10}~${n} 扩展位置参数变量  第10个位置变量必须用{}大括号括起来
./1.sh a b c

[root@zuolaoshi shell01]# cat 2.sh 
#!/bin/bash
#xxxx
echo "\$0 = $0"
echo "\$# = $#"
echo "\$* = $*"
echo "\$@ = $@"
echo "\$1 = $1" 
echo "\$2 = $2" 
echo "\$3 = $3" 
echo "\$11 = ${11}" 
echo "\$12 = ${12}" 

了解$*和$@的区别:
$* :表示将变量看成一个整体
$@ :表示变量是独立的

#!/bin/bash
for i in "$@"
do
echo $i
done

echo "======我是分割线======="

for i in "$*"
do
echo $i
done

[root@zuolaoshi shell01]# bash 3.sh a b c
a
b
c
======我是分割线=======
a b c

变量总结说明:

本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。

环境变量:当前进程有效,并且能够被子进程调用。

全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.

内置变量:shell本身已经固定好了它的名字和作用.

变量类型 作用域 生命周期
本地变量 当前shell环境(子shell不能用) 脚本结束或终端结束
环境变量 当前shell或者子shell 当前进程结束
全局变量 所有用户及shell环境 关机
内置变量 所有用户及shell环境 关机

四、变量取值

读取变量内容符: 读取方法:变量名

变量内容读出
[root@zuolaoshi ~]# echo $name
zj
[root@zuolaoshi ~]# echo $school
liangzhiedu
[root@zuolaoshi ~]# echo $age
30
[root@zuolaoshi ~]# echo $score
88.8

注意

变量读取过程中,默认单引号是不解释变量的.比如
[root@zuolaoshi ~]# echo '$name'
$name

如果必须使用单引号还要读取变量的值可以使用eval命令[重新运算求出参数的内容] 
[root@zuolaoshi ~]# eval  echo '$name'
zj

五、其他变量(扩展)

1)取出一个目录下的目录和文件:dirname和 basename 
2)变量"内容"的删除和替换
一个“%”代表从右往左去掉一个/key/
两个“%%”代表从右往左最大去掉/key/
一个“#”代表从左往右去掉一个/key/
两个“##”代表从左往右最大去掉/key/

# A=/root/Desktop/shell/mem.txt 
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A   取出目录
/root/Desktop/shell
# basename $A  取出文件
mem.txt

# url=www.taobao.com
# echo ${#url}      获取变量的长度
# echo ${url#*.}       以分隔符.界限  *匹配所有
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}

计算机程序其实就是三步:输入、运算、输出,这个理论也适应于shell编程。

那么计算机是如何将信息按照比较舒服的格式输出到屏幕或者KFC的打印纸上的呢!如果让计算机能够输出一种格式,让人看起来很舒服,那么我们就要学习一下计算机的格式化输出,让计算机程序将信息输出的时候美美哒!让人一目了然看到需要的信息。

六、shell格式化输出

一个赏心悦目的界面是一个程序给用户的第一个映像,好的界面可以让用户更加容易上手使用。windows之所以能被个人用户喜欢就是因为它的界面更加容易和用户交互,只要用户能识别文字,懂得点击鼠标就能操作电脑;而linux之所以无法被广大个人用户使用的瓶颈就是图形界面无法完成所有工作,需要命令配合才可以,这就把非专业用户拒之门外了,想用就必须去学习命令。 我们在使用shell写一个程序的时候,如果想让广大的用户都能使用,都能快速上手,那么好的交互界面就太重要了。我们可以使用多种方法开发好的、易交互的界面,常用的工具有:dialog、echo、printf等命令。 本节课主要给大家介绍一个最简单易用的命令:echo

1、echo命令

功能:将内容输出到默认显示设备

应用场景:需要计算机程序输出的地方

echo命令的功能是在显示器上显示一段文字,一般起到一个提示的作用。 功能说明:显示文字。

语法:echo [-ne][字符串]

补充说明:
1、echo会将输入的字符串送往标准输出。
2、输出的字符串间以空白字符隔开,并在最后加上换行号。

OPTIONS:
-n  不要在最后自动换行
-e  若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:

转义字符
\a  发出警告声;
\b  删除前一个字符;
\t  插入tab;
\n  换行且光标移至行首;

\c  最后不加上换行符号;
\f  换行但光标仍旧停留在原来的位置;
\r  光标移至行首,但不换行;
\v  \f相同;
\       插入\字;
\0nnn   打印nnn(八进制)所代表的ASCII字符;  备注:数字0  不要理解成字母o
\xNN  打印NN(十六进制)所代表的ASCII字符;

-–help  显示帮助
-–version显示版本信息


你的进制转换过关吗?
[root@zuolaoshi ~]# echo -e "\0123"   #ot(123) = 83  对应ascii表的S
S
[root@zuolaoshi ~]# echo -e "\x61"   #ox(61) = 97  对应ascii表的a
a

2、输出颜色字体

脚本中echo显示内容带颜色显示,echo显示带颜色,需要使用参数-e

格式如下:

echo -e "\033[字背景颜色;文字颜色m字符串\033[0m"

例如: echo -e “\033[41;36m something here \033[0m”

其中41的位置代表底色, 36m的位置是代表字的颜色

1、字背景颜色和文字颜色之间是英文的

2、文字颜色后面有个m

3、字符串前后可以没有空格,如果有的话,输出也是同样有空格

下面是相应的字和背景颜色,可以自己来尝试找出不同颜色搭配

    echo -e \033[31m 红色字 \033[0m”
  echo -e \033[34m 黄色字 \033[0m”
  echo -e \033[41;33m 红底黄字 \033[0m”
  echo -e \033[41;37m 红底白字 \033[0m”
  
字颜色:30—–37
  echo -e \033[30m 黑色字 \033[0m”
  echo -e \033[31m 红色字 \033[0m”
  echo -e \033[32m 绿色字 \033[0m”
  echo -e \033[33m 黄色字 \033[0m”
  echo -e \033[34m 蓝色字 \033[0m”
  echo -e \033[35m 紫色字 \033[0m”
  echo -e \033[36m 天蓝字 \033[0m”
  echo -e \033[37m 白色字 \033[0m”

  
字背景颜色范围:40—–47
  echo -e \033[40;37m 黑底白字 \033[0m”
  echo -e \033[41;37m 红底白字 \033[0m”
  echo -e \033[42;37m 绿底白字 \033[0m”
  echo -e \033[43;37m 黄底白字 \033[0m”
  echo -e \033[44;37m 蓝底白字 \033[0m”
  echo -e \033[45;37m 紫底白字 \033[0m”
  echo -e \033[46;37m 天蓝底白字 \033[0m”
  echo -e \033[47;30m 白底黑字 \033[0m”
  
最后面控制选项说明
  \033[0m 关闭所有属性
  \033[1m 设置高亮度
  \033[4m 下划线
  \033[5m 闪烁
  \033[7m 反显
  \033[8m 消隐

  \033[30m  \33[37m 

设置前景色
  \033[40m  \33[47m 设置背景色
  
  
  \033[nA 光标上移n行
  \033[nB 光标下移n行
  \033[nC 光标右移n行
  \033[nD 光标左移n行
  \033[y;xH设置光标位置
  \033[2J 清屏
  \033[K 清除从光标到行尾的内容
  \33[s 保存光标位置
  \033[u 恢复光标位置
  \033[?25l 隐藏光标
  \033[?25h 显示光标
  
用法例子  光标下移三行  
[root@zuolaoshi ~]# echo -e "\033[0m today is fine \033[3B"
 today is fine 

3、jobs

需求:输出一个水果购物界面 fruits_shop.sh

案例要点:

  • echo输出缩进问题
  • 字体颜色输出

代码实现:

# job代码    01_fruits_shop.sh
#!/bin/bash
# 
#Author: www.zuolaoshi.cn
#Release: 
#Description:打印水果超市列表

echo -e "\t\t    \033[32m Fruits List \033[0m      \n"
echo -e "\t   \033[31mFruit\033[0m   \t\t \033[31mPrice\033[0m \t\t\033[31mWeight\033[0m"
echo -e "\t\033[34m1)Apple\t\t¥10.00\t\t1KG\033[0m"
echo -e "\t\033[34m2)Banana\t¥9.00\t\t1KG\033[0m"
echo -e "\t\033[34m3)Orange\t¥15.20\t\t1KG\033[0m"