Shell学习笔记(1)——shell脚本基础笔记

前言

本篇笔记的参考资料是(《Linux命令行与shell脚本编程大全》(第3版),外加百度辅助,本篇笔记主要内容是Shell脚本的一些基本知识。

使用命令行的一大特点就是能够写脚本,可以将一些命令写到一个脚本中,自动运行。而在Linux的命令行中,一次基本上只能运行一个或几个命令,就像下面的这样,它可以运行两个命令,只是要用分号隔开。

1
2
3
biotest@ubuntu:~/test/testfile$ date;who
Fri Apr 27 15:56:32 CST 2018
biotest tty7 2018-04-27 11:24 (:0)

这两个命令其实也可以视为一个非常简单的脚本,虽然它只有两个命令,作用就是显示今天的日期以及当前登录到系统中的用户。

创建shell脚本文件

如果要将shell命令放到文本文件中,就需要一个文本编辑器,Linux中常用的是vim,写好的脚本文件如下所示:

1
2
3
#!/bin/bash
date;who
# this is comments

将脚本文件保存,命令为test.sh。

现在解释一下这个脚本:

第1行:写入的是#!/bin/bash,#号是用于注释的,shell并不会处理注释,注释只是起到说明的作用,但第1行是个例外,#!的意思是要告诉shell要到哪个shell来运行,这里指定的是bash。

第2行:在第1行中指定了运行脚本的shell后,就可以输入命令了,输入一个命令后,回车,再输入另外一个命令,或者是用分号隔开两个命令。

命令结束后,输入脚本,如下所示:

1
2
biotest@ubuntu:~/test/testfile$ test.sh
test.sh: command not found

这是因为,shell是通过PATH环境变量来查找命令的,现在test.sh这个脚本文件并不在环境变量PATH中,因此如果要运行这个脚本,只有2种方法:

第一,将shell脚本文件所在的目录添加到PATH环境中;

第二,使用绝对或相对路径引用shell脚本文件。

现在使用第二种方法,如下所示:

1
2
biotest@ubuntu:~$ test
biotest@ubuntu:~$

发现没有反应,此时输入bash test,如下所示:

1
2
3
biotest@ubuntu:~/test/testfile$ bash test
Fri Apr 27 16:19:14 CST 2018
biotest tty7 2018-04-27 11:24 (:0)

test是一个脚本文件,通过ls -lF命令查看发现,test不是一个可执行程序,因此运行这个脚本,需要使用bash test,如果要将它设为可执行文件,则如下操作:

1
2
3
4
5
6
7
biotest@ubuntu:~$ ls -lF test
-rw-rw-r-- 1 biotest biotest 42 Apr 27 01:28 test
biotest@ubuntu:~$ chmod u+x test
biotest@ubuntu:~$ test
biotest@ubuntu:~$ ./test
Fri Apr 27 01:29:38 PDT 2018
biotest tty7 2018-03-24 03:28 (:0)

此时输入的是相对路径./test,如果要直接运行,则需要将test添加到环境变量中去,如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
biotest@ubuntu:~$ ./test
Fri Apr 27 01:36:50 PDT 2018
biotest tty7 2018-03-24 03:28 (:0)
biotest@ubuntu:~$ pwd
/home/biotest
biotest@ubuntu:~$ echo 'export PATH=$PATH:/home/biotest/'>>~/.bashrc
biotest@ubuntu:~$ source ~/.bashrc
biotest@ubuntu:~$ test
biotest@ubuntu:~$ mv test test.sh
biotest@ubuntu:~$ test.sh
Fri Apr 27 01:37:57 PDT 2018
biotest tty7 2018-03-24 03:28 (:0)

添加到环境变量用到了echo 'export PATH=$PATH:/home/biotest/'>>~/.bashrc命令,此外,还有一点,test是用shell写的,原来没有后缀名,输入后无法运行,当添加上上了.sh后,可以正常运行,原理现在还不清楚,懂了再写。

显示消息

多数shell都会产生自己的输出,这些输出会显示脚本所运行的控制台显示器上。很多时候,在自己写脚本时,要添加自己的文本消息来告诉脚本用户正在做什么,需要echo命令,如果在echo命令后面加上一个字符串,就会显示出这个文本字符串,如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~$ echo This is a test
This is a test
biotest@ubuntu:~$ echo "This is a test to see if you're paying attention"
This is a test to see if you're paying attention
biotest@ubuntu:~$ ^C
biotest@ubuntu:~$ echo 'Rich says "scripting is easy".'
Rich says "scripting is easy".

从中可以发现:第一,常规情况下字符串并不需要使用引号;第二,如果要使用引号,字符串中间的引号不能与字符串两端的引号重复(即字符串中使用单引号,整个字符串就使用双引号,反之亦然)。

再看一案例,新建一个文件,命名为test1,输入以下代码:

1
2
3
4
5
6
#!/bin/bash
# This script displays the date and who's logged on
echo The time and date are:
date
echo "Let's see who's logged into the system:"
who

结果如下所示:

1
2
3
4
5
biotest@ubuntu:~$ bash test1
The time and date are:
Fri Apr 27 03:01:29 PDT 2018
Let's see who's logged into the system:
biotest tty7 2018-03-24 03:28 (:0)

使用变量

环境变量是常见的变量之一,在shell脚本中,也能使用环境变量,只需要在环境㸄前加上美元符号($)即可,下面的脚本中就使用了一些环境变量,如下所示:

1
2
3
4
5
#!/bin/bash
# display user information from the system.
echo "User info for userid: $USER"
echo UID:$UID
echo HOME: $HOME

运行后结果如下:

1
2
3
4
biotest@ubuntu:~$ bash test2
User info for userid: biotest
UID:1000
HOME: /home/biotest

如果在字符串中使用美元符号,如下所示:

1
2
biotest@ubuntu:~$ echo "The cost of the item is $15"
The cost of the item is 5

这里面的美元符号就不会显示,如果要显示的话,需要添加转义字符,如下所示:

1
2
biotest@ubuntu:~$ echo "The cost of the item is \$15"
The cost of the item is $15

用户变量

用户变量是用户自己定义的一些变量,这些常见的编程语言中的变量大同小异,看下面的案例:

1
2
3
4
5
6
7
8
#!/bin/bash
# testing vairbales
days=10
guest="Test"
echo "$guest checked in $days days ago"
days=5
guest="Test01"
echo "$guest checked in $days days ago"

运行后结果如下所示:

1
2
3
biotest@ubuntu:~$ bash test3
Test checked in 10 days ago
Test01 checked in 5 days ago

有关美元符号与赋值的另一案例

如果引用变量不使用美元符号,会将引用的某个变量名称识别为字符串,看下面的案例:

1
2
3
4
5
6
7
8
#!/bin/bash
# assigning a variable value to another variable
value1=10
value2=$value1
echo The resulting value is $value2
value3=value1
echo The resulting value is $value3

结果如下所示:

1
2
3
biotest@ubuntu:~$ bash test4
The resulting value is 10
The resulting value is value1

从结果可以看出来,当value2=$value1时,就把变量value1的值10赋值给了value2,当使用value3=value1时,由于没加美元符号,就把value1这个字符串赋值给了value3。

命令替换

shell脚本有一个重要的功能就是从命令输出中提取信息,并将值赋给某个变量。有2种方法可以将命令输出赋值给某个变量:

第一:使用反引号(`);
第二:使用$()格式。
使用这两种方法可以使shell命令的输出赋值给变量,具体用法如下所示:

1
2
3
testing=`date`
# 或者是
testing=$(date)

具体的案例如下所示:

1
2
3
#!/bin/bash
testing=$(date)
echo "The date and time are: " $testing

运行结果如下所示:

1
2
biotest@ubuntu:~$ bash test5
The date and time are: Fri Apr 27 07:10:16 PDT 2018

第二案例:
以下的案例就是就是通过命令替换获得当前日期,并用它来生成唯一文件名。

1
2
3
4
5
#!/bin/bash
# copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d)
# there is a blank between +%y%m%d
ls /usr/bin -al > log.$today

运行后没有输出信息,但是它会在当前目录下生成一个log文件,名称就是log加上今天的日期(今天是2018年4月27日),则这个文件名称就是log.180427。代码的运行原理是,date命令得到当前日期,经格式化后,赋值给today。关于日期的格式化,可以看下面的案例:

1
2
3
4
biotest@ubuntu:~$ date
Fri Apr 27 07:21:17 PDT 2018
biotest@ubuntu:~$ date +%y%m%d
180427

重定向输入和输出

有时候我们会遇到这样的场景,我想把某个命令的输出结果保存到某个文件中,这个文件我可以用于查看,或者是经后来的命令处理。这就用到了重定向,重定向可以用于输入,也可以用于输出。

输出重定向

最基本的重定向就是将命令的输出发送到一个文件中,bash shell中使用大于号(>)来完成,格式为command > outputfile,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
biotest@ubuntu:~$ date > test7
biotest@ubuntu:~$ ls -l test7
-rw-rw-r-- 1 biotest biotest 29 Apr 27 07:25 test7
biotest@ubuntu:~$ less test7
biotest@ubuntu:~$ cat test7
Fri Apr 27 07:25:44 PDT 2018
biotest@ubuntu:~$ who > test7
biotest@ubuntu:~$ cat test7
biotest tty7 2018-03-24 03:28 (:0)
biotest@ubuntu:~$ date >> test7
biotest@ubuntu:~$ cat test7
biotest tty7 2018-03-24 03:28 (:0)
Fri Apr 27 07:28:58 PDT 2018

使用重定向文件创建了一个文件test7,将date命令的输出结果重定向了到了test7中,如果这个文件存在,则重定向的数据会覆盖原来的数据,如果不想覆盖,只是追加,可以使用双大于号。

输入重定向

常规的输入重定向

输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令。 输入重定向符号是小于号(<),格式为command < inputfile
如下所示:

1
2
3
4
5
biotest@ubuntu:~$ cat test7
biotest tty7 2018-03-24 03:28 (:0)
Fri Apr 27 07:28:58 PDT 2018
biotest@ubuntu:~$ wc < test7
2 11 73

在这个案例中,将test7的内容输入到wc命令中,wc命令可以对数据中的文本进行计数(wc的全称为words count),wc的默认输出有3个值,从左到右分别为:
①文本的行数;②文本的词数;③文本的字节数,在前面的案例中,结果就是2行,11个单词,73个字节。

内联输入重定向

内联输入重定向的英文是inline input redirection,这种方法无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以了。内联输入重定向符号是远小于号(<<)。除了这个符号,必须指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文本标记必须一致,格式为:

1
2
3
command << marker
data
marker

maker是数据的开始,也是结果,data是位于maker之间的文本文件,看一个案例:

1
2
3
4
5
6
biotest@ubuntu:~$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
3 9 42

当输入wc << EOF时,命令行会自动变成大于号,接着输入内容,输入完毕后,再输入EOF表示结束,此时命令开始运行,结果为3,9, 42.

管道

管道的使用场景为,将命令A的输出结果导入到命令B,使A的结果成为B的输入。其实这样可以通过重定向实现,但是太麻烦,管道就是出于此目的而设计的。

举个例子:统计当前的文件数有多少个。

先看一下重定向是如何实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
biotest@ubuntu:~$ ls > filename
biotest@ubuntu:~$ cat filename
Desktop
Documents
Downloads
examples.desktop
filename
log.
log.180427
miniconda2
Miniconda2-latest-Linux-x86_64.sh
Music
Pictures
Public
Templates
test1
test2
test3
test4
test5
test6
test7
test8
test.sh
Videos
biotest@ubuntu:~$ wc filename
23 23 210 filename

使用重定向就是,先用ls列出当前的文件名,重定向到filename文件,然后使用wc命令来统计。

再看一下管道命令:

1
2
3
biotest@ubuntu:~$ ls|wc
23 23 210
biotest@ubuntu:~$

管道命令通过一条竖线,将ls的结果导入到wc中。

退出脚本

shell中运行的每个命令都是使用退出状态码(exit status)告诉shell它已经运行完毕,退出状态码是一个0~255的号数值,在命令结束运行时,由命令传给shell。

查看退出状态码

Linux提供了一个专门的变量$? 来保存上个已执行命令的退出状态码。对于需要进行检查的 命令,必须在其运行完毕后立刻查看或使用 $? 变量。它的值会变成由shell所执行的最后一条命令的退出状态码。

1
2
3
4
biotest@ubuntu:~$ date
Fri Apr 27 07:52:45 PDT 2018
biotest@ubuntu:~$ echo $?
0

通常一个成功结束的命令的退出状态码是0,如果命令结束时有错误,这个代码就是一个正整数,如下所示:

1
2
3
4
5
biotest@ubuntu:~$ asd
No command 'asd' found, but there are 22 similar ones
asd: command not found
biotest@ubuntu:~$ echo $?
127

exit命令

exit 命令允许你在脚本结束时指定一个退出状态码,如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[$var1 + $var2]
echo The answer is $var3
exit 5

运行后,结果如下所示:

1
2
3
4
biotest@ubuntu:~$ bash test9
The answer is 40
biotest@ubuntu:~$ echo $?
5