一、shell编程基础
1、shell编程
程序=指令+数据
编程风格:
过程式:以指令为中心,数据服务于指令
对象式:以数据为中心,指令服务于数据
shell程序:提供了编程能力,解释能力
2、路径设置
export $PATH:/root
export $PATH:. shell脚本到哪儿都能执行(不建议)很危险
vim .bash_profile
生效:. .bash_profile
source .bash_profile
问题:当我创建一个shell文件之后,当移动着个文件后,发现他任然在找/root/bash(之前设置了PATH路径),原因是?
答:因为hash值的原因,外部命令第一次执行时搜索的路径会记录在内存的hash表中
hash查看一下,会发现有/root/first.sh
清除一下:hash -d first.sh
问题: 脚本执行,命令错误,继续执行
脚本执行,语法错误,终止执行
bash -n 语法检查,但不检查命令错误
bash -x 查看执行过程,跟踪调试
pstree :查看进程树 pstree -p
$$:可以查看当前进程id号
PS2 : 多行提示符
3、变量赋值:
name="String" #加入多行文件时加""会保留原格式(name=`cat /etc/issue` echo "$name")
cmd=hostname--> echo $cmd --> centos7.3.vincent
4、变量调用:
$ : $name
子:
vim par.sh
#!/bin/bash
name="par"
echo "par pid is $$"
echo "par.sh:name=$name"
/bin/bash son.sh /root/sun.sh . /root/sun.sh
echo "son.sh:name=$name"
vim son.sh
#!/bin/bash
export name
#name="son"
echo "son pid is $$"
echo "son.sh:name=$name"
结果:子进程的变量只在子进程中有效
从上面的例子中,我们能够得出:./ /bin/bash source三种执行方式的不同
./ 与 /bin/bash 都是新开进程,进行执行,此时本地变量不会被继承,不改变当前环境,通常用于执行脚本文件
source 与 . 则是将子进程放到父进程进行执行,将影响当前环境,常用于读取配置文件
5、bash中变量分类、
本地变量(普通变量):生效范围为当前shell,对当前shell之外的其他shell进程,包括当前shell的子进程均无效
环境变量:生效范围为当前shell进程及其子进程(作用范围:当前shell、子shell、子子shell)
局部变量:生效范围为当前shell进程中某代码片段
位置变量:$1,$2,...来表示,用于脚本代码中调用通过命令行参数传递给它的参数
特殊变量:$?,$0,$*,$@,$#,$$
(1)声明环境变量
export name=VALUE
declare -x name=VALUE
(2)查看环境变量
env
declare -x
printenv
export
(3)本地变量赋值
name = 'value'
(4)可以使用引用value
(1)可以是直接字符串:name = "root"
(2)变量引用:name="$USER"
(3)命令引用:name=`COMMAND`
(5)变量引用:$ $name
"":弱引用,其中的变量引用会被替换成变量名
'':强引用,其中的变量引用不会被替换成变量值,而保持原字符
(6)显示自己定义的所有变量
set :显示出所有的变量包括一些函数
(7)删除变量
unset 变量名,...
(8)显示上一条命令执行情况
echo $? :返回上一个执行的结果,通常0为正确,1为错误(这个值不是固定的,可自己指定1-255)
(9)只读变量和位置变量
只读变量:只能声明,但不能删除和修改( 进程的声明周期 )
声明:readonly name
declare -r name declare -ir name ( i表示数字 )
查看:readonly -p
例如:PI = 3.1415926
位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1,$2,$3,...对应第一,第二...参数,shift [n] 换位置 [ $10 ,$ ]
$0 :命令本身
$* :传递给脚本的所有参数,全部参数合为一个字符串( "string1 string2....")
$@ :传递给脚本的所有参数,每个参数为独立字符串 ( "string1" "string2" "string3" ... )
$# :传递给脚本的参数的个数
$@ $* :只有在被双引号引起来的时候才会有差异
说明:在编写脚本时,进来先判断
[ $# -lt 1 ] && echo "Usage:$0 arg1..." && exit 0
位置变量注意点:
当引用参数大于10个时,我们在用$10,$11这种做法就不行,会出现错误,此时需要使用 $,$,......
清空位置变量
set --
位置变量的扩展功能(执行一次,移动一位,只使用$1就好)
但我们在执行一个程序时,如何判断其是否执行完,我们可以通过shift来看看
shift N(N值很大,超过了给定变量的总数),如果执行完了,就会返回相应的错误代码
6、算术运算
查看bash中算术运算:help let
常用算术运算符:+、-、*、/、%、**(乘方)
算术运算实现:
1 let var=算术表达式
注意: let i=0 -> echo $? --->1
k=0 --> let k++ -->echo $? 1
k=0 --> let ++k -->echo $? 0
2 var=$[算术表达式]
3 var=$((算术运算表达式))
4 var=$(expr arg1 arg2 arg3...)
5 declare -i var = 数值
6 echo '算术表达式' bc
算术运算中注意事项
乘法符号有些场景中需要转义,如*
bash有内建的随机数生成器:$RANDOM(1-32767)
echo $[$RANDOM%50] :生成1-49之间随机数
7、逻辑运算
布尔值
true、false
1 0
与
见0为0
或
见1为1
非
异或
相同为0,不同为1
用于交换两数的值
短路
aa && bb aa为假则结束运算 (qq执行成功,则执行bb命令)
aa bb aa为真则结束运算 (aa命令失败,则执行bb命令)
实例:
1、判断用户存在否,在则返回用户名否则创建该用户
id $name &> echo $name is exist useradd $name
2、ping某主机,通则打印up否则down
ping -w1 -c1 172.18.0.1 &> /dev/null && echo "The machine is up" echo "The machie is down"
8、退出状态
进程使用退出状态来报告成功或失败
0 --> 代表成功
1-255 --> 代表失败
$? 该变量保存最近的命令退出状态
实例
$ ping -c1 -w1 hostname &> /dev/null-c:ping的次数 -w:几秒ping一次
$?
退出状态码
bash自定义退出状态码
exit [n]:自定义退出状态码
注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
如果未给脚本指定主功能代码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
9、条件测试
判断某需求是否满足,需要有测试机制来实现
专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件执行中
若真,则返回0
若假,则返回1
测试命令
test EXPRESSION
长格式:
$ test "$A" == "$B" && ehco " Strings are equal"
$ test "$A" -eq "$B" && echo "Integres are equal"
短格式:
$[ "$A" == "$B" ] && echo "Strings are equl"
$[ "$A" -eq "$B" ] && echo "Integres are equl"
空值或不赋值时是假 test $var && echo true
var="" test $var && echo true #结果都是假
var=" " test $var && echo true #结果为真
[ EXPRESSION ]
实例:判断一个文件是否存在,存在则退出
[ ! -e $1 ] && echo "The file not exist" && exit
[ -e $1 ] (echo "$1 is not exit " && exit) (这一条语句逻辑是不正确的,exit只是退出了子shell、子进程,并没有退出判断条件开启的shell)
改进:[ -e $1 ] { echo "$1 is not exit " ;exit;}(使用了匿名函数)
[[ EXPRESSION ]][[ ]]才支持正则表达式写法
注意:EXPRESION前后必须有空白字符,[ 是一个内部命令,[[ 是关键字
查帮助
help test
条件性的执行操作符
&& :代表条件性的AND THEN
:代表条件新的OR ELSE
实例:aa && bb cc :选择表达式
ping -c1 -w1 172.18.0.1 &> /dev/null && the host is up the host is down
read -p "Please enter a dirname:" fileuse
filename=${$fileuse:-"filename"}
=========================================================================================
变量赋值方式 变量y没有设置 变量y为空值 变量yshezho
x=$ x=新值 x=空 x=$y
x=$ x=新值 x=新值 x=$y
-------------------------------------------------------------------------------------
x=$ x=空 x=新值 x=新值
x=$ x=空 x=空 x=新值
--------------------------------------------------------------------------------------
x=$ x=新值 x=空 x=$y
y=新值 y值不变 y值不变
-------------------------------------------------------------------------------------
x=$ x=新值 x=新值 x=$y
y=新值 y=新值 y值不变
--------------------------------------------------------------------------------------
x=$ 新值输出到标准错误输出 x=空 x=$y
x=$ 新值输出到标准错误输出 新值输出到标准错误输出 x=$y
======================================================================================
10、比较运算符
(1)文件判断
-d filename:判断该文件是否存在,并且是否为目录文件
-e filename:判断文件是否存在(等价-a)d
-f filename:判断该文件是否存在,并且是否为普通文件
-b (你对软连接进行判断时,它判断的是软连接指向的文件( [ -b /dev/cdrom ] && exit 0)
-L 判断链接文件本身是什么文件( -h 存在且为符号链接文件)
-p 管道文件
-S 套接字文件
-s 大小为0
-r filename:...并且该文件是否拥有读权限(该权限是实际权限,而不是ll显示的权限,Acl权限)
-w
-x
-u filename :...是否拥有SUID权限【作用在二进制程序上,对目录无效】(passwd命令 chmod 4755添加suid权限)
-g (chmod 2755 )
-k (chmod o+t)
注意:在判断文件类型时,先判断软连接文件,在判断其他文件。软连接文件指向其真实的文件会先判断
(2)文件测试
文件大小测试:
-s file:是否存在且非空
文件是否打开
-t fd :fd表示文件描述符是否已经打开且与某终端相关
-N file:文件自动上一次被读取之后是否被修改过(即:修改时间是否比读的时间新)
-O file:当前有效用户是否为文件属主
-G file:当前有效用户是否为文件属组
双目测试
file1 -ef file2 :file1和file2是否指向同一个设备上的相同inode(判断硬链接)
file1 -nt file2 :file1 是否新于file2
file1 -ot file2 :file1是否旧于file2
(3)文件比较
file1 -nt file2:判断file1的修改时间是否比file2新
file1 -ot file2:...旧
file1 -ef file2:判断file1与file2的inode号是否相同(可理解为两文件是否为同一个文件,用于判断硬链接)
文件判断:
[[ "$sum" =~ ^.*\.sh$ ]] && echo $num is script $num is not script
(4)数字比较
-eq :判断是否相等
-ne :判断是否不相等
数字判断:
[[ "$sum" =~ ^-?[0-9]+$ ]] && echo $num is number $num is not number
(5)字符串比较
-z "string":判断字符串是否为空 ,空为真(变量加引号)
-n "string":判断字符串是否为非空,非空为真
test -n "$abc" && echo true
test -n && echo true #结果为真
== :判断两字符串是否相等
!= :判断两字符串是否不相等
> :ascii码1是否大于ascii码2
=~ :左侧字符串是否能够被右侧的PATTERN所匹配(左侧的字符串是否包含右侧pattern)
str=abc
[[ "$str" =~ "abc" ]] && echo true echo false
[[ $str =~ a.c ]] && echo true echo false(判断是否包含a,c不需要加引号)
注意:此表表达式一般用于 [[ ]]中;扩展的正则表达式
用于字符串比较时用到的操作数都应该使用引号
实例:
-z:True is string is empty
x=100 --> [ -z $x ] --> echo $? --> 1 : -z为空则真,
uset x--> [ -z $x ] --> echo $? --> 1 :-z非空则假,
-n:True in string is not empty
x=100 --> [ -n $x ] --> echo $? --> 1 :
uset x--> [ -n $x ] --> echo $? --> 1 :
x=100 --> [[ -n $x ]] --> echo $? --> 0 (使用[[ ]]才能看到正确结果)
(6)多重条件判断
判断1 -a 判断2 :逻辑与,1,2都成立,结果为真
判断1 -o 判断2 :逻辑或,1个成立结果为真
!判断 :逻辑非,原始的判断式取反
查看进程:
pstree -p
echo $$ :查看当前进程编号
echo $PPIP :查看父进程的进程号
判断一个文件是否以.txt结尾 ( ~ 表示后面跟正则表达式)
[[ $file =~ txt ]] && echo true
(7)组合测试表达式
第一种方法
command1 && command2 并且
command1 command2 或者
!command (!true 与 [ !true ]是不一样的)
第二种方式:
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
!EXPRESSION
必须使用测试命令进行
实例:
#[ -f /bin/bin/cat -a -x /bin/cat ] && cat /etc/fstab
如何判断输入的是数字
方法一
m=10
expr $m + 0
方法二
利用正则表达式
(8)总结:
1、那些括号的灵活使用
[表达式] test
( ) 开子进程
x=abc;echo $$;(echo $$ ;echo $x ;sleep 100; x=def ;echo $x );echo $x 【()开了子进程,sleep在子进程下再开进程执行】
结果:abc def abc
{ cmd1,cmd2,cmd3.... } 不开子shell。相当于顺序执行
x=abc;echo $$;;echo $x
结果:abc def def
11、I/O操作
1、read命令来接收键盘输入
使用read来把输入值分配给一个或多个shell变量;
-p 指定要显示的提示
-s 静默模式
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N Timeout为n秒
read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
read -p "Enter a filename: " filename
实例
read n < /etc/issue--->echo $n ---->honstname is \n
read a b c echo $b yyy -->echo $c zzz
read m n l
12、bash中如何展开命令行
把命令行分成单个命令词
展开别名
展开大括号中的声明{}
展开波浪符声明~
命令替换$()和``
再次把命令行分成命令词
展开文件通配符(*、?、[abc]等)
准备I/O重定向()
运行命令
防止扩展
反斜线(\)会使随后的字符按愿意解释
$echo Your cost:\$5.00
Your cost:$5.00
加引号来防止扩展
单引号('')防止扩展
双引号("")也防止所有扩展,但以下情况例外:
$ ----> 变量扩展
``(反引号)->命令替换
\(反斜线)-->禁止单个字符扩展
!(叹号) -->历史命令替换
13、bash的配置文件
按生效范围划分,存在两类:
全局配置
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc */
14、shell登录两种方式
交互式登录:
(1)直接通过终端输入账号密码登录
(2)使用 "su - UserName "切换用户
执行顺序
/etc/profile --> /etc/profile.d/*.sh(登录就会执行,如果你想要配置的服务开机执行什么脚本就可以放在这里)*/ --> ~/.bash_profile (放环境变量)--> ~/.bahsrc (别名、本地变量)--> /etc/bashrc(靠后的生效)
非交互式登录
(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本
执行顺序
~/.bashrc(别名和函数本地变量) --> /etc/bashrc --> /etc/profile.d/*.sh
.bash_profile:定义环境变量和开机启动项
source (.) scriptnaem 在当前shell执行*/
/bin/bash ./ 不开子进程执行
退出执行
/etc/bash_logout 退出的时候自动执行的文件,可在该文件中添加操作,当你退出时便会执行(rm -rf /app/* 当你退出时便会清空/app目录)*/
二、shell编程进阶
/etc/profile/vim.sh
领取专属 10元无门槛券
私享最新 技术干货