1. shell编程
-
虽然可以通过chmod更改文件权限可以实现执行脚本, 但为了稳定一定要使用/bin/bash来执行
-x该选项用于脚本执行的追踪, 当脚本到达几百行甚至更多, 这时是无法及时定位报错的, 即使输出信息什么的也不如将每一条命令即脚本打印出来更直观- 尽量不要使用中文注释, 可能在不同的系统环境或终端中出现乱码的情况
$0是代表脚本名称, 是不会变的, 而$<num>和$#则可能因为函数的作用域从而发生变化, 所以为了明确性最好一开始使用语义化的变量接收脚本的参数, 并且如果函数内需要更改这个变量, 为了安全性最好也创建另一个变量接收一下
-
为了规范, 变量要使用下划线方式即hello_world或Hello_World
-
单引号的内容绝对不会被解析, 双引号会解析; 反引号和$()功能相同, 尽量少用反引号
-
export添加环境变量,unset删除变量 -
系统级别的环境变量需要添加到
/etc/profile, 如果自定义的一些系统级别环境变量可以放在/etc/profile.d/* -
脚本内的内置变量:
$0当前脚本的文件名;$<num>执行脚本时传入的第几个参数;$#执行脚本时传入参数的总数 -
命令执行成功为0, 失败为其他码; 有很多命令执行失败都会返回1, 如很多的逻辑判断等
-
字符串相关
# 查看字符串长度 echo ${#password}# 截取字符串 ## 从下表0开始截取2位 echo ${password:1:2} ## 从倒数第二位开始截取2位 echo ${password:0-2:2}# 判断字符串是否为空 test -z "$aaa"/[ -z "$aaa" ] # 判断字符串是否不为空 test -n "$aaa"/[ -n "$aaa" ] -
计算相关
# 整数运算 echo $[ 1+2 ] a=$[ 1+2 ]# 小数运算 ## scale指保留几位小数 echo "scale = 2; $a + $b" | bc a=$(echo "scale=2; $a + $b" | bc) -
表达式
# 除了赋值操作,其他操作都要在等号两边添加空格否则会出错 # 字符串使用 = (因为==可能不兼容), 数字使用 -eq等(因为无>=这种东西) # 简单判断 ## 1表示错误 test "sss" = "sss"; echo $? test "sss" = "ssa"; echo $? ## [ ] 简略写法 [ "sss" = "ssa" ]; echo $?# 逻辑判断 [ $? -eq 0 ] && echo "成功" || echo "失败"# 文件判断 ## 常用三种: -d目录是否存在,-f文件是否存在,-x指定目录或文件是否有执行权限 [ -d aha1 ] || mkdir aha1# 多条件判断 [ "asdf" = "asdf" -a "ffff" = "ffff" -a "hhhh" = "hhhf" ]; echo $? [ "asdf" = "asdf" -o "fffd" = "ffff" ]; echo $? -
数组分为索引数组和关联数组, 默认创建的数组就是索引数组, 而关联数组需要先通过
declare -A声明# 索引数组的增删改查 # 增 num_list=(111 222 333 444) num_list[11]=11; num_list[22]=22 # 删 unset num_list # 改 num_list[1]=555; # 查 declare -a ## 查看数组长度 echo ${#num_list[*]} ## 查看数组的全部值 echo ${num_list[*]} ## 查看数组的全部索引 echo ${!num_list[*]}# 关联数组的增删改查 # 增 declare -A num_list num_list=([jjj]=11 [fff]=22) num_list[hhh]=jjjj111 # 删 unset num_list # 改 num_list[jjj]=3333 # 查 declare -A ## 查看数组长度 echo ${#num_list[*]} ## 查看数组的全部值 echo ${num_list[*]} ## 查看数组的全部索引 echo ${!num_list[*]}# 查看负载 cpu_load=($(uptime | tr -s " " | cut -d " " -f 9-11 | tr "," " ")) echo "CPU 1 min平均负载为: ${cpu_load[0]}" echo "CPU 5 min平均负载为: ${cpu_load[1]}" echo "CPU 15 min平均负载为: ${cpu_load[2]}" -
逻辑控制
if [ $USER = "q" ]; then echo "又寸"; fi#!/bin/bash # ***************************************** # Filename : service_manage.sh # Author : q # Date : 1970-01-01 # Description : 示例 - 服务控制 # *****************************************svc_ops=$1 svc_ops_count=$#svc_manage (){# if# if [ ${svc_ops} = "start" -o ${svc_ops} = "开始" ];then# echo -e "\e[31m服务启动中...\e[0m"# elif [ ${svc_ops} = "stop" ];then# echo -e "\e[31m服务关闭中...\e[0m"# elif [ ${svc_ops} = "restart" ];then# echo -e "\e[31m服务重启中...\e[0m"# else# echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m"# fi# casecase ${svc_ops} in"start" | "开始")echo -e "\e[31m服务启动中...\e[0m";;"stop")echo -e "\e[31m服务关闭中...\e[0m";;"restart")for i in $(seq 2); dosleep 1echo -e "\e[31m服务重启中...\e[0m";done;;*)echo -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m";;esac }main (){if [ ${svc_ops_count} -ne 1 ];thenecho -e "\e[31m$0 脚本的使用方式: $0 [ start | stop | restart ]\e[0m"exit 1fisvc_manage }main
2. 进程
1. 基础概念
- 程序运行起来是进程(运行在内存, 执行在cpu), 就像镜像运行起来是容器
- 我对子进程和子shell的理解还是有偏差
- 内部命令不用说, 就是直接在当前shell环境运行的, 通过
pstree -p能看到的都是外部命令运行的进程 - 对于外部命令, 像可以直接执行的程序如
ls sleep nginx等, 它们只会创建子进程执行, 比如执行ls, 当前shell先调用fork()复制出一个与父进程环境一样的子进程即bash副本, 之后子进程调用execv("/bin/ls")将自己替换为ls程序, 这个进程是挂在父进程下的子进程- 像nginx一样的系统服务程序, 如果手动执行
nginx命令, 默认是挂在当前shell进程下的, 除非通过systemctl让systemd管理(意味着挂在systemd下), 否则关闭终端时这个进程也会关闭, 至此我也理解了为什么明明可以直接启动还要使用systemctl start <svc>
- 像nginx一样的系统服务程序, 如果手动执行
- 而需要创建子shell的情况只与语法结构有关, 如
() | $() &等才会触发创建子shell, 之后由子shell创建子进程来执行这些命令, 这是bash的设计思想, 宁可创建不必要的子shell也不想污染当前环境
- 内部命令不用说, 就是直接在当前shell环境运行的, 通过
- 进程与线程, 还有协程
- 内核把所有可调度执行的实体统称为任务, 它是调度器直接调度的最小单元, 以前是进程, 现在是线程
- 一开始只有单进程, 后来出现了多进程即一个主进程多个副进程, 主进程是用来管理副进程干活的, 对于cpu时间分片来说每个进程都是相同的, 时间到了就踢出cpu去运行下一个进程, 为了提高运行效率设计了有关权重的算法(可通过nice/renice设置), 可以从cpu时间分片中为指定进程多分配一些时间片, 但因为每次切换进程是需要重新加载下一个进程所需资源的, 所以cpu利用利用率相对来说还是不高
- 为了解决cpu利用率低的问题. 淘汰进程推出了线程, 现在的每个进程其实就是线程组, 由主进程和多个线程组成, 它们都有自己的tid但共用一个pid即线程组的id, 线程的出现对单进程的cpu利用率提高不大, 但对多进程来说提升是很大的, 因为把多进程中的公用资源提取出来到了主进程, 差异留在线程, 当cpu运行该进程时, 会依次执行线程1234, 如果时间到了没有执行完等到下一个时间片继续执行, 因为不需要每次执行任务都重新加载所有需要的资源, 所以大大提升了cpu利用效率; 之前只有一个核, 这时的线程只是并发, 而现在的cpu都有好多核, 每个核都是独立的cpu时间分片机制, 所以不同的核可以同时运行同一个进程的多个线程, 这些线程是并行的, 这时出现了一些竞争问题如io阻塞等, 虽然有一些解决办法如锁什么的, 但因着锁的出现又引发了很多新问题
- 为了解决竞争问题, 出现了协程, 协程并没有替代线程成为内核所认可的最小调度单元, 它是通过代码实现的, 在一个线程中创建非常多的协程, 之前线程出现竞争问题时会一直等待直到能够成功执行, 这样会浪费cpu, 所以线程的设计是当前线程中运行的协程遇到io阻塞时会自动切换另一个协程去干其他的事, 等io阻塞消失后按照算法去执行之前的协程, 极大提高了cpu利用率
- 现在出现了新类型的协程, 比如GOroutine(go协程), 它比先前的协程更轻量化, 可以选择绑定在指定的线程上, 从而实现cpu核之间的漂移, 尤其是io密集型的任务, 当出现io阻塞时, 有两种解决思路, 一种是等待把缓存行搬到当前所在核心, 另一种是把当前的协程迁移到有指定缓存行的核心, go协程选择的是后者, 不会主动退让, 而是可能会漂移到指定缓存所在的核心从而继续任务, 当该协程执行结束后也不会漂移回原来的核心, 因为当前核心有缓存行可以使用, 一直待到当前线程的时间片结束, 到下一轮时间片时, 大概率缓存就没有了, 该协程可能还会继续漂移
- 进程的状态有哪些?
- 子进程先挂, 父进程后挂, 这时子进程无人管就变成了僵尸进程, 想要管理子进程需要把父进程启动, 如果启动不了直接关闭父进程, 由systemd进程可以清理
- 当父进程执行
exit时, 按照shopt | grep huponexit配置, 如果是off则不向后台子进程发送SIGHUP信号, 这时子进程被systemd接管成为孤儿进程; 当父进程如终端直接断开时, 内核会发送SIGHUP信号到后台子进程, 这时子进程也会终止, 不会成为孤儿进程, 可以通过nohup <cmd> >/dev/null 2>&1 &创建不受父进程的SIGHUP信号管理的进程- 实际上现在的孤儿进程也没有太多应用场景, 很早以前的系统把守护进程看为是高级孤儿进程但那时还没有systemd, 现在有了systemd, 守护进程变成了systemd的子进程
- 进程运行时会遇到各种问题, 如内存泄漏/溢出/不足等, 这时需要清理内存中的进程和数据, 有很多内存清理策略, 没有最好的策略只有最合适的策略, 毕竟总有各种各样的情况
- 每个进程都会在/proc下创建pid名称的目录,
cat /proc/1872/status | grep Pid可以找到当前进程的父进程, 因为很多子进程都是动态销毁的, 没那么大必要去维护子进程列表, 而且会损耗性能─sshd(1119)───sshd(1814)───sshd(1872)───bash(1873)───su(4065)───bash(4066)───pstree(4088)因为线程共享主线程的全部资源, 如果一个线程出现内存溢出或其他问题, 会导致其他所有线程崩溃, 整个进程都要挂掉, 为了稳定sshd选择fork子进程
fd/文件描述符简单说就是内核暴露出来的api, 支持调用也支持监听, 比如socket, 配置好五元组就可以 write /read 收发数据, 之后数据进入到内核网络栈封装完毕后通过网卡就可以实现收发请求buffer(缓冲区)/cache(缓存)占用一部分内存来暂存数据, 目的是为了提高运行整体; 前者不可丢, 后者可以丢- buffer 写文件时, 先将数据写到buffer中, 攒到一定的大小或者定时将这些数据一次性写入硬盘 ,减少写入频率的同时提高写入效率
- cache 读文件时, 把读过的数据写入到cache中, 等下次读取相同的内容时可以直接从内存中命中, 提高读取效率; cpu的三级缓存也是这样的原理
2. 基础命令
-
ps aux查看进程
f简单查看进程间关系
prtstat 3683 | cat /proc/3683/status查看指定进程的详细信息 -
vmstat和topvmstat查看系统资源的使用情况, 添加参数2可表示每两秒刷新一次; 如果系统有异常如系统负载过高可通过此命令定位一下大概方向top查看各进程所消耗的资源情况; 通过先前的vmstat定位到大概方向后再通过top的排序查找具体进程Pcpu降序排序;M内存降序排序;N进程号降序排序
- 找到具体的进程后, 可以查看其日志和配置找问题
-
free -h查看内容使用情况echo 3 > /proc/sys/vm/drop_caches清理buff/cache的缓存
-
lsof /usr/bin/bash查看哪个进程正在使用指定文件-Pti查看哪个pid的进程正在使用指定端口
-
jobs查看后台运行的程序ctrl+z前台转后天fg %<num>后台转前台, 不指定时默认第一个后台程序bg %<num>使后台程序继续运行
-
kill 2133默认-15优雅终止指定进程;pkill nginx通过进程名称终止所有正在运行的进程
-9强制终止程序
-18恢复进程
-19暂停进程 -
把进程放到后台运行可以实现并行运行, 一般情况下自己操作的场景实在少
其他
-
简单配置vim
#!/bin/bash # ***************************************** # Filename : vim_conf.sh # Author : q # Date : 1970-01-01 # Description : 简单配置vim # *****************************************color () {RES_COL=60MOVE_TO_COL="echo -en \\033[${RES_COL}G"SETCOLOR_SUCCESS="echo -en \\033[1;32m"SETCOLOR_FAILURE="echo -en \\033[1;31m"SETCOLOR_WARNING="echo -en \\033[1;33m"SETCOLOR_NORMAL="echo -en \E[0m"echo -n "$1" && $MOVE_TO_COLecho -n "["if [ $2 = "success" -o $2 = "0" ] ;then${SETCOLOR_SUCCESS}echo -n $" OK "elif [ $2 = "failure" -o $2 = "1" ] ;then${SETCOLOR_FAILURE}echo -n $"FAILED"else${SETCOLOR_WARNING}echo -n $"WARNING"fi${SETCOLOR_NORMAL}echo -n "]"echo }vim_conf(){cat >> $1 <<-EOF "1 行号显示 "set number "2 语法高亮 syntax on "3 设置缩进 "3.1 Tab 占用 2 个空格 set tabstop=2 set expandtab "3.2 自动缩进时使用2 个空格 set shiftwidth=2 "4 智能缩进 set smartindent set autoindent "5 搜索时高亮 set hlsearch "6 是高亮显示当前行 set cursorline "7 自动换行 set wrap EOFcat >> $1 <<-EOF "设置脚本文件的头模板 autocmd BufNewFile *.sh exec ":call SetScriptTemplate()" function! SetScriptTemplate()" 定义注释头部模板call setline(1, "#!/bin/bash")" 自定义函数来插入作者、日期等信息call append(1, "# *****************************************")call append(2, "# Filename : " . expand("%:t"))call append(3, "# Author : q")" call append(4, "# Date : " . strftime("%Y-%m-%d"))call append(4, "# Date : " . strftime("1970-01-01"))call append(5, "# Description : ")call append(6, "# *****************************************")call append(7, "")call append(8, "")" 将光标移动到第 9 行,准备开始编写脚本内容normal! 9G endfunctionEOF }main(){if [ -f $HOME/.vimrc ];thenmv $HOME/.vimrc $HOME/.vimrc.bakfiif vim_conf $HOME/.vimrc;thencolor "Vim configuration completed" 0elsecolor "Vim configuration failed" 1fi }main
