一、Shell 解析器

1、概述

Shell 是一个命令行解释器,它接收应用程序或用户的命令,然后调用操作系统内核。

Shell 还是一个功能强大的编程语言。

2、查看 Linux 系统提供的 Shell 解析器

1
cat /etc/shells

/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh

其中最常用的是**/bin/sh 和/bin/bash**

3、bash 和 sh 的关系

1
ll /usr/bin | grep bash$

-rwxr-xr-x. 1 root root 964536 4 月 1 2020 bash
lrwxrwxrwx. 1 root root 4 5 月 17 15:37 sh -> bash

sh 是 bash 的软链接

4、Linux 默认的解析器是 bash

1
echo $SHELL

/bin/bash

二、Shell 编程

1、hello.sh

① 创建以.sh 作为扩展名的脚本文件

1
vim hello.sh

② 输入脚本内容

1
2
3
4
# 指定当前脚本的解析器:
#!/bin/bash
# 实现具体功能:
echo "hello world"

③ 运行脚本

Shell 脚本的运行方式

命令名在当前进程运行新建子进程运行
source
.
sh
bash
chmod +x 后直接运行

其中“.”是 source 的另一种写法。在当前进程中发布的全局变量可以在当前进程的其他脚本中继续沿用,也可以在子进程中使用;但是子进程 export 发布的变量仅限于子进程内部使用。

理解:

source 或者 . 执行脚本是在当前 shell 中执行的

而 sh,bash,chmod+x 后直接运行 都是在当前 shell(称为父 shell)开启一个子 shell 环境,此 shell 脚本就在这个子 shell 环境中执行。shell 脚本执行完后子 shell 环境随即关闭,然后又回到父 shell 中。

2、变量

① 系统预定义变量

常用系统变量包括:$USER、$HOME、$PWD、$SHELL 等,可以使用 echo 命令输出它们的值。

② 使用 set 命令查看所有变量

1
set | less

③ 自定义变量

[1]基本语法

(1)定义变量
1
USER_NAME=tom

[root@ShiGuang ~]# USER_NAME=tom
[root@ShiGuang ~]# echo $USER_NAME
tom

(2)撤销变量
1
unset USER_NAME

[root@ShiGuang ~]# unset USER_NAME
[root@ShiGuang ~]# echo $USER_NAME

(3)声明静态变量
1
readonly USER_NAME=Tom

[root@ShiGuang ~]# readonly USER_NAME=Tom
[root@ShiGuang ~]# echo $USER_NAME
Tom
[root@ShiGuang ~]# USER_NAME=Bob
-bash: USER_NAME: 只读变量
[root@ShiGuang ~]# unset USER_NAME
-bash: unset: USER_NAME: 无法反设定: 只读 variable

不能修改,不能撤销

[2]语法规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。
  • 等号两侧不能有空格
  • 在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算。
  • 变量的值如果有空格,需要使用双引号或单引号括起来。

④ 位置参数变量

[1]$n

n 是数字,**$0 代表当前脚本名称。从$1 开始代表对应的脚本参数**。从${10}开始数字需要使用{}括起来。

1
vim parameter.sh
1
2
#!/bin/bash
echo "$0 $1 $2"
1
2
3
4
5
6
7
8
chmod 777 parameter.sh

./parameter.sh zz xz
source parameter.sh zz xz
sh parameter.sh zz xz
bash parameter.sh zz xz
echo $0
echo $1 test

结果:

./parameter.sh zz xz
-bash zz xz
parameter.sh zz xz
parameter.sh zz xz
-bash
test

[2]$#

返回输入参数的个数

1
echo $# 1 2 3

结果:

3

[3]$*和$@

都能够返回全部参数,但是只有在循环中且放在引号中能够体现出它们的区别。(具体可以看下面的 for in 循环)

1
vim parameter.sh
1
2
3
4
#!/bin/bash
echo "$0 $1 $2"
echo $*
echo $@
1
bash parameter.sh 1 2 3

结果:

parameter.sh 1 2 3
1 2 3
1 2 3

[4]$?

返回上一条命令的执行结果。

  • 条件判断语句
    • 返回 0 表示 true
    • 返回 1 表示 false
  • 普通语句
    • 返回 0 表示成功
    • 返回非 0 数表示失败
1
echo $?

结果:

返回 0,表示上一条命令执行成功。

3、运算符

$((表达式))或$[表达式]

1
2
3
echo $((20+30))
echo $(((15+15)*2))
echo $[(15+15)*2]

输出结果:

50
60
60

4、条件判断

① 基本语法

  • 写法 1:test condition
  • 写法 2:[ condition ]
    • 注意 condition 前后有空格
    • 空字符串视为 false,非空字符串视为 true

② 常用判断条件

数据类型写法含义
数值-ltless than 小于
数值-leless equal 小于等于
数值-eqequal 等于
数值-gtgreater than 大于
数值-gegreater equal 大于等于
数值-nenot equal 不等于
文件-rread 有读权限
文件-wwrite 有写权限
文件-xexecute 有执行权限
文件-ffile 文件存在并且是一个常规的文件
文件-eexistence 文件存在
文件-ddirectory 文件存在并是一个目录

5、流程控制

① 三目运算

1
[ 5 -gt 8 ] && echo "5大于8" || echo "5小于8"

②if 判断

注意:if 后面有空格

[1]单 if

1
2
3
4
if [ 8 -gt 5 ]
then
echo "8大于5"
fi

[2]if…else

1
2
3
4
5
6
if [ 10 -gt 5 ]
then
echo "10大于5"
else
echo "10小于5"
fi

[3]if…elif

1
2
3
4
5
6
7
8
9
if [ 10 -gt 5 ]
then
echo "10大于5"
elif [ 10 -lt 5 ]
then
echo "10小于5"
else
echo "10等于5"
fi

③case 判断

1
2
3
4
5
6
7
8
9
10
11
12
AGE=10
case $AGE in
"10")
echo 10
;;
"20")
echo 20
;;
*)
echo other
;;
esac

④for 循环

1
2
3
4
for (( i=1;i<=10;i++ ))
do
echo $i
done

如果需要使用外部数据,则需要把外部数据赋值给一个变量,不能在 for 语句中直接使用

1
2
3
4
5
6
7
8
9
10
vim parameter.sh

#!/bin/bash
len=$1
for (( i=1;i<=len;i++ ))
# 从do关键字开始是循环体开始
do
echo $i;
# 到done关键字为止是循环体结束
done
1
bash parameter.sh 10

⑤for in 循环

1
2
3
4
5
6
#!/bin/bash

for i in $*
do
echo $i
done
1
bash parameter.sh a b c

输出结果:

a
b
c
d
e

没有引号的时候$*和$@一样,加了引号就有区别

1
2
3
4
5
6
#!/bin/bash

for i in "$*"
do
echo $i
done
1
bash parameter.sh a b c d e

输出结果:

a b c d e

具体测试

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
vim parameter.sh

#!/bin/bash

echo "这是带引号的$ *"
for i in "$*"
do
echo $i
done

echo "这是不带引号的$ *"
for i in $*
do
echo $i
done

echo "这是带引号的$ @"
for i in "$@"
do
echo $i
done

echo "这是不带引号的$ @"
for i in $@
do
echo $i
done
1
2
bash parameter.sh a b c
bash parameter.sh abc

bash parameter.sh a b c 运行结果:

这是带引号的$ _
a b c
这是不带引号的$ _
a
b
c
这是带引号的$ @
a
b
c
这是不带引号的$ @
a
b
c

bash parameter.sh abc 运行结果:

这是带引号的$ _
abc
这是不带引号的$ _
abc
这是带引号的$ @
abc
这是不带引号的$ @
abc

解析:通过上面的代码我们可以看出:

  • 当$*和$@没有被引用的时候,它们确实没有什么区别,都会把位置参数当成一个个体。
  • “$*“ 会把所有位置参数当成一个整体(或者说当成一个单词),如果没有位置参数,则”$*“为空,如果有两个位置参数并且 IFS(分隔符)为空格时,”$*“相当于”$1 $2”
  • “$@” 会把所有位置参数当成一个单独的字段,如果没有位置参数($#为 0),则”$@”展开为空(不是空字符串,而是空列表),如果存在一个位置参数,则”$@”相当于”$1”,如果有两个参数,则”$@”相当于”$1” “$2”等等

⑥while 循环

1
2
3
4
5
6
7
8
s=0
i=1
while [ $i -le 100 ]
do
s=$[$s+$i]
i=$[$i+1]
done
echo $s

6、函数

Shell 编程中的函数和我们以前熟悉的函数最大的区别是:Shell 编程中要求函数的返回值只能是整数。并且只能通过$?方式获得。可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return 后跟数值 n(0-255)。

1
2
3
4
function sum() {
echo $[$1+$2]
}
sum 10 20
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# 声明函数
function sum(){
# 使用$1、$2引用函数传入的参数
return $[$1+$2]
}
# 调用函数,传入参数
sum 10 20
# 使用$?获取函数执行结果
echo "sum 10 20执行的结果是$?"

7、获取脚本外部数据

① 获取参数

通过$1、$2、……方式获取,从${10}开始需要使用大括号。

② 使用 read 读取用户输入

read 命令有两个常用参数

  • -t 用于指定输入等待时间,单位是秒
  • -p 用于指定提示文字
1
2
read -t 10 -p "please enter:" NAME
echo $NAME