一、使用环境变量
1、环境变量
所谓环境变量,就是用来存储有关shell会话和工作环境信息的特性,这项特性允许你在内存中存储数据,以便程序或shell中运行的脚本能够轻松访问到它们。这也是存储持久数据的一种简便方法。
环境变量分为 : 全局环境变量、局部环境变量
(1)全局环境变量
全局环境变量对于shell会话和所有生成的子shell都是可见的。
我们可以使用env或printenv命令查看全局变量,要显示个别环境变量的值,可以使用printenv命令,也可以使用echo命令,在这种情况下引用某个环境变量的时候,必须在变量前面加上一个美元符($)。
[root@relay3.mobvista.com:101.251.254.6 shell]#env //显示做了删减,太多了
HOSTNAME=relay3.mobvista.com
SHELL=/bin/bash
USER=root
... ...
HOME=/root
LOGNAME=root
... ...
[root@relay3.mobvista.com:101.251.254.6 shell]#printenv
HOSTNAME=relay3.mobvista.com
SHELL=/bin/bash
TERM=xterm
USER=root
... ...
HOME=/root
LOGNAME=root
... ...
[root@relay3.mobvista.com:101.251.254.6 shell]#printenv SHELL //查看环境变量值
/bin/bash
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $SHELL
/bin/bash
[root@relay3.mobvista.com:101.251.254.6 shell]#env SHELL
env: SHELL: No such file or directory
(2)局部环境变量
局部环境变量只能在定义它们的进程中可见。但是在Linux系统并没有一个只显示局部环境变量的命令。set命令会显示为某个特定进程设置的所有环境变量,包括局部变量、全局变量以及用户定义变量。
[root@relay3.mobvista.com:101.251.254.6 shell]#set
BASH=/bin/bash
... ...
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=()
BASH_SOURCE=()
... ...
所有通过printenv命令能看到的全局环境变量都出现在了set命令的输出中。
但在set命令的输出中还有其他一些环境变量,即局部环境变量和用户定义变量。
2、用户自定义变量
(1)局部用户定义变量
我们可以在shell进程内创建可见的局部变量,可以通过等号给环境变量赋值,值可以是数值或字符串。系统环境变量是默认大写的,自己创建的局部变量或是shell脚本,最好使用小写字母,这能够避免重新定义系统环境变量可能带来的灾难。
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
[root@relay3.mobvista.com:101.251.254.6 shell]#my_variable=Hello
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Hello
[root@relay3.mobvista.com:101.251.254.6 shell]#my_variable="Hello World" //赋值含空格时带引号
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Hello World
设置局部环境变量后,就能在shell进程的任何地方使用它了。但是,如果生成了另外一个shell,它在子shell中就不可用。当你退出子shell并回到原来的shell时,这个局部环境变量依然可用。
类似地,如果你在子进程中设置了一个局部变量,那么一旦你退出了子进程,那个局部环境变量就不可用。
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Hello World !
[root@relay3.mobvista.com:101.251.254.6 shell]#bash //进入子shell
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable //my_variable局部变量不可用
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_test //定义my_test局部变量
[root@relay3.mobvista.com:101.251.254.6 shell]#my_test=123
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_test
123
[root@relay3.mobvista.com:101.251.254.6 shell]#exit
exit
You have new mail in /var/spool/mail/root
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Hello World !
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_test //子shell定义的my_variable局部变量不可用
(2)全局用户定义变量
export命令可以将局部环境变量定义为全局环境变量,且修改子shell中全局环境变量并不会影响到父shell中该变量的值。
[root@relay3.mobvista.com:101.251.254.6 shell]#my_variable="Global"
[root@relay3.mobvista.com:101.251.254.6 shell]#export my_variable //定义为全局变量
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Global
[root@relay3.mobvista.com:101.251.254.6 shell]#bash //进入子shell
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Global
[root@relay3.mobvista.com:101.251.254.6 shell]#my_variable=test //修改
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
test
[root@relay3.mobvista.com:101.251.254.6 shell]#exit
exit
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable //不影响父shell
Global
删除环境变量,同样,我们需要在父shell中删除,在子shell中删除也不会反映到父shell。
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
Global
[root@relay3.mobvista.com:101.251.254.6 shell]#unset my_variable
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $my_variable
二、构建基本脚本
1、显示消息
echo命令是shell脚本中与用户交互的重要工具。你会发现在很多地方都能用到它,尤其是需要显示脚本中变量的值的时候。
echo命令可用单引号或双引号来划定文本字符串。如果在字符串中用到了它们,你需要在文本中使用其中一种引号,而用另外一种来将字符串划定起来。像下面这样:
[root@relay3.mobvista.com:101.251.254.6 shell]#echo "Let's go"
Let's go
[root@relay3.mobvista.com:101.251.254.6 shell]#echo 'This method called "Comparative"'
This method called "Comparative"
要把文本字符串和命令输出显示在同一行中,该怎么办呢?可以用echo语句的-n参数
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test1.sh
#! /bin/bash
#This script displays the date and who's logged on
echo -n "The Time: " //-n将文本和输出显示在同一行
date
echo "Who's logged:"
who
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test1.sh
The Time: Tue Jul 23 17:02:30 CST 2019 //文本和输出在一行
Who's logged:
mobsa pts/0 2019-07-23 14:34 (61.34.166.62)
2、使用变量
shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户的默认主目录以及shell查找程序的搜索路径。可以用set命令来显示一份完整的当前环境变量列表。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test2.sh
#! /bin/bash
#This script displays user's information
echo USER:$USER
echo UID:$UID
echo HOME:$HOME
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test2.sh
USER:root
UID:0
HOME:/root
echo命令中的环境变量会在脚本运行时替换成当前值。如,我们$USER系统变量放置到双引号中,而shell依然能够知道我们的意图。但只要脚本在引号中出现美元符,它就会以为你在引用一个变量。在这个例子中,脚本会尝试显示变量$1(但并未定义),再显示数字5。要显示美元符,你必须在它前面放置一个反斜线。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#echo "User info for userid:$USER"
User info for userid:root
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#echo "The cost is $15"
The cost is 5
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#echo "The cost is \$15"
The cost is $15
shell脚本还允许在脚本中定义和使用自己的变量。定义变量允许临时存储数
据并在整个脚本中使用。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test3.sh
#!/bin/bash
# testing variables
days=10
guest="Katie"
echo "$guest checked in $days days ago"
days=5
guest="Jessica"
echo "$guest checked in $days days ago"
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test3.sh
Katie checked in 10 days ago
Jessica checked in 5 days ago
shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本数据时尤为方便。
有两种方法可以将命令输出赋给变量:
- 反引号字符(`)
- $()格式
示例1 : shell会运行命令替换符号中的命令,并将其输出赋给变量testing。注意,赋值等号和命令替换字符之间没有空格。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test4.sh
#!/bin/bash
testing=$(date)
echo "The date and time are: " $testing
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test4.sh
The date and time are: Tue Jul 23 17:35:15 CST 2019
示例2 : 提取日期信息来生成日志文件名
+%y%m%d格式告诉date命令将日期显示为两位数的年月日的组合,在脚本中通过命令替换获得当前日期并用它来生成唯一文件名。目录中出现的日志文件采用$today变量的值作为文件名的一部分。日志文件的内容是/usr/bin目录内容的列表输出。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#date +%y%m%d
190723
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test5.sh
#!/bin/bash
# copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test5.sh
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#ls
log.190723 test1.sh test2.sh test3.sh test4.sh test5.sh
3、重定向、追加和管道
(1)输入/出重定向、追加
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#date > test6 //输出重定向
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test6
Tue Jul 23 17:48:23 CST 2019
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#who > test6
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test6
mobsa pts/0 2019-07-23 14:34 (61.34.166.62)
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#date >> test6 //追加
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test6
mobsa pts/0 2019-07-23 14:34 (61.34.166.62)
Tue Jul 23 17:49:26 CST 2019
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#wc < test6 //输入重定向
2 11 83
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#wc test6
//wc命令可以对对数据中的文本进行计数。默认情况下,它会输出3个值:
- 文本的行数
- 文本的词数
- 文本的字节数
(2)管道
管道被放在命令之间,将一个命令的输出重定到另一个命令中:
command1 | command2
不要以为由管道串起的两个命令会依次执行。Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#rpm -qa | sort
acl-2.2.49-7.el6_9.1.x86_64
alsa-lib-1.1.0-4.el6.x86_64
... ...
除非你的眼神特别好,否则可能根本来不及看清楚命令的输出。由于管道操作是实时运行的,所以只要rpm命令一输出数据,sort命令就会立即对其进行排序。等到rpm命令输出完数据,sort命令就已经将数据排好序并显示了在显示器上。
我们可以在一条命令中使用任意多条管道。可以持续地将命令的输出通过管道传给其他命令来细化操作。在这个例子中,sort命令的输出会一闪而过,所以可以用一条文本分页命令(例如less或more)来强行将输出按屏显示。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#rpm -qa | sort | less
也可以搭配使用重定向和管道来将输出保存到文件中。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#rpm -qa | sort > rpm.list
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#more rpm.list
acl-2.2.49-7.el6_9.1.x86_64
alsa-lib-1.1.0-4.el6.x86_64
... ...
4、数学运算
最开始,Bourne shell提供了一个特别的命令用来处理数学表达式:expr命令,允许在命令行上处理数学表达式,但是特别笨拙。对于那些容易被shell错误解释的字符,在它们传入expr命令之前,需要使用shell的转义字符(反斜线\)将其标出来,否则会报错。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#expr 5 * 7
expr: syntax error
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#expr 5 \* 7
35
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#expr 5 | 0 //或
-bash: 0: command not found
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#expr 5 \| 0
5
同样,在shell脚本中使用expr命令也比较麻烦
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test6.sh
#!/bin/bash
# An example of using the expr command
var1=3
var2=7
var3=$(expr $var2 \* $var1)
echo The result is $var3
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test6.sh
The result is 21
bash shell提供了一种更简单的方法来执行数学表达式。在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#var1=$[3 * 7]
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#echo $var1
21
用方括号执行shell数学运算比用expr命令方便很多,这种技术也适用于shell脚本。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test7.sh
#!/bin/bash
var1=100
var2=50
var3=45
var4=$[$var1 * $[$var2 - $var3]] //可以替换为var4=$[$var1 * ($var2 - $var3)]
echo The final result is $var4
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test7.sh
The final result is 500
bash shell数学运算符只支持整数运算。有几种解决方案能够克服bash中数学运算的整数限制。最常见的方案是用内建的bash计算器,叫作bc。
bc中,浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
3.44/5
0
scale=3 //保留三位小数
3.44/5
.688
quit
在脚本中使用bc
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test8.sh
#!/bin/bash
var1=$(echo "scale=4;3.44 / 5" | bc)
echo The answer is $var1
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test8.sh
The answer is .6880
一旦变量被赋值,那么,这个变量也可以用于其他运算。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test9.sh
#!/bin/bash
var1=20
var2=3.141
var3=$(echo "scale=3; $var1 * $var2" | bc)
var4=$(echo "scale=3; $var3 * $var2" | bc)
echo result=$var4
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test9.sh
result=197.317
这个方法适用于较短的运算,但有时你会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。最好的办法是使用内联输入重定向,它允许你直接在命令行中重定向数据。EOF文本字符串标识了内联重定向数据的起止。
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#cat test10.sh
#!/bin/bash
var1=10.2
var2=7.7
var3=5.03
var4=21
var5=$(bc << EOF
scale=4
a=$var2 * $var1
b=$var4 * $var3
a + b
EOF
)
echo "Result is $var5"
[root@vg-adn-adxCreateImageAli:47.90.255.30 haha]#sh test10.sh
Result is 184.17
5、退出脚本
上面的脚本示例中,运行完最后一条命令时,脚本就结束了。其实还有另外一种更优雅的方法可以为脚本划上一个句号。
shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用。
Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码。我们了解一下常见的退出状态码 :
[root@relay3.mobvista.com:101.251.254.6 shell]#date
Wed Jul 31 09:57:16 CST 2019
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $? //0表示命令成功结束
0
[root@relay3.mobvista.com:101.251.254.6 shell]#asdfg
-bash: asdfg: command not found
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $? //127表示无效命令
127
[root@relay3.mobvista.com:101.251.254.6 shell]#date -t //-t是无效参数
date: invalid option -- 't'
Try `date --help' for more information.
You have new mail in /var/spool/mail/root
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $? //一般性未知错误,退出状态码为1
1
默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。exit命令允许你在脚本结束时指定一个退出状态码。
[root@relay3.mobvista.com:101.251.254.6 shell]#cat test.sh
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[$var1 + $var2]
echo The answer is $var3
exit 5
[root@relay3.mobvista.com:101.251.254.6 shell]#sh test.sh
The answer is 40
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $?
5
也可以在exit命令的参数中使用变量。
[root@relay3.mobvista.com:101.251.254.6 shell]#cat test.sh
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[$var1 + $var2]
echo The answer is $var3
exit $var3
[root@relay3.mobvista.com:101.251.254.6 shell]#sh test.sh
The answer is 40
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $?
40
注意的是,因为退出状态码最大只能是255。所以当指定值大于255时,shell会对其做模256的运算,看一个例子 :
[root@relay3.mobvista.com:101.251.254.6 shell]#cat test.sh
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[$var1 * $var2]
echo The answer is $var3
exit $var3
[root@relay3.mobvista.com:101.251.254.6 shell]#sh test.sh
The answer is 300
[root@relay3.mobvista.com:101.251.254.6 shell]#echo $? //退出状态码为300mod256
44