一、shell脚本初邂逅
把多条命令写到文件中,之后交给解释器一条一条的执行,这个解释器是/bin/bash,这个文件就是脚本。
下面老陌就编写一个脚本,用来显示当前时间和当前登录到系统的用户。 文件名用post.sh,其实文件名无所谓,自已喜好。
#!/bin/bash
date
who
其中#!/bin/bash是我们选择的解释器,告诉我们用bash来解释下面的代码。date显示日期,who显示谁登录了系统。可以看出脚本就是一条条命令堆在一起。
- 脚本的第一行必须指定用哪个解释器, 看来不只是/bin/bash一个解释器。
- 给脚本一个可执行权限,这个老陌会加权限。
- 执行脚本时需要指定脚本所在的路径,或把脚本加入到系统路径PATH中。你也可以把脚本复制到/bin目录中,因为/bin在PATH里了。
对于上面的脚本添加可执行权限,运行:
chmod u+x ./post.sh
./post.sh
原来就是这样简单。对于老陌这样的小白,计算机瘫痪经常事,所以练就了重装系统的强大本领。老陌想能不能把需要的软件写个脚本一次性安装呢?老陌准备下一次安装系统时就试一试。
二、返回值
linux系统中每个进程都有寿命。 每个进程通过另一个进程的请求而启动。发出请求的进程称为父进程,新启动的进程称为子进程。
子进程也可能再产生子进程,当子进程任务完成后退出,退出时会返回信息给父进程,叫做返回值或退出状态。
父进程可以收集子进程的返回值。(我倒!腐败无处不在啊)
返回值以整数的形式出现,范围是0-255。程序可以在退出时随意选择返回值。linux中规定返回0表示成功,以外的数表示程序运行失败。返回值保存在 $? 的特殊变量中。
linux@myccloves:~$ ./post.sh
Thu Jul 19 20:45:14 CST 2018
linux tty1 2018-07-19 14:35 (:0)
linux pts/2 2018-07-19 20:32 (172.16.18.113)
linux@myccloves:~$ echo $?
0
linux@myccloves:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied
linux@myccloves:~$ echo $?
1
我们运行自己写的脚本,由于返回值保存到 $? 中,所以我们打印一下看看:echo $? ,通过观察老陌写的脚本返回0,表示成功运行。再用cat查看一下密码文件,提示权限拒绝,看来运行打脸了,人家不让看。用echo $? 打印返回值,结果不是0,表示失败。
总结一下:
- 每个命令是一个进程,退出命令向父进程返回一个整数值
- 返回的整数值交给变量$?
提示:echo $? 这也是一条命令,也有返回值。所以打印命令是否成功你只能用一次echo $?, 第二次就不是原先那条命令的返回值了。
三、有条件的运行多个命令
之前我们学习过用;分隔命令,之后一条一条执行。但是有些时候下一条命令根据上一条命令的结果才决定执行与不执行,需要用 &&和|| 把两个命令有条件地连接在一起。
(一) &&
描述这样一个场景,老陌领着小白们去砸场子,如果砸第一家成功,再砸第二家。(第一条命令运行成功,再运行第二条)
linux@myccloves:~$ mkdir /tmp/砸第一家 && echo "走,去砸第二家"
走,去砸第二家
我们发现第一条命令:mkdir /tmp/砸第一家,运行成功后,会运行 echo "走,去砸第二家"。 也就是说用&&连接的两条命令,第一条命令运行成功之后才会运行第二条,如果不成功呢?
linux@myccloves:~$ mkdir /砸第一家 && echo "走,去砸第二家"
mkdir: cannot create directory ‘/砸第一家’: Permission denied
这次我们发现没打过第一家,运行失败,所以不会执行第二条命令。
(二) ||
||也是用于连接两条命令,如果第一条命令运行失败,再运行条二条,否则不运行第二条。
linux@myccloves:~$ mkdir /tmp/砸第一家 || echo "大哥,没打过啊,上医院吧"
通过观察,没有输出,表示运行成功。也就是第一条成功了,就不执行第二条了。
linux@myccloves:~$ mkdir /砸第一家 || echo "大哥,没打过啊,上医院吧"
mkdir: cannot create directory ‘/砸第一家’: Permission denied
大哥,没打过啊,上医院吧
通过观察,输出了“大哥,没打过啊,上医院吧”,第一条命令失败,所以执行第二条命令。
总结一下:
- 多条命令用;隔开,依次执行
- 多条命令用&&,||隔开,根据前面的执行是否成功,再判断是否执行第二条命令。
四、bash变量
我们可以自己定义变量和引用变量。
(一)shell变量有两种类型:
- 局部变量
- 全局变量
局部变量只能在创建它的shell中使用,全局变量也叫环境变量,可以在创建它的shell和子shell中使用,也就是在子shell中可以访问父shell中定义的变量。
(二)定义与访问局部变量:
linux@myccloves:~$ name=老陌
变量名=变量的值 ,通过这种方式定义变量 ,这里要注意等号两边不能有空格。如果想要访问这个变量,则用:$+变量名 , 如:
linux@myccloves:~$ echo $name
老陌
老陌发现:
linux@myccloves:~$ echo "欢迎" $name "$name" '$name'
欢迎 老陌 老陌 $name
在命令里面,$name读取这个变量的值,放到双引号中,依然读取变量的值,但放到单引号中就原样输出了。
(三)变量的命名规则
- 由[a-zA-Z0-9_]组成的任意名字,但不能以数字开头
- 变量名区分大小写
比如下面的名字因为大小写不同,则shell就不认识了。
linux@myccloves:~$ name=老陌
linux@myccloves:~$ echo 欢迎$name同学。
欢迎老陌同学。
linux@myccloves:~$ echo 欢迎$Name同学。
欢迎同学。
那些没有被定义的变量shell解析的时候当成空值。就当空气吧,看看输出时啥也没有。
(四)变量赋值要注意的问题:
linux@myccloves:~$ name=What is your name?
bash: is: command not found
linux@myccloves:~$ name="What is your name?"
linux@myccloves:~$ echo $name
What is your name?
当有多个单词时,之间会有空格,这样必须用双引号或单引号括起来,当成一个整体对待。我们发现,如果没有引号,提示错误了。
(五)访问变量的另一种方法:
老陌想要拼接一些字符串,我们一起分析一下:
linux@myccloves:~$ word=he
linux@myccloves:~$ echo $word
he
linux@myccloves:~$ echo $word llo
he llo
linux@myccloves:~$ echo $wordllo
linux@myccloves:~$
首先定义变量word,值为he,之后测试成功的输出he,之后打印变量和后面跟上的部分字符,输出he llo。但老陌发现多了一个空格,这不是老陌要的,老陌要一个完整的单词,所以$wordllo,但结果是空值……
因为shell把wordllo当成变量名了,而不是用户想的先输出$word,再输出llo
这种情况我们用另一种方法访问变量:
linux@myccloves:~$ echo ${word}llo
hello
这种通过${变量名}的方式访问就不会出错了。原来访问变量有两种方法。
- $变量名
- ${变量名}
五、SHELL中定义的变量
只读变量
变量名 | 说明 |
---|---|
? | 上一条命令的返回值。 |
- | 启用shell选项标记 |
$ | 当前shell的进程id |
! | 最新后台命令的进程id |
_ | 前一个命令的最后标记 |
PPID | shell父进程的id |
SHELLOPTS | 被冒号隔开的当前启用中的shell选项 |
UID | 当前用户的用户ID |
这里面我们熟悉返回值等,但老陌不明白的!,还有-,SHELLOPTS以后再说,不明白的原因是不理解其意义。
预赋值变量
变量名 | 说明 |
---|---|
BASH_VERSION | 当前bash的版本 |
HOSTNAME | 主机名 |
OLDPWD | 上一次工作目录 |
PWD | 当前工作目录 |
RANDOM | 产生0-32767之间的随机整数 |
SECONDS | 自shell启动以来的秒数 |
这几个变量不难理解,而且老陌发现,这些SHELL全是大写的。
六、环境变量
环境变量就是上面说的全局变量,可以被子shell继承的变量。
创建环境变量的方法:
- 先定义变量
- 提升变量为环境变量
以下面的例子说明:
linux@myccloves:~$ name=老陌
linux@myccloves:~$ export name
linux@myccloves:~$ age="very old"
linux@myccloves:~$ bash
linux@myccloves:~$ echo $name $age
老陌
linux@myccloves:~$ exit
exit
linux@myccloves:~$ echo $name $age
老陌 very old
首先定义变量name,之后提升为全局变量, 再定义变量局部变量age。之后启用子shell,在子shell中访问name与age变量,发现name被继承下来,但age是局部变量则不可以访问。
当退出之后,再次访问这两个变量,都成功输出。说明退出子shell又回到了父shell,所以局部变量可用了。
我们可以把上面的两步合成一步。
export name=老陌
七、列出变量
set命令和env命令可以查看更多的变量。其中set列出了shell变量和与shell有关的环境变量,而env只列出环境变量。
老陌运行了一下,发现了大量的变量,之前接触过的都在里面了。
常用的环境变量
变量名 | 说明 |
---|---|
TERM | 指定了用户终端的底层配置 |
PATH | 指定了可执行文件的搜索目录 |
DISPLAY | 指定了图形环境中客户程序应用使用的X服务器 |
LANG | 指定了国际化程序的首选语言 |
EDITOR | 指定了许多程序的输入依靠的外部编辑器。 |
PRINTER | 与打印机相关的环境变量 |
昨天老陌还在论坛求助,fc命令默认的编辑器怎么变成了nano,后来有朋友留言说设置EDITOR这个环境变量就可以,今天终于学到了。
实例:配置环境变量PATH
老陌想把编写的脚本放到~/cmd目录里统一管理,但每次执行的时候都要写上相对路径或绝对路径,否则找不到脚本。我们知道LINUX查找命令时会在环境变量PATH指定的目录中查找,只要把自己的cmd目录放到PATH环境变量里就行了。
linux@myccloves:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin
通过查看环境变量PATH,得知多个路径写到一行上,不同路径之间用冒号分隔就可以了。
linux@myccloves:~$ mkdir cmd
linux@myccloves:~$ echo "echo hello" > cmd/start
linux@myccloves:~$ chmod u+x cmd/start
linux@myccloves:~$ start
bash: start: 未找到命令
linux@myccloves:~$ PATH=$PATH:/home/linux/cmd
linux@myccloves:~$ start
hello
首先我们创建cmd目录,之后编写脚本start放到cmd目录里,之后添加执行权限。
当我们运行start的时候,提示未找到命令,原因是系统去PATH指定的目录中查找,但cmd没在PATH中,所以下一行我们把cmd加到PATH中。
我们发现 PATH=引用原来PATH + :/home/linux/cmd 这样就完成了内容的拼接。再次运行start就可以了。
最后可以把设置PATH这行放到~/.bashrc文件中,这样打开终端就会自动执行,把cmd放到PATH环境变量中。