avatar


3.Shell脚本

概述

Shell是一个命令行解释器,接收来自应用程序或用户的命令,然后调用操作系统内核。
Shell也被称为一个编程语言。

查看Linux提供的Shell解释器,示例代码:

1
cat /etc/shells

运行结果:

1
2
3
4
5
6
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/bin/tcsh
/bin/csh

解释一下bashsh的关系,示例代码:

1
ll /usr/bin/ | grep bash

运行结果:

1
2
3
4
-rwxr-xr-x. 1 root root     964536 Nov 25  2021 bash
lrwxrwxrwx. 1 root root 10 Jul 2 23:26 bashbug -> bashbug-64
-rwxr-xr-x. 1 root root 6964 Nov 25 2021 bashbug-64
lrwxrwxrwx. 1 root root 4 Jul 2 23:26 sh -> bash

shbash的连接。

查看CentOS默认的解释器,示例代码:

1
echo $SHELL

运行结果:

1
/bin/bash

即,CentOS的默认解释器为bash

开始

案例

创建一个Shell脚本helloworld.sh,内容如下:

1
2
#!/bin/bash
echo "helloworld"

解释说明:#!/bin/bash,指定解释器。

执行方式

sh 脚本

通过sh 脚本的方式执行,该方式不需要对脚本文件有执行的权限。

举例:

  1. sh 脚本的相对路径,示例代码:
    1
    sh helloworld.sh
    运行结果:
    1
    helloworld
  2. sh 脚本的绝对路径,示例代码:
    1
    sh /home/kaka/helloworld.sh
    运行结果:
    1
    helloworld
  3. 通过bash执行,示例代码:
    1
    bash helloworld.sh
    运行结果:
    1
    helloworld

脚本路径

直接输入脚本路径执行,该方式需要对脚本文件本身有执行的权限。

举例:

  1. 相对路径,示例代码:
    1
    ./helloworld.sh
    运行结果:
    1
    helloworld
  2. 绝对路径,示例代码:
    1
    /home/kaka/helloworld.sh
    运行结果:
    1
    helloworld

比较

第一种方法,bash/sh 脚本,本质是调用bash解释器来读取文件,执行脚本,所以脚本本身不需要执行权限。
第二种方法,直接输入路径,本质是脚本需要自己执行,所以需要执行权限。

.或source 脚本路径

先看例子。

假设存在一个Shell脚本test.sh,内容如下:

1
2
3
#!/bin/bash
A=5
echo $A

首先,我们采用sh的方式进行执行,示例代码:

1
sh test.sh

运行结果:

1
5

这一步没有任何问题,然后我们echo $A,会发现没有运行结果。

再用直接输入路径的方式,示例代码:

1
./test.sh

运行结果:

1
5

同样,我们echo $A,同样会发现没有运行结果。

第三种方法,我们通过.来执行,示例代码:

1
. test.sh

运行结果:

1
5

然后我们echo $A,示例代码:

1
echo $A

运行结果:

1
5

现在有运行结果了。

前两种方式都是在当前Shell中打开一个子Shell来执行脚本内容,当脚本内容结束,子Shell关闭。
第三种,也就是使用在脚本路径前加.或者source的方式,可以使脚本内容在当前Shell里执行,而无需打开子Shell
这也是为什么我们每次要修改完/etc/profile文件以后,需要source一下的原因。

开子Shell与不开子Shell的区别就在于,环境变量的继承关系,在子Shell中设置的环境变量,一般在父Shell中是不可见的。

一般执行自己的脚本,不用第三种方法。

变量

系统预定义变量

常用系统变量:$HOME $PWD $SHELL $USER

示例代码:

1
echo $HOME

运行结果:

1
/home/kaka

自定义变量

操作

定义变量

方法:

1
变量名=变量值

注意:=号前后不能有空格。

撤销变量

方法:

1
unset 变量名

声明静态变量

方法:

1
readonly 变量

注意:静态变量不能被修改,也不能被unset

规则

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

例子

举例:

  1. 定义变量A,示例代码:
    1
    2
    A=5
    echo $A
    运行结果:
    1
    5
  2. 给变量A重新赋值,示例代码:
    1
    2
    A=8
    echo $A
    运行结果:
    1
    8
  3. 撤销变量A,示例代码:
    1
    unset A
  4. 声明静态的变量,此时不能重新赋值,不能unset,示例代码:
    1
    2
    3
    4
    readonly B=2
    echo $B
    B=3
    unset B
    运行结果:
    1
    2
    3
    2
    -bash: B: readonly variable
    -bash: unset: B: cannot unset: readonly variable
  5. 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算。
    示例代码:
    1
    2
    C=1+2
    echo $C
    运行结果:
    1
    1+2
    这种方式,同样无法进行数值运算,示例代码:
    1
    2
    3
    4
    d1=1
    d2=2
    d3=d1+d2
    echo $d3
    运行结果:
    1
    d1+d2
    如果需要相加,可以采用如下的方式。示例代码:
    1
    2
    C=$((1+2))
    echo $C
    运行结果:
    1
    3
    示例代码:
    1
    2
    d3=$((d1+d2))
    echo $d3
    运行结果:
    1
    3
  6. 变量的值如果有空格,需要使用双引号或单引号括起来。示例代码:
    1
    D=123 456
    运行结果:
    1
    bash: 456: command not found...
    示例代码:
    1
    2
    D='123 456'
    echo $D
    运行结果:
    1
    123 456
  7. export可把变量提升为全局环境变量,供其他Shell程序使用。
    新建helloworld.sh,内容如下,示例代码:
    1
    2
    3
    #!/bin/bash
    echo "helloworld"
    echo $B
    执行./helloworld.sh,运行结果:
    1
    helloworld
    并没有打印输出变量B的值。
    我们export B,再执行./helloworld.sh,示例代码:
    1
    2
    export B
    ./helloworld.sh
    运行结果:
    1
    2
    helloworld
    2
    (题外话,在《基于JavaScript的前端开发入门:3.Node.js》,有一个关键词exports(注意,有s),作为有点类似,提升为"全局"。)

查看所有变量

set:查看当前Shell的所有变量

特殊变量

$n

$n:n为数字,$0代表该脚本名称,$1$9代表第一到第九个参数,十以上的参数需要用花括号括号包含,如${10}

parameter.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
echo $0
echo $1
echo $2
echo $3
echo $4
echo $5
echo $6
echo $7
echo $8
echo $9
echo ${10}
echo ${11}
echo ${12}
1
sh parameter.sh a b c d e f g h i j k l m

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
parameter.sh
a
b
c
d
e
f
g
h
i
j
k
l

$#

$#:获取所有输入参数个数。

parameter.sh

1
echo $#
1
sh parameter.sh 1 2 3 4 5 6 7 8 9 10

运行结果:

1
10

$* $@

$*$@都表示传递给函数或脚本的所有参数

  • 不被双引号""包含时,都以$1$2$n的形式输出所有参数。
  • 被双引号""包含时
    • $*会将所有的参数作为一个整体,以$1 $2 ... $n的形式输出所有参数
    • $@会将各个参数分开,以$1$2...$n的形式输出所有参数

示例代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/bin/bash

echo '=== $n ==='
echo $0
echo $1
echo $2
echo $3
echo $4
echo $5

echo '=== $# ==='
echo $#

echo '=== $* ==='
echo $*

echo '=== for "$*" ==='
for var in "$*"
do
echo $var
done

echo '=== for $* ==='
for var in $*
do
echo $var
done

echo '=== $@ ==='
echo $@

echo '=== for "$@" ==='
for var in "$@"
do
echo $var
done


echo '=== for $@ ==='
for var in $@
do
echo $var
done
1
sh parameter.sh 1 2 3 4 5

运行结果:

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
28
29
30
31
32
33
=== $n ===
parameter.sh
1
2
3
4
5
=== $# ===
5
=== $* ===
1 2 3 4 5
=== for "$*" ===
1 2 3 4 5
=== for $* ===
1
2
3
4
5
=== $@ ===
1 2 3 4 5
=== for "$@" ===
1
2
3
4
5
=== for $@ ===
1
2
3
4
5

$?

$?,有两个作用:

  1. 表示最后一次的命令的结果。
  2. 表示最后一次的命令是否正常结束,0代表没有问题(?),是正常结束的。非0代表有问题。

我们执行sh parameter.sh 1 2 3 4 5,再执行echo $?,示例代码:

1
echo $?

运行结果:

1
0

如果不具有对parameter.sh的执行权限,我们执行parameter.sh,示例代码:

1
./parameter.sh 1 2 3 4 5

运行结果:

1
-bash: ./parameter.sh: Permission denied

再执行echo $?,示例代码:

1
echo $?

运行结果:

1
126

运算符

语法:

  • $((运算式))
  • $[运算式]

示例代码:

1
2
a1=$((1+2))
echo $a1

运行结果:

1
3

示例代码:

1
2
a2=$[1+2+3]
echo $a2

运行结果:

1
6

条件判断

有两种语法格式:

  1. [ 条件表达式 ]
    注意条件表达式前后要有空格。
  2. test 条件表达式

其中最常见的还是第一种。

常用判断条件:

  • 两个整数之间比较:

    • -eq,等于
    • -ne,不等于
    • -lt,小于
    • -le,小于等于
    • -gt,大于
    • -ge,大于等于
  • 字符串之间的比较:

    • =,相等
    • !=,不相等
  • 文件权限的判断

    • -r,有读的权限
    • -w,有写的权限
    • -x,有执行的权限
  • 文件类型的判断

    • -e,文件存在
    • -f,文件存在并且是一个常规文件
    • -d,文件存在并且是一个目录

多条件判断:

  • &&,上一条命令执行成功时,才执行。
  • ||,上一条命令执行失败时,才执行。

举例:

  1. test格式的判断,条件为真的情况,示例代码:
    1
    2
    test 3 -ge 2
    echo $?
    运行结果:
    1
    0
  2. test格式的判断,条件为假的情况,示例代码:
    1
    2
    test 1 -ge 2
    echo $?
    运行结果:
    1
    1
  3. [ ]格式的判断,示例代码:
    1
    2
    [ 3 -ge 2 ]
    echo $?
    运行结果:
    1
    0
  4. 判断对文件是否有写的权限,示例代码:
    1
    2
    [ -w parameter.sh ]
    echo $?
    运行结果:
    1
    0
  5. 判断对文件是否有执行的权限,示例代码:
    1
    2
    [ -x parameter.sh ]
    echo $?
    运行结果:
    1
    1
  6. 判断文件是否存在,示例代码:
    1
    2
    [ -e parameter.sh ]
    echo $?
    运行结果:
    1
    0
  7. 多条件判断,示例代码:
    1
    [ kaka ] && echo ok || echo notOk
    运行结果:
    1
    ok
  8. 示例代码:
    1
    [  ] && echo ok || echo notOk
    运行结果:
    1
    notOk

流程控制

if

单分支:
两种语法格式:

  1. 第一种,示例代码:
    1
    2
    3
    if [ 条件判断式 ];then
    程序
    fi
  2. 第二种,示例代码:
    1
    2
    3
    4
    if [ 条件判断式 ]
    then
    程序
    fi

多分支:
语法格式:

1
2
3
4
5
6
7
8
9
if [ 条件判断式 ]
then
程序
elif [ 条件判断式 ]
then
程序
else
程序
fi

示例代码:

1
2
3
4
5
6
7
8
#!/bin/bash
if [ $1 -eq 1 ]
then
echo "条件一命中"
elif [ $1 -eq 2 ]
then
echo "条件二命中"
fi
1
sh if.sh  1

运行结果:

1
条件一命中

case

语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
case $变量名 in 
"值一")
如果变量的值等于值一,则执行程序一
;;
"值 2")
如果变量的值等于值二,则执行程序二
;;
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac

注意事项:

  1. case行尾必须为单词in,每一个模式匹配必须以右括号)结束。
  2. 双分号;;表示命令序列结束,相当于Java中的break
  3. 最后的*)表示默认模式,相当于Java中的default

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

case $1 in
"1")
echo "条件一命中"
;;
"2")
echo "条件二命中"
;;
*)
echo "其他情况命中"
;;
esac
1
sh case.sh 3

运行结果:

1
其他情况命中

for

两种用法:

  1. 用法一:
    1
    2
    3
    4
    for (( 初始值;循环控制条件;变量变化 )) 
    do
    程序
    done
  2. 用法二:
    1
    2
    3
    4
    for 变量 in 值一 值二 值三 ...
    do
    程序
    done

举例:

  1. 用法一,示例代码:

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

    for((i=0;i<=10;i++))
    do
    echo $i
    done
    1
    sh for1.sh

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. 用法二,示例代码:

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

    for v in a b c
    do
    echo $v
    done

    运行结果:

    1
    2
    3
    a
    b
    c

while

语法格式:

1
2
3
4
while [ 条件判断式 ]
do
程序
done

示例代码:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

sum=0
i=1
while [ $i -lt $1 ]
do
sum=$[$sum+$i]
i=$[$i+1]
done
echo $sum
1
sh while.sh 10

运行结果:

1
45

read

read:读取控制台输入。
语法格式:read 选项 参数

常用选项:

  • -p,指定读取值时的提示符
  • -t,指定读取值时等待的时间(秒),默认一直等待

参数:指定读取值的变量名

假设存在read.sh,内容如下:

1
2
3
4
#!/bin/bash

read -t 7 -p "Enter your name in 7 seconds :" NN
echo $NN

read.sh

函数

调用命令

调用命令的方法为$(命令)

示例代码:

1
2
3
4
#!/bin/bash

fileName="$1"_log_$(date +%s)
echo $fileName
1
sh func1.sh kaka

运行结果:

1
kaka_log_1658325297

有些资料把"调用命令"称为"调用系统函数",我个人认为不完全准确,部分命令并不属于系统函数,可能是额外安装的。

自定义函数

语法格式:

1
2
3
4
5
[ function ] funname[()]
{
Action;
[return int;]
}

[ ]包裹的部分为可选。

注意:必须在调用函数地方之前,先声明函数,Shell脚本是逐行运行,不会像其它语言一样先编译。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

function sum()
{
s=$[$1 + $2]
return $s
}

read -p "Please input the number1: " n1;
read -p "Please input the number2: " n2;
sum $n1 $n2;
echo $?

我们执行脚本,依次输入12,打印结果3。这个没有任何问题。

3

再来一个,输入100200

44

这就有问题了!

这是因为return的返回值只能在[0,255][0,255]之间。

解决方法为我们用echo返回,并对调用方式进行调整,从sum $n1 $n2;改为sum=$(sum $n1 $n2)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

function sum()
{
s=$[$1 + $2]
echo $s
}

read -p "Please input the number1: " n1;
read -p "Please input the number2: " n2;
sum=$(sum $n1 $n2)
echo $sum

300

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/11003
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区