语言的类型
解释型
计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所写的程序语言翻译成机器语言,计算机才能执行程序。
将其他语言翻译成机器语言的工具,被称为解释器。
编译器翻译的方式有两种:一个是编译,一个是解释。两种方式之间的区别在于翻译时间点的不同。当编译器以解释方式运行的时候,也称之为解释器。
-
交给解释器来解释执行
-
跨平台优势
-
能够直接交付源码
-
所见即所得
-
可以通过解释器来执行源码
-
shell本身就是一个解释器
编译型
需要将代码编译成二进制文件
是不能直接看到源码的,查看的永远是一个编译好的文件
shell脚本编写
shell脚本的基本元素
-
第一行声明:#!/bin/bash
-
注释:# 、脚本的功能,作者,编写时间等
-
可执行语句、程序语句、条件测试、循环语句、判断、函数、数组、变量
shell文件格式
-
一般以.sh结尾,但.sh是给人看的,在系统中无意义
-
文件的第一行是解释器,一般使用/bin/bash,使用bash shell,来执行
-
注释:#单行注释
编写规范0
-
开头:指定脚本编译器: #!/bin/bash #!/bin/sh
-
加版本信息等 使用.vimrc文件能够快速生成开头注释信息
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()" func SetTitle() if expand("%:e") == 'sh' call setline(1,"#!/bin/bash") call setline(2,"##############################################################") call setline(3, "# File Name: ".expand("%")) call setline(4, "# Version: V1.0") call setline(5, "# Author: sunyaping") call setline(6, "# Email: sunyaping@yutianedu.com") call setline(7, "# Created Time : ".strftime("%F %T")) call setline(8, "# Description:") call setline(9,"##############################################################") call setline(10, "") endif endfunc
-
脚本尽量不用中文注释,也不必用cat,使用grep
-
代码缩进,段落要有注释 红帽的规范模板:/etc/.bashrc
-
最好以.sh为扩展名
shell脚本执行的方式
-
用相对路径执行脚本
-
用绝对路径执行脚本
-
直接使用bash来执行脚本
-
再当前shell中执行bash shell 脚本 source 或者 .
区别
如果使用bash指定运行一个脚本的话,不需要声明解释器,脚本可以没有执行权限
如果使用绝对和相对路径执行,需要有权限,
如果使用source 执行,是在当前shell中执行的,而其他的方法是开启了一个子shell
位置化参数
位置变量
这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的。
$1 $2 $3 $4 $5 $6 $7 $8 $9 ${10}
预定义变量
是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。
-
$0 :脚本名
-
$* :所有的参数,双引号引起来时参数视为单个字符串
-
$@: 所有的参数,双引号引起来时,每个参数作为个体
-
$# : 参数的个数
-
$$ : 当前进程的PID
-
$? : 上一个命令的返回值0表示成功
read
从命令行输入变量,接收命令行的输入,变成变量
read -p "输入的内容1:" A read -p "输入的内容2:" B echo $A $B
read中也可以不指定变量,可以使用默认的变量REPLY
exit退出码
-
程序执行成功之后会得到一个退出码
-
0代表成功,1-255为失败
-
shell程序的退出码存放在$?中
-
exit 100 可以将退出码设定为100
-
条件运算符
用于链接命令
-
&&代表逻辑与
-
|| 代表逻辑或
&&
-
com1 &&com2如果com1输出为0则执行com2反之不执行
||
-
com1 || com2 如果com1输出为0则不执行com2 反之执行
复合指令
() 在子shell中执行,不需要分号结尾 命令和括号不需要空格
{} 在当前shell执行,需要分号结尾 命令和括号需要空格
条件测试
文件测试表达式
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否块设备文件,如果是,则返回true | [ -b $file ] 返回false |
-c file | 检测文件是否字符设备文件,如果是,则返回true | [ -c $file ] 返回false |
-d file | 检测文件是否目录,如果是,则返回true | [ -d $file ] 返回false |
-f file | 检测文件是否普通文件,如果是,则返回true | [ -f $file ] 返回false |
-g file | 检测文件是否设置了SGID位,如果是,则返回true | [ -g $file ] 返回false |
-k file | 检测文件是否设置了粘着位,如果是,则返回true | [ -k $file ] 返回false |
-p file | 检测文件是否块具名管道,如果是,则返回true | [ -p $file ] 返回false |
-u file | 检测文件是否设置了SUID,如果是,则返回true | [ -u $file ] 返回false |
-r file | 检测文件是否可读,如果是,则返回true | [ -r $file ] 返回false |
数值运算符
操作符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回true | [ $a -eq $b ] 返回true |
-ne | 检测两个数是否相等,不相等返回true | [ $a -ne $b ] 返回true |
-gt | 检测左边的数字是否大于右边的,如果是,返回true | [ $a -gt $b ] 返回true |
-lt | 检测左边的数字是否小于右边的,如果是,返回true | [ $a -lt $b ] 返回true |
-ge | 检测左边的数字是否大于等于右边的,如果是,返回true | [ $a -ge $b ] 返回true |
-le | 检测左边的数字是否小于等于右边的,如果是,返回true | [ $a -le $b ] 返回true |
布尔运算符
操作符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为true,返回false,否则返回true | [ ! true ] 返回true |
-o | 或运算,有一个表达式为true,则返回true | [ $a -lt 20 -o $b -gt 100] 返回true |
-a | 与运算,两个表达式为true,才返回true | [ $a -lt 20 -a $b -gt 100] 返回false |
字符串运算符
操作符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回true | [ $a = $b ] 返回false |
!= | 检测两个字符串是否相等,不相等返回true | [ $a != $b ] 返回false |
条件判断—if语句
单分支结构
第一种写法
if < 条件表达式 > then 指令 fi
第二种写法
if <条件表达式> ; then 指令 fi
双分支结构
if <条件表达式> ; then 指令1 else 指令2 fi
多分支结构
if <条件表达式1> ; then 指令1 elif <条件表达式2> ; then 指令2 else 指令3 else 指令n fi
条件判断—case语句
case 变量名 in 值1) 指令1 ;; 值2) 指令2 ;; 值3) 指令3 ;; *) 指令4 ;; esac
循环语句—for循环
循环规律
赋予for一个变量,给变量赋值,对变量中的值进行循环语句
方法1:直接列出元素
for i in 12345 do echo $i done
方法2:使用大括号
for i in {1..5} do echo $i done
方法3:使用seq
for i in $(seq 1 5) do echo $i done
方法4:使用命令的结果
for i in $(ls *.sh) do echo $i done
循环语句—类C风格的for循环
#判定192.168.1.0网段主机存货状态 for ((i=1;i<=254;i++)) do if ping -c 2 -W 1 192.168.1.$i > /dev/null ; then echo "192.168.1.$i is up" else echo "192.168.1.$i is down" fi done
循环语句—while循环
循环规律
先进行测试条件,当测试条件为真,则执行循环体,反之则跳出循环,条件满足则一直循环,直到条件不满足
while <测试条件> do 循环体1 循环体2 done
循环语句—until循环
循环规律
将循环体重复执行,知道条件成立,条件不满足则一直循环,直到条件满足
until <条件语句> do 循环体1 循环体2 done
循环语句—循环控制
break语句
作用是立刻跳出某个循环结构,直接退出、可以作用在for、while、until等循环体中
#!/bin/bash for i in `seq 10` do if [ $i -eq 4 ] ; then break fi echo $i done
continue语句
作用不是退出循环体,而是跳过当前循环体中该语句后面的语句,从循环语句的开始位置执行
#!/bin/bash for i in `seq 10` do if [ $i -eq 4 ] ; then continue fi echo $i done
脚本实例
批量创建用户:
1.创建用户,用户名来自于一个用户列表文件
2.给用户设置密码,密码随机
3.密码设置成功之后,显示该用户创建成功
for USER in $(cat /root/scripts/username.txt) do useradd $USER &> /dev/null PASSWORD=$(openssl rand -base64 8) echo PASSWORD | passwd --stdin $USER &> /dev/null echo "username: $USER password: $PASSWORD" >> /root/scripts/userinfo if [ $? = 0 ];then echo create $USER succefully else echo create $USER failed fi done
bash shell 循环打印一句中字母的长度不大于6的单词
word='i am a teacher from Hubei' for i in $word do a=$(echo $i | wc -L) [ $a -lt 6 ] && echo $i done
在~/.vimrc中的内容:(用于自动生成脚本眉头)
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()" func SetTitle() if expand("%:e") == 'sh' call setline(1,"#!/bin/bash") call setline(2,"##############################################################") call setline(3, "# File Name: ".expand("%")) call setline(4, "# Version: V1.0") call setline(5, "# Author: WangZhenDong") call setline(6, "# Email: 2094169032@qq.com") call setline(7, "# Created Time : ".strftime("%F %T")) call setline(8, "# Description:") call setline(9,"##############################################################") call setline(10, "") endif endfunc
函数
什么是函数:
函数看成是功能模块,是由若干的shell语句组成的语句块,实现代码重复使用和模块化编程,作为shell程序的一部分来运行
函数的组成:
-
函数名
-
函数体
函数的格式:
格式1:
#简写格式 函数名 () { 函数体(需要执行的内容) }
格式2:
#简写格式 function 函数名{ 函数体(执行内容) }
格式3:
#标准格式 function 函数名() { 函数体(执行内容) }
定义函数的方式:
-
在命令行定义
-
只是临时有效
[root@localhost ~]# function redhat () { > echo hello redhat > } [root@localhost ~]# redhat hello redhat
-
-
在脚本中定义
-
在脚本中有效,一般在脚本的最前面引入
#!/bin/bash function redhat () { echo redhat } redhat
-
函数定义了不被使用的话,函数不会生效,必须要使用了函数,函数定义了才有作用
-
-
通过文件的方式引入
-
在不同的文件中写入函数,通过读文件的方式去读取函数,从而调用函数本身
[root@localhost function]# cat hello1 function redhat() { echo hello redhat } [root@localhost function]# cat hello2 function wuhan () { echo hello wuhan } [root@localhost function]# cat hello.sh . /root/function/hello1 . /root/function/hello2 redhat wuhan [root@localhost function]# bash hello.sh hello redhat hello wuhan
-
查看函数
declare -F #查看当前已经定义的函数名 declare -f #查看当前已经定义的函数定义 declare -f 函数名 #查看指定当前定义的函数名 declare -F 函数名 #查看指定的当前已经定义的函数名定义 -a 将变量定义为数组 -A 将变量定义为关联数组 -f 显示已经定义的所有函数名以及内容 -F 显示已经定义的所有函数名 -x 声明或者显示环境变量和函数,相当于export
删除函数
unset 函数名
环境函数
和环境变量一样,需要在子shell中持续使用后函数,则需要注册为环境函数
#建立环境函数 declare -xf 函数名 export -f 函数名 #查看环境函数 declare -f export -f
函数的返回值
-
函数的执行结果返回值
-
使用echo等命令进行输出
-
函数体中调用命令的输出结果
-
-
函数的退出状态码
-
默认取决于函数中执行的后一条命令退出的状态码
-
自定义退出状态码格式为:
-
return 从函数中返回,用后状态命令决定返回值
-
return 0 无错误返回
-
return 1-255 有错误返回
-
echo $? 查看退出码
-
-
函数变量:
变量作用域
-
本地变量
-
只在当前shell进程有效,为执行脚本会启动专用子shell,因此本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
-
本地变量: 函数的生命周期,函数结束的时候,变量自动销毁
-
-
-
环境变量
-
当前shell和子shell中有效
-
如果函数中定义了普通变量,并且名称和本地变量相同,则使用本地变量,由于普通变量和普通变量会冲突,建议函数中只使用本地变量
-
函数中定义变量的方法:
local 变量名=值
-
变量的值是会覆盖的
-
-
数组
为什么要有数组?
有时需要同时定义多个变量,并且这些变量都用来做某一类事情这就将他们定义为一个数组,即为多个变量的集合。
数组的类型:
普通数组
-
使用数字作为索引,可以不用声明
[root@localhost ~]# redhat=(hello redhat) [root@localhost ~]# set | grep redhat #set查看变量 BASH_VERSINFO=([0]="4" [1]="4" [2]="19" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu") MACHTYPE=x86_64-redhat-linux-gnu redhat=([0]="hello" [1]="redhat")
-
打印数组
[root@localhost ~]# windows=(win7 win8 win10 win11) [root@localhost ~]# echo ${windows[*]} win7 win8 win10 win11 [root@localhost ~]# echo $windows win7
规律:
-
声明数组需要使用()将数组内的内容括起来
-
引用数组需要使用${}将数组括起来,并且引用的时候通过在数组后面加上[索引号]的方式来进行引用单个或多个数组值
-
当数组作为变量调用的时候,通常只会使用数组的第一个值的结果
关联数组
-
需要使用declare -A 进行声明
[root@localhost ~]# declare -A hubei [root@localhost ~]# hubei=([es]=td [xn]=gh) [root@localhost ~]# set | grep hubei hubei=([xn]="gh" [es]="td" )
数组赋值:
普通数组
-
一次赋值一个元素
[root@localhost ~]# wuhan[3]=sjc [root@localhost ~]# set | grep wuhan wuhan=([3]="sjc")
-
一次赋值全部元素
[root@localhost ~]# wuhan=(hhl yby mulan sjc) [root@localhost ~]# set | grep wuhan wuhan=([0]="hhl" [1]="yby" [2]="mulan" [3]="sjc")
-
赋值特定的元素
[root@localhost ~]# wuhan=([1]=ssj [2]=rgm) [root@localhost ~]# set | grep wuhan wuhan=([1]="ssj" [2]="rgm")
关联数组
-
一次赋值一个元素
[root@localhost ~]# declare -A hubei [root@localhost ~]# hubei[xn]=gh [root@localhost ~]# set | grep hubei hubei=([xn]="gh" )
-
一次赋值全部元素
[root@localhost ~]# declare -A hubei [root@localhost ~]# hubei=([xn]=gh [yc]=lj [xy]=nrm) [root@localhost ~]# set | grep hubei hubei=([xn]="gh" [xy]="nrm" [yc]="lj" )
引用数组
查看所有数组
-
declare -a 查看普通数组
[root@localhost ~]# declare -a declare -a BASH_ARGC=() declare -a BASH_ARGV=() declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="7") declare -a BASH_LINENO=() declare -a BASH_SOURCE=() declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="19" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu") declare -a DIRSTACK=() declare -a FUNCNAME declare -a GROUPS=() declare -a PIPESTATUS=([0]="0")
-
declare -A 查看关联数组
[root@localhost ~]# declare -A declare -A BASH_ALIASES=() declare -A BASH_CMDS=()
引用数组单个元素
${NAME[INDEX]} 如果省略[INDEX]则表示引用下标为0的元素
引用数组所有元素
${name[*]} ${name[@]} 数组的长度,即数组中元素的个数
删除数组
删除数组中的某一个元素
[root@localhost ~]# unset wuhan[1] [root@localhost ~]# set | grep wuhan _=wuhan wuhan=([0]="redhat1" [2]="redhat3")
删除整个数组
[root@localhost ~]# unset wuhan [root@localhost ~]# set | grep wuhan _=wuhan
数组数据处理
数组切片
-
跳过两个元素取一个元素
[root@localhost ~]# wuhan=(a b c d e f g h) [root@localhost ~]# echo ${wuhan[@]:2:1} c #规律 {name[@]:offset:number} offset:要跳过的元素个数 number:要取出的元素个数
-
数组追加元素
[root@localhost ~]# wuhan+=(uioui) [root@localhost ~]# echo ${wuhan[*]} a b c d e f g h uioui