shell 【bash】脚本模块化实践

起因

众所周知bash是个相当自由的语言,一个函数echo字符串直接就给你运行了,如果时网络上那分分钟就给你注入了。不过还好,没人用bash写网络应用。最近搭建openvpn,自己写了个脚本,但是bug不断。于是想分几个模块写,于是去网上找bash如何导入其他脚本。结果发现bash导入其他脚本非常简单粗暴。所有脚本的变量函数都混在一起,感觉非常不爽。于是就自己写了个导入逻辑。

遇到的问题

期间试过好几种方法都效果不好。
最开是想到的是用 export ,主脚本当作库脚本的子shell。这样就可以屏蔽库脚本的内部变量和函数,起到封装模块的目的。然而这个只有一层依赖还好,如果有多层,库函数的库函数也会暴露给主脚本。另外还有个问题就是如果export 的函数用到了没export的变量就会有问题。后来我用 unset特殊前缀的变量和函数的方法也是这个问题放弃了。期间我用生成脚本代码,或是用内置命令exec效果都不好。搞不好还会死循环,因为导入库后要调用自身。另外提一句,如果是一个没有状态的脚本,可以用一个简单的方法导入,类似./lib.sh fx avg./lib.sh var的方式调用。

代码

最后用的是重命名的方法,把没export的变量加个前缀。
实现了三个方法,

  • visit 用来访问私有的方法,根据调用的位置(上下文)加上前缀,确保访问到变量
  • import 用来导入库脚本,并且根据导入前后全局变量的差异,判断哪些未export的变量要加前缀。
  • private 这个方法并不实现主要逻辑,只是为了方便库函数访问自身的私有变量,假设有一个私有变量a,如果运行了eval "$(private)",就可以接用$a获取到变量a的值,否则只能用$(visit a)来获取到变量a的值。

visit

根据调用者来访问对应的变量或函数,由于import会重命名函数和变量,所以需要用visit函数动态调用。对于变量当然也可以这样调用,但是还是推荐用private函数。这样可以和原来代码无缝衔接 ,只需要在函数开始时加上一句eval "$(private)"

visit() {
    if [ -z "$1" ]; then
        exit 1
    fi
    local m=($(caller))
    m="m$(ls -i ${m[1]})"
    m=($m)
    m="${m[0]}_$1"
    local n=$1
    shift
    if declare -p $n >/dev/null 2>&1; then
        eval "echo \$$n"
        return 0
    elif declare -p $m >/dev/null 2>&1; then
        eval "echo \$$m"
        return 0
    elif declare -f $n >/dev/null 2>&1; then
        eval "$n $*"
        return 0
    elif declare -f $m >/dev/null 2>&1; then
        eval "$m $*"
        return 0
    fi
    return 1
}

import

这里每个脚本私有变量和函数的前缀是根据inode 索引生成的。

import() {
    local f=
    local env1=
    local env2=
    local met=
    local line=
    for f in "$@"; do
        if [ -f "$f" ]; then
            env1="$(declare -p | awk '{if($2=="--")print $3}' | sed -r 's/^([^=]+)=.*$/\1/g')"
            source "$f"
            env2="$(declare -p | awk '{if($2=="--")print $3}' | sed -r 's/^([^=]+)=.*$/\1/g')"
            #根据inode id生成前缀
            met=("m$(ls -i $f)")
            met=($met)
            # 重命名变量
            for line in $(echo -e "$env1\n$env2" | sort | uniq -u); do
                # echo "unset $line;${met[0]}_$(declare -p $line | awk '{if($2=="--")print $3}')"
                eval "unset $line;${met[0]}_$(declare -p $line | awk '{if($2=="--")print $3}')"
            done
            # 重命名函数
            for f in $(declare -F | awk -v OFS=' ' '{if($2=="-f")print $3}'); do
                # echo "unset -f $f;"$'\n'"${met[0]}_$(declare -f $f)"
                eval "unset -f $f;"$'\n'"${met[0]}_$(declare -f $f)"
            done
        else
            echo "Error: Cannot find library at: $f"
            exit 1
        fi
    done
}

private

注意 这个函数只能在函数内运行,用法: eval "$(private)"

private() {
    local m=($(caller))
    m="m$(ls -i ${m[1]})"
    m=($m)
    m="${m[0]}"
    declare -p | awk '{if($3~/^'$m'/){gsub(/'$m'_/,"local ",$3);print $3}}'
}

用法

把上面函数复制到/etc/profile 文件中,再加上

export -f visit
export -f import
export -f private

示例

a.sh

#!/bin/bash

import ./b.sh 

fx

echo $gg

b.sh

#!/bin/bash

fx() {
    eval "$(private)"
    echo "公有函数"
    echo "访问私有变量p $p"
    visit f
}
f() {
    echo '私有函数'
}
p='???'
export -f fx
export gg='全局变量'

注意事项

如果在函数内部定义了变量或函数,这些变量将不会重命名,应为在函数第一次运行之前并不会把这些变量和函数加载到内存,因此import 函数检测不到。

后记

至于为什么 visit fx 不优化成 fx。主要是bash 语法太自由了。不太好分析,再说过早的优化是一切罪恶的开始 :)

后后记

刚发布文章,思路就来了,用shopt -s expand_aliases开启alias。这样就彻底用不到visit函数了,真正实现无缝转换。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
git 脚本 bash shell 是一个用于自动执行 git 操作的脚本,在当前目录下,使用 git pull 命令自动拉取所有 git 仓库的更新。该脚本可以在 Windows 上的 Git Bash 环境中运行,因为 Git Bash 是一个 mingw64 的环境,可以运行 Linux 的大部分命令。 该脚本的实现主要包括以下几个步骤: 1. 定义了一个函数 git_pull_all,该函数用于遍历当前目录下的所有子目录。 2. 在遍历过程中,对每个子目录执行以下操作: a. 切换到当前子目录:cd $cur_dir b. 显示 git 状态:git status c. 清除未跟踪文件和文件夹:git clean -xdf d. 更新子模块:git submodule update --init --force --recursive e. 拉取最新代码:git pull 3. 输出运行结果。 要运行该脚本,只需在 Git Bash 命令行中输入 ./git_pull_all.sh。 请注意,在 Windows 上运行 shell 脚本需要先安装好 Git,并配置好环境。你可以参考提供的教程来进行 Git 的安装和配置。 总结来说,git 脚本 bash shell 是一个用于自动拉取当前目录下所有 git 仓库的脚本,可以在 Windows 的 Git Bash 环境中运行。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [git bash shell 脚本 :自动拉取目录下所有的git 仓库](https://blog.csdn.net/tcjy1000/article/details/124786613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [dotfiles:Git和bash首选项,以及用于在新笔记本电脑上进行设置的脚本](https://download.csdn.net/download/weixin_42102634/15497989)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [windows使用git bash运行shell脚本](https://blog.csdn.net/Amber__py/article/details/115199391)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值