shell编程☞基础知识
shell脚本
- Linux系统中的shell是一个特殊的应用程序,它介于操作系统内核与用户之间、充当一个“解释器”的角色负责接收用户输入的操作指令并进行解释将需要执行的操作传递给内核执行并输出执行结果。
shell编程基本格式
#!/bin/bash
#This is a shell-script
echo "Hello World"
- 第1行很重要,这个就是脚本解释程序的声明指令,由调用这个脚本的shell来检测。它仅在脚本程序的第一行有效
- 第2行是注释,在#号后面的东西,bash都视而不见给忽略掉
- 第3行是shell脚本要执行要解释的指令
执行脚本的方式
bash [OPTIONS] firsh.sh
# -n: 测试脚本是否由语法错误
# -x: 显示脚本指定的详细过程
# 直接执行脚本文件,脚本需要由执行权限
/etc/rc.d/init.d/network
# 在当前路径下执行脚本,脚本需要由执行权限
./firsh.sh
读取配置文件
- 在Linux中环境变量也有相对应的配置文件,有时候经常会遇到修改这些配置文件或在脚本中读取这些配置文件,那么可以使用指令"source"或"."进行读取这些配置文件
source /etc/sysconfig/*
或
. /etc/sysconfig/*
- 另外在一个脚本中除了读取配置文件外,还会经常需要调用其他的脚本,这个时候只需要在脚本中直接执行需要调用的脚本文件即可
. /etc/rc.d/init.d/functions
- 在Linux中对于 “.” 的含义有很多,比如有的表示隐藏文件;有的表示当前目录;有的用来执行当前脚本程序或文件等等
变量
- 变量:就是一个同期用来存储数据也是一段内存空间(内存是编制的存储单元);通过变量赋值在变量中存储数据,然后可以通过变量名访问到的存储信息的内存地址
- 数据类型:用来先定义数据的存储格式和存储长度
- 数据类型的重要性:有一种系统攻击叫缓存区溢出。如声明一个数据类型为整型并申请1字节空间,当在这个变量中存储256时,这个整型数据就会产生溢出,因为1字节存储单元的存储范围是0~255,而256存储不了就会产生溢出占用其他进程空间,解决缓冲溢出最简单的方法就是当用户存储一个数据时先检查数据是否可以存储下
- 常用的数据类型有字符型、数值型、时间日期型、布尔型等等
Bash变量分类
1、本地变量
- 本地变量作用域只对当前shell进程生效,Bash默认变量都是本地变量
# 定义本地变量,其中set可以省略
set VAR_NAME=value
# 引用变量,其中()一般可以省略,直接echo $VAR_NAME
echo $(VAR_NAME)
# 引用变量,引用变量后可跟字符串
echo $VAR_NAME:/PATH
# 撤销所有变量
unset VAR_NAME
# 查看所有变量
set
2、局部变量
- 局部变量作用域对当前代码段有效,如在一个脚本函数中的变量跟函数外的变量名同名,就可以把函数内的变量定义为局部变量,这样就不会跟其他变量冲突
# 定义局部变量
local VAR_NAME=value
3、环境变量
- 环境变量用来定义每一个用户的操作环境,变量作用域只对当前用户shell进程及其子shell生效,并且机器重启变量失效。如我们常用的PATH变量就是一个环境变量,但是我们发现PATH变量重来没有失效过啊,不管机器有没有重启。那是因为针对环境变量有特定的环境变量配置文件,每一次用户登录就会加载此配置文件,同理在此文件中的变量就会生效
# 定义环境变量,定义不存在的变量
export VAR_NAME=value
# 定义环境变量,定义已存在的变量
export VAR_NAME
# 查看当前系统环境变量命令
printenv
env
export
4、位置变量
$0 # 引用执行脚本的名称
$1 # 引用执行脚本后跟的第一个位置参数
$2 # 引用执行脚本后跟的第二个位置参数
$N # 引用执行脚本后跟的第N个位置参数
shift 1 # 轮换,用于替换一个或多个位置变量
5、特殊变量
- 脚本中$#显示输入参数的个数
$? # 此变量用于保存上一个命令执行状态的返回值(0表示正确,1~255表示错误)
$# # 此变量用于引用命令行中位置个数
$* # 此变量用于引用所有位置参数的列表
$@ # 引用位置参数的列表
$$ # 此变量保存当前bash的进程号
声明变量
$ declare [OPTIONS] VARIABLES
-i # 整型
-a # 数值
-x # 环境变量
-r # 只读变量,不能撤销,不能修改,相当于read only
-f # 声明函数
变量命令规则
- 首个字符必须为字母
- 中间不能有空格,可以使用下划线
- 不能使用标点符号
- 不能使用bash里的关键字(可用help命令查看保留关键字)
shell编程☞输入输出
shell输出方式
- 在执行任务时,shell通常会自动打开如下所示的3个标准文档
- Stdin:标准输入文档,通常对应终端的键盘,文件描述符为0
- Stdout:标准输出文档,对应终端的屏幕,文件描述符为1
- Stderr:标准错误输出文档,对应终端的屏幕,文件描述符为2
1、echo
- 在shell中我们一般使用echo命令输出文本行或变量
echo $LANG
echo "Charset: $LANG"
echo [OPTIONS] "Hello World"
-e # 表示启用解释反斜杠转义,默认是-E表示禁止转义
-n # 表示禁用最后的换行,echo本身默认最后会输出一个换行
# 支持的反斜杠转义
\a # 发出警告声
\b # 删除请一个字符
\c # 最后不加上换行符号
\f # 换行但贯标依旧停留在原来的位置
\n # 换行且光标移至行首
\r # 光标移至行首,但不换行
\t # 插入tab
2、printf
- 它是echo的增强版。默认输出没有换行,换行需要自己加"\n";支持格式化输出
# 输出简单的字符串
printf "Hello World"
# printf完整语法
printf format-string [arguments...]
# format-string为描述规则的字符串,最好加上引号。此字符串包含按字面显示的字符以及格式声明,格式声明时特殊的占位符,用来描述如何显示相应的参数
# arguments是与格式声明相对应的参数列表
%b #相对应的参数被视为含有要被处理的转义序列之字符串
%c #ASCII字符,显示相对应参数的第一个字符
%d, %i #十进制整数
%e #浮点格式
%E #浮点格式
%f #浮点格式
%g #%e或%f转换,看哪一个较短,则删除结尾的零
%G #%E或%f转换,看哪一个较短,则删除结尾的零
%o #不带正负号的八进制值
%s #字符串
%u #不带正负号的十进制值
%x #不带正负号的十六进制值,使用a至f表示10至15
%X #不带正负号的十六进制值,使用A至F表示10至15
%% #字面意义的%
- printf命令可用来指定输出字段的宽度以及进行对齐的方式
# 格式如下
%[标志][宽度][.精度]格式指示符
# 标志可选,默认对输出进行左对齐,如果加”-“表示以右对齐为准
# 宽度可选,字段以宽度为数字值,指定字段宽度时,字段的内容默认为向右对齐,如果你希望文字向左靠,必须指定-标志
#! 精度的意义
%d,%i,%o,%u,%x,%X # 打印的最小位数,当值的位数小于此数字时,会在前面补零,默认精度为1
# 打印的最小位数,当值的位数小于此数字时,会在小数点后面补零,默认精度为6.精度为0时则表示不显示小数点
%e,%E
%f # 小数点右边的位数
%g,%G # 有效位数(significant digit)的最大数目
%s # 要打印字符的最大数目
shell输入方式
- 在Shell中read命令接收标准输入,或其他文件描述符的输入。得到输入后,read命令将数据放入一个标准变量中
- 利用read读取文件时,每次调用read命令都会读取文件中的“一行”文本。当文件没有可读行时,read命令将以非零状态退出
- 在Shell中read一般都用于脚本中用来接收用户的输入
# read使用方式
read [OPTIONS] VAR_NAME
-p "" # 给出提示信息
-t # # 设置超时时间(单位:秒)
# 使用read命令读取文本一行数据方法
#!/bin/bash
# 1
while read myline;
do
echo "LINE:"$myline
done > /tmp/myfile.text
# 2
cat myfile.txt | while read /tmp/myline;
do
echo "LINE:"$myline
done
- 使用awk或for VAR_NAME in file命令也能完成读取文件中的每行数据的功能
# 读取文本单个字段数据的方法
#!/bin/hash
#
while read A B;
do
echo "$A and $B"
done < /tmp/list.txt
文件描述符
kill -9 `ps -elf | grep -v grep | grep $1 | awk '{print $4}'` 1>/dev/null 2>&1
# 该命令是杀死进程的
# 0、1、2是文件描述符,代表标准输入、输出和错误
# "/dev/null"代表空设备文件;"1>/dev/null"表示标准输出重定向到空设备文件,也就是不输出任何信息到终端
# ">&"表示等同于的意思;"2>&1"表示2的输出重定向等同于1,即标准错误也重定向到空设备文件
shell字体颜色控制
echo -e "\033[1;31mHello\033[0m"
- "\033[: " :表示开始控制符
- 1:表示加粗
- 4:表示带下划线
- 5:表示闪烁
- 7:表示把前景和背景反过来
- “;” :分号用于区分输出格式控制和输出颜色控制
- “31” :其中3表示开启字体颜色显示,而1表示字体颜色
- “\033[0m” :表示结束控制符
shell编程☞选择语句
条件测试表达式
- 一个正确编写的Liunx应用程序会告诉操作系统执行成功与否,用什么方法告诉系统?那就是我们经常用到的exit状态,exit=0表示成功,否则表示失败
- exit状态有两个很重要的功能
- 1、检测和处理错误
- 2、可用执行T/F条件测试
- T/F的条件测试经常是用过表达式和if一起配合实现的,而条件测试表达式有两种语法形式:
- I、test expression:test命令工作很简单,如果给定的表达式为T,test退出的状态值为0
- II、[ expression ]:要注意表达式位于中括号的中间,两边必须有空格
条件测试方法
1、整数测试
[ $? (OPTIONS) 0 ]
-eq # 等于
-ne # 不等于
-gt # 大于
-lt # 小于
-ge # 大于等于
-le # 小于等于
2、字符测试
[ $? (OPTIONS) 0 ]
= # 相同
!= # 不相同
-z # 为空
-n # 大于0;注意这里如果用-m测试变量的值是否为空时,变量必须加上引号,否则不管变量有没有值都会为真
3、文件测试
[ (OPTIONS) /etc/passwd ]
-e # 文件是否存在
-f # 文件是否为普通文件
-d # 指定路径的是否为目录
-s # 如果文件存在且至少有一个字符
-r # 当前用户对指定文件是否有读取权限
-w # 当前用户对指定文件是否有写入权限
-x # 当前用户对指定文件是否有执行权限
4、逻辑表达式
-a # 逻辑与
-o # 逻辑或
-i # 对以上整数测试,字符测试,文件测试在表达式中都可以进行取反操作
5、判断是否为数值或字符
- 在shell编程中还会经常需要判断一个输入是否数值或字符,但是遗憾的是shell不支持正则表达式也没有这种判断的方式,但是高兴的是可以在shell中可以使用任意shell命令,所以我们可以借助sed、awk、grep等程序来进行判断
# 变量赋值
NUM=1
# 把变量的值通过管道传给sed做处理,sed就是把数值都替换为空,如果变量的值是数值,那么变量test的值就会为空
test=$(echo $NUM | sed 's/[0-9]//g')
#再使用字符测试条件(-z)测试变量test是否为空即可
echo $test
[ -z $test ] && echo "digit"
IF选择语句
# if单分支语句语法:条件为真时执行;条件为假时不做任何事情
if condition; then
statement1
fi
# if双分支语句语法:条件为真时执行statement1;为假时执行statement2
if condition; then
statement1
else
statement2
fi
# if多分支语句语法:条件为condition1时执行statement1;为condition2时执行statement2;否则执行statement3
if condition1; then
statement1
elif condition2; then
statement2
else
statement3
fi
CASE选择语句
case VAR_NAME in
)
statement1;;
)
statement2;;
)
statement3
esac
shell编程☞循环语句
shell循环语句
1、for
- 属于固定循环。执行for循环程序时要有进入条件和退出条件
for VARIABLES_NAME in LIST; do
statement1
done
2、while
- 属于不定循环,也称作条件循环。之哟啊条件判断式成立就会一直循环,直到条件不成立才会终止循环;适用于循环次数未知的场景
while CONDITION; do
statement1
done
3、until
- 属于不定循环,和while循环正好相反。until循环是只要条件判断式不成立则进行循环,一旦条件成立就会终止循环
until CONDITION; do
statement1
done
4、循环控制语句
continue # 作用跳过本次循环中的剩余代码,直接调到下一次循环中
continue N # 跳出N层循环
break # 结束当前循环
break N # 跳出N层循环
循环相关知识
1、数字列表生成方式
# 第一种:使用{}
echo {1..5}
# 第二种:使用seq [起始数] [步进长度] 结束数
seq 100
# 或
seq 1 100
# 或
seq 1 2 100
2、shift N
- 去掉第N个参数,使 第N个参数后面的参数 变成 N
- 将输入的参数列表左移 N 位
$ cat test.sh
#! /bin/bash
#
echo $1
shift 2
echo $1
shift 2
echo $1
$ ./test.sh 1 2 3 4 5
1 3 5
3、声明变量
# 声明一个数据类型是整型变量
declare -i SUM=0
# 声明一个变量为环境变量
declare -x SUM=0
# 声明一个数据类型是数字而不是字符(bash默认是字符)
let SUM=0
4、生成随机数
echo $RANDOM # 随机生成数
echo $[$RANDOM%5] # 随机生成5以内的数
For循环的用法
1、第一种
- 这种方式的for循环可以利用位置变量用来做一些日志检索类的小程序
#!/bin/bash
# 变量i等于1,如果变量i小于等于10就执行i++
for((i=1;i<=10;i++)); do
echo $(expr $i \* 4);
done
# 其中$1可以接受查询起始日期,而$2可以是结束日期,然后再这个范围内一直区使用grep检索关键字error的信息
for((i=$1;i<=$2;i++)); do
zgrep -h -w "error" /var/dbbackup/log/world*/info/$i/*
done
2、第二种
- 利用seq生成循环数,然后变量i区调用并显示
#!/bin/bash
#
for i in $(seq 10); do
echo $1
done
3、第三种
- 利用 `` 去执行Linux系统的命令,然后变量i去调用并显示
#!/bin/bash
#
for i in `ls`; do
echo $i
done
shell编程☞特殊符号
- shell的特殊字符非常的繁杂,各种特殊的符号在我们编写的shell脚本的时候如果能够用的好,往往能起到事半功倍的效果
- shell常见的特殊字符可以分为以下几类:
特殊变量
符号 | 作用 | 示例 |
---|---|---|
$0 | 当前脚本的名称 | echo $0 |
$# | 传递给脚本或函数的参数个数 | echo $# |
$* | 传递给脚本或函数的所有参数 | echo $* |
$@ | 传递给脚本或函数的所有参数 | echo $@ |
$? | 上个命令的退出状态或函数的返回值 | echo $? |
$$ | 当前shell进程的ID,对于shell脚本,就是这些脚本所在的进程ID | echo $$ |
$n | 传递给脚本或函数的参数,n是一个数字,表示第几个参数 | echo $2 |
* | 作为匹配文件名扩展的一个通配符,能自动匹配给定目录下的每一个文件 | rm /usr/us_* |
~ | 这个和shell环境变量$HOME是一样的,默认表示当前用户的home目录 | echo ~ |
– | 两个减号,和~相同表示当前用户的home目录,但是不能用作打印输出 | cd – |
~+ | 当前的工作目录,和shell环境变量中的pwd一样 | echo ~+ |
~- | 前一个工作目录,和内部变量$OLDPWD一致,和减号-一样 | echo ~- |
- | 和~-一样表示前一个工作目录,不能用作打印输出 | cd - |
- $@ 和 $* 的区别
- $@和$*都表示脚本或函数传入的参数,不被双引号包含时,都以$1 $2 … $n的形式输出所有参数
- 但是当他们被双引号包含时,"$*“会将所有的参数作为一个整体,以”$1 $2 … $n"的形式输出所有参数,但是"$@"和$@一样,还是会将各个参数分开,以$1 $2 … $n的形式输出所有参数
替换符
1、变量替换符
符号 | 作用 | 示例 |
---|---|---|
$ | 美元符,放在变量前用于获取变量的值 | $PATH |
${} | 大括号中放变量的名称,也时用于读取变量的值 | ${PATH}path |
${var:-word} | 如果变量var为空或已被删除,那返回word,但不改变var的值 | |
${var:+word} | 如果变量var被定义,那返回word,但不改变var的值 | |
${var:=word} | 如果变量var为空或已被删除,那么返回word,并word赋值给var | |
${var:?message} | 如果变量var为空或已被删除,那么将消息message送到标准错误输出,可以用来检测变量var是否被正常赋值。若此替换出现在shell脚本中,那么脚本将停止运行 | |
${#var} | 获取字符串变量var的长度 | s="abcd"echo ${#s} 输出4 |
${var:n} | 提取。shell在var中提取第n个字符到末尾的所有字符。若n为正数,从左边开始;若为负数,从右边开始。但必须使用在冒号后面加空格后一个表达式或整个n加上括号 | ${var: -2}、${var:1-4}、 ${var:(-2)} |
${var:n1:n2} | 字符串提取 | s=“internet”;echo ${s:1:4} 输出nter |
{v1,v2,…,vn} | 逗号分隔一般用于文件列表的扩展 | echo {a,b}.txt 输出a.txt b.txt |
{v1…v2} | 用于顺序扩展 | touch {a…c}.txt 输出a.txt b.txt c.txt |
${var#pattern} | 截断。shell在var中查找给定的模式pattern。如果找到,就从命令行把var中的内容去掉左边最短的匹配模式。不改变原变量 | var=testcase; echo ${var#*s} 输出tcase |
${var##pattern} | 截断。shell在var中查找给定的模式pattern,如果存在,就从命令行把var中的内容去掉左边最长的匹配模式。不改变原变量 | var=testcase; echo ${var##*s} 输出e |
${var%pattern} | 截断。shell在var中查找,看它是否以给的pattern结尾,如果是,就从命令行把var中的内容去掉右边最短的匹配模式。不改变原变量 | var=testcase; echo ${var%*e} 输出testca |
${var%%pattern} | 截断。shell在var中查找,看它是否以给的pattern结尾,如果是,就从命令行把var中的内容去掉右边最长的匹配模式。不改变原变量 | var=testcase; echo${var%%s*e} 输出te |
${var/pattern/pattern} | 替换。表示将var字符串的第一个匹配的pattern替换为另一个pattern。不改变原变量 | var=/home/centos; echo ${var/o/t} 输出/htme/centos |
${var//pattern/pattern} | 替换。表示将var字符串中的所有能匹配到的pattern替换为另一个pattern。不改变原变量 | var=/home/centos; echo {var//o/t} 输出/htme/centts |
$[] | 整数扩展。在方括号内执行整数表达式并返回结果 | a=3;b=7; echo $[$a+$b] 输出10 |
- 注意:上面的模式不符合正则表达式的规则
2、命令替换符
符号 | 作用 | 示例 |
---|---|---|
$() | 用于执行命令,替换命令的输出结果 | echo $(date) |
`` | 反引号。作用与$()相同 | echo `date` |
转义字符
序号 | 作用 | 示例 |
---|---|---|
\ | 反斜杠,用于转义 | |
\a | 警报,响铃 | |
\b | 退格(删除键) | |
\f | 换页(FF),将当前位置移到下页开头 | |
\n | 换行 | |
\r | 回车 | |
\t | 水平制表符(Tab键) | |
\v | 垂直制表符 | |
\c | 不产生进一步的输出,也就是说在\c后,这一行后面的内容都不会输出,直接删掉了 |
- 可以使用echo命令的-e选项启动转义,-E选项禁止转义,默认是不转义的。禁止转义的话就无法识别伤命除了反斜杠外的其他转义字符
- 比如:echo "\n"输出\n,而echo -e "\n"才是换行
字符串符
序号 | 作用 | 示例 |
---|---|---|
‘’ | 单引号。单引号括住的内容,被视为常量字符串,引号内禁止变量扩展,并且单引号中不能出现单引号(转移后的也不行) | echo ‘$PATH’ 输出$PATH |
“” | 双引号。双引号包围的内容可以允许变量扩展,可以包含双引号,但需要转义 | echo “$PATH” 输出环境变量PATH的内容 |
功能符
1、语法功能符
序号 | 作用 | 示例 |
---|---|---|
# | 井号。注释符号,在shell文件的行首,作为include标记;除了被转义后的井号,其他地方作为注释使用 | |
; | 分号。语句的分隔符。在shell文件一行写多条语句时,使用分号分隔 | |
;; | 双分号。在使用case的时候,作为每个选项的终结符 | |
/ | 斜杠。路径的分隔符,路径中仅有一个斜杠表示根目录;以斜杠开头的路径表示从更目录开始的路径 | |
| | 管道符。作用是将管道前的命令产生的输出(stdout)作为管道后的命令的输入(stdin) | less fiel | wc -l,用于统计文件的行数 |
> | 输出重定向。echo lvlv>file:将标准输出重定向文件file中去,如果文件存在则覆盖,不存在则创建;>file:不指定输出的内容,则清空文件 | |
>> | 输出重定向追加符。echo lvlv 1>>file:将标准输出重定向文件file的最后面,不会覆盖file原有的内容 | |
>& | 输出重定向等同符。作用于文件描述符,即左右两边的操作数是文件描述符 | echo lvlv>file 2>&1:标准输出重定向到文件file中,标准错误输出与标准输出一致 |
&> | 标准输出和标准错误输出重定向符 | echo lvlv &>file:标准输出和标准错误输出都重定向到文件file中 |
< | 输入重定向。test.sh < file:脚本test.sh需要read的地方会从file中读取 | |
cmd<<text | 从命令行读取输入,知道一个与text相同的行结束,除非使用引号把输入括起来,此模式将对输入内容进行shell变量替换,如果使用<<-,则会忽略接下来输入行首的tab,结束行页可以是一堆tab再加上一个与text相同的内容 | |
<& | 标准输入重定向等同符。作用于文件描述符,即左右两边的操作数是文件描述符 | cmd <& m:将文件描述符m作为cmd的输入。省略了标准输入描述符1:cmd 1<&fd |
>&- | 关闭某个输出文件描述符。用法格式:exec fd>&- | exec >&- 或 exec 0>&-:关闭标准输入 |
<&- | 关闭某个输入文件描述符。用法格式:exec fg<&- | exec <&- 或 exec 0<&-:关闭标准输出 |
& | 与。如果命令后面跟上一个&符,这个命令将会在后台运行 | 用法:cmd & |
/ | 斜杠。1、作为路径的分隔符,路径中仅有一个斜杆表示根目录,以斜杠开头的路径表示从根目录开始的路径;2、在作为运算符的时候,表示除法符号 | |
() | 小括号。1、命令组。括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格;2、用于初始化数组 | 初始化数组: array=(a b c d) |
{} | 大括号。代码块标识符。一般用于函数定义时表明函数体 | |
<<< | 作用是将后面的内容作为前面命令的标准输入 | grep a <<< “$var” 意思就是在var变量值里查找字符a |
<> | 标准输入和输出重定向运算符 | exec 6<>filename 通过exec命令,以读写的方式将文件描述符6绑定到指定文件 |
2、命令功能符
符号 | 作用 | 示例 |
---|---|---|
. | 点号。1、相当于bash内建命令source;2、作为文件名的一部分,在文件名的开头,表示该文件为隐藏文件,ls一般不显示出来(ls -a可显示);3、作为目录名,一个点代表当前目录,两个点代表上层目录。注意,两个以上的点不出现,除非不用引号包围作为点符号;4、正则表达式,点号表示任意字符 | |
: | 冒号:是shell的空命令,什么也不做,但是返回true。1、可做while的死循环条件;2、占位符,if某一分支什么都不做的时候;3、域分隔符,比如环境变量$PATH中或$passwd中,都有冒号作为域分隔符的存在;4、清空文件,因为冒号不向标准输出任何内容,所以可以用来清空文件,如::>file;5、配和${var:=word}给未定义或为空的变量赋值,如:?{abc:=1234};echo $abc; 输出1234 |
运算符
1、算数运算符
符号 | 作用 | 示例 |
---|---|---|
+ | 加法 | a=10;b=20;expr $a + $b:结果为30。注意空格 |
- | 减法 | expr $a - $b:结果为-10 |
* | 乘法 | expr $a * $b:结果为200 |
/ | 除法 | expr $b / $a:结果为2 |
% | 取余 | expr $b % $a:结果为0 |
= | 赋值 | a=$b:将变量b的值赋给a |
(()) | 用于expr命令的替代,即支持算法表达式,而无需expr命令 | for((i=0;i<10;i++))或((out=$a*$b))或if(($a==$b));then … fi:无需添加空格了 |
** | 算数运算中表示求幂运算 | |
, | 逗号运算符:1、用在两阶一连串的数字表达式中,这船数字表达式均被求职,但只有最后一个求值结果被返回;2、用于参数替代中,表示首字母小写,如果是两个逗号,则表示全部小写 |
- 逗号运算符示例
# 用在连接一连串的数字表达式
#!/bin/bash
#
let t1=((a=5+1, b=7+2))
echo t1=$t1, a=$a, b=$b
## 其中t1输出为$b的值
#
# 用于参数替代
a="ATest"
echo ${a,} # aTest
echo ${a,,} # atest
2、关系运算符
符号 | 作用 | 示例 |
---|---|---|
[] | 用于判断条件是否成立 | [ $a == $b ] |
[[]] | 是[]的扩展,可使用<,>,&&,||等运算符 | [[ $a>$b ]] |
-eq | 判断两个数是否相等 | [ $a -eq $b ] |
== | 判断两个数是否相等,作用相同于-eq | [ $a == $b] |
-ne | 判断两个数是否不相等 | [ $a -ne $b ] |
!= | 判断两个数是否不相等,作用相同于-ne | [ $a != $b ] |
-gt | 判断左边的数是否大于右边的数(>) | [ $a -gt $b ] |
-lt | 判断左边的数是否小于右边的数(<) | [ $a -lt $b ] |
-ge | 判断左边的数是否大于等于右边的数(>=) | [ $a -ge $b ] |
-le | 判断左边的数是否小于等于右边的数(<=) | [ $a -le $b ] |
- 注意在[]和[[]]中的表达式两边需要添加空格;==和!=两边需要空格
- 运算符[]和[[]]的区别
- []实际上是bash中test命令的简写。即所有的[ expression ]等于test expression
- 而[[ expr ]]是bash中真正的条件判断语句,其语法更符合编程习惯
3、逻辑运算符
符号 | 作用 | 示例 |
---|---|---|
! | 非运算。表达式为true则返回false,否则返回true | [ ! false ]返回true |
-o | 或运算。有一个表达式为true,则返回true | |
-a | 与运算。两个表达式都为true,才会返回true | |
|| | 或运算。1、用于条件判断,需与[[]]配合使用,两个表达式有一个为true则返回true;2、命令连接。cmd1 || cmd2。左边的命令返回false(即返回非0,执行失败),||右边的命令才能够被执行 | |
&& | 与运算。1、用于条件判断,需与[[]]配合使用。两个表达式都为true才会返回true;2、命令连接。cmd1 && cmd2。左边的命令返回true(即返回0,执行成功),&&右边的命令才能够被执行 |
4、字符串运算符
符号 | 作用 | 示例 |
---|---|---|
= | 检测两个字符串是否相等 | |
!= | 检测两个字符串是否不相等 | |
-z | 检测字符串长度是否为0 | |
-n | 检测字符串长度是否不为0 | |
str | 检测字符串是否为空 | |
=~ | 正则表达式匹配运算符,用于匹配正则表达式,配合[[]]使用 | if[[ ! $file =~ check$ ]] 用于判断$file是否以check结尾 |
5、文件测试运算符
符号 | 作用 | 示例 |
---|---|---|
-b | 检测文件是否是块设备文件 | |
-c | 检测文件是否是字符设备文件 | |
-d | 检测文件是否是目录 | |
-f | 将测文件是否是普通文件(既不是目录,也不是设备文件) | |
-g | 检测文件是否设置了SGID位 | |
-k | 检测文件是否设置了粘着位(Sticky Bit) | |
-p | 检测文件是否是具名管道 | |
-u | 检测文件是否设置了SUID位 | |
-r | 检测文件是否可读 | |
-w | 检测文件是否可写 | |
-x | 检测文件是否可执行 | |
-s | 检测文件是否为空(文件大小是否大于0) | |
-e | 检测文件(包括目录)是否存在 |
- Linux文件的类型==sd
- -:普通文件
- d:目录
- b:块设备
- c:字符设备
- p:命名管道
- l:符号链接
- s:套接字
shell编程(书籍)
构建基本脚本
1、命令替换
- shell脚本左右用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本数据时尤为方便
$ time=`date` # 反引号``
$ time=$(date) # $()
2、输出重定向
- 最基本的重定向将命令的输出发送到一个文件中。bash shell用大于号(>)来完成这个功能
- commod > outputFile
- 重定向操作符创建了一个文件,并将命令输出重定向到该文件中。如果文件已经存在了,重定向操作符会用新的文件数据覆盖已有文件
- 如果你不想输出重定向中的文件被覆盖,可以用双大于号(>>)来追加数据
3、输入重定向
- 输入重定向和输出重定向正好相反。输入重定向将文件内容重定向到命令,非将命令的输出重定向到文件
- 输入重定向符号是小于号(<)
- commod < inputFile
- 这里有个和wc命令一起使用输入重定向的例子:
- $ wc < test6
- wc命令可以对数据中的文本进行计数。默认情况下,他会输出3个值:
- 文本的行数
- 文件的词数
- 文件的字节数
- 还有另外一种输入重定向的方法,称为内联输入重定向。这种方法无需使用文件进行重定向,只需要在命令行中指定永固输入重定向的数据既可以了。
- 内联输入重定向符号是双小于号(<<)。除了这个符号,你必须指定一个文件标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文件标记必须一致
4、管道
- 将一个命令的输出作为另一个命令的输入
- commod1 | commod2
- 不要以为由管道串起来的两个命令会依次执行。Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送到第二个命令。数据传输不会用到任何中间文件或缓冲区
5、执行数学运算
- expr能够识别少数的数学和字符串操作符
- 在bash中,在将一个数学原酸结果赋给某个变量时,可以用美元符和方括号将数学表达式围起来
- $[ operation ]
- 用方括号执行shell数学运算比用expr命令方便很多
- bash shell数学运算符只支持整数运算。
6、浮点解决方案
- 有几种解决方案能够克服bash中数学运算的整型限制最常见的方案就是内建的bash计算器,叫做bc
- bash计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash计算器能够识别:
- 数字(整数和浮点数)
- 变量(简单变量和数组)
- 注释(以#或C语言中/* */开始的行)
- 表达式
- 编程语句(例如if-then语句)
- 函数
- 在脚本中使用bc,可以用命令替换运行bc命令,并将输出赋给一个变量
- var=$(echo “options; expression” | bc)
- options:表示变量,如果不止一个变量,可以用分号将其分开
- expression:通过bc执行的数学表达式
#!/bin/bash
#
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale=4
a1=($var1 * $var2)
b1=($var4 * $var4)
a1 + b1
EOF
)
echo $var5
- 将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰,提高易读性。
- EOF字符串表示了重定向给bc命令的数据的起止。当然必须用命令替换符号标识出用来给变量赋值的命令
- 注意:在bash计算器中创建的变量只在bash计算器中有效,不能在shell脚本中使用
7、退出脚本
- shell中运行的每个命令都使用退出状态码告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以铺货这个值并在脚本中使用
- 执行成功的命令退出状态码是0,执行错误的命令退出状态码是一个正整数
- Linux常见的退出状态码
状态码 | 描述 |
---|---|
0 | 命令执行成功 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没有找到命令 |
128 | 无效的退出参数 |
128+x | 与Linux信号x相关的严重错误 |
130 | 通过Ctrl+C终止的命令 |
255 | 正常范围之外的退出状态码 |
使用结构化命令
1、选择语句
1、if-then
if command
then
commands
fi
# 或者
if command; then
commands
fi
- bash Shell的if语句后面的那个命令,如果退出状态码为0,位于then部分的命令就会被执行;如果是其他值,then部分的命令不会被执行
- 通过把分号放在待求值的命令尾部,就可以将then语句放在同一行上了,这样看起来更像其他编程语言中的if语句
2、if-then-else
if command;then
commands
else
commands
fi
- 当 if 语句中的命令返回退出状态码 0 时, then 部分中的命令会被执行;当 if 语句中的命令返回非零退出状态码时,bash shell会执行 else 部分中的命令
3、if嵌套
if command1; then
commands
elif command2; then
more commands
fi
- elif 语句行提供了另一个要测试的命令,这类似于原始的 if 语句行。如果 elif 后命令的退出状态码是 0 ,则bash会执行第二个 then 语句部分的命令。使用这种嵌套方法,代码更清晰,逻辑更易懂
4、Test命令
if test condition; then
commands
fi
- condition是test命令要测试的一系列参数和值
- 如果不写test命令的condition部分,他会以非零的退出状态码退出,并执行else语句块
- bash shell提供了另一种条件测试方法,无需在if-then语句中声明test命令
if [ condition ]; then
commands
fi
- test命令可以判断三类条件
- 数值比较
- 字符串比较
- 文件比较
5、复合条件测试
- if-then语句允许你使用布尔逻辑来组合测试
[ codition1 ] && [ condition2 ] # AND
[ codition1 ] || [ condition2 ] # OR
6、if-then的高级特性
- bash-shell提供了两项可在if-then语句中使用的高级特性
- 用于数学表达式的双括号
- 用于高级字符串处理功能的双方括号
- 双括号允许你在比较过程中使用高级数学表达式。test命令只能在比较中使用简单的算数操作。双括号命令提供了更多的数学符号
- (( expression ))
- expression可以是任意的数学赋值或比较表达式
- 双方括号命令提供了针对字符串比较的高级特性
- [[ expression ]]
- expression使用了test命令中采用的编撰字符串比较;更提供了另一个特性-模式匹配
7、case命令
- case命令可以不需要再写出所有的elif语句来不停的检查同一个变量的值,case命令会采用列表格式来检查单个变量的多个值
case variable in
pattern1 | pattern2) commands1;;
pattern3 | pattern4) commands2;;
*) default commands;;
esac
- case命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行该模式执行的命令。可以通过"|“操作符在一行中分隔出多个模式匹配,”*"号会捕获所有与已知模式不匹配的值
2、循环语句
1、for
- bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令
for var in list
do
commands
done
# 或者
for var in list; do
commands
done
- 读取列表中的值
for test in Alabama Alaska Arizona Arkansas
do
echo The next state is $test
done
# 输出
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
-
每次 for 命令遍历值列表,它都会将列表中的下个值赋给$test变量。$test变量可以像 for命令语句中的其他脚本变量一样使用。在最后一次迭代后,$test 变量的值会在shell脚本的剩余部分一直保持有效。它会一直保持最后一次迭代的值(除非你修改了它)
-
$test 变量保持了其值,也允许我们修改它的值,并在 for 命令循环之外跟其他变量一样使用
-
读取列表中的复杂值
for test in I don`t know if this`ll work; do
echo "word:$test"
done
# 输出
word:I
word:dont know if thisll
word:work
-
shell看到了列表值中的单引号并尝试使用它们来定义一个单独的数据值
-
有两种方法可解决这个问题:
- 使用转义字符(反斜线)来将单引号转义:I don\`t know if this\`ll work
- 使用双引号来定义用到单引号的值:I “don`t” know if “this`ll” work
-
for命令用空格划分列表中的每个值,如果在单独的数据值中有空格,就需要双引号将这些值括起来
-
从变量读取列表
- 通常shell脚本遇到的情况是,你将一些列值都集中存储在了一个变量中,然后需要遍历变量中的整个列表
list="Alabama Alaska"
list=$list" Connecticut"
for state in $list; do
echo "Hava you ecer visited $state?"
done
# 输出
Hava you ecer visited Alabama?
Hava you ecer visited Alaska?
Hava you ecer visited Connecticut?
-
$list 变量包含了用于迭代的标准文本值列表。注意,代码还是用了另一个赋值语句向 $list变量包含的已有列表中添加(或者说是拼接)了一个值。这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法
-
从命令读取值
- 生成列表中所需值的另外一个途径就是使用命令的输出。可以用命令替换来执行任何能产生输出的命令,然后再for命令中使用该命令的输出
file="states"
for state in $(cat $file); do
echo "Visit beautiful $state"
done
# 向文件写入内容
$ cat states
Alabama
Alaska
Arizona
# 输出
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
-
将文件名赋给变量,文件名中没有加入路径。这要求文件和脚本位于同一个目录中。如果不是的话,你需要使用全路径名(不管是绝对路径还是相对路径)来引用文件位置
-
更改字段分割符
-
上面那个例子中可以看到for命令可以按空格、换行分隔数据
-
造成这个问题的原因是特殊的环境变量IFS—内部字段分隔符,IFS环境变量定义了bash shell用作字段分隔符。默认情况下,bash shell会将下列字符当作字段的分隔符
- 空格
- 制表符
- 换行符
-
要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字段。例如:你想修改IFS的值,使其只能识别换行符:IFS=$’\n’
-
将这个语句加入脚本中,告诉bash shell在数据值中忽略空格和制表符
-
警告:在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他文职继续沿用IFS的默认值。可以这样实现:
IFS.OLD=$IFS
IFS=$'\n'
# 在代码中使用新的IFS值
IFS=$IFS.OLD
-
如果你要指定一个或多个IFS字符,只要将它们在赋值行串起来就行
- IFS=$’\n’:;"
- 这个赋值会将换行符、冒号、分号和双引号作为字段分隔符
-
用通配符读取目录
- 可以用for命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定匹配符的文件名或路径名的过程
for file in /home/rich/test/*
do
if [ -d "$file" ]; then
echo "$file is a directory."
elif [ -f "$file" ]; then
echo "$file is a file."
fi
done
- 注意:目录名和文件名中包含空格是合法的。要适应这种情况,应该将$file变量用双引号括起来,如果不这样做,遇到含有空格的目录名或文件名时就会有错误产生
2、C语言风格的for
for(( i=1; i <= 10; i++))
do
echo "The net number is $i"
done
-
注意:这里有些部分并没有遵循bash shell标准的for命令:
- 1、变量赋值可以有空格
- 2、条件中的变量不以美元符开头
- 3、迭代过程的算式未用expr命令格式
3、while
-
while命令某种意义上是if-then语句和for循环的混合体。while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的一开始测试test命令。在test命令返回非零退出状态码时,while命令会停止执行那组命令
-
while test command do other commands done
-
while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而改变,如果退出状态不发生变化,while循环就将一直不停的进行下去
-
最常见的test commands的用法是用方括号来检查命令中用到的shell变量的值
#!/bin/bash
# while command test
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
- while命令允许你在while语句行中定义多个测试命令,只有组后一个测试命令的退出状态码会被用来决定什么时候结束循环
#!/bin/bash
# test a multicommand while loop
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
-
上面这个shell脚本中的while语句中定义了两个测试命令。第一个测试简单地显示了var1变量的当前值;第二个测试用方括号来判断var1变量的值,在循环内部,echo语句会显示一条简单的消息,说明循环被执行了。
-
知道shell执行test测试命令,while循环才会停止,这说明在含有多个命令的while语句中,在每次迭代中所有的测试命令都会被执行,包括测试命令失败的最后一次迭代。
4、until
- until命令和while命令工作的方式完全相反。until命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令
until test commands
do
other commands
done
- 和while命令类似,你可以在until命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了bash shell是否执行已定义的other commands
5、嵌套循环
-
循环语句可以在循环内使用任意类型的命令,包括其他循环命令。这种循环叫做嵌套循环。注意,在使用嵌套循环时,你是在迭代中使用迭代,与命令运行的次数是乘积关系
-
通常必须遍历存储在文件中的数据,这要求结合使用嵌套循环和修改IFS环境变量
- 通过修改IFS环境变量,就能强制for命令将文件中的每行都当成独立的一个条目来处理,即便数据中有空格也是如此。一旦从文件中提取了单独的行,可能需要再次利用循环来提取行中的数据
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
do
echo " $value"
done
done
- 这个脚本使用两个不同的IFS值来解析数据。第一个IFS解析出/etc/passwd文件中单独的行,内部for循环接着将IFS的值修改为冒号,允许你从/etc/passwd的行中解析出单独的值
6、break命令
-
break命令是退出循环一个简单方法。可以用break命令来退出任意类型的循环,包括while循环和until循环
-
以下几种情况可以使用break命令:
- 1、跳出单个循环
#!/bin/bash
#
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration number: $var1"
done
echo "The fo loop is completed"
- 2、跳出内部循环
#!/bin/bash
# breaking out of an inner loop
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -eq 5 ]
then
break
fi
echo " Inner loop: $b"
done
done
-
3、跳出外部循环
-
有时你在内部循环,但需要停止外部循环,break命令接受单个命令行参数值:braek n
-
其中n指定了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前循环
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop: $b"
done
done
7、continue命令
- continue命令可以提前终止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件
#!/bin/bash
# using the continue command
for (( var1 = 1; var1 < 15; var1++ ))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done
- 当 if-then 语句的条件被满足时(值大于5且小于10),shell会执行 continue 命令,跳过此次循环中剩余的命令,但整个循环还会继续。当 if-then 的条件不再被满足时,一切又回到正轨
8、处理循环的输出
- 在shell脚本中,你可以对循环的输出使用管道或进行重定向。这可以通过done命令之后添加一个处理命令来实现:done > output.txt
9、示例
- 1、查找可执行的文件
#!/bin/bash
#
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
- 2、创建多个用户账户
#!/bin/bash
#
input="users.csv"
while IFS=',' read -r userid name
do
echo "adding $userid"
useradd -c "$name" -m $userid
done < "$input"