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/bash

cd $(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 systemctl
    if [ $user_id -ne 0 ]; then
        echo "Must run as sudo"
        exit 1
    fi

}

 可以看到,在每个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/bash


user_id=`id -u`

# we want to snapshot the environment of the config user
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
    echo "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; do
    user=$(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; do
    user=$(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 - $username

expect {
    "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自动应答,免交互的形式,方便读者学习和使用。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值