05-06 周一 Shell工程目录划分和开发最佳实践

2024-05-07 19:04

本文主要是介绍05-06 周一 Shell工程目录划分和开发最佳实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

05-06 周一 Shell工程目录划分和开发最佳实践
时间版本修改人描述
2024年5月6日10:34:13V0.1宋全恒新建文档
2024年5月6日11:07:12V1.0宋全恒完成

简介

 之前楼主曾经完成过一个shell工程的开发,记得当时项目名称叫做campus-shell,主要是用来一键完成多个模块的部署。当时的模块主要包括:

  • 多个java工程
  • python服务
  • AI芯片上的服务

 其实这也是之前积累的经验。主要是在构造这样的工程时,应该如何分配目录以及存放的东西。

shell工程结构

image-20240506104351907

注意事项

相对路径

 由于在shell工程,主要是通过相对路径进行工作的,在执行shell时,要妥善的处理相对路径。如下所示:

yangfei@ubuntu:~/ci-deploy/module$ cat check_prelinimary.sh 
#!/bin/bashcd $(dirname $0)
pwd
config_file=../config/config.json# 验证当前用户具有sudo权限
validate_sudo() {user_id=`id -u`# systemctl must run as sudo# this script is a convenience wrapper around systemctlif [ $user_id -ne 0 ]; thenecho "Must run as sudo"exit 1fi}

 可以看到,在每个module中的shell执行时,最上层都有如下的代码

cd $(dirname $0)
pwd
config_file=../config/config.json

 上述的代码的主要含义,是切换到当前shell所在的工作目录,并在当前的工作目录下定义配置文件的位置,当然是使用相对路径。这样整体就一致了。

配置文件处理

 配置文件也是一个非常重要的点,因为在shell中,不像python处理ini或者json有比较良好的库,因此我们在处理shell的时候,也需要比较复杂的结构,以相同的策略,也可以使用json的配置文件,然后读取时采用相应的库。

 具体可以参考 04-23 周二 shell环境下读取使用jq 读取json文件

 接下来演示一个案例:

配置文件内容

yangfei@ubuntu:~/ci-deploy$ cat config/config.json 
{"repositories": [{"repo_name": "LLMs_Inference","runner_name": "LLMs_Inference_runner","user": "zhangyi","passwd": "123456","url": "https://github.com/CodexDive/LLMs_Inference","token": "BCTEUWMMR7EU7BFLLO76ADTGFHF3M","directory": "actions-ci","tags": ["inference","gpu","self-hosted", "Linux", "X64"]},{"repo_name": "LLMChat","runner_name": "LLMChat_runner","user": "zhangweixing","passwd": "123456","url": "https://github.com/CodexDive/LLMChat","token": "BCTEUWJ76AILP3ZL54L25JDGE5WBS","directory": "actions-ci","tags": ["application","self-hosted", "Linux", "X64"]}]
}

 上述为一个json的配置文件,这个仓库的主要目标是一件完成为团队的仓库配置CI,想要固话这个目录,标签以及必要的信息。如上所示,我们为每个仓库配置了仓库的名称,运行器名称,以及token,和tags,在json中包含了数组结构,字符串结构,是一个相对复杂的结构。

读取配置文件

yangfei@ubuntu:~/ci-deploy$ cat config_runners.sh 
#!/bin/bashuser_id=`id -u`# we want to snapshot the environment of the config user
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; thenecho "Must not run with sudo"exit 1
fi
cd $(dirname $0)
config_file="config/config.json"jq -c '.repositories[]' "$config_file" | while IFS= read -r repository; douser=$(echo "$repository" | jq -r '.user')passwd=$(echo "$repository" | jq -r '.passwd')ci_dir=$(echo "$repository" | jq -r '.directory')url=$(echo "$repository" | jq -r '.url')token=$(echo "$repository" | jq -r '.token')tags=$(echo "$repository" | jq -r '.tags | join(",")')runner_name=$(echo "$repository" | jq -r '.runner_name')echo "tags: $Tags"work_dir_cmd="cd /home/$user/$ci_dir"cmd="./config.sh --unattended --url $url --token $token --labels "$tags" --name $runner_name"echo "$cmd"utils/expect_exec_cmd.sh "$user" "$passwd" "$work_dir_cmd" "$cmd"

 上述代码演示了使用jq来读取配置文件的过程(一般我们在部署时,需要先配置好),具体含义,就不再赘述了。

 可以看出上述的额代码,主要是读取配置文件之后,然后组装cmd命令,读取了user和passwd命令,并将这些参数以位置参数的形式传递给了脚本expect_exec_cmd.sh

shell脚本调用

 其实在配置文件处理中已经描述了这个过程。

shell调用参数组织

jq -c '.repositories[]' "$config_file" | while IFS= read -r repository; douser=$(echo "$repository" | jq -r '.user')passwd=$(echo "$repository" | jq -r '.passwd')ci_dir=$(echo "$repository" | jq -r '.directory')url=$(echo "$repository" | jq -r '.url')token=$(echo "$repository" | jq -r '.token')tags=$(echo "$repository" | jq -r '.tags | join(",")')runner_name=$(echo "$repository" | jq -r '.runner_name')echo "tags: $Tags"work_dir_cmd="cd /home/$user/$ci_dir"cmd="./config.sh --unattended --url $url --token $token --labels "$tags" --name $runner_name"echo "$cmd"utils/expect_exec_cmd.sh "$user" "$passwd" "$work_dir_cmd" "$cmd"

 通过读取配置文件中一些参数项的内容,我们完成了expect_exec_cmd.sh的脚本调用参数的组织,该脚本需要四个参数

$1: 用户名

$2: 密码

$3: 命令执行目录

$4: 待执行的命令

注,在调用shell文件的时候,依然采用的事相对位置的调用形式,如上述代码采用的是

utils/expect_exec_cmd.sh "$user" "$passwd" "$work_dir_cmd" "$cmd"

 这表明在执行这部分代码时,是位于ci-deploy目录下的,所以执行环境可以正确找到utils/expect_exec_cmd.sh

shell脚本采用expect自动应答的形式执行命令

yangfei@ubuntu:~/ci-deploy$ cat utils/expect_exec_cmd.sh 
#!/usr/bin/expect -f# author: songquanheng
# date: 2024年4月24日16:17:43
# desc: 该脚本用于使用expect判断用户是否存在以及用户名密码是否匹配# 设置超时时间,这里设置为10秒
set timeout 10# 通过命令行参数传递用户名和密码
set username [lindex $argv 0]
set password [lindex $argv 1]
set work_dir_cmd [lindex $argv 2]
set cmd [lindex $argv 3]puts "username $username"
puts "password $password"
puts "word_dir_cmd $work_dir_cmd"
set status_code 42# puts "username: $username"
# puts "password: $password"
# 启动交互式程序
spawn su - $usernameexpect {"Password:" {# 提供密码send "$password\r"}-re "exist" {puts "user $username does not exist"exit 4}
}expect {-re "Authentication failure" {puts "Login failed. password wrong."exit 1 }-re "$username@" {puts "Login success."}timeout { puts "Login failed. Connection timed out."; exit 2}
}
send "$work_dir_cmd\r"
send "$cmd\r"
expect {"Settings Saved" {puts "runner authentication、registration、settings executes successfully"; exit 0}
}exit $status_code

 当时为开发这个ci-deploy工程,上述代码也是失败的主要原因,因为该命令设置了Timeout 10,但是由于配置运行器需要和github进行交互,需要下载许多的网络资源,一因此执行时间不太保证,所以就失败了,这也是这个工程没有继续执行下去的核心原因所在。

 但,这不影响,shell脚本调用的演示。

 上述shell为expect脚本,主要的作用是接收几个参数来确定执行的用户,以及密码,并在指定目录下执行命令的过程。

注: 该脚本也演示了expect脚本设置变量,打印变量,并使用退出码来判断执行结果的演示,也演示了从调用处读取位置参数

总结

 该文件主要是记录了一个shell工程的最佳实践和目录划分,并且给出了在开发这样工程时的三个注意事项:

  1. 相对路径
  2. 配置文件处理
  3. shell脚本调用

 并且行文在演示shell脚本调用时,采用的是expect自动应答,免交互的形式,方便读者学习和使用。

这篇关于05-06 周一 Shell工程目录划分和开发最佳实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/968122

相关文章

MySQL数据目录迁移的完整过程

《MySQL数据目录迁移的完整过程》文章详细介绍了将MySQL数据目录迁移到新硬盘的整个过程,包括新硬盘挂载、创建新的数据目录、迁移数据(推荐使用两遍rsync方案)、修改MySQL配置文件和重启验证... 目录1,新硬盘挂载(如果有的话)2,创建新的 mysql 数据目录3,迁移 MySQL 数据(推荐两

Agent开发核心技术解析以及现代Agent架构设计

《Agent开发核心技术解析以及现代Agent架构设计》在人工智能领域,Agent并非一个全新的概念,但在大模型时代,它被赋予了全新的生命力,简单来说,Agent是一个能够自主感知环境、理解任务、制定... 目录一、回归本源:到底什么是Agent?二、核心链路拆解:Agent的"大脑"与"四肢"1. 规划模

SpringBoot简单整合ElasticSearch实践

《SpringBoot简单整合ElasticSearch实践》Elasticsearch支持结构化和非结构化数据检索,通过索引创建和倒排索引文档,提高搜索效率,它基于Lucene封装,分为索引库、类型... 目录一:ElasticSearch支持对结构化和非结构化的数据进行检索二:ES的核心概念Index:

Python数据验证神器Pydantic库的使用和实践中的避坑指南

《Python数据验证神器Pydantic库的使用和实践中的避坑指南》Pydantic是一个用于数据验证和设置的库,可以显著简化API接口开发,文章通过一个实际案例,展示了Pydantic如何在生产环... 目录1️⃣ 崩溃时刻:当你的API接口又双叒崩了!2️⃣ 神兵天降:3行代码解决验证难题3️⃣ 深度

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Python+wxPython开发一个文件属性比对工具

《Python+wxPython开发一个文件属性比对工具》在日常的文件管理工作中,我们经常会遇到同一个文件存在多个版本,或者需要验证备份文件与源文件是否一致,下面我们就来看看如何使用wxPython模... 目录引言项目背景与需求应用场景核心需求运行结果技术选型程序设计界面布局核心功能模块关键代码解析文件大

C++多线程开发环境配置方法

《C++多线程开发环境配置方法》文章详细介绍了如何在Windows上安装MinGW-w64和VSCode,并配置环境变量和编译任务,使用VSCode创建一个C++多线程测试项目,并通过配置tasks.... 目录下载安装 MinGW-w64下载安装VS code创建测试项目配置编译任务创建 tasks.js

C#实现插入与删除Word文档目录的完整指南

《C#实现插入与删除Word文档目录的完整指南》在日常的办公自动化或文档处理场景中,Word文档的目录扮演着至关重要的角色,本文将深入探讨如何利用强大的第三方库Spire.Docfor.NET,在C#... 目录Spire.Doc for .NET 库:Word 文档处理利器自动化生成:C# 插入 Word

MySQL存储过程实践(in、out、inout)

《MySQL存储过程实践(in、out、inout)》文章介绍了数据库中的存储过程,包括其定义、优缺点、性能调校与撰写,以及创建和调用方法,还详细说明了存储过程的参数类型,包括IN、OUT和INOUT... 目录简述存储过程存储过程的优缺点优点缺点存储过程的创建和调用mysql 存储过程中的关键语法案例存储

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7