终端语言 Shell Script
作者: dkvirus 发表于: 2018-04-25 09:20:37 最近更新: 2018-08-31 17:10:42

有很长一段时间被Shell、终端这两个概念所混淆,以及困惑于为啥把 Xshell 关掉我的 Node 程序就自动死掉了。后来知道将 node 进程写成守护进程貌似可以规避这个问题,在这过程中接触了一些 Shell 脚本。本文为 Shell 脚本学习笔记。

一、Shell 介绍

1.1 概述

Shell 中文意思壳,也叫做外壳,与之对应的是 Linux 操作系统内核。内核一听名字就跟大家闺秀似的不能轻易抛头露面,用户需要使用 Linux(其实是使用 Linux 内核),都是通过 Shell 这个中间人进行交流的。

Shell 是用 c 开发的一个应用程序,提供一个界面,用户通过该界面与操作系统进行交互;

Shell 脚本是在 Shell 上面运行的脚本语言,Shell 是程序,二者是两个完全不同的概念,我们所说的通过 Shell 去操作系统实际上是通过 Shell 脚本去使用操作系统。

1.2 Shell 程序种类

这里说的 Shell 是程序,不是脚本,常见的 Shell 程序有以下几种:

  • Bourne Shell(/usr/bin/sh 或 /bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)

aliyun centos7.4 用的 Shell 是 bash 也就是 Bourne Again Shell。(bourne 中文 小溪,目的地)

1.3 Shell 脚本运行方式

方式一:./test.sh

不能直接写 test.sh,这样会先去环境变量 PATH 中查找,./test.sh 告诉系统在当前目录下找。

方式二:/bin/bash test.sh

/bin/bash 本身就是一个 Shell 程序,后面接 shell 脚本可直接运行。

1.4 Shell 与 终端

Shell 是个程序,终端也是个程序。

Shell 程序的作用是让用户通过 Shell 脚本可以使用操作系统,Shell 程序在哪里呢,终端提供。

终端程序的作用是提供 Shell 程序,常用的终端有 Xshell。

二、Shell 脚本传参

2.1 Shell 程序中给 Shell 传参

./arg.sh 1 2 3

2.2 Shell 脚本中获取参数

1
2
3
4
5
echo "Shell 传递参数实例!";
echo "执行的文件名:$0"; # 打印 ./arg.sh
echo "第一个参数为:$1"; # 打印第一个参数 1
echo "第二个参数为:$2"; # 打印第二个参数 2
echo "第三个参数为:$3"; # 打印第三个参数 3

2.3 几个特殊符号

符号 说明
$# 传递参数的个数,复习一下,# 用来计数
$* 会将 1 2 3 看成一个参数传给 shell 脚本
$@ 会将 1 2 3 看成三个参数一次传给 shell 脚本

三、Shell 变量

3.1 每个用户管理自个的变量

切换 dk 用户,添加一个 shell 变量,并且打印。

1
2
3
name=dk
echo $name
dk

此时再切换到 dkvirus 用户,打印 name 变量,会发现并没有值,这是因为每个用户都管理自个的变量。

1
echo $name

同样的,使用 history 命令可以查看历史操作命令,可以发现不同用户敲入 history 只会显示该用户自个的历史操作命令。

这么做是合理的,因为 Linux 本身就是多用户操作系统。即多个人通过不同的终端可以操作同一台电脑,彼此之间无干扰,各管各的,完美。

3.2 永久保存用户变量

紧接着上面的例子,退出 dk 用户登录的终端,重新登录,再次打印 name 变量,会发现这一次没有值了。

这是因为创建的变量没有永久保存,随着终端的关闭,运行在终端上的 shell 实例程序也就关了,变量随之消失。

实际操作中想要永久保存变量,可以通过修改文件实现。切换到 dk 用户,键入 cd ~ 切换到 dk 用户的 home 目录下,键入 ll -a 命令,-a 可以查看到隐藏的文件,.bash_profile (不同Linux 发行版该文件名称可能不同,但都包含 profile)文件里保存当前登录用户的变量,编辑该文件:

1
2
3
4
5
6
# .....
PATH=$PATH:$HOME/.local/bin:$HOME/bin
NAME=dk

# define alias
alias c=clear

此时再次重登终端,键入 echo $NAME 可以看到打印值。注意,变量名区分大小写,写成 echo $name 是不会有打印结果的。

3.3 永久保存所有用户变量

上面介绍了对单个用户如何保存变量,如果每个用户都要改变一个变量,一个个操作未免麻烦。

编辑 /etc/profile 文件,可以改变所有用户的变量值并永久保存,具体自个试试吧~

四、注释

# 井号可以注释,没有多行注释。

如果多行要注释,可以用花括弧括起来作为一个函数,在调用这个函数前面加 # 实现注释多行功能。

五、数据类型

5.1 字符串

单引号

单引号中的任何字符都会原样输出,不能写变量,不能再写单引号,即便转义也不行;

双引号

双引号可以写变量,可以出现转义符号;

拼接字符串

gretting="hello, ${name} !"

截取字符串

1
2
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

5.2 数组

定义数组

用括弧表示数组,数组元素用空格隔开 array_name=(value0 value1 value2)

单独定义数组 array_name[1]=apple

读取数组

${数组名[下标]} 读取指定下标的数组元素

${数组名[@]} 读取数组中所有元素

${数组名[*]} 读取数组中所有元素

总结:* 表示所有,# 表示统计数目

六、运算符

6.1 算数运算符

运算符 说明 示例
+ 加法 expr $a + $b
- 减法 expr $a - $b
* 乘法 expr $a \* $b(* 需要转义)
/ 除法 expr $b / $a
% 取余 expr $b % $a
= 赋值 a=$b 将把变量 b 的值赋给 a
== 条件表达式判断相等 [ $a == $b ](条件表达式需要放在方括弧内)
!= 条件表达式判断不相等 [ $a != $b ](条件表达式需要放在方括弧内)

注意

  • 原生 bash 不支持数字运算,使用 expr(evaluate expressions) 程序可以数字运算;
  • 一般来说,除赋值运算符 = 左右两边不能有空格,其它运算符左右两边都是要留空格;
  • 乘法 * 在 bash 里本身就有含义,表示所有的意思,这里需要先转义 \* 才能表示乘法运算符;
  • 条件表达式需要放在方括弧 [] 内,且留有空格;

6.2 关系运算符

运算符 说明
-eq 检测两个数是否相等,相等返回 true。
-ne 检测两个数是否相等,不相等返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。

注意

  • 条件判断都要放在方括弧 [] 里,并且留有空格;
  • 关系运算符只对数字有用,或者是数字的字符串,如:”10”;

6.3 布尔运算符

运算符 说明
! 非运算符,表达式为 true 返回 false,为 false 返回 true
-o (or) 或运算符,两者有一个为 true 结果就为 true
-a (and) 与运算符,两者都为 true 结果才为 true

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a=10
b=20

# 练习 -a 运算符
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi

# 练习 -o 运算符
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi

6.4 逻辑运算符

运算符 说明
&& 逻辑 AND
两根竖线,Enter 键上面那个键 逻辑 OR(这里由于 markdown 表格渲染缘故打不出来,看下方示例)

注意

  • 布尔运算符和逻辑运算符作用一样,只不过逻辑运算符需要放在双方括弧里 [[]]

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a=10
b=20

# 练习 && 运算符
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

# 练习 || 运算符
if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

6.5 字符串运算符

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

示例

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
a="abc"
b="efg"
c=""

# 测试 -z 属性
if [ -z $a ]
then
ehco "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi

# 测试 -n
if [ -n $a ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi

# 测试字符串是否为空
if [ $c ]
then
ehco "$c : 字符串不为空"
else
echo "$c : 字符串为空"
fi

6.6 文件运算符

操作符 说明
-d file 检测文件是否是目录,如果是,则返回 true。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。
-r file 检测文件是否可读,如果是,则返回 true。
-w file 检测文件是否可写,如果是,则返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。

示例

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
file="/home/bash/test"

# 测试 -r 可读属性
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi

# 测试 -w 可写属性
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi

# 测试 -x 可执行属性
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi

# 测试 -f 是否为文件属性
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi

# 测试 -d 是否为目录属性
if [ -d $file ]
then
echo "文件为目录"
else
echo "文件不是目录"
fi

# 测试 -s 文件是否为空,即大小是否为 0 kb
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi

# 测试 -e 文件是否存在属性
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi

七、打印字符串

每种语言都有内置方法往控制台打印消息:

语言 打印语法
python print “hello python”
javascript console.log(‘hello js’)
java System.out.print(“hello java”)
shell echo “hello shell” 或者 printf “%s\n” “hello shell”

7.1 echo

echo "hello shell"

7.2 printf

示例

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

printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876

打印结果

1
2
3
4
姓名     性别   体重kg
郭靖 男 66.12
杨过 男 48.65
郭芙 女 47.99

注意

  • %s%d%f 都是字符串格式化替代符;
  • %10s 表示字符串占 10 个字符的位置,默认居右对齐,%-10s 占 10 个字符,居左对齐;
  • %4.2f%f 表示这是个小数,整数位 4 表示占 4 个字符宽度,小数位 2 表示保留 2 位小数位数;

八、流程语句

8.1 条件语句 if

if

1
2
3
4
if condition
then
command1
fi

写成一行,每个语句后加分号结束。

1
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

if…else…

1
2
3
4
5
6
if condition
then
command1
else
command
fi

if…else-if…else

1
2
3
4
5
6
7
8
9
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

8.2 条件语句 case

语法

1
2
3
4
5
6
7
8
case 值 in
模式1)
command1
;;
模式2)
command1
;;
esac

关于 esac

case的语法和C family语言差别很大,它需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac

打印结果

1
2
3
4
输入 1 到 4 之间的数字:
你输入的数字为:
3
你选择了 3

8.3 循环语句 for

语法

1
2
3
4
for var in item1 item2 ... itemN
do
command1
done

写成一行

1
for var in item1 item2 ... itemN; do command1; command2… done;

示例

1
2
3
4
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done

打印结果

1
2
3
4
5
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5

8.4 循环语句 while

只要条件为真就一直循环,直到条件为假退出循环。

语法

1
2
3
4
while condition
do
command
done

示例

1
2
3
4
5
6
7
#!/bin/sh
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done

let 命令让 int 变量自增 1。

8.5 循环语句 until

直到条件为真才会退出循环,与 while 刚好相反。

语法

1
2
3
4
until condition
do
command
done

示例

1
2
3
4
5
6
7
a=0

until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done

8.6 无限循环

1
2
3
4
while true
do
command
done

8.7 跳出循环

break

终止循环。

continue

仅仅结束当前循环,紧接着执行下一次循环。

九、Shell 函数

9.1 定义函数

1
2
3
4
5
6
7
8
9
10
11
funWithReturn(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

函数可以使用 return 返回结果,在函数调用之后的下一行通过 $? 接收返回值。

9.2 执行函数

1
2
3
4
5
demoFun(){
echo "这是我的第一个 shell 函数!"
}

demoFun

函数执行只需要调用函数名即可。

在 js 中直接调用函数名只能获得函数的字符串表示,函数名() 才是执行函数,但 shell 中调用函数名就能执行函数。

函数在调用前必须先声明,要注意先后位置。

9.3 函数传参

1
2
3
4
5
6
7
8
9
10
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

打印结果

1
2
3
4
5
6
7
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

注意

获取参数 n < 10,可以直接通过 $n 获取,如 $2 获取第二个参数;

获取参数 n >= 10,通过 ${n} 获取,如 ${10} 获取第十个参数。

十、模块化

在一个文件内引入另一个文件。

test1.sh

1
url="http://www.runoob.com"

test2.sh

1
2
3
4
#使用 . 号来引用test1.sh 文件
. ./test1.sh

echo "菜鸟教程官网地址:$url"

十一、shell 内置命令

1. shift

位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
until [ $# -eq 0 ]
do
echo "第一个参数为: $1 参数个数为: $#"
shift
done

执行以上程序x_shift.sh:
$./x_shift.sh 1 2 3 4

结果显示:
第一个参数为: 1 参数个数为: 4
第一个参数为: 2 参数个数为: 3
第一个参数为: 3 参数个数为: 2
第一个参数为: 4 参数个数为: 1
首页
友链
归档
dkvirus
动态
RSS