Shell编程学习之路
LanyuanXiaoyao's Blog ヽ(✿゚▽゚)ノ

Shell编程学习之路

2018-07-29

TODO

  • 简介
  • 环境
  • Hello World
  • 变量
  • 传递参数
  • 运算符
  • 重要命令
    • echo
    • printf
    • test
  • 流程控制
  • 函数
  • 输入输出
  • 文件包含
  • 参考

简介

Shell是一个用C语言编写的程序,它是==用户使用 Linux 的桥梁==。Shell 既是一种==命令语言==,又是一种==程序设计语言==
Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务
Ken Thompson的sh是第一种Unix Shell,Windows Explorer是一个典型的图形界面Shell

Shell脚本

Shell 脚本(shell script),是一种为 shell 编写的脚本程序,这个程序是==使用纯文本==,将一些shell的语法与指令(含外部指令)写在里面,搭配正规表示法、管线命令与文件流重导向等功能,以达到我们所想要的处理目的
所以,简单的说, shell script 就像是Windows上的==批处理==(.bat) ,最简单的功能就是将许多指令写在一起, 让使用者很轻易的就能够==one touch==的方法去处理复杂的动作(执行一个档案”shell script” ,就能够==一次执行多个指令==)。而且shell script 更提供分支、循环、条件与逻辑判断等重要功能,让使用者也可以直接以shell 来撰写程序,而不必使用类似C 程式语言等传统语言
业界所说的 ==shell 通常都是指 shell 脚本==,但我们要知道,==shell和shell script是两个不同的概念==
由于习惯的原因,简洁起见,本文出现的 “shell编程” 都是指==shell脚本编程==,不是指开发shell自身

Shell 脚本的作用

  • ==自动化管理==的重要依据
  • 追踪与管理系统的重要工作
  • 简单入侵侦测功能
  • ==连续指令单一化==
  • 简易的资料处理
  • 跨平台支援与学习历程较短

其实说白了,使用Shell脚本就是为了可以一键运行一系列复杂的命令,而不用一行行将这些命令打一次,这为我们的工作提供了足够多的便利,提高了我们使用Linux系统的效率

环境

Shell编程跟 Java、PHP编程一样,只要有一个能编写代码的==文本编辑器==和一个能解释执行的==脚本解释器==就可以了
Linux 的 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)

由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell
在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为#!/bin/bash
#!告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序

Linux

Linux默认安装就带了shell解释器

Mac OS

Mac OS不仅带了sh、bash这两个最基础的解释器,还内置了ksh、csh、zsh等不常用的解释器

Windows

Windows出厂时没有内置shell解释器,需要自行安装,为了同时能用grep, awk, curl等工具,最好装一个工具来模拟Linux环境,常见的模拟工具有

  • cygwin
  • mingw

Windows Subsystem for Linux

在Windows 10上还有新的选择。Windows Subsystem for Linux(WSL)是一个为了在 Windows 10 上能够原生运行Linux二进制可执行文件(ELF格式)的兼容层。
它是由微软与Canonical公司合作开发,目标是使纯正的 Ubuntu 16.04 “Xenial Xerus” 映像能下载和解压缩到用户的本地电脑,并且映像內的工具和实用工具能在此子系统上原生运行。
WSL 提供了一個微软开发的 Linux 兼容內核接口(不包含 Linux 代码),使得 Linux 用户模式的二進制文件能在其上執行。 运行原理很像是 Linux上Wine的Windows版,它可以被替换成 Arch。你需要使用一个现有的 Arch 安装去构建一些软件包

安装

WSL的安装方法非常简单,直接在Windows自带的应用商店里搜索Ubuntu即可,然后下载自动安装,就可以在开始菜单里看到一个名字叫Ubuntu的应用

WSL

打开即可进入WSL,这是==基于Ubuntu内核==的

WSL Ubuntu

注:本文使用的Shell环境即为WSL,尽管WSL无法在真实的编程开发中与真实的Linux环境相媲美,但是仅仅只是为了学习Shell编程,已经足够了,而且安装非常简单,这对Windows用户是非常友好的

Hello World

下面开始第一个Shell脚本

#!/bin/sh
echo "hello world"

这就是一个简单的Shell脚本,包含了最基本的两个部分,第一行是==指定脚本解释器==,可以指定我们的脚本要用什么解释器来执行,因为==不同的解释器通常会提供一些特定的功能==,所以如果我们有使用到解释器特性,那么我们就需要指定脚本的解释器;第二行开始就是我们执行的语句,在这里就是在命令行打印一句hello world

脚本执行

写好了脚本应该怎么执行呢?假设我们将上面的脚本保存为hello.sh,那么执行脚本有两种方式:

作为可执行程序执行

只需要将我们写好的脚本赋予可执行权限即可

chmod +x ./hello.sh
./hello.sh

这样我们的==脚本相当于一个应用程序==,可以像其他二进制程序一样直接执行

作为可执行程序执行

作为解释器命令参数

直接运行解释器,并把脚本的路径作为参数赋给解释器,路径是解释器的一个参数,这也是我们常用的执行脚本的方式

# 假设我们就在脚本所在的文件夹里
# 所以可以直接使用脚本名作为路径
# 由于sh命令已经在环境变量里了,所以通常我们不需要将sh命令的路径打出来
/bin/sh hello.sh

作为解释器命令参数

变量

变量名

Shell编程里的变量名对于有编程经验的人来说没什么好说的,和大部分编程语言的命名规则是相同的,这里简单地提一下:

  1. 命名==只能使用英文字母,数字和下划线==,首个字符不能以数字开头
  2. ==中间不能有空格==,可以使用下划线
  3. 不能使用标点符号
  4. 不能使用bash里的关键字(可用help命令查看保留关键字)

定义与赋值

变量的定义与赋值和其他语言同样没有很明显的不同,但有几个要点需要注意:

  • 变量的==定义不需要关键字==。直接赋值即可完成变量的定义,如name="Tony",即定义了一个叫做name的变量,并且赋值了一个"Tony"的值给它

变量定义与赋值

  • ==变量赋值的等号两边不能有空格==。这一点非常重要,通常我们在Java里定义变量的时候为了美观,会在等号两边加上空格,但是在Shell编程里,这样会导致解释器错误,将变量名当成一个命令

命令未找到

使用变量

Shell编程里面,使用变量需要在变量名前面==加上关键字==$,如

echo $name
echo ${name}

这里的花括号是可选的,通常情况下加不加都行,但是在变量使用会产生歧义的时候,需要加上==花括号来将变量的边界区别出来==

echo "$namespace"
echo "${name}space"

在上面的两行代码里,如果变量name不加花括号,那么解释器会将namespace当成一个变量名,而$namespace这个变量不存在,所以会输出空,而加了花括号之后,解释器就可以正常识别$name变量了

花括号表示变量名边界

注: 推荐给所有的变量都加上花括号,这算是一个良好的编程习惯吧,避免因为漏花括号导致出错

只读变量

顾名思义,就是定义为只读变量的变量值==无法再被更改==,定义只读变量的关键字为readonly,尝试修改只读变量,解释器会报错

name="Tony"
readonly name
name="Jenny"

只读变量值不能被修改

当然,readonly关键字可以在定义赋值变量的时候同时使用

readonly name_2="Tony"

readonly关键字可以在变量赋值的时候使用

删除变量

变量的删除使用关键字unset

name_3="Tony"
echo name_3
unset name_3
echo name_3

执行结果:

执行结果

特别的:只读变量不能使用关键字unset删除

只读变量不能用unset关键字删除

作用域

在Shell编程中,会有三种作用范围的变量:

  1. 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,==其他shell启动的程序不能访问局部变量==
  2. 环境变量 ==所有==的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量
  3. Shell变量 Shell变量是由Shell程序设置的特殊变量。Shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了Shell的正常运行

常见变量类型

字符串

字符串是shell编程中最常用的数据类型,字符串可以使用单引号,也可以用双引号,也可以不用引号

定义与赋值

和大多数的变成语言类似,Shell编程中的字符串可以同时使用''或者""表示,两者的区别如下:

  • 单引号
    • 单引号里的任何字符都会==原样输出==,单引号字符串中的==变量是无效的==
    • 单引号字串中==不能出现单引号==(对单引号使用转义符后也不行)
# 所有符号会原样输出
string='~!@#$%^&*()_+{}|<>?:"'
echo ${string}

# 引用的变量无效
number=3
string='number is ${number}'
echo ${string}

# 单引号中不能出现单引号
string='I'm Tony.'
echo ${string}
string='I\'m Tony'
echo ${string}

执行结果:

单引号的使用

  • 双引号
    • 双引号里==可以有变量==
    • 双引号里==可以出现转义字符==
# 可以在字符串中直接插入变量
number=3
string="number is ${number}"
echo ${string}

# 可以在字符串中使用转义字符
string="number is \${number}"
echo ${string}

# 可以在字符串中使用单引号
string="I'm Tony."
echo ${string}

执行结果:

双引号的使用

常用字符串操作

  1. 拼接字符串
    stringOne="hello"
    stringTwo="world"
    stringThree=${stringOne}${stringTwo}
    echo ${stringThree}
    

    执行结果:

拼接字符串

  1. 获取字符串长度
    string="hello world"
    echo length = ${#string}
    

    执行结果:

获取字符串长度

  1. 截取子字符串
    string="hello world"
    # 两个数字分别表示起始和结束的位置
    echo ${string:1:4}
    

    执行结果:

截取子字符串

  1. 查找字符位置
    string="hello world"
    # 注意这里的关键字是反引号`,不是单引号'
    echo `expr index "$string" o`
    

    执行结果:

查找字符位置

数组

Shell编程支持一维数组,==不支持多维数组==,并且==没有限定数组的大小==,和其他编程语言类似,数组下标从0开始,下标可以是整数或算术表达式,值为非负数

定义与赋值

数组用括号表示,数组元素之间使用空格分隔,一般写作数组名=(值1 值2 值3)

array=("Tony" "Jenny" "Tom")

或者分行定义

array=(
  "Tony"
  "Jenny"
  "Tom"
)

或者直接赋值数组中的元素

# 这种定义方式不需要按照连续的下标赋值,下标的范围没有限制
array[0]="Tony"
array[1]="Jenny"
array[2]="Tom"

读取数组

数组元素的读取格式为${数组名[下标]}

echo ${array[2]}

执行结果:

通过下标读取数组元素

数组常用操作

  1. 使用@下标获取数组所有元素
    echo ${array[@]}
    

    执行结果:

获取数组所有元素

  1. 获取数组长度
    # 关键字和获取字符串长度相同
    # 使用 * 或者 @ 都可以表示数组全部的意思
    echo ${#array[@]}
    echo ${#array[*]}
    # 使用具体的下标则表示取指定元素的长度
    echo ${#array[0]}
    

    执行结果:

获取数组长度

注释

Shell编程中==没有多行注释==,只有单行注释,在需要注释的行首使用#符号即可

# ----------
# 这是注释
# ----------

“伪”多行注释

每一行都加#号确实是太费劲了,那么有什么办法呢?答案就是把注释伪装成一个函数,把要注释的内容用{}大括号括起来,定义成一个函数,但是没有调用,自然就达到和注释相同的效果

{
  注释内容......
}

注:不要滥用这种方法,这种代码块如果不注意,会引起不可预测的结果

参数传递

在我们执行Shell脚本的时候,经常需要向脚本传递一些参数,比如路径、策略等。在脚本代码里面,我们通过$n来获取传递进来的参数,这里的n是一个数字,表示第n个参数

echo "First argument is : $1"
echo "Second argument is : $2"
echo "Third argument is : $3"

执行结果:

参数传递

内置参数

为了方便我们操作传递进来的参数,Shell本身提供了一些起特殊功能的参数

参数 说明
$# 传递到脚本的==参数的个数==
$* ==以一个单字符串==显示所有向脚本传递的参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ $*相同,但是使用时加引号,并在引号中==返回每个参数==
$- 显示Shell使用的当前选项,与set命令功能相同
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误
echo "First argument is : $1"
echo "Second argument is : $2"
echo "Third argument is : $3"

echo "Arguments's length is : $#"
echo "Current process ID is : $$"
echo "Last process ID in background is : $!"
echo "Set : $-"

echo "\$* : $*"
echo "\$@ : $@"

echo "Exit status is : $?"

执行结果:

内置参数

$@$*

两个参数都是引用了所有的变量,但是区别是非常大的,$*是将所有参数合并为一个字符串(一个参数),而$@是拿到分开的三个字符串(三个参数)

echo "---$*---"
for i in "$*";
do
  echo $i
done

echo "---$@---"
for i in "$@";
do
  echo $i
done

执行结果:

区别

运算符

Shell编程和其他编程语言相同,提供了很多的不同功能的运算符,包括:

  • 算术运算符
  • 关系运算符
  • 布尔运算符
  • 字符串运算符
  • 文件测试运算符

算术运算符

原生Shell编程不支持简单的数学运算
只能通过别的命令实现,如awkexpr,其中expr最常用。expr是一款表达式计算工具,使用它能完成表达式的求值操作
下面列出了常用的算术运算符:

运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
% 取余
= 赋值
== 相等则返回true
!= 不相等则返回true

使用expr进行运算有几个非常重要的要点:

  1. 表达式必须要用==反引号==括起来,如`expr 2 + 2`
  2. 表达式必须要有expr关键字
  3. ==表达式和运算符之间必须要有空格==,2+2是不可以的,需要写成2 + 2
  4. 条件表达式必要放在中括号[]内,如[ $a == $b ],当然空格不能少,尤其是==中括号前后的空格也要加上==
  5. *==乘号必须要加上反斜杠==进行转义才能正常使用,如$a \* $b
a=10
b=20

echo `expr $a + $b`
echo `expr $a - $b`
echo `expr $a \* $b`
echo `expr $a / $b`
echo `expr $a % $b`

if [ $a = $b ]
then
  echo "a equals b"
fi

if [ $a != $b ]
then
  echo "a not equals b"
fi

执行结果:

算术运算符

布尔运算符

布尔运算是数字符号化的逻辑推演法,下面是常用的布尔运算符

运算符 说明
! 非运算,表达式为true则返回false,否则返回true
-o 或运算,有一个表达式为true则返回true
-a 与运算,两个表达式都为true才返回true
a=10
b=20

if [ $a != $b ]
then
   echo "$a != $b : a不等于b"
else
   echo "$a != $b: a等于b"
fi

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

if [ $a -lt 100 -o $b -gt 100 ]
then
   echo "$a小于100或$b大于100: 返回true"
else
   echo "$a小于100或$b大于100: 返回false"
fi

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

执行结果:

布尔运算符

逻辑运算符

下面是常用的逻辑运算符

运算符 说明
&& 逻辑与
|| 逻辑或
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

执行结果:

字符串运算符

文件测试运算符

参考


上一篇 Kafka学习之路

评论