0%

Shell

入门与规范

shell脚本编写规范

  • 脚本文件后缀名:本质上就是一个文本文件,建议保存为.sh
  • 首行格式规范: #! /bin/bash 含义:声明shell解析器

注释格式

1
2
3
4
# 单行注释
:<<!
多行注释
!

脚本文件的执行方式

1
2
3
4
5
6
7
8
# 新建子进程执行
sh helloworld.sh

# 在原进程执行
source helloworld.sh

# 注意:这种方式脚本文件需要有执行权限
./helloworld.sh

变量

环境变量

linux系统加载Shell的配置文件中定义的变量共享给所有的Shell程序

环境变量所在的配置文件

  • 全局配置文件(系统级环境变量)

    • /etc/profile
    • /etc/profile.d/*.sh
    • /etc/bashrc
  • 个人配置文件(用户及环境变量)

    • 当前用户/.basg_profile
    • 当前用户/.bashrc

环境变量相关指令

  • 查看当前Shell 环境变量:env
  • 查看所有变量(系统环境变量+自定义变量+函数):set

自定义系统环境变量

  • 编辑/etc/profile全局配置文件
  • 添加环境变量:export var1=var
  • 重载配置文件:source /ect/profile

环境变量加载流程原理

Shell工作环境

  • 交互式与非交互式
    • 交互式:需要用户输入
    • 非交互式:不需要参与
  • 登录与非登录
    • 登录:正常用户需要登录才能使用linux系统
    • 非登录:一些特殊的用户,不需要登录就可以进入linux系统的Shell环境
  • 识别登录与非登录环境:$0
    • -bash Shell登录环境
    • bash Shell非登录环境
  • 工作环境切换
    • 用户切换时
      • 登录:su -l root
      • 非登录:su root
    • 登录->非登录:bash
    • 以工作化环境执行脚本:sh -l / bash -l

环境加载的过程

用户登录后,开始加载以下流程

image-20201109222252780

自定义变量

自定义脚本内部全局变量

  • var_name=value
  • 定义规则:
    • 等号两侧不能有空格
    • 变量的默认类型是字符串,无法直接进行数值运算
    • 变量的值如果有空格,必须用双引号括起来

自定义常量:realonly

  • realonly var_name

自定义局部常量:local

  • local var_name
  • 一般用于函数内部

自定义环境变量

  • 定于语法:export var_name=value
  • 全局变量的定义:在父环境中定义,在子环境中也可以使用
  • 父子Shell环境:在A.sh中执行B.sh,则A.sh为父Shell环境,即调用者为父

删除变量 unset var

使用变量 $a ${a:2}

${}{}用以标识变量及其附属属性的一体性,主要作用于数组与子串以及map类型,其他时候$a与${a}无异

字符串变量格式及其区别

  • 单引号:可以转义,可以包含空格,无法解析的变量,但可以用子串与变量拼接构造大字符串,主要用于字符串中存在大量"时(如json),用'规避转义
  • 双引号:可以解析变量,可以转义,可以包含空格
  • 无引号:不能包含空格,不能转义

字符串操作

  • 获取长度:

    1
    ${#var}		#"{#"触发hexo渲染器bug
  • 拼接:"${var1}${var2}"

  • 截取:

image-20201112011810715.png

map类型变量

1
2
3
4
5
#定义关联数组(map),关联数组只能用declare
declare -A aMap=([key]=value [key1]=value2)
echo ${aMap[key]} #获取值
aMap[key]=22
echo ${aMap[key]} #获取值

特殊符号变量

  • $n:接收脚本文件入参

    • $1-$9,代表获取第一到第九的参数
    • 第10个参数以上:${数字}
    • $0 用于获取脚本名称
    • 参数传入语法:sh helloworld.sh param1 param2
  • $#:获取输入参数的个数

  • $*/$@:都是获取所有输入参数并当成一个字符串

    • 不使用双引号,功能是一样的
    • 使用双引号
      • $* 输出的是一个整体
      • $@ 输出"$1" "$2" "$3"
      • 在循环时体现区别,$@可以for var in $@,$* 不能
  • $?:获取上一个Shell或者函数的返回值

    • 每个Shell都有一个返回值,用以说明时候执行成功
    • 一般来说,0表示成功,非0表示失败
  • $$$$:获取当前Shell系统的进程ID号

变量数组

注意:Bash Shell 只支持一维数组

数组的定义

1
2
3
# ()表示数组,空格来分隔
array1=(item1 item2 item3) #方式1
array2=(${array1[2]} ${array1[1]} ${array1[0]}) #方式2

数组的获取

  • 通过下标获取

    1
    ${arr[1]}
  • 使用@*获取所有元素,遍历数组时需要使用此表示法

    1
    2
    ${arr[@]}
    ${arr[*]}
  • 获取数组的个数

    1
    2
    ${#arr[@]}
    ${#arr[@]}

数组拼接与删除

数组拼接

1
2
arr1=(${arr2[@]} ${arr3[@]})
arr1=(${arr2[*]} ${arr3[*]})

数组删除

1
2
unset arr[1] #删除元素
unset arr #删除整个数组

常用内置命令

含义

  • 是Shell内部的命令,可以直接使用,与之对应的是外部脚本文件
  • 两者(内置命令与外部脚本文件)的区别
    • 内置命令:Shell进程的一部分(指令在内存),在进程内部执行
    • 外部脚本文件:存储在磁盘上,运行需要fork新的进程运行
  • 使用type命令可以判断是内部还是外部

设置别名:alias

1
2
alias ls="ls -l" #设置别名,最常见的应用就是 ll
unalias ll #删除别名

输出字符串:echo

1
2
echo -n "输出内容" #输出不换行的字符串
echo -e "hello\nworld" #-e 执行转义字符:\n:换行 \c:清除换行

读取控制台输入:read

简介:用于从标准输入中读取数据并给变量赋值,若标准输入重定向,这可以从文件中读取数据

1
read [-options] [var1 var2 ....] #基础表达式

options:

意味着Shell获取命令行数据时,可以实现以下功能:

  • 读取数据
  • 指定结束字符串
  • 是否转义
  • 读取指个数的字符read -n 1 var
  • 设置提示信息read -p "请输入地址:" addr
  • 拒绝转义
  • 输入不重现 : read -p "请输入密码" pwd -s
  • 设置输出超时时间,配合 #?使用 : read -t 10 var #10内完成输入,否则失败
  • 设文件为输入源

结束当前Shell环境进程:exit

exit 可以返回一个状态码,使用$?可以获取状态码

1
2
exit #返回0,一般任务命令执行成功
exit 1 #数字的范围建议0-255

设置变量类型 :declare

1
2
3
4
5
6
7
8
9
10
11
12
#设置变量的属性
#a:索引数组,A:关联数组(map),
#r:只读,x:设置为环境变量,
#i:整型变量,f:函数
declare [+/-][aArxif][变量名称=初始值]
#查看全部变量与函数
declare
declare -f #所有函数定义
declare -F #所有函数名称列表
#定义关联数组(map),关联数组只能用declare
declare -A aMap=([key]=value [key1]=value2 ...)
${aMap[key]} #获取值

运算表达式

总结:执行命令用$(),数值的计算(()),判断用[[]],复杂数学计算用bc

``与$():

  • 嵌套执行时,``需要转义
  • $()比``可读性更强,推荐使用$()
    1
    starttime=$(date '+%s')

expr命令:求值表达式

1
result=`expr 1 + 2` #这里使用的是反引号``,且只能进行整数运算,用操作符间要用空格隔开

let

1
2
3
#let 只能用于赋值
let a=b+1
let a=b+1 b=c+1 #多个用空格隔开

$[]

1
2
#$[]表达式内部不用对变量赋值
b=$[a+1]

(()): 整数运算

  • (())内变量有无$均可
  • (())中会忽视空格
  • 支持算数运算与关系运算
1
2
3
4
5
6
7
# 赋值
((a=b+1))
a=$((b+1))
((a=35+2,b=1+5)) # 多个表达式用,隔开
# 比较
(($a<$b)) # a小于b,成立返回0,不成立1
(($a<$b&&$a<$c)) # 支持||与&&

test[]和[[]]:判断符:测试

[[或[之后,]]或]之前都需要空格

[]与[[]]支持的运算符

测试内容 [ ] [[ ]]
数字测试 -eq(=,==),-ne(!=),-lt(\>),-le,-gt(\>) -ge,不支持=和<= 同[ ],<和>不需要转义
文件测试 -r,-l,-w,-x,-f,-d,-s,-nt,-ot等 同[ ]
字符测试 -eq(=,==),-ne(!=),-n -z 同[ ]
逻辑测试 -a,-o,! **! ,\ \ ,&&**
数字运算 不可以使用 +,-,*,/,%
1
2
3
4
[[ $a==$b ]] #字符串比较不支持,>=、<=,只能用这种方式表达
[[ -z $a ]] #字符串是否为空
[[ -n $a ]] #字符串是否不为空
[[ $a ]] #字符串是否不为空

文件测试运算符

1
2
3
4
5
6
7
8
9
[[ -d file ]] #是否是目录
[[ -f file ]] #是否是普通文件
[[ -r file ]] #是否可读
[[ -w file ]] #是否可写
[[ -x file ]] #是否可执行
[[ -s file ]] #是否为空
[[ -e file ]] #是否存在
[[ file1 -nt file2 ]] #file1是否比file2新
[[ file1 -ot file2 ]] #file1是否比file2旧

bc命令:linux的内置计算器

  • 语法:bc [options] [参数]

    • 使用内置函数:bc -l
    • 显示告警信息:bc -w
    • 不显示欢迎信息:bc -q
    • 退出:quit
  • 内置变量

    • scala:结果精度
    • ibase:输入的进制
    • obase:输出的进制
    • last或者.:获取最近打印的结果
  • 内置函数

    • s(x):计算正弦值,x为数值,即Π=360°
    • c(x):计算余弦值
    • a(x):计算正反切
    • l(x):计算x的自然对数
    • e(x):计算e的x次方
    • j(n,x):计算n到x的阶数
  • 互动式的数学运行

    • 标准计算器的使用:即输入表达式,回车输出结果
    • 也可以直接读取整个文件:bc file,一次性把表达式全输入,结果全输出
  • 非互动式的管道运算

    1
    2
    3
    4
    echo "e(2)" | bc #以管道的方式把计算公式传给bc,bc计算后返回结果 
    echo "obase=2;7" | bc #多表达式同行,用;隔开
    var_name=`echo "e(2)" | bc` #使用反引号给变量赋值
    var_name=$(echo "e(2)" | bc) #使用$()给变量赋值,但不是所有linux系统都支持
  • 非互动式输入重定向bc运算

    不常用,略

流程控制语句

if else 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
## 注意点:shell中成功为0,失败为1
## test的功能[]的效果一致

#if语句
if [[ $a > $b ]]
then
ehco "a>b"
fi
if [[ $a > $b ]] ;then ;fi #一行时,用;隔开,多命令同行都是用;隔开,下同


#if else语句
if [[ $a > $b ]]
then
echo "ture"
else
echo "false"
fi


#if elif else语句
if [[ $a > $b ]]
then
ehco "a>b"
elif [[ $a == $b ]]
then
ehco "a=b"
else
ehco "a<b"
fi

case 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
# case的匹配模式支持,数字,字符串,部分正则表达式:
# *:类似default
# 支持正则
# |:多条件
a=59
case $a in
[1-5]?|[8-9])
echo "[1-5]"
;;
*)
echo "*"
;;
esac

while 与 until 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#untilwhile相反,false时执行
a=0
while ((a < 10))
do
if (($a%2 == 0)); then
echo "$a+=3"
a=$[a+3]
continue;
fi
echo "$a+=1"
a=$[a+1]
if (($a > 7)); then
echo "$a>7"
break;
fi
done

# 无限循环
while true
do
done
while :
do
done

for 语句

1
2
3
4
5
6
7
8
9
#枚举遍历
for i in 1 2 3;do ; done
#枚举遍历数组
arr=(1 2 3)
for i in ${arr[@]};do echo $i; done
#范围遍历
for i in {1..100};do ;done
#类C遍历
for((i=1;i<100;i++));do ;done

select 语句

1
2
3
4
5
6
7
#用于交互式的菜单选择,没有结束条件,只能使用break跳出
#会输出菜单项在控制台上供选择
#经常配合case使用
select var in itme1 itme2 itme3
do
break;
done

函数

系统函数

  • basename :获取文件名
  • dirname:获取文件所在目录

普通函数语法

shell函数与shell程序的区别:外部脚本文件在新进程中运行,函数在原进程运行

1
2
3
4
5
6
7
8
9
10
11
12
#函数定义
[ function ] fun()
{
[return 返回值] #返回值的范围在[0-255];无返回值,返回最后一条命令的结果,
}

#函数调用
fun 参数1 参数2 参数3 ...
a=$? #获取返回值

#与shell程序一样 使用$1 $2 $3 $*等获取参数

输入输出重定向

默认输入输出

文件名 类型 文件描述符
stdin 标准输入 0
stdout 标准输出 1
stderr 标准错误 2

重定向语法

命令 作用
> file ; < file 覆盖方式重定向
>> file 追加方式重定向
< file1 >file2 从file1读取,结果输出到file2
fd> file ; fd >>file 把fd的数据重定向到文件中
> file fd1>&fd2 两个fd都输出到fd中

Shell 工具

  • cut工具:按列分割,并提取
  • sed工具:字段增删改查
  • awk工具:按表格处理数据
  • sort工具:排序

常用脚本

一键进入docker容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
svr="";
subCond=""
arr=$(docker ps | grep $svr | grep -v $subCond | awk '{print $1}');
if [[ ${#arr[@]}>1 ]]; then
docker ps | grep $svr | grep -v $subCond;
read -p "please input CONTAINER ID:" contid;
else
contid=${arr[0]};
fi;
if [[ -n $contid ]]; then
docker exec --privileged -u root -it $contid bash;
else
echo "no find $svr";
fi