Shell 入门(一):变量和流程控制

刚入职新公司,碰到一个棘手的线上莫名宕机问题。我需要查看应用日志,应用端口等信息,以前,只是知道tail -f service.log | grep '关键词'这样的简单查询日志操作。另外,需要频繁打开应用目录,启动应用,然后切换到日志目录,查看日志,比较繁琐,后面我通过 Shell 的alias功能,让操作得到一定程度的简化。但是,为了写出更好的执行脚本,于是有了这系列文章。

本系列文章分为两小节
1,Shell 是什么?
2,Shell 基础

  • 变量
  • 流程控制
  • 数组
  • 函数

1,Shell 是什么?

简单来说,Shell 就是操作系统上运行的一个应用程序。
普通用户接触最多的操作系统是 Windows,而程序员使用更频繁的是 Linux 的服务端系统。
Windows 提供图形化界面,让我们可以通过移动光标,鼠标右键等一系列操作完成文件的编辑工作,降低普通用户的使用难度。
而程序员是以软件开发为职业,操作的是很少提供图形化界面的服务端系统,而 Shell 可以让用户更方便的操作 Linux 系统,
文本或字符串检索,文件的查找或创建,大规模软件的自动部署,监控服务器性能,压缩文件等。

Shell 原理图

1.2,初识 Shell

  • test.sh
#!/bin/bash
echo "Hello World!"

1.3 shell 脚本 Debug 调试

  • 基本写法:sh [-nxv] 脚本
    • -n: 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误;
    • -v: 一边执行脚本,一边将执行过的脚本命令打印到标准输出;
    • -x:提供跟踪执行信息,将执行的每一条命令和结果依次打印出来;

2, Shell 基础

  • 变量
  • 流程控制
  • 数组
  • 函数

2.1 变量

  • 自定义变量
  • 系统环境变量
  • 预先定义变量

2.1.1 自定义变量

  • 定义变量:变量名=变量值
  • 引用变量:$变量名${变量名}
  • 查看变量:echo $变量名 或者 set显示所有变量,包括自定义变量和环境变量

2.1.2 系统环境变量

  • 定义环境变量:export 变量名=变量值
  • 引用环境变量:$变量名
  • 查看环境变量:echo $变量名 或者 env | grep 变量名

2.1.3 预先定义变量

  • $0: 脚本文件名称
  • $*: 当前脚本传入的所有参数
  • $@: 当前脚本传入的所有参数
  • $#: 当前脚本传入参数的个数
  • $$: 执行当前脚本进程的 PID
  • $!: 上一个后台进程的 PID
  • $?: 上一个命令的返回值,0 表示成功执行

2.1.4 单引号和双引号的困惑

[root@roclinux ~]$ echo $PWD
/root
[root@roclinux ~]$ alias dirA="echo work directory is $PWD"
[root@roclinux ~]$ alias dirB='echo work directory is $PWD'
 
# 正确显示
[root@roclinux ~]$ dirA            
work directory is /root
 
# 正确显示
[root@roclinux ~]$ dirB
work directory is /root 
 
# 显示不正确, 怎么回事?     
[root@roclinux ~]$ cd /
[root@roclinux /]$ dirA
work directory is /root     
 
# 正确显示 
[root@roclinux /]$ dirB
work directory is /    

# 查看 alias 的真实内容
[roc@roclinux ~]$ alias dirA
alias dirA="echo work directory is /root"
 
[roc@roclinux ~]$ alias dirB
alias dirB='echo work directory is $PWD'
  • 示例来源:单引号和双引号的困惑
  • 使用双引号的 dirA,通过 Shell 的变量转换后已经变成了字符串echo work directory is /root,当目录切换后,显示的还是原目录内容;
  • 使用单引号的 dirB,由于不受 Shell 的影响,仍然保留着原来的位置echo work directory is $PWD,当切换目录后再执行,变量$PWD被 Shell 替换掉,因此内容被正确显示。
  • 换句话说,双引号是弱引用,会解析变量,单引号是强引用,不会解析变量。
# 示例二:
[xiaoa@linux ~]$ name=zhangsan
[xiaoa@linux ~]$ echo 'My name is ${name}'
My name is ${name}

[xiaoa@linux ~]$ echo "My name is ${name}"
My name is zhangsan

2.1.5 命令替换

  • \``命令等价于 $()
# 示例一,创建文件
touch `date +%F`_file1.txt
touch $(date +%F)_file2.txt

# 示例二,查看可用磁盘容量
disk_free3="df -Ph | grep '/$' | awk '{print $4}'"   # 错误
disk_free4=$(df -Ph | grep '/$' | awk '{print $4}')
disk_free5=`df -Ph | grep '/$' | awk '{print $4}'`

2.1.6 变量数值运算

# 示例一: 整数运算  expr + - * / %
[xiaoa@linux ~]$ a=1
[xiaoa@linux ~]$ b=2
[xiaoa@linux ~]$ expr $a + $b
3
[xiaoa@linux ~]$ expr $a * $b


# 示例二:整数运算  let + - * / %
[xiaoa@linux ~]$ let sum=2+3;
[xiaoa@linux ~]$ echo $sum
5


# 示例三: 小数运算  bc  + - * / %
[xiaoa@linux ~]$ echo "1+2" |bc
[xiaoa@linux ~]$ echo "scale=2;4/8" |bc

2.1.7 默认变量

  • ${变量名-新的变量值}:
    • 变量没有被赋值,会使用“新的变量值”替代;
    • 变量有被赋值(包括空值):不会被替代
  • echo ${var1-test}
  • ${变量名:-新的变量值}:
    • 变量没有被赋值(包括空值),会使用“新的变量值”替代;
    • 变量有被赋值:不会被替代
  • echo ${var1:-test}

2.2 流程控制

  • 条件测试与逻辑运算表达式
  • 流程控制语句 if
  • 流程控制语句 case
  • 循环语句 for,while

2.2.1 条件测试 test 条件测试与逻辑运算表达式

  • 基本语法
  • 文件测试
  • 数值比较
  • 字符串比较
  • 正则比较

2.2.1.1 基本语法

# 条件表达式
1,`test 条件表达式`
2,`[ 条件表达式 ]`
3,`[[ 条件表达式 ]]`


# 逻辑运算表达式
命令 1 ; 命令 2     无论命令 1 是否执行成功都会执行命令 2
命令 1 && 命令 2    命令 1 必须执行成功,才会执行命令 2
命令 1 || 命令 2    命令 1 必须执行失败,才会执行命令 2

2.2.1.1 文件测试

  • man [: 查看[表达式用法;
  • [ -e dir|file ]: 目录或文件是否存在;
  • [ -d dir ]: 目录是否存在;
  • [ -f file ]: 文件是否存在;
  • [ -r file ]: 当前用户对该文件是否具有读权限;
# 判断目录是否存在,不存在,则创建该目录
test -d ~/Desktop/test || mkdir -p ~/Desktop/test

2.2.1.2 数值比较

  • [ 整数1 操作符 整数 2 ]
  • 大于:[ 1 -gt 10 ]
  • 小于: [ 1 -lt 10 ]
  • 等于: [ 1 -eq 10 ]
  • 不等于: [ 1 -ne 10 ]
  • 小于等于: [ 1 -le 10 ]
  • 大于等于: [ 1 -ge 10 ]
# 示例一:磁盘使用率大于 80, 打印输出

#!/bin/bash
Disk_Free=$(df -h|grep "$"|awk '{print $5)'|awk -F '%' '{print $1}'})

if [ $Disk_Free -ge 80 ];then
    echo "Disk is Use: $Disk_Free%" > /tmp/disk_use.txt
fi


# 示例二:多整数比对
# -a: and
[ 1 -lt 2 -a 5 -gt 10 ];echo $?

# -o: or
[ 1 -lt 2 -o 5 -gt 10 ];echo $?

# &&
[ 1 -lt 2 && 5 -gt 10 ];echo $?

# ||
[ 1 -lt 2 || 5 -gt 10 ];echo $?

2.2.1.3 字符串比较

[xiaoa@linux ~]$ name=zhangsan
[xiaoa@linux ~]$ [ $name = "zhangsan" ]; echo $?
0
[xiaoa@linux ~]$ [ $name == "zhangsan" ]; echo $?
0
[xiaoa@linux ~]$ echo ${#name}  # 计算字符长度
8
[xiaoa@linux ~]$ BBB=""
[xiaoa@linux ~]$ echo ${#BBB}
0
[xiaoa@linux ~]$ [ -z "$BBB" ]  字符长度为 0
0
[xiaoa@linux ~]$ [ -n "$BBB" ]  字符长度不为 0
1

# 小结:变量为空或未定义,长度都为 0

2.2.1.4 正则比对

# 判断 name 是不是以 z 开头
[xiaoa@linux ~]$ [[ $name =~ ^z ]];echo $?
0

# 判断变量是不是数字
[xiaoa@linux ~]$ num=123
[xiaoa@linux ~]$ num2=123abc
[xiaoa@linux ~]$ [[ $num =~ ^[0-9]+$ ]];echo $?
0
[xiaoa@linux ~]$ [[ $num2 =~ ^[0-9]+$ ]];echo $?
1

2.2.2 流程控制语句 if

# 单分支结构
if [ 分数大于 60 ];then
    echo "及格"
fi

# 双分支结构
if [ 分数大于 60 ];then
    echo "及格"
else
    echo "不及格"
fi

# 多分支结构
if [ 分数大于 80 ];then
    echo "优秀"
elif [ 分数大于 60 ];then
    echo "及格"
else
    echo "不及格"
fi

2.2.3 流程控制语句 case

case 变量 in
模式 1)
    执行命令 1;;
模式 2)
    执行命令 2;;
模式 3)
    执行命令 3;;
    *)
    执行默认命令
esac

# 示例 1

#!/bin/bash
case $1 in
        Linux)
                echo "linux ..."
                ;;
        Shell)
                echo "shell ..."
                ;;
        MySQL)
                echo "mysql ..."
                ;;
            *)
                # echo "USAGE $0 [Linux|Shell|MySQL]"
                echo "USAGE `basename $0` [Linux|Shell|MySQL]"
esac

# 示例 2

#!/bin/bash
read -p "Are you sure?[y/n]:" action
case "$action" in
    y|Y|yes|YES)
        echo "user is deleted!"
        ;;
    *)
        echo "error"
esac

# 示例 3: 系统工具
#!/bin/bash

cat <<-EOF
===================
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
===================
EOF

while true
do
    read -p "请输入你想查看的系统状态对应码[d/m/u/q] " sys
    case $sys in
        h)
            help|less
            ;;
        f)
            clear
            lsblk
            ;;
        d)
            clear
            df -h
            ;;
        m)
            clear
            free -m
            ;;
        u)
            clear
            uptime
            ;;
        q)
            break
            ;;
        *)
            echo "error"
            exit 1;
    esac
done

2.2.4 循环语句

2.2.4.1 for 循环

# 基本语法
for 变量名 [ in 取值列表 ]
do
    循环体
done


# 示例 1: 计算 1 到 100 的和
#!/bin/bash
sum=0
for i in {1..100}
do
    let sum=sum+$i
done

echo $sum


# 示例 2:计算 1 到 100 之间所有奇数的和
#!/bin/bash
sum=0
for i in `seq 1 2 100`
do
    let sum=sum+$i
done

echo $sum

2.2.4.2 while 循环

# 基本语法
while 条件测试
do
    循环体
done

# 示例 1:计算 1~100 的和
#!/bin/bash
i=1
sum=0
while [ $i -le 100 ]
do
    let sum=sum+$i  # 注意:此处没有空格
    let i++
done

echo $sum


# 示例 2: 猜数字游戏
#!/bin/bash

sum=$((RANDOM%51))

sleep 1

echo "请输入1-50之间的数,开始猜吧!"

count=0

function type_num(){
    read -p "请输入一个数吧:" n
    expr $n + 1 &>/dev/null
    if [ $? -ne 0 ]; then
        echo "请输入一个数字"
        type_num
    fi
}

function guess(){
    (( count++ ))
    if [ $n -eq $sum ]; then
        echo "你猜中了,你的次数是:"${count}
        if [ $count -lt 3 ]; then
            echo "你太厉害了"
        elif [ $count -ge 3 -a $count -lt 6 ]; then
            echo "还是不错的,加油"
        else
            echo "你有点水啊"
        fi
        exit 0
    elif [ $n -gt $sum ]; then
        echo "猜大了"
        type_num
    else
        echo "猜小了"
        type_num
    fi
}

function main(){
    type_num
    while true
    do
        guess
    done
}

main

**参考资料:**
原文地址:https://www.cnblogs.com/linkworld/p/15063580.html