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 进程的进程 IDBASHOPTS
:当前 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】可以省略报错信息
上面四种语法如果用在脚本中,变量名的部分可以用数字1
到9
,表示脚本的参数。
声明输出变量 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#number
:base
进制的数
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
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/15759764.html