shell脚本
2021-09-09
-
用户可以透过应用程序来指挥kernel,让kernel来完成我们所需要的硬件任务,因为程序是在最外层,类似鸡蛋的外壳一样,所以就叫壳程序(shell)。shell是提供用户操作系统的一个接口。
-
bash全称就是Bourne Again SHell,是linux预设的shell。
-
系统中合法的shell都会写入到/etc/shells这个文件。当用户登录的时候,系统会提供一个shell,这个shell就记录在/etc/passwd这个文件中。
-
bash有很多内建指令,如cd等,可以通过type这个指令观察。
-
一行输不完,可以通过\来换行。
-
shell脚本中第一行的
#!/bin/bash
宣告这个script使用的是bash的语法。 -
当使用bash(sh)或者./xxx.sh来下达指令时,是在子程序中执行的;通过source来执行脚本时,是在父程序中执行的。子程序:在当前shell的情况下,去启动另一个新的shell,新的shell就是子程序。子程序会继承父进程的环境变量,但不会继承父进程的自定义变量。
-
login shell是取得bash时需要输入完整的登录账号密码的就是login shell。
-
non-login shell就是取得bash接口不需要重复的登入。如从图形化界面进入linux后,开启每个bash都不需要再次输入用户名和密码。这就是non-login shell。
-
login shell会读取
/etc/profile
(系统整体的设定,每个使用者登入取得bash时一定会读取的配置文件。)和~/.bash_profile或~/.bash_login或~/.profile
(个人的设定)的文件。 -
non-login shell会读取~/.bashrc、/etc/bashrc、/etc/profile.d(不同的linux会有些不同)。
-
source可以立即读入配置文件的内容。
变量定义 #
- 环境变量如PATH等,通常使用大写字符来表示,当输入了一个命令ls,系统会通过PATH这个遍历里面的内容记录的路径来顺序搜索指令。
- 变量在使用时,需要加上在前面加上
$
。如$PATH,${PATH}
。 - 通过
env
和export
可以查询系统中所有的环境变量。
变量 #
- 设定变量直接使用等号=,如
myname=xiaoxiang
,等号两边不能有空格; - 变量内容若有空格可以使用单引号或者双引号括起来,但是双引号内的特殊符号如
$
会保持原本的特性,而单引号内的特殊字符则是纯文字。 - 当需要为变量扩增内容内容时,可以使用
PATH=$PATH:/home/bin
- 通过
export JAVA_HOME
使得变量JAVA_HOME变成了环境变量。 - 通常大写字符为系统默认变量,自行设定的变量可以使用小写字符。
- 取消变量可以使用
unset myname
。
read,array,declare #
- read可以读取键盘输入的变量。如
read -p "please input:" -t 10 name
。其中-p是提示信息,-t是倒计时,系统不会一直等待输入,name就是变量,会将输入的信息给这个变量。 - declare、typeset是宣告变量的类型(shell默认类型是字符串)。
declare
命令如果用在函数中,声明的变量只在函数内部有效,等同于local
命令。declare -a var
将var声明为数组类型array。declare -i var
将var声明为整数类型integer(bash中的数值计算最多只能达到整数形态)。declare -x var
将var声明为环境变量。declare -r var
将变量声明为只读类型readonly,改变量不可更改,不能unset。declare -p var
查看变量的类型。
数组 #
- 数组可以采用逐个赋值的方法创建。也可以一次性赋值。
ARRAY[INDEX]=value
ARRAY=(value1 value2 ... valueN)
- 读取元素
#输出单个元素,${}是必须的
echo ${array[0]}
#输出所有元素
echo ${foo[@]}
#循环遍历所有元素,数据要放在双引号中
for i in "${names[@]}"; do
echo $i
done
#数组赋值
hobbies=( "${activities[@]}" )
#获取数组长度
echo ${#a[@]}
通配符与特殊符号 #
符号 | 意义 |
---|---|
* | 代表0到无穷多个任意字符 |
? | 代表一定有一个的任意字符 |
[] | 代表一定有一个在括号内的字符 |
[-] | 表示一定有一个在编码顺序内的所有字符,如[0-9]表示一定有一个数字 |
[^] | 表示反向选择,如[^abc]就表示一定有一个字符,但不是abc中的一个。 |
变量内容的删除与取代 #
符号 | 含义 |
---|---|
# | 从最左边开始,删除匹配最短的那个 |
## | 从最左边开始,删除匹配最长的那个 |
% | 从最右边开始,删除匹配最短的那个 |
%% | 从最右边开始,删除匹配最长的那个 |
/旧字符串/新字符串 | 若变量内容符合旧字符串,则第一个旧字符串会被新字符串取代 |
//旧字符串/新字符串 | 若变量内容符合旧字符串,则全部旧字符串会被新字符串取代 |
#例:
path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
#去掉了开始的/usr/local/sbin:
echo ${path#/*:}
#输出为空
echo ${path##/*:}
#去掉了末尾的/bin:
echo ${path%/*:}
#输出为空
echo ${path%%/*:}
#将第一个出现的usr替换为USR
echo ${path/usr/USR}
#将所有的sbin替换为SBIN
echo ${path//sbin/SBIN}
tr:删除字符串中的一段文字,或者进行文字的替换。 #
cat example.txt | tr [a-z] [A-Z]
将输出的文件中的所有小写字符变成大写字符。cat /etc/passwd | tr -d ':'
删除输出文件中的所有冒号:
变量的测试与内容替换 #
#减号-:当str不存在的时候,str=root,而当str存在(为空也视为存在)则str=$str
str=${username-root}
#:- 当str为空或不存在,str=root;str不为空则str=$str
str=${str:-root}
cut:将一段数据切出来 #
cat filename |cut -d '分隔字符' -f num
:-d后面跟分隔字符,将数据分为几段,-f表示取出第几段。cat filename |cut -c 12-
:-c表示每一行都获取从第12个字符后面的所有字符(注意12后面有个减号)。
命令执行的判断依据 ; && || #
- 命令之间用
;
隔开,分号前的指令执行完后会立刻接着执行后面的指令。 - 命令之间用
&&
连接,表示当上一个指令正确执行完成后,才会接着执行下一个命令,这里使用到了上面提到的$?
变量。如ls /tmp/abc && touch /tmp/abc/hehe
,当不存在abc这个目录时,touch命令不会执行。 - 命令之间用
||
连接,当上一个指令错误执行完成后,才会接着执行下一个命令。如ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
,当abc路径不存在时,会创建abc路径,并执行touch命令;而如果abc路径存在,则会直接执行touch命名。
格式化打印输出 #
- %ns表示n个字符;%ni表示n个整数数字数;%N.nf表示一共N个数字,小数点占n个。
\n表示换行,\t表示tab键
。
正则 #
- vi、grep、awk、seq等工具支持正则表达式;cp、ls不支持正则表达式,只能使用bash本身的通配符。
规则 | 意义 |
---|---|
^word | 寻找以word开始的行 |
word$ | 寻找以行尾为word的行 |
. | 代表任意一个字符 |
\ | 转义字符 |
* | 重复前一个字符0到无穷次 |
[abc] | 搜寻含有a或b或c的那一行,[]只代表一个待搜寻的字符 |
[a-z] | 搜寻两个字符间的所有连续字符,这个连续与ASCII编码有关。 |
[^abc] | 反向选择,不要有a或者b或者c的行 |
\{n,m\} | \{n,m\} 连续n到m个的前一个字符 \{n\} 连续n个前一个字符 \{n,\} 连续n个以上的前一个字符 |
[:alnum:] | 代表a-z,A-Z,0-9 |
[:alpha:] | 代表A-Z,a-z |
[:blank:] | 代表空格和[Tab] |
[:digit:] | 代表数字 |
[:lower:]、[:upper:] | 代表小写字符、代表大写字符 |
[:space:] | 任何会产生空白的字符,包括空格,[Tab],CR等 |
[:xdigit:] | 代表16进位的数字类型。 |
test指令 #
文件的判断 #
命令 | 解释 |
---|---|
test -e filename | 判断文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
-r | 是否有可读的权限 |
-w | 是否有可写的权限 |
-x | 是否有可执行的权限 |
-nt(newer than) | 判断file1是否比file2新,例:test file1 -nt file2 |
-ot(older than) | 判断file1是否比file2旧 |
-ef | 判断两个文件是否是同一个文件,其实是判断两个文件是否指向同一个inode。 |
两个整数之间的判断 如:test n1 -eq n2 #
命令 | 介绍 |
---|---|
-eq | 两数值相等 |
-ne | 两数值不等 |
-gt(greater than)/ -ge(greater than or equal) | n1大于 / 大于等于n2 |
-lt / -le | n1小于 / 小于等于n2 |
判断字符串的数据 #
命令 | 介绍 |
---|---|
test -z string | 若字符串为空串,则为true |
test -n string | 若字符串为非空串,则为true |
test str1 == str2(bash中一个等号和两个等号是一样的) | 若str1等于str2,则返回true |
test str1 != str2 | 若str1不等于str2,则返回true |
条件判断,如test -r filename -a -x filename #
命令 | 介绍 |
---|---|
-a(and) | 与,两条件同时成立返回true |
-o(or) | 或,任何一个条件成立就返回true |
! | 非,如test ! -x file,当file不具备执行条件时返回true |
shell中的默认变量 #
- shell定义了一些默认的变量。
tar -c -v -f example.tar example/
$0 $1 $2 $3 $4 $5
$#
表示命令后面的参数个数,上面就是5。$@
表示"$1" “$2” “$3” “$4” “$5”。$*
表示"$1 $2 $3 $4 $5"$$
当前shell的PID。$?
上一个执行指令的回传值,一般一个指令执行完后,会回传一个0值。$
本身也是一个变量,代表着当前shell的PID,通过$$
使用该变量。?
是一个变量,代表上一个指令传回来的值,一般来说,当我们成功执行了一个指令,则会返回0值,如果执行错误,就会传回一个错误代码,通过$?
来调用。
算数运算 #
((...))
语法可以进行整数的算术运算,((...))
会自动忽略内部的空格。
((foo = 5 + 5))
echo $foo
- 如果要结果,需要在
((...))
前面加上美元符号$((...))
total_mem=$(( 1 + 2 ))
#可以使用括号来改变运算顺序
echo $(( (2 + 3) * 4 ))
((...))
支持+ - * / %(取余) **(指数) ++ --
,++
和--
这两个运算符有前缀和后缀的区别。作为前缀是先运算后返回值,作为后缀是先返回值后运算。
expr #
expr
命令支持算术运算,可以不使用((...))
语法,只支持整数。
foo=3
expr $foo + 2
条件判断式 #
if #
- 中括号[ ]和test类似,也可以来进行数据的判断,中括号的两端需要有空格来分割。
if [ 条件判断式1 ];then
指令
指令
exit 0
elif [ 条件判断式2 ];then
指令
指令
else
指令
指令
fi
#可以if后面可以跟上多个条件
if [ 条件判断式1 ] || [ 条件判断式2 ];then
if [ 条件判断式1 ] && [ 条件判断式2 ];then
case #
case $变量名称 in
"第一个变量名称")
指令
指令
;;
"第二个变量名称")
指令
指令
;;
#下面这个有点类似于C语言的default
*)
指令
指令
;;
esac
#例:
case ${1} in
"--host"|"--ip")
init_dir
build_image
modify_ip_address ${2}
echo -e "文件已安装到\e[36m${base_dir}\e[0m下, 稍后请进入该目录下执行后续操作"
;;
"build_image")
build_image
;;
*)
display_help
;;
esac
函数function #
- shell是从上往下执行,所以函数要放在程序的最前面。
#function关键字是可选的
#可以通过test abc来给函数传递参数
function test(){
指令
指令
}
return
命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。return后面可以不跟参数。
循环 #
不定循环 #
#当condition条件不成立时终止循环
while [ condition ]
do
指令
指令
done
#当condition条件成立时终止循环
until [ conditon ]
do
指令
指令
done
固定循环 #
for var in con1 con2 con3
do
指令
指令
done
#或
for (( 初始值; 限制值;执行步阶))
do
指令
指令
done
#一个案例
for (( i=1; i<=10; i=i+1)); do
printf "%i\n" $i
done
#一个ping很多机器的shell脚本
#!bin/bash
network=82.156.3
#seq是sequence,会连续生成1到254之间的数
for site in $(seq 1 254)
do
#-c表示ping几次,-w表示超时时间,单位是秒
ping -c 1 -w 1 ${network}.${site} > /dev/null 2>&1
if [ $? -eq 0 ];then
echo "${network}.${site} is ok"
else
echo "${network}.${site} id down"
fi
done
#计算多个容器使用内存之和
#!/bin/bash
total_mem=0
#获取要计算内存的容器名称
for item in $(docker ps | grep kuam | awk '{print $NF}'); do
#获取容器的pid
for pid in $(docker top ${item} | grep -v PID | awk '{print $2}'); do
#获取占用的内存
mem=$(cat /proc/${pid}/status | grep -i vmrss | awk '{print $2}')
echo "${item} PID:${pid} MEM:${mem}"
#内存求和
total_mem=$(( ${total_mem} + ${mem} ))
done
done
echo "total mem: ${total_mem}"
shell调试 #
shell [-nvx] xxx.sh
:-n查询语法是否正确;-v先输出文件内容再执行shell脚本;-x执行前先将使用到的script输出到屏幕上。
例 #
#!/bin/bash
#上传的文件路径
file_path="/tmp/blog.tar"
#文件放的位置
blog_dir="/opt/web/front/xiaoxiang.space"
#检查上传的文件是否存在
if [ ! -e "${file_path}" ];then
echo "文件不存在"
exit 0
fi
#检查当前是否有scp进程,没有就说明文件已经上传完成
scp_status=$(ps aux | grep scp | grep -v grep)
while [ -n "${scp_status}" ]
do
sleep 1s
scp_status=$(ps aux | grep scp | grep -v grep)
done
echo "传输完成..."
#将上传的文件解压到对应位置
mkdir -p ${blog_dir}
rm -rf "${blog_dir:?}/*"
if tar -xvf ${file_path} -C ${blog_dir};then
echo "部署成功,文件位置为${blog_dir}"
fi
#删掉上传的文件
rm -f ${file_path}