变量的类别
Bash
中的变量可以存储字符串、数字、只读型和数组,按照作用范围可以分为全局变量(环境变量)和局部变量。
全局变量
全局变量也被叫做环境变量,其作用范围包括所有的 shell
,可以通过以下方式查看全局变量:
> printenv
> env # 除了打印环境变量之外还有很多其他功能
我们可以通过 export
命令设置临时的环境变量. 但要注意, 脚本中 export
的变量只能影响其子进程, 因此子进程不能通过 export
变量的方式影响其父进程.
局部变量
局部变量仅在当前 shell
中生效( subshell
可以访问但无法直接修改, child shell
无法访问),例如:
> cat var1.sh # 事先写好的脚本
echo $local_ip
echo $ENV_IP
> local_ip=1.1.1.1
> export ENV_IP=2.2.2.2
> bash var1.sh # 在 child shell 中执行
2.2.2.2
> source var1.sh # 在当前 shell 中执行
1.1.1.1
2.2.2.2
> (echo $local_ip;local_ip=3;);echo $local_ip # subshell 可以访问但无法修改
1.1.1.1
1.1.1.1
使用变量
声明变量的类型
我们可以按照如下方式生命一个指定类型的变量:
declare OPTION(s) VARIABLE=value
declare
中支撑声明的类型如下表所示:
| Option | Meaning |
| :-- | :-- |
| -a | 数组型 |
| -f | 函数名 |
| -i | 整数型 |
| -p | 查看某个变量的类型和属性, 如果选了这个选项则其它选项失效.|
| -r | 只读变量, 不能被再次赋值, 也不能被 unset
.|
| -t | 给变量添加 trace
属性.|
| -x | 可以被 child shell
访问的变量.|
> declare -i VAR=12
> VAR=string
> echo $VAR
0 # "string"被强转为整型 赋值给VAR 所以是0
> declare -p VAR
declare -i VAR="0"
# 未被声明类型的变量
> OTHERVAR=blah
> declare -p OTHERVAR
declare -- OTHERVAR="blah"
定义变量
默认情况下,变量名是区分大小写的,按照习惯,环境变量名全大写,局部变量名全小写。变量的命名规则如下:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用
bash
里的关键字(可用help命令查看保留关键字)。
KAGURA # 合法
LD_LIBRARY_PATH # 合法
_var # 合法
var2 # 合法
?var=123 # 非法
2var=223 # 非法
user*name=remilia # 非法
[warning]警告:给变量赋值时,等号左右两侧都不能有空格,这可能和你之前学过的所有编程语言都不一样。
除了使用 =
进行显式赋值外,还可以用语句进行隐式赋值,例如,以下语句会遍历打印 /etc
目录下的文件名:
> for file in `ls /etc` ; do echo $file; done
> for file in $(ls /etc); do echo $file; done
使用 export
内置命令可以定义(或转换)全局变量:
> full_name="Remilia Scarlet"
> bash
> echo $full_name
# 无输出
> exit
> export full_name # 转换为全局变量
> bash
> echo $full_name
Remilia Scarlet
> export full_name="Sakura Miko" # 直接定义全局变量
> echo $full_name
Sakura Miko
> exit
> echo $full_name # 都可以访问
Sakura Miko
父 shell
定义的局部变量 full_name
无法被子进程访问,但将其导出为全局变量后就可以被子进程访问了;而且我们在子进程中修改 full_name
同时会影响父 shell
。
引用变量
通过在变量名前添加# 变量的类别
Bash` 中的变量可以存储字符串、数字、只读型和数组,按照作用范围可以分为全局变量(环境变量)和局部变量。
全局变量
全局变量也被叫做环境变量,其作用范围包括所有的 shell
,可以通过以下方式查看全局变量:
> printenv
> env # 除了打印环境变量之外还有很多其他功能
我们可以通过 export
命令设置临时的环境变量. 但要注意, 脚本中 export
的变量只能影响其子进程, 因此子进程不能通过 export
变量的方式影响其父进程.
局部变量
局部变量仅在当前 shell
中生效( subshell
可以访问但无法直接修改, child shell
无法访问),例如:
> cat var1.sh # 事先写好的脚本
echo $local_ip
echo $ENV_IP
> local_ip=1.1.1.1
> export ENV_IP=2.2.2.2
> bash var1.sh # 在 child shell 中执行
2.2.2.2
> source var1.sh # 在当前 shell 中执行
1.1.1.1
2.2.2.2
> (echo $local_ip;local_ip=3;);echo $local_ip # subshell 可以访问但无法修改
1.1.1.1
1.1.1.1
使用变量
声明变量的类型
我们可以按照如下方式生命一个指定类型的变量:
declare OPTION(s) VARIABLE=value
declare
中支撑声明的类型如下表所示:
| Option | Meaning |
| :-- | :-- |
| -a | 数组型 |
| -f | 函数名 |
| -i | 整数型 |
| -p | 查看某个变量的类型和属性, 如果选了这个选项则其它选项失效.|
| -r | 只读变量, 不能被再次赋值, 也不能被 unset
.|
| -t | 给变量添加 trace
属性.|
| -x | 可以被 child shell
访问的变量.|
> declare -i VAR=12
> VAR=string
> echo $VAR
0 # "string"被强转为整型 赋值给VAR 所以是0
> declare -p VAR
declare -i VAR="0"
# 未被声明类型的变量
> OTHERVAR=blah
> declare -p OTHERVAR
declare -- OTHERVAR="blah"
定义变量
默认情况下,变量名是区分大小写的,按照习惯,环境变量名全大写,局部变量名全小写。变量的命名规则如下:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用
bash
里的关键字(可用help命令查看保留关键字)。
KAGURA # 合法
LD_LIBRARY_PATH # 合法
_var # 合法
var2 # 合法
?var=123 # 非法
2var=223 # 非法
user*name=remilia # 非法
[warning]警告:给变量赋值时,等号左右两侧都不能有空格,这可能和你之前学过的所有编程语言都不一样。
除了使用 =
进行显式赋值外,还可以用语句进行隐式赋值,例如,以下语句会遍历打印 /etc
目录下的文件名:
> for file in `ls /etc` ; do echo $file; done
> for file in $(ls /etc); do echo $file; done
使用 export
内置命令可以定义(或转换)全局变量:
> full_name="Remilia Scarlet"
> bash
> echo $full_name
# 无输出
> exit
> export full_name # 转换为全局变量
> bash
> echo $full_name
Remilia Scarlet
> export full_name="Sakura Miko" # 直接定义全局变量
> echo $full_name
Sakura Miko
> exit
> echo $full_name # 都可以访问
Sakura Miko
父 shell
定义的局部变量 full_name
无法被子进程访问,但将其导出为全局变量后就可以被子进程访问了;而且我们在子进程中修改 full_name
同时会影响父 shell
。
引用变量
通过在变量名前添加符号来引用变量:
> vat1=24
> echo $var1
24
> echo ${var1}sec. # 加大括号标识变量边界
24sec.
> echo $var1+$var1 # 因为 `+` 不是合法变量字符,因此不用加大括号
24+24
> echo $var1p$var1 # 这种就需要加大括号
24 # 第一个变量 var1p 找不到,只输出第二个
[info]提示:除了只读变量之外,变量可以被多次赋值,以最后一次定义为准。
用 readonly
命令可以将变量定义(或转换)为只读变量:
> var2=''scarlet"
> var2="remilia"
> readonly var2 # 转换为只读变量
> var2="Sakuya"
bash: var2: readonly variable
> readonly var3="miko" # 只能在定义时给只读变量赋值
> var3="sakura"
bash: var3: readonly variable
> readonly var3="miko" # 只读变量不可以重复定义
bash: var3: readonly variable
> unset var3 # 只读变量不能删除
bash: unset: var3: cannot unset: readonly variable
[info]提示:除了只读变量之外,可以用
unset <变量名>
的方式删除变量。
字符串
字符串是shell编程中最常用最有用的数据类型,但是在使用中要注意区分单引号、双引号和转义字符。
> your_name="remilia"
# 使用单引号拼接
> greeting_2='hello, '$your_name' !'
> greeting_3='hello, ${your_name} !'
> echo $greeting_2 $greeting_3
hello, remilia ! hello, ${your_name} !
# 使用双引号拼接
> greeting="hello, "$your_name" !"
> greeting_1="hello, ${your_name} !"
> echo $greeting $greeting_1
hello, remilia ! hello, remilia !
# 转义字符
> echo “\$your_name -- \'$your_name\'”
“$your_name -- 'remilia'”
# 高级用法
> echo ${#your_name} # 字符串长度
7
> echo ${your_name:1} # 从索引 1 开始截取 3 个字符
emi
> echo ${your_name:2:-1} # 从索引 2 开始截取到倒数第 1 个字符
mili
> expr index $your_name ia
查找字符 i 或 a 的索引,查找到就立即返回,不继续查找
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,转义字符也是无效的;
- 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
应用案例
变量的特点就是可以循环利用,我们可以将脚本中要用的路径名、用户名、文件名等都定义为变量,这样我们就可以通过修改变量的值完成对脚本中所有相关配置的修改:
#!/bin/bash
BACKUPFILES=$USER.back
SERVER=localhost
REMOTEDIR=/opt/backup/$USER
LOGFILE=/tmp/log/home_backup.log # 定义日志文件路径
# 变量作为参数
scp $BACKUPFILES $SERVER:$REMOTEDIR > /dev/null 2>&1
date >> $LOGFILE # 多次使用
echo backup succeeded >> $LOGFILE # 多次使用
特殊变量
字符 | 定义 |
---|---|
$* |
以一个单字符串显示所有向脚本传递的参数。如果在 " 中使用,则以 "$1 $2 … $n" 的形式输出所有参数。 |
$@ |
比 $* 更安全的参数查看方式,如果在 " 中使用、则以 "$1" "$2" … "$n" 的形式输出所有参数。 |
$# |
查看传入的参数个数。 |
$? |
上一条命令的退出状态。 |
$- |
当前 shell 的状态标记。 |
$ |
当前 shell 的进程ID。 |
$! |
最近在后台(异步)执行的命令的进程ID。 |
$0 |
shell 或者 shell 脚本名。 |
$_ |
上一条命令的最后一个参数。 |
我们来看一个参数传递脚本 positional.sh
的例子:
#!/bin/bash
POSPAR1="$1"
POSPAR2="$2"
POSPAR3="$3"
echo "$1 is the first positional parameter, \$1."
echo "$2 is the second positional parameter, \$2."
echo "$3 is the third positional parameter, \$3."
echo “\$@: $@”
echo "The total number of positional parameters is $#."
# 运行这个脚本并传递参数
> bash positional.sh 1 2 3 4 5
1 is the first positional parameter, $1.
2 is the second positional parameter, $2.
3 is the third positional parameter, $3.
“$@: 1 2 3 4 5”
The total number of positional parameters is 5.
其他特殊参数的使用例子:
> uname -a
Linux CT7GK 5.3.0-40-generic #32-Ubuntu SMP Fri Jan 31 20:24:34 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
> echo $_
-a
> echo $
22372
> bash
> echo $0 $PPID $
bash 22372 28505
> exit
> top &
[1] 28574
> echo $!
28574
> ls doesnotexit
> echo $?
1
保留关键字
PS1
、 PATH
等变量名为 sh
的保留关键字,更多信息可以查看保留字表。
数组
我们可以通过以下方式创建数组:
# 注意索引是从 0 开始
# 这样初始化则其他索引对应的数据为空
> ARRAY[INDEXNR]=value
# 声明一个数组 不做初始化 全为空
> declare -a ARRAYNAME
# 批量初始化
> ARRAY=(value1 value2 ... valueN)
> ARRAY=( `COMMAND` ) # 如果用 cat、ls 等则每一行是一个元素
通过 ${ARRAY[*]}
可以访问数组中的所有元素, 通过 $ARRAY
可以访问数组的 index
为 0
的元素.
> ARRAY=(one two three)
> echo $ARRAY[*]
one[*] # 注意这种写法是错误的
> echo ${ARRAY[*]}
one two three
> echo ${#ARRAY[*]}
3 # 统计元素个数
> echo ${!ARRAY[*]}
0 1 2 # 获得全部索引
> ARRAY[3]=four
> echo ${ARRAY[*]}
one two three four
> echo ${ARRAY[2]} # 注意大括号
three
我们可以通过 unset
删除数组的某个元素或者整个数组:
> ARRAY=(one two three four)
> unset ARRAY[1]
> echo ${ARRAY[*]}
one three four
> unset ARRAY
> echo ${ARRAY[*]}
# 没有任何输出
以数字作为索引的数组支持切片:
> echo ${ARRAY[@]:1} # 从索引 1 开始
> echo ${ARRAY[@]:1} # 同上
> echo ${ARRAY[@]:1:2} # 从索引 1 开始, 访问 2 个元素
哈希数组用于统计数量:
# 输入文件格式 name gender
declare -A gender
while read line; do
g=·echo line | awk ‘{print $2}’·
let gender[$g]++
done < gender.txt
# 等价于
> awk '{print $2}' gender.txt | sort | uniq -c
> awk '{s[$2]++} END { for(i in s) { print i, s[i] }}'
产生随机整数
我们使用内置变量 RANDOM
可以产生范围在 [0, 32767]
之间的整数.
函数
定义方式:
1. 函数名() { 函数体 }
2. function 函数名() { 函数体 }
3. { 匿名函数 }
函数的参数独立于脚本:
pod () {
ret = 1
for i in {1..$1}; do
let ret*=$i
done
return $ret
}
result= `pod 5` # 本例中函数体中的 $1 就是 5
# 也可以传递数组
pod_array() {
local newarray($*) # 重新构建数组
local ret=1 # 局部变量
for i in $@; do
# 等价于 for i
# 不写 in 就等于所有参数
let ret*=$i
done
return $ret
}
array=(1 2 3 4 5)
pod_array ${array[@]}
在函数中使用内置变量 FUNCNAME
可以查看当前函数的名字.