文章目录
在Linux中,条件测试(也称为条件判断或条件表达式)是用于在脚本或命令行中基于某些条件的真或假来执行不同操作的一种机制。常用于Shell脚本中,条件测试可以帮助编写控制流语句,例如if、while、until和for循环。以下是一些详细信息和常见用法:
基础概念
条件测试的结果是一个布尔值:真(true)或假(false)。
若真,则状态码变量$?返回0
若假,则状态码变量$?返回1(非零值)
条件测试命令:
test EXPRESSION
[ EXPRESSION ] 和test等价,建议使用[]
[[ EXPRESSION ]] 相当于增强版的[],支持[]的用法,且支持扩展正则表达式和通配符
注意:EXPRESSION前后必须有空白字符
[root@leiRocky ~]# type [
[ is a shell builtin
[root@leiRocky ~]# help [
[: [ arg... ]
Evaluate conditional expression.
This is a synonym for the "test" builtin,
but the last argument must be a literal `]', to match the opening `['.
帮助:help test
[root@leiRocky ~]# help test
test: test [expr]
Evaluate conditional expression.
Exits with a status of 0 (true) or 1 (false) depending on the evaluation of EXPR.
Expressions may be unary or binary.
Unary expressions are often used to examine the status of a file.
There are string operators and numeric comparison operators as well.
The behavior of test depends on the number of arguments.
Read the bash manual page for the complete specification.
File operators:
-a FILE True if file exists.
-b FILE True if file is block special.
-c FILE True if file is character special.
-d FILE True if file is a directory.
-e FILE True if file exists.
-f FILE True if file exists and is a regular file.
-g FILE True if file is set-group-id.
-h FILE True if file is a symbolic link.
-L FILE True if file is a symbolic link.
-k FILE True if file has its `sticky' bit set.
-p FILE True if file is a named pipe.
-r FILE True if file is readable by you.
-s FILE True if file exists and is not empty.
-S FILE True if file is a socket.
-t FD True if FD is opened on a terminal.
-u FILE True if the file is set-user-id.
-w FILE True if the file is writable by you.
-x FILE True if the file is executable by you.
-O FILE True if the file is effectively owned by you.
-G FILE True if the file is effectively owned by your group.
-N FILE True if the file has been modified since it was last read.
FILE1 -nt FILE2 True if file1 is newer than file2 (according to modification date).
FILE1 -ot FILE2 True if file1 is older than file2.
FILE1 -ef FILE2 True if file1 is a hard link to file2.
String operators:
-z STRING True if string is empty.
-n STRING
STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.
Other operators:
-o OPTION True if the shell option OPTION is enabled.
-v VAR True if the shell variable VAR is set.
-R VAR True if the shell variable VAR is set and is a name
reference.
! EXPR True if expr is false.
EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
arg1 OP arg2 Arithmetic tests.
OP is one of -eq, -ne,-lt, -le, -gt, or -ge.
Arithmetic binary operators return true if ARG1 is equal, not-equal,
less-than, less-than-or-equal, greater-than, or greater-than-or-equal
than ARG2.
Exit Status:
Returns success if EXPR evaluates to true;
fails if EXPR evaluates to false or an invalid argument is given.
条件测试命令
变量测试
判断shell变量是否定义,使用set是否能看到该变量的定义。
-v VAR True if the shell variable VAR is set.
-R VAR True if the shell variable VAR is set and is a name
[ -v NAME ]
数值测试
-eq 是否等于 (equal)
-ne 是否不等于 (not-equal)
-gt 是否大于 (greater-than)
-ge 是否大于等于 (greater-than-or-equal)
-lt 是否小于 (less-than)
-le 是否小于等于 (less-than-or-equal)
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
Arithmetic binary operators return true if ARG1 is equal, not-equal,
less-than, less-than-or-equal, greater-than, or greater-than-or-equal
than ARG2.
字符串测试
-z "STRING" 字符串是否为空,没定义或空为真,不空为假, 建议加上双引号
-n "STRING" 字符串是否不空,不空为真,空为假
"STRING"同上
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii码是否大于ascii码
< 是否小于
String operators:
-z STRING True if string is empty.
-n STRING
STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.
在比较字符串时,建议变量存放在双引号""中
文件测试
存在性测试
-a FILE: 同-e,文件是否存在 True if file exists.
-e FILE: 文件存在性测试,存在为真,否则为假 True if file exists.
-b FILE: 是否存在且为块设备文件 True if file is block special.
-c FILE: 是否存在且为字符设备文件 True if file is character special.
-d FILE: 是否存在且为目录文件 True if file is a directory.
-f FILE: 是否存在且为普通文件 True if file exists and is a regular file.
-h FILE 或 -L FTLE: 存在且为符号链接文件 True if file is a symbolic link.
-p FILE: 是否存在且为命名管道文件 True if file is a named pipe.
-S FILE: 是否存在且为套接字文件 True if file is a socket.
范例:-e和-a 表示判断文件的存在性,建议使用-e
文件是否不存在
[root@leiRocky ~]# [ -a /etc/nologin ]
[root@leiRocky ~]# echo $?
1
[root@leiRocky ~]# ! [ -a /etc/nologin ]
[root@leiRocky ~]# echo $?
0
文件是否存在
[root@leiRocky ~]# [ -a /etc/issue ]
[root@leiRocky ~]# echo $?
0
写在中括号里面的-a取反结果没有变化,-e则有变化
[root@leiRocky ~]# [ ! -a /etc/issue ]
[root@leiRocky ~]# echo $?
0
[root@leiRocky ~]# ! [ -a /etc/issue ]
[root@leiRocky ~]# echo $?
1
因此-e为推荐写法
[root@leiRocky ~]# [ ! -e /etc/issue ]
[root@leiRocky ~]# echo $?
1
[root@leiRocky ~]# ! [ -e /etc/issue ]
[root@leiRocky ~]# echo $?
1
是否是符号链接文件
[root@leiRocky ~]# [ -L /bin ]
[root@leiRocky ~]# echo $?
0
[root@leiRocky ~]# [ -L /bin/ ]
[root@leiRocky ~]# echo $?
1
[root@leiRocky ~]#
文件权限测试
注意:最终结果由用户对文件的实际权限决定,而非文件属性决定
-r FILE: 是否存在且可读
-w FILE: 是否存在且可写
-X FILE: 是否存在且可执行
-u FILE: 是否存在且拥有suid权限
-g FILE: 是否存在且拥有sgid权限
-k FILE: 是否存在且拥有sticky权限
文件属性测试
-s FILE 是否存在且非空 True if file exists and is not empty
-t fd fd 文件描述符是否在某终端已经打开 True if FD is opened on a terminal.
-N FILE 文件自从上一次被读取之后是否被修改过 True if the file has been modified since it was last read.
-O FILE 当前有效用户是否为文件属主 True if the file is effectively owned by you.
-G FILE 当前有效用户是否为文件属组 True if the file is effectively owned by your group.
FILE1 -ef FILE2 FILE1是否是FILE2的硬链接 True if file1 is a hard link to file2.
FILE1 -nt FILE2 FILE1是否新于FILE2 (mtime) True if file1 is newer than file2 (according to modification date).
FILE1 -ot FILE2 FILE1是否旧于FILE2 True if file1 is older than file2.
关于()和 {}
(CMD1;CMD2;…)和{CMD1;CMD2;…;}都可以将多个命令组合在一起,批量执行。
[root@leiRocky ~]# man bash
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境。帮助参看:man bash 搜索(list)
{ list; }不会启子shell,在当前shell中运行,会影响当前shell环境。帮助参看:man bash 搜索 { list; }
()
思考下面语句的输出结果
name=mage;(echo $name; name=wang; echo $name ); echo $name
(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that
affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.
{ list; }
list is simply executed in the current shell environment. list must be terminated with a newline or semicolon. This is known as a
group command. The return status is the exit status of list. Note that unlike the metacharacters ( and ), { and } are reserved words
and must occur where a reserved word is permitted to be recognized. Since they do not cause a word break, they must be separated from
list by whitespace or another shell metacharacter.
小括号()会开启子进程,子进程定义的变量是不会影响父进程的;第一个mage的输出是子进程继承了副进程的变量,这是一个个例。
() 会开启子进程
[root@leiRocky ~]# echo $BASHPID
2200
[root@leiRocky ~]# ( echo $BASHPID; sleep 100 )
2288
[root@leiRocky ~]# pstree -p
├─sshd(2195)───sshd(2199)───bash(2200)───bash(2288)───sleep(2289)
{ }
思考下面语句又是怎样的输出结果呢?
name=mage;{ echo $name; name=wang; echo $name; }; echo $name
{ }不会开启子shell
[root@leiRocky ~]# echo $BASHPID
2200
[root@leiRocky ~]# { echo $BASHPID; sleep 100; }
2200
[root@leiRocky ~]# pstree -p
├─sshd(2195)───sshd(2199)───bash(2200)───sleep(2356)
组合测试条件
第一种方式
说明: -a和-o需要使用测试命令进行,[[ ]]不支持。
[EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSTON2都是真,结果才为真
[EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[! EXPRESSION ] 取反
范例:
[root@centos8 ~]#FILE="/data/scrips/test.sh"
[root@centos8 ~]#ll /data/scrips/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh
[root@centos8 ~]#[ -f $FILE -a -x $FITLE ]
[root@centos8 ~]#echo $?
1
[root@centos8 ~]#chmod +x /data/scripts/test.sh
[root@centos8 ~]#ll /data/scripts/test.sh
-rwxr-xr-x 1 root root 382 Dec23,09:32 /data/script/test.sh
并且
[root@centos8~]#[ -f $FILE -a x $FILE ]
[root@centos8~]#echo $?
0
[root@centos8 ~]#chmod -x /data/scripts/test.sh
[root@centos8 ~]#ll /data/scripts/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh
或者
[root@centos8 ~]#[ f $FILE -o -x $FILE ]
[root@centos8 ~]#echo $?
0
[root@centos8 ~]#[ -x $FILE ]
[root@centos8 ~]#echo $?
1
取反
[root@centos8 ~]#[ ! -x $FILE ]
[root@centos8 ~]#echo $?
[root@centos8 ~]#! [ -x $FILE ]
0
第二种方式
COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN 如果COMMAND1成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE 如果COMMAND1成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND 非,取反
判断用户是否存在
1.判断用户是否存在,存在就提示已经存在,否则就创建该用户。
[root@leiRocky ~]# id lei &> /dev/null && echo "lei is exist"
lei is exist
[root@leiRocky ~]# NAME=gouzi; id $NAME &> /dev/null || echo "$NAME is not exist"
gouzi is not exist
错误的逻辑提示
上图最开始的错误就是一个逻辑错误,
1.短路与和短路或的组合"cmd1 && cmd2 || cmd3",其实是一个大的短路或的组合"(cmd1 && cmd2) || cmd3"
2.当"(cmd1 && cmd2)"不成功时,cmd3才会执行,只要cmd1不成功,那么"(cmd1 && cmd2)"就是不成功的;
3.因为cmd1失败了,那"(cmd1 && cmd2)"就失败了,因此cmd2就不会执行,所以执行了cmd3;
4,相反cmd1成功了,就会执行cmd2,短路或的cmd3就不会被执行了。
正确的逻辑提示
正确的 && 和 || 顺序
结论:如果&&和||混合使用,&&要在前,ll放在后
用户不存在则创建
网络状态判断
#!/bin/bash
IP=192.168.10.155
ping -c1 -w1 $IP &> /dev/null && echo "$IP is up" || { echo "$IP is unreachable"; exit; }
echo "Script is finished"
分区磁盘空间的判断
我这里的分区磁盘没有规划有“/dev/ma…”开头的,由“/dev/nv…”开头的,生成环境中应该规划为统一的名称。
磁盘空间利用率为5%时就报警并发送邮件
#!/bin/bash
WARNING=5
SPACE_USED=`df | grep '^/dev/ma' | grep -oE '[0-9]+%' | tr -d % | sort -nr | head -1`
[ "$SPACE_USED" -gt $WARNING ] && echo "DISK USED: $SPACE_USED%, will be full" | mail -s diskingwarning root #配置邮件发送
磁盘空间和Inode号的检测
#!/bin/bash
WARNING=5
SPACE_USED=`df | grep '^/dev/ma'|grep -oE '[0-9]+%' | tr -d % | sort -nr | head -1`
INODE__USED=`df -i | grep '^/dev/ma'|grep -oE '[0-9]+%' | tr -d % | sort -nr | head -1`
[ "$SPACE_USED" -gt $WARNING -o "$INODE_USED" -gt $WARNING ] && echo "DISK USED: $SPACE_USED%, INODE_USED: $INODE_USED, will be full" | mail -s "DISK Warning" Email address
read接受输入
read读取键盘输入
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY。
[root@leiRocky ~]# read --help
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt]
[-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
If no NAMEs are supplied, the line read is stored in the REPLY variable.
Options:
-a array assign the words read to sequential indices of the array
variable ARRAY, starting at zero
-d delim continue until the first character of DELIM is read, rather
than newline
-e use Readline to obtain the line in an interactive shell
-i text use TEXT as the initial text for Readline
-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than
NCHARS characters are read before the delimiter
-N nchars return only after reading exactly NCHARS characters, unless
EOF is encountered or read times out, ignoring any
delimiter
-p prompt output the string PROMPT without a trailing newline before
attempting to read
-r do not allow backslashes to escape any characters
-s do not echo input coming from a terminal
-t timeout time out and return failure if a complete line of
input is not read within TIMEOUT seconds. The value of the
TMOUT variable is the default timeout. TIMEOUT may be a
fractional number. If TIMEOUT is 0, read returns
immediately, without trying to read any data, returning
success only if input is available on the specified
file descriptor. The exit status is greater than 128
if the timeout is exceeded
-u fd read from file descriptor FD instead of the standard input
Exit Status:
The return code is zero, unless end-of-file is encountered, read times out
(in which case it's greater than 128), a variable assignment error occurs,
or an invalid file descriptor is supplied as the argument to -u.
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N TIMEOUT为N秒
#!/bin/bash
read -p "Are you rich? yes or no: " ANSWER
[ $ANSWER = "yes" -o $ANSWER = "y" ] && echo "You are rich" || echo "Good Good Study,Day Day Up!"
实现运维菜单
work_menu.sh
#!/bin/bash
echo -en "\E[$[RANDOM%7+31];1m"
cat <<EOF
请选择:
1) 备份数据库
2) 清理日志
3) 软件升级
4) 软件回滚
5) 删库跑路
EOF
echo -en '\E[0m'
read -p "请选择上面项对应的数字1-5: " MENU
[ $MENU -eq 1 ] && ./backup.sh
[ $MENU -eq 2 ] && echo "清理日志"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删库跑路"
backup.sh
#!/bin/bash
SRC=/etc
DEST=/data/backup_`date +%F_%H-%M-%S`
cp -a $SRC $DEST
[ $? -eq 0 ] && echo "backup.sh脚本执行完毕,备份完成!" || echo "备份失败"
read和管道符面试题
man bash
由此可知为什么x和y的值都没有赋值进去
1.由于管道中的每个命令运行在独立的子进程中;
2.所以"echo 1 2 |read x y; echo x=$x y=$y"中 "echo 1 2 |read x y;"是子进程,"echo x=$x y=$y"是父进程;
3.父进程读取不了子进程中的变量;
4.因此可使用()和{},将(read x y; echo x=$x y=$y)作为整体。
管道符开启了子进程
bash shell配置文件
bash shell的配置文件很多,可以按照生效范围和登录方式来分类。
1.按生效范围划分两类
全局配置
全局配置:针对所有用户有效
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置
个人配置:只针对特定用户有效
~/.bash_profile
~/.bashrc
以上配置文件都是在用户登录时,就会被读取到的配置文件。
2.shell登录两种方式分类
交互式登录
1.直接通过终端输入账号密码登录
2.使用su - userName切换的用户
配置文件生效和执行顺序: (以下的文件都是可执行的脚本文件)
放在每个文件最前
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
~/.bash_ profi1e
~/.bashrc
/etc/bashrc
放在每个文件最后
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc #此文件执行两次
~/.bashrc
~/.bash_profile
注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序。
source和bash命令的区别
我们知道,一个脚本在没有赋予执行权限时,是可以通过bash命令来执行的。实际上不仅bash命令可以执行脚本,source命令也可以执行脚本。source和点“.”又是等价的。
编写一个test.sh脚本,打印当前进程PID和父进程PID
#!/bin/bash
echo PID=$BASHPID
echo PPID=$PPID
sleep 100
bash test.sh
bash命令开启子进程,bash运行的脚本中的变量在影响当前子进程的环境。
source test.sh
source不会开启子进程,直接在当前进程中执行,也就意味着source执行的脚本中的变量会影响当前进程中的全局环境。
结论
bash和source的使用场景不一样,
1.bash用于普通脚本的执行,
2.source用于当前shell环境中的全局配置文件。
非交互式登录
1.su UserName
2.图形界面下打开的终端
3.执行脚本
4.任何其它的bash实例
执行顺序
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc
3.按功能划分分类
profile类和bashrc类
用户登录时才会触发的文件
Profile类
profile类为交互式登录的shell提供配置.
全局: /etc/profile, /etc/profile.d/*.sh
个人: ~/.bash_profile
功用:
(1)用于定义环境变量
(2)运行命令或脚本
将来自己要写全局的配置文件就写在/etc/profile.d/里以.sh为后缀。这样可以放心修改自己的配置,不然容易改坏/etc/profile的配置,导致环境异常。可以看下/etc/profile文件里关于/etc/profile.d/*.sh读取的配置,如下
cat /etc/profile
for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
Bashrc类
bashrc类:为非交互式和交互式登录的shell提供配置
全局: /etc/bashrc
个人: ~/.bashrc
功用:
(1)定义命令别名和函数
(2)定义本地变量
编辑配置文件生效
修改profile和bashrc文件后需生效两种方法:
1.重新启动shell进程
2.source|.配置文件
注意:source 会在当前shell中执行脚本,所有一般只用于执行置文件,或在脚本中调用另一个脚本的场景
范例:
. ~/.bashrc
Bash 退出任务
保存在 ~/.bash_logout 文件(此文件就是脚本)中(用户), 在退出登录shell时运行
功能:
1.创建自动备份
2.清除临时文件
vim .bash_logout
# ~/.bash_logout
rm -rf /data/backup*
退出终端时。删除/data目录下的所有backup开头的文件。