# 变量

# 赋值

  • 在 shell 中可以执行以下格式的命令,给变量赋值:
    var=value [var=value]...
    
    • 赋值时,如果变量不存在,则会自动创建它。
    • 创建变量时不需要声明其数据类型。
    • 赋值符号 = 的前后不能有空格,否则会被视作一条命令。如下:
      [[email protected] ~]# A=1
      [[email protected] ~]# A =1
      -bash: A: command not found
      [[email protected] ~]# A= 1
      -bash: 1: command not found
      

# 取值

# 变量的作用域

# 全局变量

:global variable ,是直接通过赋值符号 = 创建的变量,只能在当前 shell 中访问。

  • 可以在赋值变量的同时执行一条命令,此时该变量的作用域仅限于该命令。
    [[email protected] ~]# A=1 B=1 env | grep -e A= -e B=
    A=1
    B=1
    [[email protected] ~]# echo $A $B                   # 临时赋值的变量并不会保留
    
    
    [[email protected] ~]# export A=1 env | grep A=     # export 等命令不支持该语法
    [[email protected] ~]# echo $A
    
    
  • 可以从文件中导入多个变量,如下:
    echo IP=127.0.0.1 >   .env
    echo PORT=80      >>  .env
    
    作为全局变量导入:
    source .env
    
    作为环境变量导入:
    export `cat .env | sed 's/#.*//g' | xargs`
    

# 局部变量

:local variable ,是通过 local 命令定义的变量,只能在函数内创建,当函数运行结束时就会被自动删除。

# 环境变量

:environment variable ,是通过 set 、export 等命令定义的变量,可以被当前 shell 及其子 shell 访问。

  • 环境变量的名称通常全部大写。

  • 退出当前 shell 之后,定义的所有局部变量、环境变量都会被删除。因此,

    • 在子 shell 中创建的环境变量,不能被父 shell 访问。
    • 子 shell 会继承父 shell 的环境变量,但不能影响父 shell 的环境变量。
  • 常用的环境变量:

    USER=root
    LOGNAME=root
    HOME=/root
    HOSTNAME=VM_16_6_CentOS
    SHELL=/bin/bash
    PWD=/root
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin  # 目录之间用冒号 : 分隔
    TERM=linux
    LANG=en_US.utf8
    
    # PROMPT_COMMAND="history -a"   # 每次显示终端提示符 $PS1 之前,先执行命令 $PROMPT_COMMAND
    PS1='[\[email protected]\h \w]\$ '   # 终端提示符的格式。在终端每输入一行命令,就会显示一行新的终端提示符
    PS2='>'               # 输入多行命令时,终端提示符的格式
    PS3='#?'              # 在 shell 脚本中使用 select 命令时,每行显示的前缀
    PS4='+'               # set -x 模式下,每行显示的前缀
    
    • 变量 PS1、PS2 只在 shell 解释器的交互模式中存在。
  • 每次用户新建终端时会重新加载环境变量,可以采用以下方式永久配置环境变量:

    echo 'export PATH=$PATH:/root/bin'    >>  /etc/profile
    echo 'export PATH=$PATH:/usr/lib/jdk' >   /etc/profile.d/java.sh
    

# 内置变量

:shell 解释器提供了一些内置变量,可以在终端、脚本中调用。

  • 常用的内置变量:

    $0      # 当前脚本启动时,使用的文件名。这取决于启动命令,可能是绝对路径或相对路径
    $1      # 当前脚本启动时,输入的第一个参数。类似的还有 $2、$3、$4 等
    $*      # 当前脚本启动时,输入的全部参数(作为一个字符串返回)
    [email protected]      # 当前脚本启动时,输入的全部参数(作为多个字符串返回)
    $#      # 当前脚本启动时,输入参数的个数
    $!      # 最近一次运行的后台进程的 PID
    $?      # 最近一次执行的命令的返回码
    $$      # 当前进程的 PID
    $PPID   # 父进程的 PID
    $RANDOM # 返回一个 0 ~ 32767 范围的整数
    
  • 例:

    [[email protected] ~]# echo $0
    -bash
    [[email protected] ~]# echo $1
    
    [[email protected] ~]# echo $*
    
    [[email protected] ~]# echo $#
    0
    [[email protected] ~]# echo $?
    0
    [[email protected] ~]# echo $$
    11252
    
  • 例:获取随机数

    [[email protected] ~]# echo $RANDOM
    20383
    [[email protected] ~]# echo $[RANDOM*10/32768]    # 返回一个 0~9 范围的随机数
    7
    

# 数据类型

# 整型

# 字符串

  • 关于字符串的定界符。

  • 如果字符串中包含了与定界符相同的字符,会导致字符串被截断。可采用以下几种解决方法:

    • 将与定界符相同的字符声明为转义字符:
      [[email protected] ~]# echo \"
      "
      [[email protected] ~]# echo \'
      '
      [[email protected] ~]# echo "\"A\'B"    # 定界符为双引号时,不支持转义 \'
      "A\'B
      [[email protected] ~]# echo '\"'        # 定界符为单引号时,不支持转义字符
      \"
      
    • 将多个子字符串拼接在一起,依然会被视作一个字符串:
      [[email protected] ~]# echo 'A'\"\''B'    # 每个子字符串可以使用不同的定界符
      A"'B
      
  • 定界符为单引号时:

  • 字符串的处理示例:

    [[email protected] ~]# str=hello
    [[email protected] ~]# echo ${#str}       # 获取字符串的长度
    5
    [[email protected] ~]# echo ${str:0}      # 获取从第 0 个字符开始的所有字符
    hello
    [[email protected] ~]# echo ${str:0:3}    # 获取从第 0 个字符开始的 3 个字符
    hel
    [[email protected] ~]# echo ${str:4:3}
    o
    
    [[email protected] ~]# echo ${str/l/_}    # (从左开始)寻找第一个匹配 l 的位置,执行一次字符串替换
    he_lo
    [[email protected] ~]# echo ${str//l/_}   # 寻找所有匹配 l 的位置,执行一次字符串替换
    he__o
    [[email protected] ~]# echo ${str#*l}     # 删掉前缀 *l
    lo
    [[email protected] ~]# echo ${str%l*}     # 删掉后缀 l*
    hel
    

# 数组

  • shell 只支持一维数组,但不限制数组的长度。
  • 定义数组时要用小括号包住、用空格作为分隔符。
  • ${数组名[下标]} 的格式可以读取数组的某项元素,用 $数组名 的格式读取到的则是数组的第一项元素。
  • 例:
    [[email protected] ~]# array=(1 2 3 4)
    [[email protected] ~]# echo ${array[3]}
    4
    [[email protected] ~]# echo ${array[4]}   # 下标无效时,返回值为空
    
    [[email protected] ~]# echo $array
    1
    
  • 数组的处理示例:
    echo ${array[@]}     # 用 @ 作为下标,可获取数组的全部元素
    echo ${#array[@]}    # 获取数组的长度
    echo ${array[@]:0}   # 获取切片(0:]
    echo ${array[@]:0:5} # 获取切片(0:5]
    a[${#a[*]}]=5        # 在数组末尾添加元素
    

# 创建变量

# read

$ read <var>...           # 读取键盘的输入,赋值给一个或多个变量
      -p "Please input:"  # 显示提示
      -t 5                # 等待用户的输入最多 5 秒
      -n 5                # 用户最多输入 5 个字符,就会被自动结束输入
      -a                  # 将输入保存为数组类型
      -e                  # 允许在输入时按 Tab 自动补全
      -r                  # 不将反斜杠 \ 视作转义字符。默认会转义
      -s                  # 采用密文的形式输入
  • 例:
    read a
    read -p "请按下任意按键" -n 1 key
    
  • 当用户按下回车时就会结束输入。
    • 输入多个值时要以空格分隔。
    • 如果用户输入了太多值,多余的值会一起赋值给最后一个变量。如下:
      [[email protected] ~]# read a b
      1 2 3
      [[email protected] ~]# echo $a, $b
      1, 2 3
      

# set

$ set            # 显示当前 shell 的所有变量、函数
     -a <var>    # 将变量声明为环境变量(如果该变量不存在则无效)

     -e          # exit ,开启 e 模式,如果执行某个命令的返回码非 0 ,则立即退出当前 shell 。通常在 shell 脚本中启用,当命令执行失败时自动终止脚本
     +e          # 用减号 - 开启一个模式,用加号 + 关闭一个模式
     -u          # unset ,开启 u 模式,用到了未定义的变量时会报错
     -x          # execute ,开启 x 模式,执行每条命令之前,打印该命令到 stdout(每行开头会显示加号 + )
     -o pipefail # 通过管道符执行多个 shell 命令时,如果所有命令的返回码都为 0 ,最终返回码才为 0 。如果有多个命令的返回码非 0 ,则将最后一个非 0 的返回码作为最终返回码
  • 在一行内执行多个 shell 命令时,最后执行的一个命令的返回码,会作为最终的返回码。如下:
    [[email protected] ~]# true | false     # 先执行 true 命令,后执行 false 命令
    [[email protected] ~]# echo $?
    1
    
    [[email protected] ~]# true || false    # 最后执行的是 true
    [[email protected] ~]# echo $?
    0
    
    [[email protected] ~]# x=`false`        # 最后执行的是 false
    
    [[email protected] ~]# echo $?
    1
    
    [[email protected] ~]# echo `false`     # 最后执行的是 echo
    
    [[email protected] ~]# echo $?
    0
    
    • 通过管道符执行多个 shell 命令时,例如:
      [[email protected] ~]# false | true
      
      • 如果处于 set -e 模式,则不会导致 shell 退出。
      • 如果处于 set -eo pipefail 模式,则会导致 shell 退出,因此能检查管道符连接的每个命令都执行成功。

# unset

$ unset <var>...  # 删除当前 shell 的指定变量

# env

$ env             # 显示当前 shell 的所有环境变量

# export

$ export                  # 显示当前 shell 的所有环境变量及其 declare 语句
        <var>...          # 将变量声明为环境变量(如果该变量不存在则创建它)
        <var>=<value>...  # 将变量声明为环境变量并赋值

# declare

$ declare        # 显示当前 shell 的所有变量、函数
         -i      # 显示所有整型变量
         -i x    # 将变量声明为整型
         -i x=0  # 将变量声明为整型并赋值
         +i x    # 用加号 + 取消声明
  • shell 中创建的变量默认为字符串类型,用 declare 命令可以声明变量的数据类型。
  • 可用的选项:
    • a :数组
    • f :函数
    • i :整型变量
    • r :只读变量
    • x :环境变量
  • 如果用字符串给整型变量赋值,则整型变量的值会变成 0 。如下:
    [[email protected] ~]# declare -i x
    [[email protected] ~]# echo $x
    
    [[email protected] ~]# x=hello
    [[email protected] ~]# echo $x
    0
    
  • 只读变量一旦创建就不能被修改、不能被删除,只能等当前 shell 退出之后被销毁。
    • 不能用 unset 命令删除,不能用 declare +x 取消只读,也不能用 declare、readonly 命令重新赋值,

# readonly

$ readonly               # 显示所有只读变量
          <var>          # 将变量声明为只读变量
          <var>=<value>  # 将变量声明为只读变量并赋值

# 数学运算

# bc

:用于打开一个简单的计算器终端。

  • 例:通过管道符输入要计算的表达式
    [[email protected] ~]# echo "scale=2; 1/3.14" | bc    # scale 是小数点后保留的位数
    .31
    

# expr

expr <expression>   # 计算一个表达式的值,并输出到 stdout

# let

let <expression>...   # 执行一个或多个算术表达式,可以是赋值语句