[TOC]

0x00 快速入门

(1)前言
Shell是什么?
答:Shell(壳)本身是一个用C语言编写的程序也是一个是解释执行的脚本语言即命令解释器,它在操作系统最外层是用户使用Unix/Linux的桥梁,把用户输入解释给操作系统等待操作系统处理后,将结果输出返回给用户,用户的大部分工作都是通过Shell完成的,还是一个功能强大的编程语言,易编写,易调试,灵活性较强

它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。

WeiyiGeek.shell位置层次

WeiyiGeek.shell位置层次

什么是shell脚本?
答:当Linux命令会语句不在命令行下执行(严格的说命令行执行的语句也是shell脚本),而是通过一个脚本程序文件执行时候,该程序就被称为shell脚本或shell程序;与windows中的bat批处理很类似;用户可以在shell脚本中嵌套命令/变量以及流程控制语句从而形成一个功能强大的shell脚本;

Shell是弱类型语言,既是一种命令语言,又是一种程序设计语言,

  • 作为命令语言:它交互式地解释和执行用户输入的命令,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序
  • 作为程序设计语言:它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括变量和流程控制语句

为什么要学Shell编程
答:Shell脚本是实现Linux系统及运维自动化管理的重要且必备的工具,尤其是shell脚本擅长处理村文本类型数据,而linux系统中的配置文件万物皆文件,所有极大的方便了我们使用者进行文件处理;几乎每一个合格的Linux系统管理或者运维工程师都需要学习Shell编程;

脚本的优势:

  • 我们所知道的PHP/java他们主要是用来编写应用程序或是网站主要是实现服务端程序;
  • shell编程他是一个脚本语言(所见即所得),它不需要执行编译过程之后再执行,他是将编译过程放在执行过程中,所以执行起来要慢得多。
  • Shell脚本帮助我们通过简单的脚本或其它方式来自动化、简单化任务的需求。比如,批量增加用户,定时备份脚本,批量记录什么LOG.

Shell有两种执行命令的方式

  • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
  • 批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令让Shell一次把这些命令执行完,而不必一条一条地敲命令。

我们需要掌握哪一些基础知识?

  • 命令基础(Linux上常常使用的命令文件管理/字符截取等);
  • Linux正则表达式以及三剑客(Grep/sed/awk)要熟练;
  • 俗话说工欲善其事必先利其器,vi/vim 或者 nano 必须熟练操作;


(2)Shell 发展史
人物介绍:Steve Bourne 也是贝尔实验室的成员,Dennis RitchieKen Thompson 的同事,本来他也就是一个默默无闻的码农。但由于 Ritchie 和 Thompson 玩游戏玩出了个 UNIX,一下子整个贝尔实验室都炸开了锅!

刚开始的时候Thompson 写了个简单的程序作为 UNIX 操作系统的接口界面,有了它人类和操作系统就可以进行交流了,叫 shell(我们在 Linux 打开的那个 Terminal 就是一个 shell), Thompson 还给它起了个名字,叫“Thompson shell”(这哥们可真不低调 )Thompson shell 的功能很简单,用户通过它输入一些指定的命令

  • 利用:它负责解释为需要计算机做的操作并去执行,另外它还能够支持一些简单的脚本,就是把一堆命令写进一个文件里依次执行
  • 缺点:但并没有更高级的例如流程控制,分支,变量,函数之类的东西。

主人公 Bourne 出现了(逮到机会赶紧上 ),也设计了一个 shell,叫“Bourne shell,sh”(似乎都生怕别人不知道程序是谁写的 )。

  • 升级:有了基本的流控制源,可以写简单的函数。

这俩好面子的大牛都力挺自己设计的 shell,渐渐的两种 shell 都有了各自的追随者,渐渐的形成了两大阵营(就像如今的 Vim 和 Emacs)。
Thompson 的粉丝觉得简洁才是真理,Bourne 的死忠则认为实用才是王道!就这样撕逼大战一触即发……

WeiyiGeek.SHELL标准转变

WeiyiGeek.SHELL标准转变

那现在打开的 Terminal 到底是什么 shell?
答:事实上现在大多数 Linux 发行版的默认 Shell 叫 Bash,它的名字是 Bourne-Again SHell 的缩写,这是关于 Bourne shell(sh)的一个双关语:“Bourne again / born again,Bourne再次/重生”,Brian J. Fox所编写(看还是低调的哥们赢得了最后的胜利 )。

(3)Shell的分类
由于历史原因,shell有很多不同的版本,而且也有很多有相同功能的命令需要我们进行取舍,以至于代码的规范很难统一。

Shell的两种主要语法类型有Bourne和C这两种语法彼此不兼容,由于Linux的标准Shell是Bash!Bash和sh是相互兼容的。
Bourne Shell:也称为B Shell,1979年起Unix就开始使用它主文件名为

1
2
3
4
5
#sh、ksh、bash、psh、zsh
Bourne shell :sh
Korn Shell : ksh
Bourne Again shell : bash
POSIX shell : psh

C Shell:主要是BSD版的Unix系统中使用,因其语法和C语言相类似而得名:

1
2
3
#csh、tcsh
c shell : csh
TENEX/TOPS C shell : tcsh

WeiyiGeek.shell分类介绍

WeiyiGeek.shell分类介绍

Linux支持的shell

1
2
3
4
5
6
7
echo $SHELL #通过执行echo $SHELL就可以看到当前系统支持的哪种Shell

# cat /etc/shells  (脚本文件执行路径) //可以看到当前Linux支持的所有Shell
/bin/sh 或者 /usr/bin/sh #前者实际指向一个文件就是后者
/bin/bash 或者 /usr/bin/bash
/bin/tcsh
/bin/csh


0x01 Shell脚本初识

Step1.建立和编写Shell脚本

1
2
3
4
5
6
7
vi hello.sh #创建一个名为hello的脚本文件,#按`I`插入内容
#first program# #是注释,,会被解释器忽略。可以注释中文

#!/bin/bash #开头必须加上这行注释,脚本才能执行(解释器)建议采用#!/usr/bin/env bash
echo "hello world!" #echo是输出命令

#VIM编辑器,按`ESC`退出编辑,键入`:wq`保存并退出(具体参考vim编辑器的使用)

Step2.赋予执行权限运行脚本直接运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
chmod 755 hello.sh #赋予执行权限 rwxr-xr-x
./hello.sh #`./`用来执行脚本,表示当前目录下,也可以用绝对路径
bash hello.sh #通过bash调用执行脚本

#补充:当然也可以不同赋予权限采用source执行(两种形式)
# File文件默认权限:644 rw-r--r-- ,最大权限是x
# Directory目录默认权限:755 rwxr-xr-x,最大权限是w
source hello.sh
. hellp.sh

#进入shell域退出shell
sh #从Bash进入到了sh
exit #退出当前Shell
#使用bash命令可以在已有的Bash下创建一个子Shell,同样使用exit退出,调用和退出都是一级一级连贯的.

注意事项

  • sh里面没有多行注释,只能每一行加一个#号;

0x02 Shell编程之变量

描述:主要学习用户自定义变量、环境变量、语系变量、位置参数变量和预定义变量等变量;变量是PC内存的单元Part,其中修改的值可以改变。

(1)变量类型分类

变量的分类: (变量分类从上到下越来越严格)

  • 1,用户自定义变量
  • 2,环境变量:主要保存的是和系统操作环境相关的数据
  • 3,位置参数变量:主要用来向脚本传递参数或数据,变量名不能自定义,变量名固定的
  • 4,预定义变量:Bash中已经定义好的变量变量名不能自定义,变量作用也是固定的

变量命名规则:

  • 1,和c语言一样,首字母/下划线为首加数字变量名的长度不得超过255个字符.
  • 2,中间不能有空格,可以使用下划线(_),不能使用标点符号。
  • 3,变量名在有效范围内必须唯一且不能使用bash里的关键字(可用help命令查看保留关键字)
  • 4,在Bash中变量的默认类型都是字符串型.
  • 5,用户自定义变量小写,系统环境变量大写(注意shell编程格式保持良好的风格)

变量的数据类型:

1
2
3
4
字符串    CHAR  #shell默认类型
# 整型 INT #需要将字符转整形
# 浮点型 Float、Double
# 日期型 Date

运行Shell的时候会同时存在三种变量:

  • 局部变量: 在脚本或命令中定义,仅仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量;
  • 环境变量:所有程序包括shell启动的程序都能访问环境变量,有些程序需要环境变量来保证其正常运行,必要时候shell脚本也可以定义环境变量;
  • shell变量:由shel程序设置的特殊变量,其包括了环境变量已经局部变量,这些变量保证了shell的正常运行;

注意事项:

  • Linux中默认变量类型都是字符串类型不含有其他类型,所以对数字计算时要用特殊方法将字符串转变为数字才能计算。


(2)各种括号的作用()、(())、[]、[[]]、{}
描述:为了更好的学习shell中的变量,我们需要先学习各个括号的作用;

1.小括号,圆括号()

  • 命令组:
    • 括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用
    • 括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
  • 命令替换:
    • 等同于cmdshell扫描一遍命令行,发现了$(cmd)结构,便将$(cmd)中的cmd执行一次,得到其标准输出,再将此输出放到原来命令。
    • 注意:有些shell不支持如tcsh。
  • 初始化数组:
    • 如:array=(a b c d)


2.双小括号 (( ))

  • 整数扩展
    • 扩展计算是整数型的计算不支持浮点型((exp))结构扩展并计算一个算术表达式的值
    • 如果表达式的结果为0,那么返回的退出状态码为1,或者 是”假”,而一个非零值的表达式所返回的退出状态码将为0,或者是”true”
    • 若是逻辑判断,表达式exp为真则为1,假则为0。
  • 括号中的运算符、表达式符合C语言运算规则
    • 都可用在$((exp))中,甚至是三目运算符。作不同进位(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成了十进制
    • 单纯用 (( )) 也可重定义变量值,比如 a=5; $((a++)) 可将 $a 重定义为6
    • 如:echo $((16#5f)) 结果为95 (16进位转十进制),echo $((8#12)) = 10 ,值得学习借鉴;
  • 用于算术运算比较,双括号中的变量可以不使用$符号前缀。括号内支持多个表达式用逗号分开
    • 只要括号中的表达式符合C语言运算规则,比如可以直接使用for((i=0;i<5;i++)),
    • 如果不使用双括号, 则为for i in $(seq 0 4)或者for i in {0..4},再如 if(($i<5)), 如果不使用双括号,则为if [ $i -lt 5 ]

3.单中括号,方括号[]

  • bash 的内部命令:
    • [和test是等同的,如果我们不用绝对路径指明,通常我们用的都是bash自带的命令。
    • if/test结构中的左中括号是调用test的命令标识/右中括号是关闭条件判断的,注意结构中并不是必须有中括号,但是新版的Bash中要求必须这样。
    • 这个命令把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码。
  • Test和[]中可用的比较运算符只有==和!=
    • 两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq,-gt形式
    • 无论是字符串比较还是整数比较都不支持大于号小于号。如果实在想用,对于字符串比较可以使用转义形式,如果比较"ab"和"bc":[ ab \< bc ],结果为真也就是返回状态为0。
    • 中的逻辑与和逻辑或使用-a 和-o 表示。
  • 字符范围:用作正则表达式的一部分,描述一个匹配的字符范围,但是作为test用途的中括号内不能使用正则。
  • 在一个array 结构的上下文中,中括号用来引用数组中每个元素的编号,如a[1]


4.双中括号,方括号[[]]

  • [[是 bash 程序语言的关键字:
    • 它并不是一个命令,[[ ]] 结构比[ ]结构更加通用。
    • 在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
  • 支持字符串的模式匹配:
    • 使用=~操作符时甚至支持shell的正则表达式
    • 字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真
    • [[ ]] 中匹配字符串或通配符,不需要引号
  • 使用[[ … ]]条件判断结构,而不是[ … ],能够防止脚本中的许多逻辑错误。
    • 比如:&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。
    • 比如:可以直接使用if [[ $a != 1 && $a != 2 ]] 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ] 或者 if [ $a -ne 1 -a $a != 2 ]
  • bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if [ $i -lt 5 ] 

if [ $a -ne 1 -a $a != 2 ]
if [ $a -ne 1] && [ $a != 2 ]
if [[ $a != 1 && $a != 2 ]]

for i in $(seq 0 4);do echo $i;done
for i in `seq 0 4`;do echo $i;done
for i in {0..4};do echo $i;done

for ((i=0;i<5;i++));do echo $i;done
# 执行结果
# 0
# 1
# 2
# 3
# 4


5.大括号、花括号 {}

  • 常规用法:
    • 大括号拓展:通配(globbing))将对大括号中的文件名做扩展。在大括号中不允许有空白,除非这个空白被引用或转义。
    • 代码块:又被称为内部组结构事实上创建了一个匿名函数 。与小括号中的命令不同,大括号内的命令不会新开一个子shell运行,即脚本余下部分仍可使用括号内变量。括号内的命令间用分号隔开最后一个也必须有分号,十分注意:{}的第一个命令和左括号之间必须要有一个空格。
  • 四种特殊的替换结构:(非常重要后面详解
  • 四种模式匹配替换结构:
    • # 是去掉左边(在键盘上#在$之左边)
    • % 是去掉右边(在键盘上%在$之右边)
    • #和%中的单一符号是最小匹配,两个相同符号是最大匹配。
  • 四种模式字符串提取和替换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#第一种:对大括号中的以逗号分割的文件列表进行拓展。如 touch {a,b}.txt 结果为a.txt b.txt。
#第二种:对大括号中以点点(..)分割的顺序文件列表起拓展作用,如:touch {a..d}.txt 结果为a.txt b.txt c.txt d.txt
# ls {ex1,ex2}.sh
ex1.sh ex2.sh
# ls {ex{1..3},ex4}.sh
ex1.sh ex2.sh ex3.sh ex4.sh
# ls {ex[1-3],ex4}.sh
ex1.sh ex2.sh ex3.sh ex4.sh


#间接引用(值得注意)
var1=1024
var2="var1"
echo ${!var2} #输出1024


#替换结果
${var:-string} #若变量var为空,则用在命令行中用string来替换${var:-string},否则变量var不为空时,则用变量var的值来替换
${var:=string} #用string替换${var:=string}的同时,把string赋给变量var,常用模式
${var:+string} #替换规则和上面的相反,即只有当var不是空的时候才替换成string,若var为空时则不替换或者说是替换成变量 var的值,即空值。(因为变量var此时为空,所以这两种说法是等价的)
${var:?string} #替换规则为:若变量var不为空,则用变量var的值来替换${var:?string};若变量var为空,则把string输出到标准错误中,并从脚本中退出。我们可利用此特性来检查是否设置了变量的值。

#匹配结果
${var%pattern} #shell在variable中查找,看它是否一给的模式pattern结尾,如果是,就从命令行把variable中的内容去掉 右边最短 的匹配模式
${var%%pattern} #如果是,就从命令行把把variable中的内容去掉 右边最长 的匹配模式
${var#pattern} #如果是,就从命令行把variable中的内容去掉 左边 最短的匹配模式
${var##pattern} #如果是,就从命令行把variable中的内容去掉 左边 最长的匹配模式

#字符串提取和替换
${var:num} #shell在var中提取第num个字符到末尾的所有字符
#若num为正数,从左边0处开始
#若num为负数,从右边开始提取字串,但必须使用在冒号后面加空格或一个数字或整个num加上括号,如${var: -2}、${var: 1-3}(注意这里)或${var:(-2)}。
${var:num1:num2} #num1是位置/num2是长度。表示从$var字符串的第$num1个位置开始提取长度为$num2的子串,不能为负数。
${var/pattern/pattern} #表示将var字符串的第一个匹配的pattern替换为另一个pattern。
${var//pattern/pattern} #表示将var字符串中的所有能匹配的pattern替换为另一个pattern。

这四种模式中都不会改变variable的值,其中只有在pattern中使用了*匹配符号时,%和%%,#和##才有区别。

结构中的pattern支持通配符,*表示零个或多个任意字符,?表示仅与一个任意字符匹配,[…]表示匹配中括号里面的字符,[!…]表示不匹配中括号里面的字符 (基础正则)。

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#示例1、shell变量替换
ZBX_MAIN_DB=${ZBX_MAIN_DB:-"mysql"} #如果ZBX_MAIN_DB变量为空则采用mysql进行替换
$echo $ZBX_MAIN_DB #mysql

#示例2、shell变量匹配
var=testcase
$echo $var
testcase
$echo ${var%s*e} #去除右边 最短 匹配
testca
$echo ${var%%s*e} #去除右边 最长 匹配
te

$echo ${var#?e} # 去掉 左边 最短的匹配模式
stcase
$echo ${var##?e}
stcase
$echo ${var##*e} # 去掉 左边 最长的匹配模式
$echo ${var##*s}
e
$echo ${var##test}
case


var=/home/centos
echo $var
/home/centos
echo ${var:5} #取子字符串
/centos
echo ${var: -6}
centos
echo ${var:(-6)}
centos
echo ${var:1:4} #注意字符是从0开始,这个案例是表示从1开始偏移后面的4位数;
home
echo ${var/o/h} #匹配从左到右开始的第一个字符并将其替换
/hhme/centos
echo ${var//o/h} #完全匹配替换
/hhme/cenths


6.符号$后的括号以及大括号

  • (1)${a} 变量a的值, 在不引起歧义的情况下可以省略大括号,加花括号是为了帮助解释器识别变量的边界;
  • (2)$(cmd) 命令替换,和cmd效果相同,结果为shell命令cmd的输,过某些Shell版本不支持$()形式的命令替换, 如tcsh。
  • (3)$((expression)) 和exprexpression效果相同, 计算数学表达式exp的数值, 其中exp只要符合C语言的运算规则即可, 甚至三目运算符和逻辑表达式都可以计算。

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#示例1.建议采用${变量形式},推荐给所有变量加上花括号这是个好的编程习惯
#如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了
$ for skill in Ada Coffe Action Java
> do
> echo "I am good at ${skill}Script"
> done
# I am good at AdaScript
# I am good at CoffeScript
# I am good at ActionScript
# I am good at JavaScript


#示例2.拼接字符串
yourname="test"
greeting="hello,"$yourname"!"
greeting1="hello,${yourname}!" #建议形式
echo $greeting;echo greeting1
# hello,test!
# hello,test!


#示例3.字符串长度 ${#test}、 取子字符串${test:1:4}(上面已经说了)
test=linux123456linux
echo ${#test} #统计字符个数
# 16

string="alibaba is a great company" #提取字符串
echo ${string:0:5} #输出aliba , 注意是:号 【从0开始后面的五位字符】
echo `expr index "$string" o` #输出字符串索引位置 【从1开始记】

补充:单个小括号与单个大括号多条命令执行

  • (1)单小括号 (cmd1;cmd2;cmd3) 新开一个子shell顺序执行命令cmd1,cmd2,cmd3, 各命令之间用分号隔开, 最后一个命令后可以没有分号。
  • (2)单大括号 { cmd1;cmd2;cmd3;} 在当前shell顺序执行命令cmd1,cmd2,cmd3, 各命令之间用分号隔开, 最后一个命令后必须有分号, 第一条命令和左括号之间必须用空格隔开
  • 对{}和()而言, 括号中的重定向符只影响该条命令而括号外的重定向符影响到括号中的所有命令。

(3)用户自定义变量

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似,识别变量和不识别变量。

符号 作用
‘ ‘ 单引号:在单引号中所有的特殊符号,如’$’和’`’(反引号)都没有特殊含义
“ “ 双引号:在双引号中特殊符号都没有特殊含义,但是”$”、”`”和”\”是例外,拥有”调用变量的值”、”引用命令”和”转义符”的特殊含义
`id` 插入的命令可以直接执行
$() 和反引号一样,用来引用系统命令
$ 用于调用变量的值,如需要调用变量name的值时,需要用$name的方式得到变量的值。
\ 转义符,跟在\之后的特殊符号将失去特殊含义,变味普通字符。如\$将输出”$”符号,而不当作是变量引用

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#单引号
- 任何字符都会原样输出,所以单引号字符串中的变量是无效的;
- 单引号字串中不能出现单引号(对单引号使用转义字符后也不不行);
str='this is a string $var'

#双引号
- 可以解析变量并且可以出现转义字符
str1="this is a variable $test"


#示例1.Shell变量声明和调用
变量名=变量值
echo $变量名

test=12346
echo "$test" # $调用变量 123456
echo $str #变量不被解析 this is a string $var
echo $str1 #变量被解析 this is a variable 123456


#示例2.命令替换
$echo `id`
$echo $(id) #这样执行的命令不会换行
uid=0(root) gid=0(root) 组=0(root)
#常用方式
aa=$(who)
echo "$aa" ${aa}


#示例3.变量叠加
x=123
x="$x"456
echo $x # 则x变成123456

WeiyiGeek.单双引号

WeiyiGeek.单双引号

不同变量类型申明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#示例1.declare与set和unset联合使用
$declare TPATH='Visual C++ -- JAVA'
$set -a TPATH #设置环境变量
$env | grep "TPATH"
TPATH=Visual C++ -- JAVA #写入了env环境变量,变量名大写
$unset -v TPATH #删除tpath变量
$env | grep "TPATH"


#数组整型
# -a 数组类型
# -i 整数
# -x 环境变量
# -r 只读变量
# -p 显示类型
declare -a test=0
declare -a test[1]=1
declare -a test[2]=2
$echo ${test[1]} ${test[2]}
echo ${test[*]}

#类型查看
declare -i test=1024
$declare -p test
# declare -i test="1024"

注意事项:

  • 变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样.
  • 用户可以使用env命令和grep命令对其进行查询,已经删除的环境变量再次使用指令查询时,将出现查询不到指定环境变量的输出信息。

(4)环境变量

  • 用户自定义变量:局部变量(只在当前shell中生效,临时
  • 系统环境变量:全局变量但是对系统生效的环境变量名和变量作用是固定的一般大写(当前父shell和所有子shell中生效)

环境变量配置文件:

1
2
3
4
5
6
7
全局配置文件:/etc/profile
用户配置文件:~/.bash_profile
注销时生效的环境变量配置:~/.bash_logout
历史操作:~/.bash_history

#重新加载bash配置文件:
source .bashrc #立即生效

系统启动加载Shell环境顺序图:

WeiyiGeek.Shell环境顺序图

WeiyiGeek.Shell环境顺序图

设置环境变量的方法:

1
2
3
4
#示例1:
declare -x T1="demo"
set -a T2="demo"
export T3="demo"

查看环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
env   #env命令用于显示系统中已存在的环境变量,以及在定义的环境中执行指令。
# XDG_SESSION_ID=60
# HOSTNAME=master
# TERM=xterm #终端环境
# SHELL=/bin/bash
# HISTSIZE=1000
# SSH_CLIENT=10.20.172.108 51849 22 #当前SSH连接信息
# SSH_TTY=/dev/pts/1 #ssh连接的终端时pts/1
# USER=root

$LOGNAME #登录用户相关信息
$UID
$Shell
$HOME #家目录
$PWD
$PATH #用户所输入的命令是在哪些目录中查找
$PS1
$PS2
$RANDOM #随机数 (重要)


PATH环境变量中Linux中执行可执行文件常用的方法是输入绝对路径,但是如果不输入路径时,系统会在PATH中的路径中寻找该可执行文件,直到找到该指定文件,就执行,但是找不到时就报错。
直接输入文件名就能执行自定义脚本

  • ①将该脚本文件复制到PATH中的任意路径中;
  • ②在PATH中添加该脚本文件的路径;
1
2
3
4
5
echo $PATH    #系统搜索命令的路径,路径之间用:分割。
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

#增加PATH环境变量(拼接即可),tab补全命令也是按照PATH中的来补全
PATH="$PATH":/root/sh
WeiyiGeek.

WeiyiGeek.

下面就是设置命令行显示格式对于有强迫症的患者来说是极大的好处,可以随心所欲的设置$PS1变量;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
set | grep PS1
#PS1='[\[email protected]\h \W]\$ ' ==>> [[email protected] ~]#
[[email protected] ~] # [当前用户的账号名称@主机名的第一个名字 工作目录的最后一层目录名]#

#PS1的常用参数以及含义:
  \d :代表日期,格式为weekday month date,例如:"Mon Aug 1"
  \H :完整的主机名称
  \h :仅取主机名中的第一个名字
  \t :显示时间为24小时格式,如:HH:MM:SS
  \T :显示时间为12小时格式
  \A :显示时间为24小时格式:HH:MM
  \u :当前用户的账号名称
  \v :BASH的版本信息
  \w :完整的工作目录名称
  \W :利用basename取得工作目录名称,只显示最后一个目录名
  \# :下达的第几个命令
  \$ :提示字符,如果是root用户,提示符为 # ,普通用户则为 $

# 在PS1中设置字符颜色的格式为:
\[\e[F;Bm\] 其中“F“为字体颜色编号为30-37,“B”为背景颜色,编号为40-47。

WeiyiGeek.Front-backgroudcolor

WeiyiGeek.Front-backgroudcolor

1
2
3
4
5
6
7
8
9
10
11
12
#设置命令行的格式为绿字黑底(\[\e[32;40m\]),显示当前用户的账号名称(\u)、主机的第一个名字(\h)、完整的当前工作目录名称(\w)、24小时格式时间(\t)
PS1='[\[\e[32;40m\]\[email protected]\h \w \t]\$ '

#经过多次测试后,最终确定了一个适合我自己的格式
#Centos
vim .bashrc ##设置永久生效,修改.bashrc文件,加入PS1
PS1="[\[\e[32;40m\]\u\[\e[37;40m\]@\h \[\e[31;40m\]\w \[\e[43;40m\]\t\[\e[0m\]]\\$ "
source .bashrc #立即生效

#Debian:
/etc/bash.bashrc
PS1='${debian_chroot:+($debian_chroot)}\[\e[32;40m\]\u\[\e[37;40m\]@\h:\[\e[34;40m\]\w \[\e[31;40m\]\A\[\e[0m\]\$ '


还有一个重要的环境变量就是语序环境变量,我们在讲解locale命令的时候是有所涉及的,就是$LANG定义系统主语系的变量;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#输出当前语系:
echo $LANG
zh_CN.UTF-8

#查看系统可支持的语系:
locale -a | more
LC_ALL : 定义整体语系变量

#设置语系:(临时)
LANG=en_US.UTF-8 #英文
LANG=zh_CN.UTF-8 #中文

#永久设置:
语系信息放在文件 /etc/sysconfig/i18n 下(`Centos 7以下版本才有`)下次开机以后的系统环境,然后你需要做的就是找到LANG 和 SUPPORTED 这两行,然后将以下内容对应着替换上
LANG="zh_CN.UTF-8"
SUPPORTED="ZH_CN.UTF-8:ZH_CN:ZH"

WeiyiGeek.语言环境

WeiyiGeek.语言环境

如果没有图形界面还非要显示中文的两种方法:

  • 一个是使用第三方工具,且支持中文显示
  • 还有一个使用第三方插件(要在linux字符界面下显示支持中文,需要安装zhcon插件)
1
2
3
rpm -ql zhcon.rpm   #查询
rpm -ivh zhcon.rpm #安装
rpm -e zhcon.rpm #卸载


内部字段分隔符变量IFS

描述:internal field separator,IFS 是shell脚本中的一个特殊变量在处理文本数据时很有用。
把单个数据流划分成不同的数据元素的定界符,内部字段分隔符就是用于特定用途的定界符。
IFS是存储定界符的环境变量,是shell环境中的默认定界符字符串,默认值为空白字符(换行符、制表符、空格)

1
$ echo $IFS

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
#迭代一个字符串或者CSV(comma separated value,都好分隔型数值)中的单词:
data="111,222,333|444,555,666"

oldIFS=$IFS #定义一个变量为默认IFS
IFS='|' #设置IFS为逗号

for i in $data # date的取值列表
do
echo S:$i
done

IFS=$oldIFS #还原IFS为默认值

#执行结果
S:111
S:222
S:333
S:444
S:555
S:666
#如果IFS为1
S:111,222,333
S:444,555,666

IFS被设置为逗号’,’ , shell将逗号解释为一个定界符,因此变量$i在每次迭代中读取由逗号分隔的字符串作为变量值


(5)位置参数变量

主要是向脚本文件中传递脚本运算需要的值,更适合给程序的编写者使用,在我们写可变参数脚本尤其有用的;

主要位置参数变量:

1
2
3
4
${n} : $0代表脚本本身,$0~9代表第一个到底九个参数,如果是9个以上参数需要使用大括号包含 ${10}
${*} :代表命令行中的所有参数,它把所有参数 看成一个整体
${@} :代表命令行中的所有参数,它把所有参数 区分对待(划分个体)
${#} :代表命令行中所有参数的个数;

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#说明:${0} 或者 $0 就是输出文件本身,从命令行中输入得参数$1/9,${10}


#!/usr/bin/env bash
echo "文件名称:" ${0} $0
basename ${0}

num1=$1;num2=$2
let num3=$num1+$num2
echo -e "$num1 + $num2 = $num3 \n"

for i in `seq $num3`;do
echo "for output: $i"
done

echo -e "\n"
#其他三个特色是字符(区别)
echo "A total of $# Parameters" #代表所有参数的个数
echo "Every Paraments is:$*" #代表所有参数一个整体
echo -e "The Paraments is:[email protected] \n" #用这个代表各个参数的值(个体)

#整体和个体的区别
for i in "$*";do
echo "整体 output:$i" #整体
done
for j in "[email protected]";do
echo "个体 output:$j" #个体
done


#执行结果
# 文件名称: ./var.sh ./var.sh
# var.sh
# 1 + 2 = 3

# for output: 1
# for output: 2
# for output: 3

# A total of 2 Parameters
# Every Paraments is:1 2
# The Paraments is:1 2

# 整体 output:1 2
# 个体 output:1
# 个体 output:2

shift 迁移语句
描述:用于迁移位置变量,将$1~$9依次向左传递;通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本)。
例如当shell程序处理完前九个命令行参数后,可以使用shift 9命令把$10移到$1;并且位置参数熟料也会随之而变化;

简单说明:

1
2
3
4
5
6
7
8
#例如:若当前脚本程序获得的位置变量如下:
$1=file1、$2=file2、$3=file3、$4=file4

#执行一次shift命令后,各位置变量为:
$2=file2、$3=file3、$4=file4
 
#在执行一次:
$3=file3、$4=file4

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#示例1:依次读取输入的参数并打印参数个数
#每次运行shift(不带参数的),销毁一个参数,后面的参数前移
#!/bin/bash
while [ $# != 0 ]
do
echo "prama is $1,prama size is $#"
shift
done

#输入如下命令运行:
./shift_test.sh a b c
prama is a,prama size is 3
prama is b,prama size is 2
prama is c,prama size is 1


#示例2.同理shift n后,前n位参数都会被销毁,比如:
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done

shift 3

echo "执行shift 3操作后:"
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
#通过eval把i变量的值($i)作为变量j的名字
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done
#输出结果为:
参数个数为:5,其中:
第1个参数($1):a
第2个参数($2):b
第3个参数($3):c
第4个参数($4):d
第5个参数($5):e
#执行shift 3操作后:
参数个数为:2,其中:
第1个参数($1):d
第2个参数($2):e


(6)预定义变量

描述:该变量在发挥着及其重要的角色特别是在Shell编程的时候;

符号 作用
$? 检测上一个命令的返回值,判断是不是执行成功,是则为0不是则不为0
$$ 当前 Shell 进程的 pid
$! :上一个后台进程的 pid 可以使用这两个指令来获取相应的进程 pid

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#示例1.上一个进程执行情况
echo $?;text=`echo $?`;
echo $text
0
# bash: 0: 正确执行
# bash: 127(random): 没正确执行(非0)


#示例2.当前进程号
$ echo $$
3672
$ ps aux | grep "3672"
root 3672 0.0 0.2 116040 2676 pts/1 Ss 21:28 0:00 -bash

#示例3.例如,如果需要获取某个正在执行的进程的 pid(并写入指定的文件)
date >> /tmp/txt.log &
[1] 2795 #上一个进程号pid
echo $!
2795
WeiyiGeek.当前进程PID

WeiyiGeek.当前进程PID

总结事项:

  • 多看书本多操作,很简单的代码。其实真正自己写出来,在运行起来得到结果,也不容易
  • 切勿眼高手低要不得
  • 有时候要求有空格(比如条件判断时),有时候不能有空格(变量赋值时),有时候,单引号有时候又 反引号

(7)变量的间接引用

描述:在Shell高级编程中可以采用eval命令进行变量间的间接引用,eval命令会多次扫描自己的参数(变量)并且运行;些需要进行两次扫描的变量有时候被称为复杂变量。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#示例1.间接引用变量
vara=varb
varb="ls -lah" #注意这里双引号很重要
eval \$$vara #注意转义字符 == 执行了 ls -alh
-rwxr-xr-x 2 Administrator 197121 39K 九月 11 2018 git.exe
-rwxr-xr-x 1 Administrator 197121 145K 九月 11 2018 git-gui.exe


#实例2.间接引用变量进行输出
var=varc
varc="weiyigeek"
eval vard=\$$var
echo "Name: ${vard}" #Name: weiyigeek

#示例3.变量间接调用之 ${!}
x=var
var=1024
${!x}

shell脚本案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
# 实现变量的间接引用
BLACK='\E[1;30m'
RED='\E[1;31m'
GREEN='\E[1;32m'
YEELOW='\E[1;33m'
BLUE='\E[1;34m'
PINK='\E[1;35m'
CYAN='\E[1;36m'
WHITE='\E[1;37m'
RES='\E[0m'

#关键点1
function demo()
{
#获得最后一个参数
echo "Last argument is $(eval echo \$$#)"
echo "Last argument is $(eval echo $#)"
}

function colorPrint()
{
eval COLOR=\$$1
echo -e "${COLOR}${2}${RES}"
}

#关键点2
function usage()
{
echo -e "${RED}Usage:$0 COLOR=[BLACK|RED|YELLOW|BLUE|PINK|CYAN|WHITE] [CONTENT]${RES}"
exit 1
}

#判断参数个数 2 > 2 | $# 从0 开始 计数
[ 2 -gt $# ] && usage

demo $*
colorPrint $*

执行结果:
1
2
#脚本帮助
Usage:eval.sh COLOR=[BLACK|RED|YELLOW|BLUE|PINK|CYAN|WHITE] [CONTENT]

WeiyiGeek.eval变量间接引用

WeiyiGeek.eval变量间接引用


(8)变量接收命令

描述:在写shell的时候常常利用执行命令后返回结果作为参考或者进行判断,可以说是非常的常用;

命令的执行结果重定向到变量的几种方式

1
2
3
4
5
6
#示例1:
var1=$(command)
var3=$(command 2>&1) #要将stdout和stderr保存到变量,需要在结尾处添加2>&1。

#示例2
var2=`command`


(9)补充变量

  • $- : 记录着当前设置的shell选项,可通过set命令进行修改;

    1
    2
    3
    4
    5
    6
    7
    8
    echo $-
    himBH
    #5个字母分别有各自含义
    * h:hashall 将命令所在的路径记录下来避免每次都要查询。#举例:当h选项开启时,如果将某个自定义命令从/usr/bin/目录下移动到/usr/local/bin/再运行,会提示无此命令。而当通过set +h将h选项关闭后,上述情况就不会出现。
    * i:interactive-comments 当前的 shell 是一个交互式的
    * m:monitor 打开监控模式就可以通过Job control来控制进程的停止、继续,后台或者前台执行等。
    * B:braceexpand 大括号扩展。`cp A_File{,.back_up}`
    * H:history 执行过的命令将被记录下来,然后再用户登出的时候保存再用户家目录下的bash_history中;
  • $_ (类比\!\$): 记录上一个命令执行的参数(最后一个参数前面以空格进行分割否则整体)

    1
    2
    3
    4
    5
    6
    7
    8
    #案例1:
    $mkdir -p /k8s/kubernetes/ssl/ && cd $_ && pwd
    /k8s/kubernetes/ssl/

    #案例2:
    echo "WeiyiGeek" && echo $_
    WeiyiGeek
    WeiyiGeek