# Shell脚本

# SheBang

"shebang" 是指在Unix和Unix-like操作系统中,用于指定解释脚本文件的解释器的特殊字符序列。通常,这个字符序列是#!,紧随其后是解释器的路径。

  • 以#!/bin/sh 开头的文件,程序在执行的时候会调用 /bin/sh ,也就是bash解释器。
  • 以#!/usr/bin/python 开头的文件,代表指定python解释器去执行。
  • 以#!/usr/bin/env 解释器名称,是一种在不同平台上都能正确找到解释器的办法。

注意执行顺序:

  • 未指定shebang,默认用当前shell去解释脚本,即$SHELL
  • 指定了shebang,脚本执行时文件名会作为参数传递给解释器去执行。
  • 如果解释器没有执行权限或者不存在,会报相应问题,如果解释器不是一个可执行文件,会忽略指定的解释器,并交给当前shell去执行这个脚本。
  • 如果使用了bash test.sh这种命令执行脚本,会忽略shebang指定的解释器,使用命令中的bash。

# 查看shell版本

# 查看默认shell
echo $SHELL
ls -al /bin/sh
# 查看系统支持的shell
cat /etc/shells
1
2
3
4
5

# shell执行方式

shell执行有以下几种方式:

# 方式一:文件本身没有x执行权限的,或者脚本未指定shebang的使用这种方式
bash script.sh
sh script.sh

# 方式二:文件有x执行权限,使用绝对或者相对路径执行脚本
# /root/script.sh

# 方式三:其他方式
source script.sh
. script.sh
sh < script.sh
1
2
3
4
5
6
7
8
9
10
11

# bash基本特性

# 查看bash的历史命令
history
cat ~/.bash_history
# 查看历史命令存储位置
echo $HISTFILE
# 最大查看多少条命令行数
echo $HISTSIZE

# 清空历史命令
history -c
# 恢复历史命令
history -r ~/.bash_history
# 调用历史记录命令
!历史命令id 
# 执行上次的命令
!!
# 快速清屏
ctrl l
# 光标跳转到开头
ctrl a
# 光标跳转到行尾
ctrl e
# 历史命令搜索
ctrl r
# 有意思的打印
echo {1..100}
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

# Shell变量

# 基本规则

  1. 变量和值之间不能有空格。
  2. 定义完变量,可以使用$(name),${name},$name来引用。
  3. 不能以数字开头,不能用标点符号,只能包含数字,字母,下划线。
  4. 变量名严格区分大小写。
  5. Shell中变量默认定义为字符串类型。

# 作用域

父子shell中定义相同的变量名,在不同的shell环境中就是使用相应的变量。

#!/bin/bash

function test(){
  varInTest=123
}

test
echo $varInTest
# 是否会输出相应值
1
2
3
4
5
6
7
8
9

注意上面的代码会输出相应的值,但是注释掉test就不会输出,这个行为有点怪,好像是运行时定义的全局变量。如果定义为local,则怎么都不会输出变量值。这个类似于Javascript,默认是全局变量,只有使用var关键词定义,才是局部变量。

#!/bin/bash

function test(){
  local varInTest=123
}

test
echo $varInTest
1
2
3
4
5
6
7
8

上面的变量都只是在当前shell进程中有效,如果使用export将其定义为环境变量,那么该变量则会在所有子进程中有效。注意该环境变量也是临时的,如果最顶层的父进程关闭了,该变量也就无效了。

注意:

  • 每次调用bash/sh解释器执行脚本,都会开启一个子shell,因此不会保留当前shell除环境变量以外的变量。
  • 调用source或者.来在当前shell执行脚本,保留变量。

# 环境变量

环境变量一版指的是export命令导出的变量,可以在命令行中临时创建,用户退出shell终端,变量即丢失,如需永久生效,需要:

  1. 针对某个用户,可在~/.bash_profile,~/.bashrc中添加。
  2. 针对系统的所有用户,可在如下中添加
    • /etc/profile
    • /etc/bashrc
    • /etc/profile.d/

相关命令:

  • set:输出所有变量,包括全局变量,局部变量。
  • env:输出所有全局变量。
  • declare:输出所有变量。
  • export:显示和设置环境变量值。
  • unset:删除变量和函数。
  • readonly:设置只读变量。

下面是环境变量的加载顺序:

loadenv

# 脚本传参

Shell脚本传参有几种特殊变量:

  • 位置参数,以$1,$2,$3形式使用的变量。
  • 特殊变量:
    • $0:脚本的名称。
    • $#:传递给脚本的参数的数量。
    • $*:所有参数的列表,作为一个单独的字符串。
    • $@:所有参数的列表,作为一个数组。
    • $?:为0表示上一次的命令执行成功。

下面看下实例:

#!/bin/bash

echo "脚本名称: $0"
echo "传递给脚本的参数数量: $#"
echo "所有参数(作为单个字符串): $*"
echo "所有参数(作为数组): $@"

# 依次处理参数
while [ $# -gt 0 ]; do
    echo "参数: $1"
    shift
done
1
2
3
4
5
6
7
8
9
10
11
12

shift 命令用于移动位置参数的位置,让您可以依次处理所有传递的参数。

# 接收命令参数

while getopts evt: flag; do
    case "${flag}" in
    e)
        INSTALL_TYPE="ee"
        ;;
    t)
        BUILD_TAG=${OPTARG}
        ;;
    v)
        usage
        exit 0
        ;;
    esac
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:

  • 参数名后有:说明该参数需要传入一个值,默认会放入到OPTARG变量里。

# IF判断

下面是常用的判断符号:

判断符 含义
-eq 相等
-ne 不相等
-lt 小于
-gt 大于
-le 小于等于
-ge 大于等于
-z 当字符串长度为0时为真(空串)
-n 当串的长度大于0时为真(串非空)
= 当两个串有相同内容,长度时为真
!= 当两个串不等时为真
if [str1] 当串str为非空时为真
-v str 判断str变量是否被设置
-e 判断文件是否存在
-d 判断文件是否存在且是一个目录
-f 判断文件是否存在且是一个文件
-s 判断文件是否存在且非空
-r 判断文件是否存在且可读
-w 判断文件是否存在且可写
-x 判断文件是否存在且可执行
-O 判断文件是否存在且属于当前用户所有
-G 判断文件是否存在且默认组和当前用户相同
-nt 判断两个文件,是否前者比后者新
-ot 判断两个文件,是否前者比后者旧

# 特殊表达式

有几个特殊的表达式:

# 算术表达式

旧式的算术运算(Old-Style Arithmetic)形式是:

$[数学运算表达式]

现代 Shell 推荐使用下面形式,它被称为 "算术表达式" 或 "算术扩展"(Arithmetic Expression or Arithmetic Expansion):

((数学运算表达式))

注意几点:

  1. 表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( )) 命令的执行结果。也可以使用&&或||来进行运算。
  2. 使用$获取 (( )) 命令的结果。
  3. 在这里面使用变量不用加$号。不支持字符串比较,只能处理数字。

来看下实例:

#!/bin/bash
var=10

if ((10 == var))
then
    echo "panduan $var = 10"
fi

if ((10 ** 2 > var + 10))
then
    square=$[ 10**2 ]
    echo "panduan 10 ** 2 = $square > $var + 20"
fi

if (( 10 >= 10  ))
then
    echo "大于等于"
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 条件表达式

在 Shell 中,[[ ]] 被称为 "条件测试" 或 "条件表达式"(Conditional Test or Conditional Expression)。这是一种用于执行条件测试和比较的语法结构,通常在控制流结构(如 if 语句和循环)中使用。形式为:

[[ 条件表达式 ]]

注意:

  1. 该表达式支持用于条件测试,字符串比较,文件测试和逻辑操作。
  2. [] 通常称为 "方括号"(Brackets),它在条件测试中用于执行基本的条件检查。这种形式的条件测试通常用于旧版本的 Shell,而现代的 Shell 更倾向于使用 [[ ]] 。

# 命令替换

在 Shell 中,$() 被称为 "命令替换"(Command Substitution)。命令替换允许您执行命令并将其输出结果嵌套到另一个命令或变量中。

# Select语法

Linux Shell 中的 select 结构用于创建一个简单的菜单,允许用户从一组选项中进行选择。比如

#!/bin/bash
select choice in "查看文件" "编辑文件" "退出"
do
    case $choice in
        "查看文件")
            echo "您选择了查看文件。"
            # 在这里添加查看文件的代码
            ;;
        "编辑文件")
            echo "您选择了编辑文件。"
            # 在这里添加编辑文件的代码
            ;;
        "退出")
            echo "谢谢使用,再见!"
            break  # 退出循环
            ;;
        *)
            echo "无效选项,请重新选择。"
            ;;
    esac
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

上面的输入是1,2,3。但是choice变量实际是文字内容:

#!/bin/bash
select install_option in "Community Edition" "Enterprise Edition"; do
    case $REPLY in
    1)
        echo $REPLY
        echo $install_option
        ;;
    2)
        echo $REPLY
        echo $install_option
        ;;
    *)
        echo $REPLY
        echo "other exit"
        break
        ;;
    esac
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

$REPLY 是 Bash Shell 中的一个特殊内部变量,用于存储用户在脚本中使用 read 命令时所提供的输入。当用户输入文本并按下 Enter 键时,read 命令将用户的输入存储在 $REPLY 变量中。

# 其他场景

# 查看命令是否存在

if ! command -v test-cli-tool &>/dev/null; then
    # 如果test-cli-tool不存在,则执行此段逻辑
fi
1
2
3

# 脚本需要以root运行

if [ $(id -u) -ne 0 ]; then
    echo "This script must be run as root"
    exit 1
fi
1
2
3
4