Shell回顾
Shell是什么
- 通过
Linux内核
可以实现对CPU、内存等硬件的调用 - 与
Linux内核
的沟通, 则需要借助Shell
Shell
是一个命令行解释器: 接收应用程序 / 用户命令, 相应地调用操作系统内核- 把命令一行一行地写进一个文件,
Shell
同样会一行一行地执行, 这样的一个可执行文件
, 即为脚本
, 各种复杂的流程控制因此而得到实现
查看Shell
- 查看当前系统提供支持的
Shell
解析器
(base) kv@ubuntu:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/screen
- 查看当前默认的
Shell
解析器
(base) kv@ubuntu:~$ echo $SHELL
/bin/bash
- 查看进程时, 显示的也会是
bash
(base) kv@ubuntu:~$ ps
PID TTY TIME CMD
2892 pts/6 00:00:00 bash
4149 pts/6 00:00:00 ps
上手Shell脚本
脚本格式
- 后缀名默认
.sh
, 但实际上, 只要可执行文件的内容是按照Shell语法写出的, 都是可以按Shell脚本来执行 #
后可进行注释, 而脚本中的第一行注释, 或者说脚本的开头, 是用来选择当前命令行的解析器, 例如
#!/bin/sh
或者是
#!/bin/bash
脚本的执行方式
- 创建一个名为
helloworld.sh
的Shell脚本文件, 内容为1 #!/bin/bash 2 echo "Hello world!"
bash
命令 /sh
命令 + 可执行文件的相对路径 / 绝对路径
- 具体执行结果
(base) kv@ubuntu:~/scripting$ bash helloworld.sh
Hello world!
- 或者
(base) kv@ubuntu:~$ sh scripting/helloworld.sh
Hello world!
- 具有
可执行权限x
时, 直接输入脚本的相对路径 / 绝对路径
- 当可执行文件没有可执行权限时, 需要使用
chmod
命令及时地添加
(base) kv@ubuntu:~$ scripting/helloworld.sh
bash: scripting/helloworld.sh: Permission denied
(base) kv@ubuntu:~$ cd scripting
(base) kv@ubuntu:~/scripting$ ls -l
total 4
-rw-rw-r-- 1 kv kv 32 Nov 4 19:03 helloworld.sh
(base) kv@ubuntu:~/scripting$ chmod u+x helloworld.sh
(base) kv@ubuntu:~/scripting$ ls -l
total 4
-rwxrw-r-- 1 kv kv 32 Nov 4 19:03 helloworld.sh
- 执行当前目录下的可执行文件时, 需要使用下面的方式
.
表示当前目录下, 那么这种方式本质上也就是通过相对路径来执行
(base) kv@ubuntu:~/scripting$ ./helloworld.sh
Hello world!
- 否则系统将把文件名认为是一条命令来执行
(base) kv@ubuntu:~/scripting$ helloworld.sh
helloworld.sh: command not found
- 使用可执行文件的绝对路径来直接执行
(base) kv@ubuntu:~$ /home/kv/scripting/helloworld.sh
Hello world!
- 第一种执行方式所用到的脚本文件路径, 相当于传给命令的参数, 不涉及可执行权限的问题
- 第二种执行方式是脚本自己本身来执行, 因此必须要有可执行权限
source
命令 /.
命令 + 可执行文件的相对路径 / 绝对路径
source
命令和.
命令是Shell
内嵌的一个命令(base) kv@ubuntu:~/scripting$ type source source is a shell builtin (base) kv@ubuntu:~/scripting$ type . . is a shell builtin
- 具体的执行结果
(base) kv@ubuntu:~$ source scripting/helloworld.sh
Hello world!
(base) kv@ubuntu:~$ cd scripting
(base) kv@ubuntu:~/scripting$ . helloworld.sh
Hello world!
- 前两种方式都是又启动了一个
子Shell
进程, 都是在子Shell
中执行脚本 - 而这种方式则是直接在
当前的Shell
中进行执行
Shell编程中的变量
- 使用
bash
命令可在当前的Shell
中创建子Shell
, 并使用exit
命令退出子Shell
(base) kv@ubuntu:~/scripting$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2892 2885 0 18:42 pts/6 00:00:00 bash kv 5049 2892 0 20:55 pts/6 00:00:00 ps -f (base) kv@ubuntu:~/scripting$ bash (base) kv@ubuntu:~/scripting$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2892 2885 0 18:42 pts/6 00:00:00 bash kv 5050 2892 1 20:56 pts/6 00:00:00 bash kv 5130 5050 0 20:56 pts/6 00:00:00 ps -f (base) kv@ubuntu:~/scripting$ exit exit (base) kv@ubuntu:~/scripting$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2892 2885 0 18:42 pts/6 00:00:00 bash kv 5131 2892 0 20:56 pts/6 00:00:00 ps -f
Shell
与子Shell
所涉及到的、最为关键的问题是环境变量的继承关系
子Shell
中设置的当前变量, 在父Shell
中是不可见的- 在
子Shell
中是无法更改父Shell
的全局变量的
什么是变量
- 希望临时地在当前Shell会话中保存的一些信息, 即为(环境)变量, 又可以分为
- 系统预定义变量
- 用户自定义变量
- 或者有可以将变量划分为
- 全局变量: 对所有的子Shell同样有效
- 局部变量: 只对当前的Shell有效
- 查看当前的用户、主目录、工作目录, 以及使用的Shell解析器等系统变量
(base) kv@ubuntu:~/scripting$ echo $USER kv (base) kv@ubuntu:~/scripting$ echo $HOME /home/kv (base) kv@ubuntu:~/scripting$ echo $PWD /home/kv/scripting (base) kv@ubuntu:~/scripting$ echo $SHELL /bin/bash
- 使用
env
命令可以查看系统定义的全局变量(base) kv@ubuntu:~/scripting$ env XDG_VTNR=7 XDG_SESSION_ID=c2 CLUTTER_IM_MODULE=xim XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/kv SESSION=ubuntu GPG_AGENT_INFO=/home/kv/.gnupg/S.gpg-agent:0:1 TERM=xterm-256color ... ...
set
命令可以查看所有Shell中的环境变量, 包括用户自定义的变量, 以及关于这个变量的函数, 由此得到的结果是最为全面的- 使用
printenv
命令, 后面可直接跟变量名, 无需$
符号 - 这只是将变量的信息打印出来, 如果要使用这个变量, 还是要跟上
$
符号的
(base) kv@ubuntu:~/scripting$ printenv DISPLAY
:0
定义环境变量
-
对于环境变量的定义,主要有以下几个方面需要注意
- 变量名称可以由大小写字母、数字和下划线组成, 但是不能以数字开头
- 变量赋值表达式的等号左右两侧, 不能有空格 ! ! !
- 无需指定变量类型, 默认是字符串类型, 无法直接进行数值运算
- 给变量赋的值中, 包含空格则需要用引号引起来该值, 否则将被识别为命令
-
变量赋值的基本操作
(base) kv@ubuntu:~$ variable=Helloworld! (base) kv@ubuntu:~$ echo $variable Helloworld! (base) kv@ubuntu:~$ variable=Hello world! world!: command not found (base) kv@ubuntu:~$ variable='Hello world!' (base) kv@ubuntu:~$ echo $variable Hello world!
确认全局和局部变量
- 在
子Shell
中查看变量variable是否存在(base) kv@ubuntu:~$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2229 2222 0 22:36 pts/17 00:00:00 bash kv 2438 2229 0 22:52 pts/17 00:00:00 ps -f (base) kv@ubuntu:~$ bash (base) kv@ubuntu:~$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2229 2222 0 22:36 pts/17 00:00:00 bash kv 2439 2229 2 22:52 pts/17 00:00:00 bash kv 2519 2439 0 22:52 pts/17 00:00:00 ps -f (base) kv@ubuntu:~$ echo $variable (base) kv@ubuntu:~$ exit exit
- 从结果上看, 变量variable并不存在于
子Shell
中, 因此, 变量variable是一个局部变量 - 通过表达式定义的变量, 都是局部变量
- 使用
export
命令可以直接定义全局变量, 或者将局部变量更改为全局变量(base) kv@ubuntu:~$ export variable (base) kv@ubuntu:~$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2229 2222 0 22:36 pts/17 00:00:00 bash kv 2530 2229 0 22:56 pts/17 00:00:00 ps -f (base) kv@ubuntu:~$ bash (base) kv@ubuntu:~$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2229 2222 0 22:36 pts/17 00:00:00 bash kv 2531 2229 1 22:56 pts/17 00:00:00 bash kv 2611 2531 0 22:56 pts/17 00:00:00 ps -f (base) kv@ubuntu:~$ echo $variable Hello world!
- 通过
export
命令确实实现了在子Shell
中调用变量variable - 但需要注意的是, 在
子Shell
中更改变量variable, 并不会在父Shell
中生效(base) kv@ubuntu:~$ variable='Hello linux!' (base) kv@ubuntu:~$ echo $variable Hello linux! (base) kv@ubuntu:~$ exit exit (base) kv@ubuntu:~$ ps -f UID PID PPID C STIME TTY TIME CMD kv 2229 2222 0 22:36 pts/17 00:00:00 bash kv 2633 2229 0 23:00 pts/17 00:00:00 ps -f (base) kv@ubuntu:~$ echo $variable Hello world!
反观执行方式
- 创建全局变量
jesse
和局部变量walter
(base) kv@ubuntu:~$ export jesse='My name is Jesse.' (base) kv@ubuntu:~$ echo $jesse My name is Jesse. (base) kv@ubuntu:~$ walter='My name is Heisenberg.' (base) kv@ubuntu:~$ echo $walter My name is Heisenberg.
- 创建的可执行文件
fatherandson.sh
, 写入下面的内容1 #!/bin/bash 2 3 echo $jesse 4 echo $walter
- 调整文件的可执行权限后, 分别使用相对路径和
source
命令的方式来执行文件(base) kv@ubuntu:~/scripting$ ./fatherandson.sh My name is Jesse. (base) kv@ubuntu:~/scripting$ source fatherandson.sh My name is Jesse. My name is Heisenberg.
- 使用相对路径的方式来执行文件时, 变量walter并非全局变量, 因此不存在执行时创建
子Shell
中 source
命令直接在当前的Shell中执行, 因此变量walter是存在的
使用unset
命令来删除变量
- 将变量作为参数传给
unset
命令(base) kv@ubuntu:~$ jesse='My name is Jesse.' (base) kv@ubuntu:~$ echo $jesse My name is Jesse. (base) kv@ubuntu:~$ set | grep jesse jesse='My name is Jesse.' (base) kv@ubuntu:~$ unset jesse base) kv@ubuntu:~$ echo $jesse (base) kv@ubuntu:~$ set | grep jesse (base) kv@ubuntu:~$
创建只读变量
- 使用
readonly
命令, 可以定义只读变量 - 该变量的值将无法更改, 且该变量无法由
unset
命令来撤销(base) kv@ubuntu:~$ readonly y=2023 (base) kv@ubuntu:~$ echo $y 2023 (base) kv@ubuntu:~$ y=2024 bash: y: readonly variable (base) kv@ubuntu:~$ unset y bash: unset: y: cannot unset: readonly variable
特殊变量
特殊变量$n
- $n表示传入可执行文件的第n个参数, $6即表示第6个参数
- $0表示执行脚本的路径
- 第10个位置及以后的参数, 需要用{}括起来, 比如${10}
- 创建可执行文件
parameter.sh
, 其内容为1 #!/bin/bash 2 3 echo '==========$n==========' 4 echo script path: $0 5 echo 1st parameter: $1 6 echo 2nd parameter: $2
- 设置好可执行权限后, 输入相对路径来执行该文件
(base) kv@ubuntu:~/scripting$ ./parameter.sh ==========$n========== script path: ./parameter.sh 1st parameter: 2nd parameter: (base) kv@ubuntu:~/scripting$ ./parameter.sh 2023 ==========$n========== script path: ./parameter.sh 1st parameter: 2023 2nd parameter: (base) kv@ubuntu:~/scripting$ ./parameter.sh 2023 11.05 ==========$n========== script path: ./parameter.sh 1st parameter: 2023 2nd parameter: 11.05
特殊变量$#
- $#可以获取输入参数的个数
- 结合条件和循环语句, 可以使脚本实现更为丰富的功能
- 在可执行文件
parameter.sh
中添加下面的内容8 echo '==========$#==========' 9 echo number of parameters: $#
- 再次执行该文件
(base) kv@ubuntu:~/scripting$ ./parameter.sh ==========$n========== script path: ./parameter.sh 1st parameter: 2nd parameter: ==========$#========== number of parameters: 0 (base) kv@ubuntu:~/scripting$ ./parameter.sh 2022 ==========$n========== script path: ./parameter.sh 1st parameter: 2022 2nd parameter: ==========$#========== number of parameters: 1 (base) kv@ubuntu:~/scripting$ ./parameter.sh 2022 11.05 ==========$n========== script path: ./parameter.sh 1st parameter: 2022 2nd parameter: 11.05 ==========$#========== number of parameters: 2 (base) kv@ubuntu:~/scripting$ ./parameter.sh 2022 11.05 15:25 ==========$n========== script path: ./parameter.sh 1st parameter: 2022 2nd parameter: 11.05 ==========$#========== number of parameters: 3
特殊变量$*
- $*可以将所有参数看成一个整体 / 一个输入, 或者说, 得到的是所有参数的字符串
- 在可执行文件
parameter.sh
中添加下面的内容11 echo '==========$*==========' 12 echo all of parameters: $*
- 再次执行该文件
(base) kv@ubuntu:~/scripting$ ./parameter.sh 2023 11.05 15:43 ==========$n========== script path: ./parameter.sh 1st parameter: 2023 2nd parameter: 11.05 ==========$#========== number of parameters: 3 ==========$*========== all of parameters: 2023 11.05 15:43
特殊变量$@
- $@相当于将所有参数放入一个列表
- 这必然会在后续会涉及到判断、循环语句
- 在可执行文件
parameter.sh
中添加下面的内容14 echo '==========$#==========' 15 echo list of parameters: $@
- 再次执行该文件
(base) kv@ubuntu:~/scripting$ ./parameter.sh 2023 11.05 15:43 ==========$n========== script path: ./parameter.sh 1st parameter: 2023 2nd parameter: 11.05 ==========$#========== number of parameters: 3 ==========$*========== all of parameters: 2023 11.05 15:43 ==========$#========== list of parameters: 2023 11.05 15:43
特殊变量#?
- #?可以返回最后一次执行命令的返回状态
- #?为0, 表示上一个命令被正确执行
(base) kv@ubuntu:~/scripting$ ./parameter.sh ==========$n========== script path: ./parameter.sh 1st parameter: 2nd parameter: ==========$#========== number of parameters: 0 ==========$*========== all of parameters: ==========$#========== list of parameters: (base) kv@ubuntu:~/scripting$ echo $? 0