一些基础知识

程序

程序:算法+数据结构
数据:是程序的核心
数据结构:数据在计算机中的类型和组织方式
算法:处理数据的方式

程序编程风格:

过程式:以指令为中心,数据服务于指令
对象式:以数据为中心,指令服务于数据

  • shell程序:提供了编程能力,解释执行

程序的执行方式

计算机:运行二进制指令
编程语言:人与计算机之间交互的语言
低级编程语言:
机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
汇编:用一些助记符号替代机器指令,称为汇编语言
如:ADD A,B 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中
汇编语言写好的程序需要汇编程序转换成机器指令
汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言
高级编程语言:
编译:高级语言-->编译器-->机器代码-->执行
C,C++
解释:高级语言-->执行-->解释器-->机器代码
shell,python,php,JavaScript,perl


image


编程逻辑处理方式:

顺序执行
循环执行
选择执行

shell编程:过程式、解释执行

编程语言的基本结构:
各种系统命令的组合
数据存储:变量、数组
表达式:例如 a + b
语句:例如if ,for

SHELL脚本

shell脚本:

包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

shell脚本的用途有:

自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本或文件

创建shell脚本

  1. 第一步:使用文本编辑器来创建文本文件
    第一行必须包括shell声明序列:#!
    示例:#!/bin/bash :centos7 里面/bin 和/user/bin相同,/bin就是一个软链接
    添加注释
    注释以#开头
  2. 第二步:运行脚本
    给予执行权限,在命令行上指定脚本的绝对或相对路径
    直接运行解释器,将脚本作为解释器程序的参数运行
    • 注意:写脚本后缀虽然没有要求(linux不看后缀),但尽量写成sh,容易分辨,同时脚本名称要与功能相关
    • 加了执行权限之后,因为它既不在PATH中,也没有存入hash表中,因此想要执行这个脚本,有下面几种方式:
  3. 直接写绝对路径
  4. 把当前目录加入PATH路径中 PATH=./:$PATH,用这个方法执行时脚本要在当前目录
  5. 把脚本存放的目录的绝对路径存放到PATH中 PATH=/dir:$PATH
    • 注意:如果只在shell里修改,则退出无法保存,因此要写到文件中,文件家目录为/etc/profile.d/####.sh ,可以仍然写在之前定义的evn.sh中
  6. 即使脚本没有执行权限,也可以用 bash 脚本路径 的方式来执行它,就相当于是运行解释器执行了脚本
  7. 上面方法的变种: cat 脚本 | bash ,它可以用在浏览器上来进行远程执行,许多安装程序就是这样做的.
    例子:
#后缀改为txt打开192.168.0.2/hello.txt路径则可以在网站上直接查看,后缀为sh则会把它下载下来   
scp /data/script/hello.sh 192.168.0.2:/var/www/html
输入口令:
curl http://192.168.0.2/hello.sh |bash
#这样就可远程直接运行这个脚本了
  • 如果想要脚本中命令输出带颜色,则例如 echo -e "\e[1;33mhello world\e[0m",而更多命令查看转义帮助
  • 注意:想要cat文本显示颜色,则在VIM插入模式下,ctrl+v快捷键[1;33m文本ctrl+v快捷键[0m ,则会显示为×××
容易错的部分:
  1. 一定要在insert模式下按ctrl+v,不然会进入visual block模式
  2. 注意^[[1;33m文本^[[0m ,注意快捷键会加上一个托字符加[ ,相当于echo里面的\033或者\e ,后面还得再加[
  3. 注意中间的分隔符是;分号,不是逗号

脚本规范

  • 脚本代码开头约定
    1、第一行一般为调用使用的语言
    2、程序名,避免更改文件名为无法找到正确的文件
    3、版本号
    4、更改后的时间
    5、作者相关信息
    6、该程序的作用,及注意事项
    7、最后是各版本的更新简要说明

脚本的基本结构

#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE

脚本调试

检测脚本中的语法错误

bash -n /path/to/some_script

  • 注意:脚本中如果有语法错误,当运行它的时候会把语法错误前面的命令全部执行,所以一定要提前检测脚本中的语法错误

调试执行

bash -x /path/to/some_script

  • 脚本中如果有命令错误,则这一条命令可以不执行,脚本仍然会执行到最后,可以用这个命令来检测每条命令的执行结果

变量

变量相关知识

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
  • 动态编译语言:不用事先声明,可随时改变类型,如bash,Python
    -强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c# ,python
    如:以下python代码
    print(‘magedu’+ 10) 提示出错,不会自动转换类型
    print(‘magedu’+str(10)) 结果为magedu10,需要显示转换类型
    -弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
    如:bash 不支持浮点数,php,javascript

变量基本

  • 变量:命名的内存空间
  • 变量:变量类型
    作用:
    1. 数据存储方式
    2. 参与的运算
    3. 表示的数据范围
      类型:
      字符型
      数值型:整型、浮点型

Shell中变量命名法则:

1、不能使程序中的保留字:例如if, for
2、只能使用数字、字母及下划线,且不能以数字开头
3、见名知义
4、统一命名规则:驼峰命名法 :利用下划线或者每个名称开头都大写等等

Shell中命名建议规则:

1、变量名大写
2、局部变量小写
3、函数名小写
4、用英文名字,并体现出实际作用

bash中变量的种类

  • 根据变量的生效范围等标准划分下面变量类型
    局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
    环境变量:生效范围为当前shell进程及其子进程
    本地变量:生效范围为当前shell进程中某代码片断,通常指函数
    位置变量:$1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数
    特殊变量:$?, $0, $*, $@, $#,$$

  • $$:当前的shell进程编号PID :可用echo $$显示,或者echo $BASHPID; 上一级bashPID可以用 echo $PPID

局部变量

  • 变量赋值:name=‘value’
  • 可以使用引用value
    (1) 可以是直接字串:name=“root"
    (2) 变量引用:name="$USER"
    (3) 命令引用:name=`COMMAND` ,name=$(COMMAND)
  • 注意:之前也总结过,变量的引用用$或者${} ,而命令的引用用 ``或者$(),注意括号的区别. 一个是变量,一个是命令.
    变量引用:${name} 或者 $name
    " " 弱引用,其中的变量引用会被替换为变量值
    ' ' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
  • 显示已定义的所有变量:set 注意set显示的不仅仅是变量,还有函数等
  • 删除变量:unset name
    - pstree :进程树状结构 -p 可以显示PID
18:26[root@centos7 /data]# which init
/usr/sbin/init
18:26[root@centos7 /data]# ll /usr/sbin/init 
lrwxrwxrwx. 1 root root 22 Mar  5 21:16 /usr/sbin/init -> ../lib/systemd/systemd

环境变量

  • 变量声明、赋值:
    export name=VALUE
    declare -x name=VALUE
    export name
    declare -x name
  • 变量引用:
    $name, ${name}
  • 显示所有环境变量:
    env
    printenv
    export
    declare -x
  • 删除变量:
    unset name
  • ()自动开启子进程

注意点:

  1. 变量赋值的时候 变量=所附的值 ,它们之间不能有任何的空格,如果想要所附的值与=之前有空格,需使用'' "" `` 等括起来才可(原因是会把空格当做分隔符,变量= 会看做命令从而找不到出现错误).最好方式就是都加上双引号,不要省略默认的双引号,避免空格(被看做分隔符)引起的错误。
  2. 变量赋值如果后面的字符串不加上双引号,则以空格为分隔符)因此,如果是一个整参数但其中有空格,必须加上双引号包住
  3. 很多命令后面的参数也是这样,空格会把他们分成多个参数,比如 mkdir " /data/backup/`date "+%F %T"`" ,date后面的两个参数得加上双引号包住,同时又因为date后面两个参数之间有空格,如果mkdir后面整体不加上双引号,则会看作是两个文件夹并给它创建上了,一个在backup文件夹内,另一个在当前文件夹内.
  4. 可以用echo $变量$变量 同时显示两个变量 ,但不可echo $变量变量 ,这样会把变量变量看做一个变量, 同上面前两条相同的逻辑,都是以空格为分隔符.
  5. $名字 ,如果不以数字开头的名字这个变量并不存在,则echo $名字 则会输出空行
  6. $#.*,如果以数字开头,则会把$#这个看作是一个变量(参考$#当做引用参的特殊变量),echo $#.* 输出它的值(若为空值就是输出空),然后输出后面的.*里的字符内容。注意原本的变量规定不能以数字开头。
  7. $###,如果要原封不动书画粗,则 echo $### ,要么用''单引号包含,要么用转义字符\ ,例如echo \$100
  8. 如果要输出转义字符,则需要写成\\,若要输出两个\,则要 echo \\\\ ,
  9. NUM=1 ,可以输出 echo No$NUM ,不可输出 echo $NUMNo ,必须 echo ${NUM}No 或者echo "$NUM"No .注意:花括号是包含在变量外面的 ,双引号是包含在变量外加$字符整体外面的
  10. M=10 ,N=$M ,则 echo $N 结果为10 ,但是如果这时候修改M的赋值, 比如M=20 ,则echo $N 结果仍然为10 .
  11. 主要是因为当创建N的时候虽然引用的是M的值,但是N变量会开创一个新的数据空间,存入一个10的数据,N和M指向的并不是同一个数据空间,只是内部的数值相同而已. 此时更改M的值,只是把M的数据空间内的值改掉,N指向的数据控件的值仍然是10,已经和M无关了.(疑问:到底是M的指向在赋值的时候重新更改了,N创建的时候和M指向的是同一个现在仍然没有变化;还是说N创建的时候指向的就是新开辟的数据空间,只是引用了M的数据把10这个数值存入进去而已?)
  12. 变量赋值的时候不仅可以引用别的变量的内容(第9个注意点,它只是省略了双引号),也可以引用命令的输出结果,不过注意要用反向双引号;当用echo显示变量内容时如果想要保留命令输出结果中的各种换行符等等,需要在$变量外加双引号,注意双引号是加在echo命令后面的$变量整体外面的。原因经过例子也可看出例如:
18:02[root@centos7 /data]# Userinfo="     --1  `who`  "
18:03[root@centos7 /data]# echo $Userinfo
--1 root pts/0 2019-03-16 08:56 (gateway) root pts/1 2019-03-16 17:58 (gateway)
18:03[root@centos7 /data]# echo "$Userinfo"
     --1  root     pts/0        2019-03-16 08:56 (gateway)
root     pts/1        2019-03-16 17:58 (gateway)  
  1. 所有变量都是字符,例子:
18:03[root@centos7 /data]# Num1=10
18:06[root@centos7 /data]# Num2=20
18:06[root@centos7 /data]# Num3=Num1+Num2
18:06[root@centos7 /data]# echo $Num3
Num1+Num2
18:06[root@centos7 /data]# Num3=$Num1+$Num2
18:07[root@centos7 /data]# echo $Num3
10+20
  1. 变量用完最好删除,注意删除的时候直接就unset 变量名 ,不需要加$
  2. 定义的普通变量只能在1当前终端.2当前shell.3不能关机 中使用,不能在其它终端(或者窗口),子shell或者父shell,或则重启后(会消失)使用。
  3. bash是一个后台持续运行的程序,可以在bash中再开bash子进程,用exit退出
  4. 定义的环境变量也是只能在当前shell和子shell中使用,不能在父进程或者重启后使用
  5. 想要自己定义的环境变量开机存在,可以在/etc/profile.d/evn.sh 中写入export name=VALUE即可,不过这个值也只能开机时就是这个值,关机之前最后的值必须手动写入才可。可以自己写一个脚本让环境变量的值关机时保留存入这里。
  6. set命令不仅可以看普通变量,还可以看环境变量,还可以看其他的函数,等等。PATH是环境变量,PS1不是。
  7. ()自动开启子进程,里面命名的变量不会影响到父进程看例子:
19:49[root@centos7 /data/lintst]# (name=zhang;echo $name)
zhang
19:50[root@centos7 /data/lintst]# echo $name

19:50[root@centos7 /data/lintst]# (name=zhang;echo $name);echo $name
zhang

19:51[root@centos7 /data/lintst]# name=duan;(name=zhang;echo $name);echo $name
zhang
duan
但是如果
19:52[root@centos7 /data/lintst]# name=duan;(echo $name);echo $name
duan
duan
详细一点:
19:56[root@centos7 /data/lintst]# name=duan;echo 1$name;(name=zhang;echo 2$name);echo 3$name
1duan
2zhang
3duan
19:57[root@centos7 /data/lintst]# name=duan;echo 1$name;(echo 2$name);echo 3$name
1duan
2duan
3duan
19:57[root@centos7 /data/lintst]# 
更详细一点:
19:57[root@centos7 /data/lintst]# echo $name
duan
19:58[root@centos7 /data/lintst]# (echo $name)
duan
19:58[root@centos7 /data/lintst]# (name=zhang; echo $name)
zhang
19:59[root@centos7 /data/lintst]# echo $name
duan
19:59[root@centos7 /data/lintst]# (echo $name)
duan
经过测试发现,当不进行变量写入(赋值)的时候,并未开启子进程
20:06[root@centos7 /data/lintst]# echo $name;echo a$$;(echo b$$;echo $name);echo $name;echo c$$;sleep 200
duan
a16131
b16131
duan
duan
c16131
但是这样的话:
echo $name;echo a$BASHPID;(echo b$BASHPID;echo $name;);echo $name;echo c$$;sleep 200
duan
a16131
b40122
duan
duan
c16131

bash内建的环境变量

PATH
SHELL
USER
UID
HOME
PWD
SHLVL :shell嵌套的深度,也就是开了几个shell
LANG
MAIL
HOSTNAME
HISTSIZE
_ 下划线 :上一个命令的最后一个字符串(或参数,以空格为分隔符来判断)

  • 附加:
echo ###### | passwd --stdin zhang
scp -r zhang@###.###.##.### /data/scripts /data/cpdir/

只读变量:只能声明,但不能修改和删除

  • 声明只读变量:
    readonly name
    declare -r name
  • 查看只读变量:
    readonly [-p]
  • declare -r只读 -x全局 -i数字型
  • 注意:它删不掉也改不了,只能退出的时候才能去掉

位置变量:在脚本代码中调用通过命令行传递给脚本的参数

$1, $2, ... 对应第1、第2等参数,shift [n]换位置
$0 命令本身
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数

注意点:

  1. 注意:$@ $* 只在被双引号包起来的时候才会有差异,类似前面的 变量=`who` ; echo "$变量" 需要用双引号引起来
    • 想要看出它两个的区别,需要在脚本中的次级脚本中的命令再次进行引用的时候才能看出来。比如初级脚本用$* ,但次级脚本用 "$*" 和用 $@就会有区别。
  2. set -- 清空所有位置变量
  3. 注意,在history中 !n:m 第n行历史的m个参数 , *所有 ^第一个 $最后一个
  4. $0命令本身如果用 bash 脚本名的方式来执行,则不会显示路径,只显示名称。但如果直接执行脚本的话会把路径也给显示出来,所以要用basename.(复习:bash 脚本名不需要执行权限,直接执行脚本需要执行权限x)
  5. $#传递参数的时候如果#数字超过10个则必须用{}给括起来不然会当做两个数字.
  6. $0 如果有软链接指向脚本,则运行这个软连接的时候,$0就是这个软链接的名字,但是执行的却是脚本的代码,因此在脚本里面进行判断过后可以输出不同的功能
  7. 在脚本里面用shift命令,可以让$1参数去掉,让$2变成$1,后面依次前进1位。当然也可以shift n, 可以一次移位多个。

进程使用退出状态来报告成功或失败

0 代表成功,1-255代表失败
$? 变量保存最近的命令退出状态

  • 例如:
    ping -c1 -W1 hostdown &> /dev/null
    echo $?

bash自定义退出状态码

exit [n]:可以自定义退出状态码

  • 注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
  • 注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
  • 例子:可以用它来判断命令执行错误与否
ping -c1 -w1 ###.###.###.### &>/dev/null
echo $?

算术运算

  • bash中的算术运算:help let
    +, -, *, /, %取模(取余), **(乘方),乘法符号有些场景中需要转义
    实现算术运算:
    (1) let var=算术表达式
    (2) var=$[算术表达式]
    (3) var=$((算术表达式))
    (4) var=$(expr arg1 arg2 arg3 ...) ,expr本身具有计算功能,但是后面的参数注意要用空格符分开用来区分,参数只能是数字,可以用$var的方式把变量内的数字当做参数,同时乘号之前要加\需要转义,不然会当做通配符
    (5) declare –i var = 数值 , 比如 declare -i N ; N=x+y 便可以直接运算,x,y前不需要再加$等其他符号;或者说赋值和计算同时进行也可以。
  • 注意:如果将已经定义了数字型的变量赋值字符串,则它的结果为0 ,有问题。
    (6) echo ‘算术表达式’ | bc
  • 注意:上面的算术表达式里面的数值变量(中括号里,小括号里和let后面的)已经不再需要前面加上$符号了,因为已经指定它后面的是数值,既然不是数值就可以判断出它是变量,但是加上也不会错
18:07[root@centos7 /data]# W=$[X*Y]
18:08[root@centos7 /data]# echo $W
12
18:08[root@centos7 /data]# W=$[$X-$Y]
18:09[root@centos7 /data]# echo $W
-1
  • bash有内建的随机数生成器变量:$RANDOM(0-32767)
    示例:生成 0 - 49 之间随机数
    echo $[$RANDOM%50]
    例子:随机打印彩色字符$[RANDOM%7+31]或者$[$RANDOM%7+31]或者 CNUM=$[RANDOM%7+31]
echo -e "\033[$[RANDOM%7+31]mCOLOR\033[0m"
或者 echo -e "\033[${NCUM}mCOLOR\033[0m"

赋值

  • 增强型赋值:
    +=, -=, *=, /=, %=
  • let varOPERvalue
    例如:let count+=3
    自加3后自赋值
  • 自增,自减:
    let var+=1
    let var++
    let var++
    let var-=1
    let var-- :先赋值再计算
    let --var :先计算再赋值 比如 let j=i++ 和let j=++i ,结果就不同。
  • 注意:只能用let命令来控制上面的这些

逻辑运算

  • true, false
    1, 0
  • 与 &
    1 与 1 = 1
    1 与 0 = 0
    0 与 1 = 0
    0 与 0 = 0
  • 或 |
    1 或 1 = 1
    1 或 0 = 1
    0 或 1 = 1
    0 或 0 = 0
  • 非:!
    ! 1 = 0 ! true
    ! 0 = 1 ! false
  • 短路运算
    短路与 && : cmd1 && cmd2
  • 注:它先判断cmd1,根据它的结果真假,再判断cmd2是否需要执行;最后它整体也会输出一个判断值,下同。
    第一个为0,结果必定为0
    第一个为1,第二个必须要参与运算
    短路或 ||
    第一个为1,结果必定为1
    第一个为0,第二个必须要参与运算
  • 注意: 可以 ###### && ###### || ###### ,能够有判断效果
  • 在某种情况下也可以 ##### || ##### && ##### ,这样的时候需要仔细判断
  • 注意,想要##### 作为一个整体,不能用小括号,它开了一个子进程,要用中括号。
  • 异或:^
    异或的两个值,相同为假,不同为真
  • 附加:根据异或的特性,两个值异或出来的结果,再与其中一个值异或,得到的是另外一个值,可以将两个函数的值对调,例子:
18:10[root@centos7 /data]# x=10;y=8;x=$[x^y];let y=x^y;x=$((x^y));echo $x $y
8 10
18:31[root@centos7 /data]# x=10;y=8;temp=$x;x=$y;y=$temp;echo $x $y
8 10

条件测试

  • 判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程
  • 评估布尔声明,以便用在条件性执行中
    若真,则返回0
    若假,则返回1
  • 测试命令:

test EXPRESSION : test $name1 = $name2
[ EXPRESSION ] :和上面相同,精确匹配,不支持正则表达式和通配符
[[ EXPRESSION ]] :支持扩展正则表达式和通配符,可以模糊匹配

  • 注意:EXPRESSION前后必须有空白字符(不带空格变成了变量赋值),还要注意它与前面的数值运算的区别:数字运算的中括号前面有$,括号里面的表达式不需要与边上有空格。
  • 双中括号正则表达式不能加双引号,否则会变成字符匹配,但是前面的变量引用要加上双引号,为了避免空字符错误
  • 双中括号还可以用 (EXPRESSION) ; !EXPRESSION ; $$ ;|| 的用法,但是不支持后面的单中括号的-a -o这种用法来代表$$ ||.
  • 更多区别可查看他人总结的各种括号的区别以及内部帮助

数值

-v VAR
变量VAR是否设置 :这个就是用来判断变量是否存在,比如var="", 虽然它是空值,但是它仍然赋值设置过已经存在了,[ -v var ] ,echo $? =0 结果为真

  • 注意这个里面判断的时候直接跟变量名,不需要加$符号
  • 注意它和-n的区别,-n如果变量不存在或者变量为空都返回1false,而-v只关心变量存不存在,不管里面的值是什么,变量只要存在就为0true

数值测试:
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于

  • 注意:当用数值比较的时候,前面的变量最好要提前先做一次判断,判断它是否非空且为一个数值,然后再进行比较判断。这样可以避免报错以及数值变量两边不需要加双引号。

字符串测试:

= 是否等于
\> ascii码是否大于ascii码,字符串从左往右一个一个比较
< 是否小于

  • 注意:上面两个比较在单中括号[]里面都需要转义,不然看做重定向,但是在[[]]里面不需要转义可以直接用,但也因此导致了词首词尾锚定不能用了,往下看有例子会解释。
13:58[root@centos7 /data]# AAA="abc$"
13:58[root@centos7 /data]# BBB="abc#"
13:58[root@centos7 /data]# [ $AAA \> $BBB ]
13:58[root@centos7 /data]# echo $?
0
13:58[root@centos7 /data]# BBB="abc%"
13:58[root@centos7 /data]# [ $AAA \< $BBB ]
13:58[root@centos7 /data]# echo $?
0

!= 是否不等于
== 或者!= ,后面匹配的是partern通配符,而不是正则表达式
=~ 左侧字符串是否能够被右侧的正则表达式ReExPression所匹配

  • 注意它的匹配只要能匹配到即可,包含什么东西,带什么东西都行,并非要完全匹配,比如匹配以 .sh结尾的 [[ "$var" =~ \.sh$ ]]
  • 注意: 此表达式一般用于[[]]中;扩展的正则表达式
    例如:str=abc ; [[ $str =~ ^a ].

-z "STRING“ 字符串是否为空,空为真,不空为假 :[-z "$str"] ([-z $str]也可使用,但最好不要这样写)
-n "STRING“ 字符串是否不空,不空为真,空为假 : [ $awdfc ] ,echo $?=1 (awdfc没赋值和定义)

  • 注意:[] 中间省略了-n,只要里面有内容,字符串或者数字等等不为空,他的结果就为真,$?就为0,反之随便跟一个没定义赋值的变量,因为里面是空的,所以它的结果就为假,$?为1.
  • 例如: [ -n "" ] ,echo $?=1
  1. 但是注意,如果 [ -n $var ],如果var没有定义,它也显示为真的,因为var两边没有加上“$var", 必须加上双引号并且var没有定义或者为空,结果才为假的.
  2. 同时注意这里面不写-n的时候双引号加上不加上都可以没有影响,除非是判断空格,得把空格级两边加上双引号
    • 还有个技巧 [ "x$var" = "x"] 也可以判断var里面是否为空 ,为空结果为真。
    • 注意空格也算是字符,只有任何字符没有才是空
  • 注意:用于字符串比较时的用到的操作和变量都应该使用双引号括起来,(单引号会强引用,注意场合使用它),这样是为了避免空变量或者空字符在判断中因其错误,比如下面例子:
如果两个变量都是空的
[ $wadc = $wvaf ] ;echo &?=0
[ "$wadc" = "$wvaf" ] ;echo &?=0
但是如果一个变量是空的,一个变量是非空的则会报错
[ "$wadc" = "$wvaf" ] ;
-bash: [: wad: unary operator expected
echo &?=2
因此字符串比较都要加上引号
  • 注意:如果把数字看做字符串来进行比较,也可以用 [ "##" = "##" ], 但此时需要注意两边都要加上引号变为字符串, 但加上引号之后就不能再用 -eq等命令了。

  • 判断一个变量是否为数字:
    [[ "$n" =~ ^[0-9]+$ ]]
    注意用词首词尾不能判断,原因是它把\<看做转义了<这个字符,看例子:
12:44[root@centos7 /data]# num="<123>"
12:44[root@centos7 /data]# [[ "$num" =~ \<[0-9]+\> ]]
12:44[root@centos7 /data]# echo $?
0
12:46[root@centos7 /data]# num=/123/
12:46[root@centos7 /data]# [[ "$num" =~ \<[0-9]+\> ]]
12:46[root@centos7 /data]# echo $?
1
12:48[root@centos7 /data]# num="123>"
12:48[root@centos7 /data]# [[ "$num" =~ \<[0-9]+\> ]]
12:48[root@centos7 /data]# echo $?
1
12:52[root@centos7 /data]# num="\<123\>"
12:52[root@centos7 /data]# [[ "$num" =~ \\\<[0-9]+\\\> ]]
12:52[root@centos7 /data]# echo $?
0
反斜杠\b也用不了:
13:01[root@centos7 /data]# [[ `echo 123` =~ \b[0-9]+\b  ]]
13:03[root@centos7 /data]# echo $?
1
13:03[root@centos7 /data]# [[ `echo b123` =~ \b[0-9]+\b  ]]
13:03[root@centos7 /data]# echo $?
1
13:03[root@centos7 /data]# [[ `echo b123b` =~ \b[0-9]+\b  ]]
13:03[root@centos7 /data]# echo $?
0
13:03[root@centos7 /data]# [[ `echo \b123\b` =~ \b[0-9]+\b  ]]
13:04[root@centos7 /data]# echo $?
0

13:16[root@centos7 /data]# [[ `echo b123rb` =~ \b[0-9]+\b  ]]
13:17[root@centos7 /data]# echo $?
1
13:17[root@centos7 /data]# [[ `echo b123\b` =~ \b[0-9]+\b  ]]
13:17[root@centos7 /data]# echo $?
0

但是用grep命令可以,看下面的例子:

11:58[root@centos7 /data/scriptest]# num=123; [[ $num =~ ^[0-9]+$ ]]
11:59[root@centos7 /data/scriptest]# echo $?
0
11:59[root@centos7 /data/scriptest]# num=123; [[ $num =~ \<[0-9]+\> ]]
11:59[root@centos7 /data/scriptest]# echo $?
1
但是用grep命令可以:
12:19[root@centos7 /data]# echo /123/ |egrep "\<[0-9]+\>"
/123/
12:19[root@centos7 /data]# echo 123/ |egrep "\<[0-9]+\>"
123/
12:19[root@centos7 /data]# echo a123b | egrep "\<[0-9]+\>"
12:20[root@centos7 /data]# echo 123b | egrep "\<[0-9]+\>"
12:20[root@centos7 /data]# num=123
12:20[root@centos7 /data]# [[ "$num" =~ "\<[0-9]+\>" ]]
12:20[root@centos7 /data]# echo $?
1
12:21[root@centos7 /data]# echo $num
123
  • 判断一个变量内容是否是以sh后缀:
    [[ "$variable" =~ \.sh$ ]]
  • 注意后面的正则表达式不能再加双引号了,不然会把双引号里面的看作是字符串,就不再是正则表达式了,当然单引号或者反向双引号了也不能加。
    例子:
12:56[root@centos7 /data]# [[ `echo "123"` =~ ".*" ]]
12:57[root@centos7 /data]# echo $?
1
12:57[root@centos7 /data]# [[ `echo ".*"` =~ ".*" ]]
12:59[root@centos7 /data]# echo $?
0
12:59[root@centos7 /data]# [[ `echo "".*""` =~ ".*" ]]
12:59[root@centos7 /data]# echo $?
1
12:59[root@centos7 /data]# [[ `echo "".*""` =~ "".*"" ]]
13:00[root@centos7 /data]# echo $?
0
  • 但是前面的最好用上双引号,虽然不用也不会报错,但结果可能会出问题。还有,这种匹配都是匹配了字符,因此前面即使是数字的变量也要加上双引号。
num=123
12:26[root@centos7 /data]# [[ "$num" =~ "^[0-9]+$" ]]
12:26[root@centos7 /data]# echo $?
1
12:26[root@centos7 /data]# [[ "$num" =~ ^[0-9]+$ ]]
12:26[root@centos7 /data]# echo $?
0
同时还需要注意,不论怎样都无法用次首词尾来判断:
12:28[root@centos7 /data]# num=/123/
12:28[root@centos7 /data]# [[ "$num" =~ \<[0-9]+\> ]]
12:28[root@centos7 /data]# echo $?
1
但是grep中可以,但注意grep后面判断部分是最好加引号,否则无法将反斜杠\,以及<>重定向符号转义来来进行判断,因为会把它看作是新的一行来输入:
12:29[root@centos7 /data]# echo $num | egrep \<[0-9]+\>
12:31[root@centos7 /data]# echo $num | egrep "\<[0-9]+\>"
/123/
12:31[root@centos7 /data]# echo $num | egrep [0-9]+
/123/
但可以转义它,不过太过于复杂,所以还是加上引号比较好:
12:36[root@centos7 /data]# echo $num | egrep \\\<[0-9]+\\\>
/123/
  • 短路或后面的语句如果是两条命令想要同时执行的,并且不开启子进程,必须得使用花括号括起来,并且它与每条命令之间要有空格分开,每条命令后面也要加上分号

Bash的文件测试

存在性测试

-a FILE:同-e
-e FILE: 文件存在性测试,存在为真,否则为假

存在性及类别测试

-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件

  • 注意:用-d判断是否是文件夹的时候,如果一个软链接指向一个文件夹,它也会把这个软连接当做一个文件夹返回正确的结果,因此,判断是否是文件夹之前,应先用-h或者-L判断是否是软链接
  • 因此我们知道判断软连接的时候都是判断它指向的目标的文件的类型,应该需要提前判断是否为软链接再判断其它的。
  • 或者两个条件同时判断:[ ! -h "file" ] && [ -d "file"] 或者 [ ! -h "file" -a -d "file"]

文件权限测试:

-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行

  • 注意:它是针对当前用户而言的真实权限,并不是完全按照ll列表中显示的来判断的。就比如root账号什么权限都有。

文件特殊权限测试:

-u FILE:是否存在且拥有suid权限
-g FILE:是否存在且拥有sgid权限
-k FILE:是否存在且拥有sticky权限

文件大小测试:

-s FILE: 是否存在且非空

文件是否打开:

-t fd: fd 文件描述符是否在某终端已经打开
-N FILE:文件自从上一次被读取之后是否被修改过
-O FILE:当前有效用户是否为文件属主
-G FILE:当前有效用户是否为文件属组

双目测试:

FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)
FILE1 -ot FILE2: FILE1是否旧于FILE2

Bash的组合测试条件

第一种方式:

[ EXPRESSION1 -a EXPRESSION2 ] 并且
[ EXPRESSION1 -o EXPRESSION2 ] 或者
[ ! EXPRESSION ]

  • 注意:必须使用测试命令进行,[[ ]] 不支持 -a -o ,只支持符号的&& || !
  • 但是 -a代表文件存在的表达式则在[[]]中可以使用

第二种方式:

COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE
! COMMAND 非
如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]

  • 注意:这种符号表示的可以在[[]]中使用。
    举例:
判断有没有用户:
grep -q no\_such\_user /etc/passwd || echo 'No such user
No such user
判断一个IP是否连通:
ping -c1 -W2 station1 &> /dev/null  && echo "station1 is up"  || (echo 'station1 is unreachable'; exit 1)
station1 is up
  • 注意:这里小括号虽然开启了子进程,但是exit的输出值仍然会继承,就和之前echo $name (echo $name) 一样,只要变量并未再次进行赋值,便可继承。
13:31[root@centos7 /data/scriptest]# ( hostname ; exit 101 )
centos7.6test
15:09[root@centos7 /data/scriptest]# echo $?
101
  • 可以用小括号临时创建不同文件权限的文件等操作
(umask 066 ;touch f1)

附加:禁止普通用户登录可以创建一个普通文件/etc/nologin即可,删除它便可以解除。对root登陆没有影响。

read命令接收输入

  • 使用read来把输入值分配给一个或多个shell变量
    -p 指定要显示的提示wall
    -s 静默输入,一般用于密码
  • ctrl+s ,ctrl+q | stty -echo ,stty echo

-n N 指定输入的字符长度N:只要达到这个长度,命令就退出了
-d ‘字符’ 输入结束符:只要输入这个字符,命令也就退出了
-t N TIMEOUT为N秒

  • read 从标准输入中读取值,给每个单词分配一个变量
  • 所有剩余单词都被分配给最后一个变量

例子:
read -p “Enter a filename: “ FILE

注意点:

  1. 如果不指定要接收的变量名,则默认赋值输入给 REPLY 这个变量。
  2. read后面直接跟变量名,不需要加$
  3. read命令赋值参数如果用管道传给它的话,并不是说不可以,只不过管道会开批一个子shell,会让赋的值无法在当前shell中使用,例子:
  4. 如果只有n个变量,但却输入了N个值(用空格隔开的),N\>n, 前n-1个变量正常赋值,第n个变量会把剩下的字符包括空格全部赋值进去。
  5. 同理如果有M个变量,但是输入了m个值(用空格隔开),M\>m,则前面的m个变量正常赋值,后面的变量为空。
15:09[root@centos7 /data/scriptest]# echo a b c | read x y z
15:29[root@centos7 /data/scriptest]# echo $x $y $z

15:34[root@centos7 /data/scriptest]# echo a b c | (read x y z;echo $x $y $z;)
a b c
15:30[root@centos7 /data/scriptest]# echo a b c | { read x y z;echo $x $y $z; } 
a b c
  • 注意例子中小括号中括号都可以,分别是小括号代表一个子进程中的命令, 以及中括号看作是管道自己开启的子进程中的整体命令,略有不同。需要注意中括号两边必须要有空格,小括号不需要。

条件选择语句if

  • 选择执行:
    注意:if语句可嵌套
  • 单分支
    if 判断条件;then
    条件为真的分支代码;
    fi
  • 双分支
    if 判断条件; then
    条件为真的分支代码;
    else
    条件为假的分支代码;
    fi
    -多分支
    if 判断条件1; then
    条件1为真的分支代码;
    elif 判断条件2; then
    条件2为真的分支代码;
    elif 判断条件3; then
    条件3为真的分支代码;
    else
    以上条件都为假的分支代码;
    fi
  • 注意:逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句

条件判断case

case 变量引用(word) in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac

case支持glob风格的通配符:

*: 任意长度任意字符
?: 任意单个字符
[]:指定范围内的任意单个字符
a|b: a或b