前言
Shell作为连接用户与操作系统内核的桥梁,扮演着不可或缺的角色。它不仅是系统管理员日常工作的得力助手,也是程序员、数据分析师及任何希望深入了解并操控计算机底层逻辑的用户的必备工具。从简单的命令执行到复杂的脚本编程,Shell以其强大的功能和灵活性,在自动化任务处理、系统监控、数据处理等多个领域展现出了无与伦比的魅力。
通过本文的阅读与实践,您将能够:
- 理解Shell的概念以及工作原理;
- shell常用语法介绍;
- 如何编写一个简单的shell,部署vue项目;
1、什么是shell
Shell 是一个命令行界面,它允许用户与操作系统进行交互。在Unix、Linux、macOS等类Unix系统中。它作为用户和操作系统内核之间的接口,用户输入的命令被 Shell 接收,然后 Shell 解释这些命令,并将它们转换成操作系统能够理解的形式,最终由操作系统内核执行。执行的结果再由 Shell 返回给用户。
Shell 有多种类型,每种类型都有其独特的特性和命令集。最常见的 Shell 包括 Bash(Bourne Again SHell,大多数Linux发行版的默认Shell)、Zsh(Z Shell,提供了更强大的特性和更友好的用户体验)、Fish(Friendly Interactive SHell,以其用户友好性和自动建议功能而闻名)等。
2、shell的执行过程
shell具体是怎样执行的呢。在这里举一个简单的例子,比如在控制台/终端输入ls
命令,最终执行结果是列出当前路径下的文件信息。
1、Shell解析命令:
- 当你输入
ls
并按下回车键时,你的shell(比如bash、zsh等)会接收到这个输入。 - Shell会解析这个输入,识别出
ls
是一个命令,而不是一个文件名或者其他类型的输入。
2、查找命令:
- Shell接下来会在环境变量
PATH
指定的目录中查找名为ls
的可执行文件。PATH
环境变量包含了一系列目录的路径,这些目录被系统用来搜索可执行文件。 - 如果在
PATH
指定的某个目录中找到了ls
,shell就会确定这个可执行文件的完整路径。
3、执行命令:
- 一旦找到了
ls
命令的完整路径,shell就会加载并执行这个命令。这通常涉及到调用一个叫做exec
的系统调用,将控制权从shell传递给ls
程序。
4、ls程序执行:
ls
程序被加载到内存中并开始执行。ls
会读取命令行参数(如果有的话,比如ls -l
中的-l
),这些参数会影响ls
的输出格式。ls
会打开当前工作目录(CWD,Current Working Directory),这通常是通过调用getcwd()
函数实现的,以获取当前目录的路径。ls
会读取当前目录下的文件和目录列表。这通常是通过调用opendir()
函数打开目录流,然后反复调用readdir()
函数来读取目录中的每个条目。- 对于每个读取到的文件或目录,
ls
会执行适当的权限检查,以确定用户是否有权查看它(这通常涉及到调用access()
函数)。 - 根据命令行参数和文件/目录的属性,
ls
会格式化输出信息,并通过标准输出(stdout)显示这些信息。
5、输出显示:
- 最后,
ls
命令的输出会显示在你的终端或控制台窗口上。
6、命令结束:
- 当
ls
程序执行完毕,它会将控制权交还给shell。 - Shell会等待下一个命令的输入。
3、shell中常用语法
1、变量
在Shell中,变量不需要显式声明其类型(如整型、浮点型等),也不需要关键字来声明变量。变量的赋值使用等号(=
)进行,等号两边不能有空格。变量名可以是字母、数字或下划线,但不能以数字开头。
赋值和访问:
# 赋值
variable_name="value"
# 访问变量
echo $variable_name
# 或者使用大括号以避免解析错误
echo ${variable_name}
设置环境变量:
在Shell中,可以使用export
命令将变量导出为环境变量。
环境变量只会在它们被设置的Shell会话及其子会话中有效。一旦你关闭了Shell会话,这些变量就会丢失。
export MY_PATH="/usr/local/myapp"
参数变量:
在Shell脚本执行时传递给脚本的参数。
$0
代表脚本的名称,$1
、$2
、$3
等分别代表第一个、第二个、第三个等传递给脚本的参数。
echo "$0 $1 $2"
特殊变量:
$*
功能描述:这个变量代表命令行中所有的参数,$*
把所有的参数看成一个整体。
$@
功能描述:这个变量也代表命令行中所有的参数,不过$@
把每个参数区分对待。
2、条件表达式
在Shell中,条件表达式用于在if
、while
、until
等控制结构中根据条件执行不同的代码块。条件表达式可以基于字符串比较、文件测试、数值比较等多种条件来构建。
[
实际上是一个命令,与test
命令等价。它的语法要求条件表达式两边有空格,并且以]
结束。需要注意的是,[
并不是Shell的内置关键字,而是一个普通的命令
字符串比较
[ "$str" = "test" ] # 检查$str是否等于test
[ "$str" != "test" ] # 检查$str是否不等于test
[ -z "$str" ] # 检查$str是否为空(长度为0)
[ -n "$str" ] # 检查$str是否非空(长度非0)
文件测试
[ -e "$file" ] # 检查文件是否存在
[ -f "$file" ] # 检查是否为普通文件
[ -d "$dir" ] # 检查是否为目录
[ -r "$file" ] # 检查文件是否可读
[ -w "$file" ] # 检查文件是否可写
[ -x "$file" ] # 检查文件是否可执行
数值比较(注意:数值比较时,变量通常不需要引号,但加上引号可以避免某些情况下的错误)
[ "$num1" -eq "$num2" ] # 检查$num1是否等于$num2
[ "$num1" -ne "$num2" ] # 检查$num1是否不等于$num2
[ "$num1" -gt "$num2" ] # 检查$num1是否大于$num2
[ "$num1" -ge "$num2" ] # 检查$num1是否大于等于$num2
[ "$num1" -lt "$num2" ] # 检查$num1是否小于$num2
[ "$num1" -le "$num2" ] # 检查$num1是否小于等于$num2
3、流程控制
if 判断
(1)中括号和条件判断式之间必须有空格
(2)if后要有空格
#单条件语法
if [ 条件判断式 ]; then
# 条件为真时执行的命令
else
# 条件为假时执行的命令(可选)
fi
#多条件语法
if [ 条件判断式1 ]; then
# 条件1为真时执行的命令
elif [ 条件判断式2 ]; then
# 条件1为假且条件2为真时执行的命令
else
# 所有条件都为假时执行的命令(可选)
fi
举例:
num=10
if [ "$num" -lt 5 ]; then
echo "小于5"
elif [ "$num" -ge 5 ] && [ "$num" -le 10 ]; then
echo "在5和10之间(包括5和10)"
else
echo "大于10"
fi
case判断
基本语法:
case 变量名 in
模式1)
# 如果变量匹配模式1,则执行这里的命令
;;
模式2)
# 如果变量匹配模式2,则执行这里的命令
;;
...
*)
# 默认模式,如果变量不匹配任何模式,则执行这里的命令
;;
esac
举例:
#!/bin/bash
fruit="apple"
case "$fruit" in
"apple")
echo "I have an apple."
;;
"banana")
echo "I have a banana."
;;
"cherry")
echo "I have a cherry."
;;
*)
echo "Sorry, I don't have that."
;;
esac
在上边的例子中,fruit变量会命中第一个case判断,从而输出"I have an apple."。
for循环
shell脚本中的for
循环主要有两种形式:C风格的for
循环和Shell风格的for
循环(也称为列表遍历)。
1、C风格
基本语法:
for (( 初始化; 条件判断; 更新 ))
do
# 循环体
done
举例:打印数字1-5
for (( i=1; i<=5; i++ ))
do
echo $i
done
2、shell风格
基本语法:
for 变量 in 列表
do
# 循环体
done
举例:打印数字1-5
for i in {1..5}
# `{1..5}`是Bash扩展的语法,用于生成从1到5的数字序列。
do
echo $i
done
while循环
基本语法:
while [ 条件判断式 ]
do
# 循环体,即条件为真时要执行的命令
done
举例:
#!/bin/bash
counter=1
while [ $counter -le 5 ]
do
echo "Counter: $counter"
((counter++)) # 等同于 counter=$((counter + 1))
done
在上边的例子中,counter
变量被初始化为1,然后while
循环检查counter
是否小于或等于5。如果是,则执行循环体内的命令(打印counter
的值),并将counter
的值增加1。这个过程会一直重复,直到counter
的值大于5,此时条件判断式为假,while
循环结束。
4、输入输出
echo
命令用于在终端输出字符串或变量。
echo "Hello, World!"
# 输出变量
name="John"
echo "Hello, $name!"
printf
命令用于格式化输出文本,类似于C语言中的 printf
函数。
printf "Hello, %s\n" "World"
read
命令用于从标准输入(通常是键盘)读取数据,并将输入的数据赋值给变量。
echo "Enter your name: "
read name
echo "Hello, $name!"
4、shell脚本的执行方式
- 直接执行:
如果已经给脚本文件赋予了执行权限(使用chmod +x scriptname.sh
命令),可以直接通过./scriptname.sh
的方式在命令行中执行该脚本。注意,这里的./
表示当前目录,是必需的,因为系统默认不会在当前目录下查找可执行文件。 - 使用 shell 解释器执行:
即使脚本文件没有执行权限,也可以通过指定 shell 解释器来执行它。这通常通过在命令行中输入解释器的路径和脚本文件的路径来实现,例如使用 Bash 作为解释器,可以这样做:bash scriptname.sh
或者/bin/bash scriptname.sh
(取决于 Bash 在系统中的安装位置)。 - 在子 shell 中执行:
通过在命令行中使用括号()
,可以将脚本的执行放入一个子 shell 中。这种方式下,脚本中的变量更改不会影响到当前 shell 环境。例如:(./scriptname.sh)
。 - 使用 source 或 . 命令执行:
如果想要在当前 shell 环境中执行脚本,而不是在一个新的子 shell 中,可以使用source
命令或者它的简写形式.
。这种方式下,脚本中定义的变量、函数等在当前 shell 会话中也是可见的。例如:source scriptname.sh
或. scriptname.sh
。 - 通过脚本自身的 shebang 行执行:
在脚本文件的第一行,可以指定解释器的路径,这被称为 shebang(#!
)。这样,无论脚本文件的权限如何,只要直接通过脚本文件名(假设它位于 PATH 环境变量的某个目录中)执行它,系统就会使用指定的解释器来执行脚本。例如,如果脚本的第一行是#!/bin/bash
,那么你可以通过scriptname.sh
(假设脚本文件位于 PATH 中)来执行它,系统会自动调用/bin/bash
来执行脚本。
5、编写脚本
标准的shell脚本要包含哪些元素
- 以"#!"开头的声明,这个声明也称作She-Bang
- "#"开头的注释行
- 执行的命令
补充:
#!bin/bash
声明有哪些好处:
1、在使用bash 1.sh
命令执行脚本时,由于bash是直接被调用来执行脚本的,因此它会忽略脚本文件的第一行(即shebang行)。shebang行的主要作用是告诉系统使用哪个解释器来执行脚本,但在这个场景中,解释器(bash)已经被明确指定了。
2、除了直接使用bash 1.sh
命令来执行脚本文件1.sh
,还可以通过赋予脚本执行权限后,直接输入./1.sh
来执行。这种方式会调用系统通过文件属性确定的解释器来执行脚本。当文件内的第一行(即shebang行)存在并正确指定了/bin/bash
时,它就像是一个指令,告诉系统需要使用/bin/bash
这个解释器来执行该脚本,即使系统的默认shell可能不是bash。这样的设计使得脚本在不同用户和系统间具备了更高的可移植性和兼容性。
在实际开发中,团队成员可能只有几个人,项目规模也不大,所以不需要使用复杂的 CI/CD 工具,或者还没有搭建Jenkins等工具。那么就可以通过编写简单的脚本来自动化这个过程,省去重复劳动,提高效率。
一般来说,部署项目的核心操作就是将我们开发的项目(vue项目举例)通过执行 npm run build 以后,打包成dist文件,将dist文件发布到远程服务器上。通过配置nginx即可完成项目的部署。
针对上述过程,可以确定编写脚本的核心功能就是拉取项目代码,进行编译,上传到服务器。
核心步骤:
1、拉取代码仓库中项目代码
2、安装依赖
3、打包构建
4、备份数据
#!/bin/bash
# 设置项目参数
REPO_URL="https://gitee.com/myproject/front_end.git" # Git仓库URL,这里拿gitee举例
REPO_DIR="/www/deploy/projects" # 替换为你希望克隆项目到服务器的路径
BACKUP_DIR="/www/deploy/backups" # 替换为你希望存放备份zip文件的路径
# 确保备份目录存在
mkdir -p "$BACKUP_DIR"
# 切换到项目目录
# 如果目录已存在,则切换到master分支,假定master分支是主分支,拉取最新代码
# 如果项目不存在,那么克隆项目到服务器上
if [ -d "$REPO_DIR" ]; then
echo "项目目录已存在"
cd "$REPO_DIR" # 进入到项目目录
git fetch
git checkout master
git pull
else
echo "正在从Gitee克隆项目..."
git clone -b master "$REPO_URL" "$REPO_DIR"
fi
echo "项目处理成功"
# 进入项目目录
cd "$REPO_DIR"
# # 安装依赖(这里假设使用npm)
echo "正在安装依赖..."
npm install
# # 构建Vue项目
echo "正在构建Vue项目..."
npm run build # 或者使用 yarn build,根据你的项目配置
# 打包dist目录为zip文件
echo "正在备份dist目录..."
DIST_ZIP_FILE="$BACKUP_DIR/$(date +%Y%m%d%H%M%S)_dist.zip"
zip -rq "$DIST_ZIP_FILE" dist
# # 打印完成信息
echo "Vue项目已部署并备份到 $DIST_ZIP_FILE"
# 返回到脚本开始时的目录
cd -
执行结果:
当脚本编写完成,通过赋予该文件适当的读取(r)和执行(x)权限(使用chmod u+rx filename
命令),便可以直接在Shell环境中调用并执行该脚本。
通过执行上边的脚本,从代码仓库中拉取代码到服务器上,切到主分支进行编译打包。如果在开发中有类似的部署需求,可以直接粘贴代码,修改仓库地址即可使用。
作者:洞窝-美阳