End

bash 教程-3 shell 变量 数组 算术表达式 函数

本文地址


目录

Bash 教程

本文改编自 网道的 Bash 教程,主要为了精简大量本人不感兴趣的内容。

变量 $

Bash 变量分成环境变量和自定义变量两类。

  • 环境变量是 Bash 环境自带的变量,可以直接使用。通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。
  • 自定义变量是用户在当前 Shell 里面自己定义的变量,仅在当前 Shell 可用。
set   # 显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数

环境变量 $PATH

很多环境变量很少发生变化,而且是只读的,可以视为常量。

由于环境变量的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。

env           # 【printenv】,显示所有环境变量
echo $PATH    # 【printenv PATH】,查看单个环境变量的值
echo -e ${PATH//:/"\n"}  # 将环境变量 PATH 中的所有分隔符,由【:】替换成【\n】,并解释为换行符

echo -e "$BASHPID \n$BASHOPTS \n$DISPLAY \n$EDITOR \n$HOME \n$HOST \n$IFS \n$LANG"
echo -e "$PS1 \n$PS2 \n$PWD \n$RANDOM \n$SHELL \n$SHELLOPTS \n$TERM \n$UID \n$USER"

常见的环境变量。

  • BASHPID:【104】Bash 进程的进程 ID
  • BASHOPTS:当前 Shell 的参数,可以用shopt命令修改
  • DISPLAY:图形环境的显示器名字,通常是:0,表示 X Server 的第一个显示器
  • EDITOR:默认的文本编辑器
  • HOME:【/home/bqt】用户的主目录
  • HOST:当前主机的名称。
  • IFS:词与词之间的分隔符,默认为空格
  • LANG:【C.UTF-8】字符集以及语言编码,比如zh_CN.UTF-8
  • PATH:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表
  • PS1:Shell 提示符
  • PS2:【>】输入多行命令时,次要的 Shell 提示符
  • PWD:【/home/bqt】当前工作目录
  • RANDOM:【19211】返回一个0到32767之间的随机数
  • SHELL:【/bin/bash】Shell 的名字
  • SHELLOPTS:启动当前 Shell 的set命令的参数
  • TERM:【xterm-256color】终端类型名,即终端仿真器所用的协议
  • UID:【1000】当前用户的 ID 编号
  • USER:【bqt】当前用户的用户名

定义变量

  • 变量名的规则和 Java 等编程语言一样
  • 变量名区分大小写
  • Bash 没有数据类型的概念,所有的变量值都是字符串
  • 注意,变量名和等号之间不能有空格
var=value    # 变量名区分大小写,变量值都是字符串
b="b qt"     # 如果变量值包含空格,必须放在引号里面
e=$(pwd)     # 【e=`pwd`】变量值可以是命令的执行结果
f=$((5*7))   # 变量值可以是数学运算的结果
foo=1;bar=2  # 同一行定义多个变量,必须使用分号分隔

for file in $(ls $(pwd)); do echo $file; done

读取变量

  • 如果变量不存在,会输出空字符,而不会报错
  • 在变量名与其他字符连用的情况下,必须使用 花括号 包围的格式:${var}
  • 如果变量的值本身也是变量,必须在变量前面加 感叹号 才能读取最终的值:${!var}
  • 变量值包含连续空格、制表符、换行符时,直接读取时会被合并成一个空格;放在 双引号 里面能保持原来的格式
echo $100.00   # 【00.00】Bash 将【$1】解释成了变量,该变量不存在,会输出空字符
echo \$100.00  # 【$100.00】加反斜杠进行转义

bqt=baiqiantao
echo $bqt2022    # 【(空)】Bash 将其整个 bqt2022 解释为变量,而这个变量是不存在的
echo ${bqt}2022  # 【baiqiantao2022】在变量名与其他字符连用的情况下,必须使用花括号包围

var=USER
echo $var     # 【USER】
echo ${!var}  # 【bqt】如果变量的值本身也是变量,必须使用【${!var}】读取最终的值

a="1 2   3"
echo $a     # 【1 2 3】直接读取时,Shell 会将连续空格合并成一个
echo "$a"   # 【1 2   3】放在双引号里面读取时,能保持原来的格式

删除变量 unset

unset 命令用来删除一个变量,这个命令等价于将变量设成空字符串。

这个命令不是很有用,因为不存在的 Bash 变量一律等于空字符串,所以即使用 unset 命令删除了变量,还是可以读取这个变量,值为空字符串。

# 以下三种方式效果是一样的
unset bqt
bqt=''
bqt=

特殊变量 $0 $# $*

Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。

  • $0:当前 Shell 的名称(在命令行直接执行时),或者脚本文件名(在脚本中执行时)
  • $n:传递到脚本的第 n 个参数的值,建议使用 ${n} 来获取
  • $#:传递到脚本的参数数量
  • $*:显示所有向脚本或函数传递的参数,与$@基本相同,区别见下文
  • $@:显示所有向脚本或函数传递的参数,与$*基本相同,区别见下文
  • $?:上一个命令的退出码,返回 0 表示上一个命令执行成功;否则,表示上一个命令执行失败
  • $$:当前 Shell 的进程 ID,常用来辅助命名临时文件
  • $-:当前 Shell 的启动参数
  • $_:上一个命令的最后一个参数,不一定是用户输入的参数
  • $!:最近一个后台执行的异步命令的进程 ID,没有的话返回空
# 调用格式【./test.sh 29 男】
chmod 777 test.sh
echo $0   #【./test.sh】脚本文件名
echo $1   #【29】
echo $2   #【男】

echo $#   #【2】传递到脚本的参数数量
echo $*   #【29 男】以一个字符串显示所有向脚本或函数传递的参数
echo $@   #【29 男】

echo $?   #【0】上一个命令的退出码
echo $$   #【14372】当前 Shell 的进程 ID
echo $-   #【hBH】当前 Shell 的启动参数
echo $_   #【hBH】上一个命令的最后一个参数,不一定是用户输入的参数
echo $!   #【】最近一个后台执行的异步命令的进程 ID

$*$@ 的区别:在双引号中,$* 的作用是将所有参数当做一个参数

# 【./test.sh 29 男 白乾涛】
for i in $@;   do echo -n $i-; done;  # 【29-男-白乾涛】
for i in $*;   do echo -n $i-; done;  # 【29-男-白乾涛】

for i in "$@"; do echo -n $i-; done;  # 【29-男-白乾涛】
for i in "$*"; do echo -n $i-; done;  # 【29 男 白乾涛】注意,这里是将所有参数当做一个参数

for i in '$@'; do echo -n $i-; done;  # 【$@】
for i in '$*'; do echo -n $i-; done;  # 【$*】

变量未定义的默认值 ${}

  • ${var:-word}:如果变量 var 存在且不为空,则返回它的值,否则返回 word
  • ${var:=word}:如果变量 var 存在且不为空,则返回它的值,否则将它设为 word,并且返回 word
  • ${var:+word}:如果变量 var 存在且不为空,则返回 word,否则返回空值,可用来测试变量是否存在
  • ${var:?info}:如果变量 var 存在且不为空,则返回它的值,否则中断脚本的执行,并打印 var: info
echo ${abc:-"变量未定义"}  # 【变量未定义】
echo ${abc:="baiqiantao"} # 【baiqiantao】
echo ${bqt:+"变量已定义"}  # 【变量已定义】
echo ${abc:?"变量未定义"}  # 【bash: abc: 变量未定义】变量未定义时就中断执行,并返回给定的报错信息
echo ${abc:?}             # 【bash: abc: parameter null or not set】可以省略报错信息

上面四种语法如果用在脚本中,变量名的部分可以用数字19,表示脚本的参数。

声明输出变量 export

  • export 命令用来向子 Shell 输出变量。
  • 用户创建的变量仅可用于当前 Shell,使用 export 命令可以把变量传递给子 Shell
  • 通过 export 命令输出的变量,对于子 Shell 来说就是环境变量
  • 子 Shell 如果修改继承的变量,不会影响父 Shell
bqt=baiqiantao
export bqt       # 输出变量 bqt,执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量 bqt
export var=value # 给变量赋值并输出

# 子 Shell 如果修改继承的变量,不会影响父 Shell
bash             # 新建子 Shell
echo $bqt        # 读取 $bqt
bqt=baz          # 修改继承的变量
exit             # 退出子 Shell
echo $bqt        # 读取 $bqt

声明只读变量 readonly

声明只读变量,等同于 declare -r,声明后无法改变变量值,也不能 unset 变量

readonly 命令有三个参数

  • -f:声明的变量为函数名
  • -p:打印出所有的只读变量
  • -a:声明的变量为数组

声明时执行算术式 let

使用 let 命令声明变量时,可以直接执行算术表达式。

let a=1+2      # 使用 let 命令声明变量时,可以直接执行算术表达式
let "b =1 +2"  # 参数表达式如果包含空格,需要使用引号
echo $a $b     # 【3 3】

let "v1 = 1" v2=v1++  # 可以同时对多个变量赋值,赋值表达式之间使用空格分隔
echo $v1,$v2          # 【2,1】,v2=v1++ 的意思是:先返回 v1 的值,然后 v1 再自增

声明特殊变量 declare

declare 命令可以声明一些特殊类型的变量。declare 命令如果用在函数中,声明的变量只在函数内部有效,等同于 local 命令。

  • 声明整数变量 -i:声明后可以直接进行数学运算
  • 声明只读变量 -r:等同于 readonly 命令,声明后无法改变变量值,也不能 unset 变量
  • 声明大写字母 -u:声明后可以自动把变量值转成大写字母
  • 声明小写字母 -l:声明后可以自动把变量值转成小写字母
  • 输出变量信息 -p:输出已定义变量的值,未定义的变量会提示找不到;不带参数时输出所有变量的信息
  • 输出环境变量 -x:等同于 export 命令,可以将一个变量输出为子 Shell 的环境变量
  • 输出所有函数名 -F:不包含函数定义
  • 输出所有函数定义 -f:包含函数定义
  • 声明数组变量 -a
# 声明整数变量
declare -i a=2 b=5 # 声明整数变量,声明后可以直接进行数学运算
declare -i c=a*b # 只要一个变量声明为整数,它的赋值就会自动解释为整数运算,这里 a、b 可不声明为整数变量
echo $a*$b=$c    # 【2*5=10】
c=bqt; echo $c   # 【0(不确定的值)】变量声明为整数以后,如果被改写为字符串,不会报错,但会赋以不确定的值

# 声明只读变量
declare -r r=1   # 声明只读变量,等同于 readonly 命令,声明后无法改变变量值,也不能 unset 变量
r=2              # 【bash: r: readonly variable】报错,命令执行失败
unset r          # 【bash: unset: r: cannot unset: readonly variable】

# 声明大/小写
declare -u u     # 声明大写字母,声明后可以自动把变量值转成大写字母
u=bQt; echo $u   # 【BQT】
declare -l l     # 声明小写字母,声明后可以自动把变量值转成小写字母
l=bQt; echo $l   # 【bqt】

# 输出变量信息
p=bqt
declare -p p     # 【declare -- p="bqt"】
declare -p pp    # 【bash: declare: pp: not found】
declare -p       # 输出所有变量的信息

# 其他
declare -x x     # 输出环境变量,等同于【export x】,可以将一个变量输出为子 Shell 的环境变量
declare -F       # 输出当前环境的所有函数名,不包含函数定义
declare -f       # 输出当前环境的所有函数,包括它的定义
declare          # 等同于【set】命令,输出当前环境的所有变量以及函数

数组 () []

数组 array 是一个包含多个值的变量。成员的编号(或称为下标、索引、位置)从 0 开始,数量没有上限,也没有要求成员被连续索引。

创建数组

数组可以采用逐个赋值的方法创建,也可以采用一次性赋值的方式创建。

array[$INDEX]=value       # 采用逐个赋值的方法创建,索引可以是任意整数或算术表达式
array=(v0 v1 ... vN)      # 采用一次性赋值的方式创建,成员之间使用空格或换行分割
array=aaa                 # 赋值时,如果没有指定位置,默认使用 0 号位置

array=(a b c)             # 采用一次性赋值的方式创建时,可以按照默认顺序赋值
array=([2]=c [0]=a [1]=b) # 也可以在每个值前面指定位置
array=(a [5]=b c)         # 也可以只为某些值指定位置,a 的位置是 0,c  的位置是 6
  • 数组名 ARRAY 可以是任意合法的变量名,编号 INDEX 可以是任意 >=0 的整数或算术表达式
  • 采用一次性赋值的方式创建时,可以按照默认顺序赋值,或在每个值前面指定位置,或只为某些值指定位置
  • 没有赋值的数组元素的默认值是空字符串
mp3_array=(*.mp3) # 将当前目录的所有 MP3 文件,放进一个数组
declare -a ARRAY  # 可以使用命令【declare -a】声明一个数组
read -a dice      # 可以使用命令【read -a】可将用户的命令行输入,存入一个数组

读取数组 ${a[i]}

可以使用 ${array[index]} 读取数组成员,其中的大括号是必不可少的。

array=(a b c)
echo ${array[0]} - $array[0]   # 【a - a[0]】
echo ${array}                  # 【a】读取时,如果没有指定位置,默认使用 0 号位置
echo ${array[@]} - ${array[*]} # 【a b c】特殊索引 @ 和 * 表示返回数组的所有成员

for i in "${array[@]}"; do echo -n $i-; done  # 【a-b-c-】
array_copy=("${array[@]}")                    # 拷贝数组
array_copy=("${array[@]}" xxx)                # 拷贝数组的同时,为新数组添加成员

需要特别注意 @* 的区别

array=(a "b c" d) # 这个数组只有 3 个成员,其中一个成员内部有空格
for i in "${array[@]}"; do echo -n $i-; done # 【a-b c-d-】有双引号时,正确遍历出 3 个成员
for i in ${array[@]}; do echo -n $i-; done   # 【a-b-c-d-】无双引号时,错误遍历出 4 个成员

for i in "${array[*]}"; do echo -n $i-; done # 【a b c d-】有双引号时,错误遍历出 1 个成员
for i in ${array[*]}; do echo -n $i-; done   # 【a-b-c-d-】无双引号时,错误遍历出 4 个成员

删除数组 unset

  • 可以使用 unset array[index] 删除一个数组成员,使用 unset array 清空整个数组
  • 将某个成员设为空值,可以从返回值中隐藏这个成员,隐藏后这个成员仍然存在,只是值变成了空值
  • 直接将数组变量赋值为空字符串,相当于隐藏数组的第一个成员
array=(a b c) && unset array[1]                # 删除一个数组成员
echo ${array[@]} - ${#array[@]} - ${!array[@]} # 【a c - 2 - 0 2】

array=(a b c) && array[1]=""                   # 从返回值中隐藏一个成员,等价于 array[1]=
echo ${array[@]} - ${#array[@]} - ${!array[@]} # 【a c - 3 - 0 1 2】

unset array                                    # 清空整个数组,等价于 array=()
echo ${array[@]} - ${#array[@]} - ${!array[@]} # 【- 0 -】

获取数组长度 ${#a[@]}

可以使用 ${#array[@]}${#array[*]} 获取数组的长度。

unset array                           # 清空整个数组,等价于 array=()
array[3]=a && echo ${#array[@]}       # 【1】数组只有 1 个元素
array=([3]=a b) && echo ${#array[*]}  # 【2】数组只有 2 个元素
array[3]=abcdef && echo ${#array[3]}  # 【6】注意,此语法是返回该成员的字符串长度

提取数组序号 ${!a[@]}

可以使用 ${!array[@]}${!array[*]} 提取数组的成员序号,即有值的位置序列。

array=([5]=a [2]=b [99]=c) && echo ${!array[@]}       # 【2 5 99】
for i in ${!array[@]}; do echo -n ${array[i]}-; done  # 【b-a-c-】

提取数组成员 ${a[@]:p}

可以使用 ${array[@]:position:length}${array[@]:position} 提取数组成员。

这个语法的真实含义是:从第 position 个位置开始,提取 length 个有值的成员。

array=(a b c d e)
echo ${array[@]:1:1} - ${array[@]:1:3} - ${array[@]:1}  # 【b - b c d - b c d e】

array=([0]=a [3]=b [99]=c)
echo ${array[@]:1:1} - ${array[@]:1:3} - ${array[@]:1}  # 【b - b c - b c】

追加数组成员 a+=( )

可以使用 array+=(...) 在数组末尾追加成员。否则,就需要知道数组的最大序号。

array=(a b c) && array+=(d e) && echo ${array[@]}     # 【a b c d e】
array=(a [3]=b c) && array+=(d e) && echo ${array[@]} # 【a b c d e】

声明关联数组 declare -A

可以使用 declare -A array 声明关联数组(Bash 新版本中才支持),关联数组使用字符串而不是整数作为数组索引。

declare -A array                  # 声明关联数组
array["a"]="android"
array["0"]="xx" && array[1]="yy"
echo ${array[a]}                  # 【android】访问关联数组成员
echo ${array[0]} - ${array[1]}    # 【xx - yy】这里的整数不代表索引,而是代表字符串

整数的算术运算 (( )) $[]

((...)) 语法可以进行整数的算术运算。另外,也可以用 $[] 语法。

  • $[ ] 作为 算术表达式 时,中括号与内部的表达式之间【不需要有空格】
  • [ ] 作为 条件表达式 时(即 test 命令语法),中括号与内部的表达式之间【必须有空格】
((foo = 5 +5))  # 算术表达式,可以进行整数的算术运算,会自动忽略内部的空格
echo $? $foo    # 【0 10】只要算术结果不是 0,命令就算执行成功
(( 3 - 3 ))
echo $?         # 【1】如果算术结果为 0,命令就算执行失败

echo $((2 + 2)) # 【4】算术表达式不返回值,但可在前面加上 $ 读取算术运算的结果
echo $[2+2]     # 【4】此语法也可以做整数运算,但容易和 test 命令混淆,不建议使用
echo $((1.5+1)) # 【syntax error】只能对整数进行运算,如果有小数会报错

# 在 $((...)) 内部,逗号前后的两个表达式都会执行,并返回后一个表达式的值
echo $((a=1+2, b=3*4))  # 【12】两个表达式都会执行,并返回后一个表达式的值
echo $a $b              # 【3 12】

如果在 $((...)) 里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,而不会报错。

n=2; m="k"; k=n
echo $(($n + n))    # 【4】圆括号中,变量名前面的 $ 可以省略
echo $(("n" + 1))   # 【3】圆括号中的字符串会认为是一个变量名
echo $(("nn" + 1))  # 【1】如果不存在同名变量,会将其作为空值,而不会报错
echo $((k + m + n)) # 【6】动态替换

数值的进制

Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数
  • 0number:八进制数
  • 0xnumber:十六进制数
  • base#numberbase进制的数
echo $((0xff)) - $((2#11111111))  # 【255 - 255】

max=$(( (1<<63)-1 )); # 最大值
echo $max $((max+1))  # 【9223372036854775807 -9223372036854775808】

# 十进制转为其它进制,需要使用到外部命令 bc 完成
echo "obase=2; $max" | bc  # 【111...111】一共有 64 个 1

算术运算 +-*/% ++

  • +-*/
  • %:余数
  • ++:自增运算
  • --:自减运算
  • **:指数运算,只有这个和 Java 等编程语言不一样
echo $((5 / 2)) - $((5 % 2)) - $((2 ** 3))  # 【2 - 1 - 8】
echo $(( (2+3)*4 )) -  $(( $((2+3)) * 4 ))  # 【20 - 20】可以嵌套

i=0; j=0
echo $((i++)) - $i  # 【0 - 1】作为后缀是先返回值,再进行自增运算
echo $((++j)) - $j  # 【1 - 1】作为前缀是先进行自增运算,再返回值

位运算 << >> &|^~

  • <</>>:位的左/右移运算,所有位向左/右移动指定的位
  • &|^:位的与/或/异或运算,对两个数字的所有位执行一个AND/OR/异或操作
  • ~:位的运算,对一个数字的所有位取反
echo $((4>>2)) - $((4<<2))              # 【1 - 16】
echo $((17&3)) - $((2#10001 & 2#00011)) # 【1 - 1】
echo $((17|3)) - $((2#10001 | 2#00011)) # 【19 - 19】
echo $((17^3)) - $((2#10001 ^ 2#00011)) # 【18 - 18】

min=$(( (1<<63) ));    # 最小值
max=$(( (1<<63)-1 ));  # 最大值
echo $((~-2)) $((~-1)) $((~min)) $((~1)) $((~2))  # 【1 0 -1 -2 -3】
echo $((min-1)) $((min-2)) $((min-3))  # 【-1 -2 -3】

逻辑运算 ><= && !

  • > < >= <= == !=
  • && || !:逻辑与或非
  • expr1?expr2:expr3:若表达式 expr1 的计算结果为非零值,则执行表达式 expr2,否则执行表达式 expr3
echo $((3 > 2))             # 【1】如果逻辑表达式为真,返回 1
echo $(( (3>2) && (4<=1) )) # 【0】如果逻辑表达式为假,返回 0
echo $(( 2<1 ? 8 : 6 ))     # 【6】注意,后面只能是表达式,而不是是 echo 等语句

赋值运算

  • parameter = value
  • += -= *= /= %= <<= >>= &= |= ^=
echo $((a=1+1))  # 【2】对变量 a 进行赋值,这个式子的返回值就是等号右边的值
echo $a          # 【2】
echo $((a*=2))   # 【4】

echo $(( a>1 ? (a+=1) : (a-=1) ))   # 【5】在表达式内部赋值,需要放在圆括号中

expr 和 let 命令

  • expr 是一款表达式计算工具,使用它也能完成以上所有 整数运算,但是运算符并不完全一样
  • let 命令用于将算术运算的结果,赋予一个变量。
a=3
expr $a + 2         # 运算符前后必须至少有一个空格
echo `expr $a + 2`  # 使用反引号可以获取表达式的返回值
echo `expr $a \* 2` # 乘法 * 需要加反斜杠 \ 进行转义

expr 3.5 + 2 # 【expr: non-integer argument】同样不支持非整数的运算

let x=2+3    # 表达式【x=2+3】里面不能有空格,否则会报错
echo $x      # 【5】

函数 function

  • 函数体内可以使用参数变量获取函数参数,函数的参数变量与脚本的参数变量是一致的
  • 函数与别名的区别:别名适合封装简单的单个命令,函数则适合封装复杂的多行命令
  • 函数与脚本的区别:函数在当前 Shell 中执行,脚本是在新建的一个子 Shell 中执行
  • 如果函数与别名、脚本同名,执行的优先级是:别名 > 函数 > 脚本
function funName() {  # 定义函数的关键字 function 可以省略
  some codes          # 不加 return 时将以最后一条命令的运行结果作为返回值
  # return someValue  # return 命令用于立即结束函数执行,并返回一个值给调用者
  # return            # return 命令后面不跟参数时,此时函数执行后通过 $? 拿到的返回值是 0
}

funName xxx yyy zzz   # 调用函数,函数名后面是参数列表
unset -f funName      # 删除函数,不加 -f 时是删除变量的语法
declare -f            # 按照函数名的字母表顺序输出所有已经定义的函数,包含函数名及所有定义
declare -F            # 按照函数名的字母表顺序输出所有已经定义的函数的函数名,不含函数体
declare -f funName    # 查看单个函数的定义

和 Java、C 等编程语言不同,Bash 函数体内声明的、读取的、修改的变量,都是全局变量,这一点需要特别小心。函数里面可以用 local 命令声明局部变量。

fn () {
  foo=1               # 默认声明的是全局变量
  local bar=1         # 使用 local 声明局部变量
  echo "$foo - $bar"  # 【1 - 1】
}

fn
echo "$foo- $bar"     # 【1 -】

2022-01-03

posted @ 2022-01-03 15:59  白乾涛  阅读(453)  评论(1编辑  收藏  举报