Shell脚本和编程 | 青训营笔记

90 阅读3分钟

shell

shell既是命令解释器,也是一门编程语言。

为什么要学shell?

学习 shell 的价值:

  1. Linux 服务器的基本操作和管理
  2. 前端 Node.js 服务的进程管理、问题排查、资源监控等运维操作
  3. 使用 shell 编写 TCE、SCM、Docker 脚本,完成服务编译和部署

0.发展

image.png

1.语法

类型作用域声明方式规范
自定义变量当前shell=字符串、整型、浮点型、日期型
环境变量当前shell及其子shellexport、declare -x
系统环境变量所有shell启动加载

父子shell: image.png

1.1 使用变量的例子

注意:定义变量时, =号的两边不可以留空格

#! /bin/bash

# 变量名=变量值(等号两边不能有空格⚠)
page_size=1
page_num=2

# 将命令赋值给变量
_ls=ls

# 将命令结果赋值给变量
file_list=$(ls -a)

# 默认字符串,不会进行 + 运算
# total=page_size*page_num  # 错误写法

# 声明变量为整型
let total=page_size*page_num
declare -i total=page_size*page_num

# 导出环境变量
export total
declare -x total

其中,declare的形式为:declare [+/-] 选项 变量

选项含义
-给变量设定类型属性
+取消变量的类型属性
-a将变量声明为数组类型
-i将变量声明为整数型
-x将变量声明为环境变量
-r将变量声明为只读变量
-p显示指定变量的被声明的类型

1.2 系统环境变量

变量名含义常见操作
$0当前shell名称/脚本名称$1, $2等可以获取到传入参数
$#传入脚本的参数数量if[$# -gt 1]
$*传入脚本的所有参数
$?上条命令执行的状态码if[$? -eq 0]
$PS1命令提示符export PS1="\u@\h \w>",这样命令行就会从[root@localhost src]变成root@localhost /usr/local/src>,具体可见 $PS!详解
$HOME用户主文件夹cd ~
$PATH全局命令的搜索路径PATH=$PATH:[新增路径]

1.3 配置文件加载

image.png

① 如果取得 bash 需要完整的登录流程,我们称之为login shell,比如 ssh 远程登录一台主机; ② 不需要登陆的 bash 称为non-login bash,比如在原来的bash中执行bash开启子进程、执行一些外部命令。

此外,如果修改了配置文件,不会立即生效,需要我们重启终端或者执行source命令。

2 运算符

类型符号作用方法
算术运算符+ - * / % | &常规运算
逻辑运算符|| && !
比较运算符== != < >
引号①双引号""
②单引号''
③反引号``
①部分引用,仅仅 $ ` \ 保留作用
②完全引用
③执行命令
①foo="${a}123"
②foo='foo$a'
③foo=`ls -a`
圆括号①(())
②()
①算术运算
②执行命令
①foo="$((1+2))"
②foo='$(ls -a)'
命令连接①||
②&&
③;
①cmd1执行完且返回码非0,则继续执行cmd2
②cmd1执行完且返回码为0,则继续执行cmd2
cmd1、cmd2串行执行
①cmd1 || cmd2
②cmd1 && cmd2
③cmd1; cmd2
后台运行&让命令在后台运行,可与nohup一起使用cmd &

2.1 管道

管道:cmd1 | cmd2,作用是将前一个命令的结果传递给后面的命令。 要求:管道右侧的命令必须能接受标准输入,如ls、mv等不能直接使用,可以用xargs预处理。 注意:管道命令仅仅处理stdout,会忽略stderr,可以使用set -o pipefail设置shell遇到管道错误退出。

	#!/bin/bash
	cat platform.access.log | grep ERROR
	netstate -an | grep ESTABLISHED | wc -l
	find . -maxdepth 1 -name "\*.sh" | xargs ls -l

2.2 重定向

image.png 每个shell命令在执行时,都会打开三个问文件描述符,文件描述符0、1、2,分别对应stdinstdoutstderr,这三个文件描述符默认指向终端输入终端输出终端输出,那么当命令需要获取输入的时候,它会去读取fd0,当要输出的时候它会向fd1、fd2写入,改变这些描述符指向的行为叫做重定向。

2>&1 必须写在 > 之后 << 比较特殊,表示继续沿用当前的标准输入,只是当识别到指定的标识符(例如EOF)后停止,将接收到的内容作为stdin

2.3 判断命令

有test、[ ] 和 [[ ]]。 语法:test condition[ condition ][[ condition ]]

注意:

  1. 中括号前后要有空格符(中括号多用于正则表达式);
  2. [ ] 和 test 是命令,只能使用自己支持的标志位,<、>、=只能用来比较字符串
  3. 中括号内的变量,最好都是用引号括起来
  4. [[ ]] 更丰富,在整型比较中支持<、>、=,在字符串比较中支持 =~ 正则

2.4 语句

条件语句:一个IF、一个CASE

#!/bin/bash
# Here is `IF`
level=0
# IF语句
if [ -n "$level" ]; then
    if [ "$level" == 0 ]; then
        prefix=ERROR
    elif [ "$level" == 1 ]; then
        prfix=INFO
    else
       echo "log level not supported"
    fi
echo "[${prefix}] $message"
# -------------------------------
# Here is `CASE`
name=john
case $name in
    "nick")
        echo "hi nick"
    ;;
    "john")
        echo "my name is john"
    ;;
    *)
        echo "404"
    ;;
esac

循环语句:whileuntilfor循环

#!/bin/bash
# Here is `while`
let num=0
while [ $num -lt 10 ];
do
  echo "current idx: $num"
  ((num++))
done
# -------------------------------
# Here is `until`
until [ $num -gt 10 ];
do
  echo "current idx: $num"
  ((num++))
done
# -------------------------------
# Here is `for` - ver1
for foo in a b c
do
  echo $foo
done

# Here is `for` - ver2
for((i=0;i<10;i++))
do
  echo $i
done

2.5 函数

  1. funcName() {程序段;}
  2. function funcName() {程序段;}

注意:

  • shell自上而下执行,所以函数要先定义后使用;
  • $0代表函数名,后续参数为 $1,$2....;
  • 函数内return仅仅表示函数执行状态,不代表函数执行结果;
  • 返回结果一般使用echo、printf,在外面使用$()、``获取结果;
  • 如果没有return,函数状态是上一条命令的执行情况,存储在 $? 中。

3. 常用命令

命令使用
grep查找错误日志:grep -n "ERROR" -A3 -B3 cloudfun.log
统计次数:grep -n "ERROR" -c cloudfun.log
sort指定分隔符后以第三列进行排序:sort -t " " -k 3
wc统计出现的行数、单词数、字符数 wc -lwm
head查看前十行:head -n 10 cloudfun.log
tail等待追加内容:tail -f -n 10 cloudfun.log
cut对数据行的内容进行处理 cut -d "" -f 3
find文件和目录查找
xargs参数处理
which查找命令路径

4. 展开

  1. 大括号展开
# 字符串序列
a{b,c,d}e # abe ace ade
# 表达式序列,(数字可以使用incr调整增量,字母不行)
{1...5} # 1 2 3 4 5 
{1..5..2} # 1 3 5
{a..e} #  a b c d e
  1. 波浪号展开
# 当前用户主目录
~/foo # $HOME/foo
# 指定用户的主目录
~fred/foo # 用户fred的$HOME/foo
# 当前工作目录
~+/foo # $PWD/foo
# 上一个工作目录
~-/foo # ${$OLDDPWD-'~-'}/foo
  1. 参数展开

Ⅰ. 间接参数扩展: ${!parameter},其中引用的参数并不是parameter而是parameter的实际值

Ⅱ. 参数长度: ${#parameter}

Ⅲ. 空参数处理:

  • ${parameter:-word} # 为空替换
  • ${parameter:=word} # 为空替换,并将值赋给$parameter变量
  • ${parameter:?word} # 为空报错
  • ${parameter:+word} # 不为空替换

Ⅳ. 参数切片:

  • ${parameter:offset}
  • ${parameter:offset:length}

Ⅴ. 参数部分删除:

  • ${parameter%word} # 最小限度从后面截取word
  • ${parameter%%word} # 最大限度从后面截取word
  • ${parameter#word} # 最小限度从前面截取word
  • ${parameter##word} # 最大限度从前面截取word
#! /bin/bash
str=abcdefg
sp1=${str##*d}
sp2=${str%%d*}
echo $sp1 # efg
echo $sp2 # abc
  1. 命令替换 在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种:$(...)或`...`
#! /bin/bash
echo $(whoimi)
foo() {
  echo "asdasd"
}
a=`foo`
  1. 数学计算
#! /bin/bash
echo $((1+2)) # 3
  1. 文件名展开 当有单词没有被引号包裹,且其中出现了'*','?',and '[' 字符,则shell会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变。
#! /bin/bash
echo D* # 输出当前目录下所有以D字母开头的目录、文件

参考资料

[1] $PS!详解

[2] 正则表达式 – 教程 | 菜鸟教程 (runoob.com)