【Linux】Shell - 变量

Posted by 西维蜀黍 on 2020-04-26, Last Modified on 2022-04-01

定义变量

定义变量时,变量名不需要加美元符号($),如:

variableName="value"

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

  • 首个字符必须为字母(a-z,A-Z)。
  • 中间不能有空格,可以使用下划线(_)。
  • 不能使用标点符号。
  • 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。

Bash 没有数据类型的概念,所有的变量值都是字符串。

下面是一些自定义变量的例子。

# 不使用任何引号
$ myGender=man
# 使用单引号
$ myName='Wei Shi'
# 使用双引号
$ myUrl="http://test.com"
# 可以将一个数字赋值给变量(但是这个变量仍然是一个字符串变量)
$ myNum=100

三种定义方法

直接赋值就是使用一个等于号了,这在其他编程语言里也很常见,比如:

a=123
b=abc
c=123
d=hello world

注意空格

需要注意的是等号左右不能有空格!!!比如如果出现a = 22,执行该.sh时则会直接报错

无引号

b=abc

单引号

Bash 允许字符串放在单引号或双引号之中,加以引用。

单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号(*)、美元符号($)、反斜杠(\)等。

str='this is a string'

这意味着:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
  • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行,因为转义字符”\“也变成了普通字符),但可成对出现(即作为字符串拼接使用)。
# 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
$ a="sss"
$ str='this is a string $a'
$ echo $str
this is a string $a

# 单引号字串中不能出现单独一个的单引号(即使对单引号使用转义符后也不行)
$ str='this is a string\' for testing'
zsh: parse error near `for'

# 单引号字串中的单引号可以成对出现(来实现字符串拼接)
$ str='this is a string'' hahaha'
$ echo $str
this is a string hahaha

双引号

双引号比单引号宽松,可以保留大部分特殊字符的本来含义,但是三个字符除外:美元符号($)、反引号(`` )和反斜杠(`)。也就是说,这三个字符在双引号之中,会被 Bash 自动扩展。

# 双引号的引号之间可以使用变量,来进行字符串拼接
$ your_name="Wei"
$ str="Hello, I know you are $your_name"
$ echo $str
Hello, I know you are Wei
# 或者
$ str="Hello, I know you are "$your_name
$ echo $str
Hello, I know you are Wei

# 进行字符串拼接
$ str="I am $your_name, Hello"
$ echo $str
I am Wei, Hello
$ str="I am ${your_name}, Hello"
$ echo $str
I am Wei, Hello

# 双引号里可以出现双引号(通过使用转义字符)
$ str="I am \"Wei\""
$ echo $str
I am "Wei"

双引号的优点:

  • 双引号的引号之间可以使用变量,来进行字符串拼接
  • 双引号里可以出现双引号(通过使用转义字符)

从标准输入读取并为变量赋值

读取终端的输入给变量赋值,就是使用read命令。read和echo一样都是内嵌命令。直接看代码:

echo -n "Please Input your name:"
read name
echo "Hi,$name,welcome to uncle Jelly's cabin!"

或者可以使用read命令的 -p 选项来简化上述代码:

read -p "Please Input your name:" name
echo "$name,welcome to uncle jelly's cabin!"

变量类型

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

下面的例子尝试更改只读变量,结果报错:

#!/bin/bash

myUrl="http://test.com"
readonly myUrl
myUrl="http://test1.com"

运行脚本,结果如下:

/bin/sh: NAME: This variable is read only.

数值变量

数值变量赋值与运算

需要注意的是 Shell 默认赋值是字符串赋值,因此进行下面的操作:

$ myAge=20
$ myAgeAdd=$myAge+1
$ echo $myAgeAdd
29+1

使用 let

在 Bash 中,如果要将算术表达式的数值赋值给一个变量,可以使用 let 命令,如下所示:

$ let var=2+1
$ echo $var
3
# 自增
$ let var+=1
$ echo $let
4

使用 $[] - 表示进行数值运算

$ x=1
$ echo $[$x+1]
2
# 自增
$ ((x++))
$ echo $x
3

使用 expr

expr命令为Linux中的命令,一般用于整数值计算,但也可用于字符串操作。

使用格式:

expr var1 operator var2
$ x=1
$ expr $x + 4 
5
# b=expr $x + 4 是错误的
$ b=`expr $x + 4 `
$ echo $b
5
$ a=6
# 加法
$ expr $a + $b
11
# 减法
$ expr $a - $b
1
# 乘法
$ expr $a \* $b
30
$ val=`expr $a \* $b`
$ echo "a * b : $val"
a * b : 30
# 除法
$ expr $a / $b
1
# 求余
$ expr $a % $b
1

需要注意的是:

  • 操作符和操作数之间一定要有空格间隔
  • 操作数(即变量)前必须有$符
  • 使用 expr 进行乘法运算时,要使用 \*, 即使用反斜杠 \ 进行转义
  • 该命令会将计算结果打印到标准输出
  • 仅支持整数运算(如果操作数不为整数,命令将会报错)
  • 也可以直接使用数字的字面值

使用 $(())

$ b=5
# a=(($b+1)) 是错误的
$ a=$(($b+1))
$ echo $a

$ x=1
# 自增
$ ((x++))
$ echo $x
3

字符串变量

获取字符串长度

$ str="abcd"
$ echo ${#str} 
4

提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

$ string="swsmile is a great site"
$ echo ${string:1:4} 
wsmi

从左边第几个字符开始一直到结束

$ string="swsmile is a great site"
$ echo ${string:1} 
wsmile is a great site

查找子字符串

查找字符 io 的位置(哪个字母先出现就计算哪个):

$ string="runoob is a great site"
$ echo "expr index $string io"  
4

# - 从左向右搜索指定字符(串),搜索到后向右取指定字符右侧的所有字符(不包括指定字符)

这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell 可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符。

使用#号可以截取指定字符(或者子字符串)右边的所有字符,具体格式如下:

${string#*chars}

其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)。

$ url="https://swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/"
$ echo ${url#*:}
//swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/

如果不需要忽略 chars 左边的字符,那么也可以不写*,例如:

$ url="https://swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/"
$ echo ${url#http://}
swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/

## - 从右向左搜索指定字符(子字符串),搜索到后向右取指定字符(或者子字符串)右边的所有字符(不包括指定字符)

如果希望直到遇到最后一个指定字符(子字符串),才匹配结束,那么可以使用##,具体格式为:

${string##*chars}

比如:

$ url="https://swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/aa"
$ echo ${url#*/}
/swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/aa
$ echo ${url##*/}
aa

% - 从右向左搜索指定字符(子字符串),搜索到后取指定字符(或者子字符串)左边的所有字符(不包括指定字符)

${string%*chars}

比如:

$ url="https://swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/aa"
$ echo ${url%*/}
/swsmile.info/2020/08/22/%E3%80%90Microservices%E3%80%91service-mesh/aa
$ echo ${url##*/}
aa

数组(array)

数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。

与大部分编程语言类似,数组元素的下标由0开始。

Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下:

array_name=(value1 value2 ... valuen)

声明

# To declare static Array 
arr=(prakhar ankit 1 rishabh manish abhinav)

# or
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"

# If you are using the bash shell, here is the syntax of array initialization −
array_name=(value1 ... valuen)
my_array=("a" "b" "c")

Access Array Values

After you have set any array variable, you access it as follows −

${array_name[index]}

Here array_name is the name of the array, and index is the index of the value to be accessed. Following is an example to understand the concept −

#!/bin/sh

NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
echo "First Index: ${NAME[0]}"
echo "Second Index: ${NAME[1]}"

The above example will generate the following result −

For Loop

#!/bin/bash
array=("A" "B" "ElementC" "ElementE")
for element in "${array[@]}"
do
    echo "$element"
done

echo
# get array's len
echo "Number of elements: ${#array[@]}"
# or
echo "Number of elements: ${#array[*]}"
echo
echo "${array[@]}"

Result:

A
B
ElementC
ElementE

Number of elements: 4

A B ElementC ElementE

总结

格式 说明
${string: start :length} 从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。
${string: start} 从 string 字符串的左边第 start 个字符开始截取,直到最后。
${string: 0-start :length} 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。
${string: 0-start} 从 string 字符串的右边第 start 个字符开始截取,直到最后。
${string#*chars} 从 string 字符串第一次出现 *chars 的位置开始,取 *chars 右边的所有字符(不包括*chars)。
${string##*chars} 从 string 字符串最后一次出现 *chars 的位置开始,取 *chars 右边的所有字符(不包括*chars)。
${string%*chars} 从 string 字符串第一次出现 *chars 的位置开始,取 *chars 左边的所有字符(不包括*chars)。
${string%%*chars} 从 string 字符串最后一次出现 *chars 的位置开始,取 *chars 左边的所有字符(不包括*chars)。

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:

$ your_name="wei"
$ echo $your_name
$ echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

for skill in Ada Coffe Action Java 
do
    echo "I am good at ${skill}Script"
done

如果不给 skill 变量加花括号,写成 echo “I am good at $skillScript”,解释器就会把 $skillScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

推荐给所有变量加上花括号,这是个好的编程习惯。

将命令的执行结果赋值给变量

$ var=`pwd`
$ echo $var

此时输出:

/Users/wei.shi/Downloads

或者也可以使用 $(...) 来实现同样的功能

$ var=$(pwd)
$ echo $var

变量操作

重新定义变量

已定义的变量,可以被重新定义,如:

$ myUrl="http://test.com"
$ echo ${myUrl}
$ myUrl="http://test1.com"
$ echo ${myUrl}

删除变量

使用 unset 命令可以删除变量。语法:

$ unset variable_name

变量被删除后不能再次使用;unset 命令不能删除只读变量。

举个例子:

#!/bin/sh

myUrl="http://test.com"
unset myUrl
echo $myUrl

上面的脚本没有任何输出。

变量类型

运行 shell 时,会同时存在三种变量:

1) 局部变量

局部变量在脚本或命令中定义,仅在当前 shell 实例中有效,其他 shell 启动的程序不能访问局部变量。

2) 环境变量

所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量。

3) shell 特殊变量(special parameters)

shell 特殊变量(special parameters)是由 shell 程序设置的特殊变量。shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 shell 的正常运行:

  • $$:Shell本身的PID(ProcessID)
  • $!:Shell最后运行的后台Process的PID
  • $?:最后运行的命令的结束代码(返回值)
  • $- :使用Set命令设定的Flag一览
  • $*:传递到Shell的所有参数列表(每个参数之间用空格连接)
  • $@:传递到Shell的所有参数列表(每个参数之间用换行连接)
  • $#:传递到Shell的参数个数
  • $0:Shell本身的文件名
  • $1~$n:传递到Shell的各参数值。$1是第1参数、$2是第2参数…。

Example:

$ vim bash.sh
#!/bin/bash
#
printf "%s\n" "$$"
printf "%s\n" "$!"
printf "%s\n" "$?"

printf "%s\n" "$*"
printf "%s\n" "$@"

printf "%s\n" "$#"
printf "%s\n" "$0"
printf "%s\n" "$1"
printf "%s\n" "$2"
$ bash ./test.sh aaa bbb
78487     # $$:当前Shell的PID)
          # $!:Shell最后运行的后台Process的PID 
0         # $?:最后运行的命令的结束代码(返回值) 

aaa bbb   # $*:传递到Shell的所有参数列表(每个参数之间用空格连接)
aaa       # $@:传递到Shell的所有参数列表(每个参数之间用换行连接)
bbb 

2         # $#:传递到Shell的参数个数 
./test.sh # $0:Shell本身的文件名
aaa       # $1: 传递到Shell的第0个参数的值
bbb       # $2: 传递到Shell的第2个参数的值

Reference