使用Goa-Web框架执行Bash脚本时遇到命令未执行的问题 - Golang技术讨论

使用Goa-Web框架执行Bash脚本时遇到命令未执行的问题 - Golang技术讨论 技术栈:HyperledgerFabric 1.4,项目仓库 - FabricSamples 下的 FirstNetwork,Go – v1.15,Goa- v3.2.5

需求:使用Goa框架,我需要触发一个POST请求(http://localhost:8000/createChannelProfile),并且它必须返回文件内容作为响应。用户将从前端输入channelName和channelProfile。基本上,路由是这样发生的:触发rest-api请求 -> 该方法必须调用脚本文件 -> 脚本文件中的命令应生成一个文件 -> 该文件必须作为响应发送。我目前卡住的地方是脚本文件中的命令没有执行。它以状态码127退出并返回“failed”响应。作为第一次尝试的一部分,我只是返回字符串作为响应,而不是生成的输出文件。我对Go和Goa有点陌生,仍在弄清楚如何将文件作为响应发送。如果有人知道,请尝试给我一个提示。这些是我根据GOA文档执行的命令:goa gen GoApp2/design,goa example GoApp2/design,go build ./cmd/fabric,./fabric

design.go 
    
    package design
    
    import (
        "fmt"
        . "goa.design/goa/v3/dsl"
    )
    
    var _ = API("fabric", func() {
        Title("An api for fabric")
        Description("A simple goa service")
        Server("fabric", func() {
            Host("localhost", func() {
                URI("http://localhost:8000")
            })
        })
    })
    
    var _ = Service("fabric", func() {
        Description("The service to create a channel.tx file under channel-artifacts folder")
        //Method to add a new Channel Profile
        Method("create", func() {
            Payload(func() {
                Field(1, "channelName", String, "Name of the channel")
                Field(2, "channelProfile", String, "Name of the channel profile")
                Required("channelName", "channelProfile")
            })
            Result(String)
            Error("not_found", ErrorResult, "channelProfile not found")
            HTTP(func() {
                POST("/createChannelProfile")
                Response(StatusCreated)
            })
        })
        Files("/openapi.json", "./gen/http/openapi.json")
    })
fabric.go

//This is the file which calls the generateChannel.sh file

package fabricapi

import (
    fabric "GoApp2/gen/fabric"
    "context"
    "fmt"
    "log"
    "os/exec"
)

// fabric service example implementation.
// The example methods log the requests and return zero values.
type fabricsrvc struct {
    logger *log.Logger
}

// NewFabric returns the fabric service implementation.
func NewFabric(logger *log.Logger) fabric.Service {
    return &fabricsrvc{logger}
}

// Create implements create.
func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Print("fabric.create")
    cmd := exec.Command("/bin/bash", "../generateChannel.sh", p.ChannelName, p.ChannelProfile)
    stdout, err := cmd.Output()
    fmt.Errorf("error %s", err)
    s.logger.Print(p.ChannelName)
    s.logger.Print(p.ChannelProfile)
    output := string(stdout)
    s.logger.Print(output)
    res = output

    return res, nil
}
generateChannel.sh file

#!/bin/bash
export CHANNELNAME="$1"
export CHANNELPROFILE="$2"

../bin/configtxgen -profile "${CHANNELPROFILE}" -outputCreateChannelTx "./channel-artifacts/${CHANNELNAME}.tx" -channelID "${CHANNELNAME}"
res=$?
echo ${res}
if [ $res -eq 0 ]; then
  echo "success"
  else
  echo "failed"
fi

注意:我已经单独测试过这个文件,它执行得很完美。脚本文件中的上述命令会查找configtx.yaml文件中定义的channelProfile和channelName,并相应地生成channel.tx文件。我已经定义了环境变量,并将路径添加到了.bashrc和.profile文件中。请帮我看看我哪里出错了。

go env 截图 image|690x425

Postman输出:

image|690x307

终端输出:

:~/workspace/fabric-samples/first-network/GoApp2$ ./fabric [fabricapi] 12:47:59 HTTP “Create” mounted on POST /createChannelProfile [fabricapi] 12:47:59 HTTP “./gen/http/openapi.json” mounted on GET /openapi.json [fabricapi] 12:47:59 HTTP server listening on “localhost:8000” [fabricapi] 12:48:05 id=95ISzo9L req=POST /createChannelProfile from=127.0.0.1 [fabricapi] 12:48:05 fabric.create [fabricapi] 12:48:06 mychannel [fabricapi] 12:48:06 TwoOrgsChannel [fabricapi] 12:48:06 127 failed


更多关于使用Goa-Web框架执行Bash脚本时遇到命令未执行的问题 - Golang技术讨论的实战教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

错误已解决。现在工作正常。

更多关于使用Goa-Web框架执行Bash脚本时遇到命令未执行的问题 - Golang技术讨论的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,脚本文件本身执行得非常好。是的,bash脚本文件的位置是正确的。是的,bash脚本是可执行的。

是的,它现在找不到它的配置了。我会尝试将配置作为命令输入,然后检查一下。

看起来,127 不一定是 bash 创建的退出代码,而可能是从你的脚本传播的退出代码。根据显示的输出来看,脚本似乎找不到其配置文件。

附言,刚刚意识到你复制的堆栈跟踪并没有明确提示退出代码……

能否请你尝试提供一些更简单的输出,同时使用 Markdown 代码块来格式化这些输出?

Archana1:

脚本文件中的命令未被执行。它以状态码127退出并返回“失败”响应。

你是否尝试过在不涉及所有API相关代码的情况下直接执行?你确定Bash脚本位于你认为的位置吗(在你Go代码执行位置的上层目录)?Bash脚本是否具有可执行权限?

它能够选取 generateChannel.sh 文件。脚本文件中命令 …/bin/configtxgen -profile “${CHANNELPROFILE}” -outputCreateChannelTx “./channel-artifacts/${CHANNELNAME}.tx” -channelID “${CHANNELNAME}” 的执行结果是以状态码 127 退出的。单独执行该脚本文件时,它工作正常。我曾使用 nodejs-Express 执行相同的功能,当时工作正常。

你按照我说的检查过你Go程序的标准输出吗?Bash在那里打印了什么内容吗?具体是什么?请分享你的项目结构,并告诉我们你从哪里运行了哪些命令。

如果有疑问,请在一个最小的git仓库中重现这个问题,并告诉我们URL,在README中提供说明,说明要从哪里运行什么命令来重现这个错误。

如果bash以127退出,这确实意味着它无法找到某些东西,它会在标准错误输出中告诉你它无法找到什么。

请检查 Stderr,如果你不想对你的程序做太多改动,只需使用 CombinedOutput 而不是 Output

它会告诉你它无法找到你的 ../generateChannel.sh,因为这就是退出码 127 的含义。

请记住,相对路径是根据你运行程序的位置来解析的,而不是根据你存放源文件的位置。

因此,如果你从项目根目录运行 go run,那么脚本必须位于项目根目录的上一级。

func main() {
    fmt.Println("hello world")
}

根据你的描述,问题主要出现在执行Bash脚本时返回状态码127,这通常表示"命令未找到"。结合你的环境配置,问题可能出在以下几个方面:

主要问题分析

  1. 环境变量未正确传递:Go的exec.Command默认不会加载shell环境变量
  2. 相对路径问题:在Go中执行脚本时,工作目录可能与预期不同
  3. 权限问题:脚本文件可能没有执行权限

解决方案

1. 修复环境变量问题

修改fabric.go中的Create方法,确保环境变量正确传递:

func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Print("fabric.create")
    
    // 设置必要的环境变量
    cmd := exec.Command("/bin/bash", "../generateChannel.sh", p.ChannelName, p.ChannelProfile)
    
    // 获取当前环境变量并添加Fabric相关路径
    env := os.Environ()
    env = append(env, "PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
    env = append(env, "FABRIC_CFG_PATH="+os.Getenv("FABRIC_CFG_PATH"))
    cmd.Env = env
    
    // 设置工作目录到正确的位置
    cmd.Dir = "/home/ubuntu/workspace/fabric-samples/first-network"
    
    // 获取输出和错误
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    
    err = cmd.Run()
    
    if err != nil {
        s.logger.Printf("命令执行失败: %v", err)
        s.logger.Printf("标准错误输出: %s", stderr.String())
        return "", fmt.Errorf("脚本执行失败: %v, stderr: %s", err, stderr.String())
    }
    
    output := stdout.String()
    s.logger.Printf("脚本输出: %s", output)
    
    return output, nil
}

2. 使用绝对路径替代相对路径

创建一个更健壮的版本,使用绝对路径:

func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Print("fabric.create")
    
    // 使用绝对路径
    baseDir := "/home/ubuntu/workspace/fabric-samples/first-network"
    scriptPath := filepath.Join(baseDir, "generateChannel.sh")
    binPath := filepath.Join(baseDir, "bin/configtxgen")
    artifactsDir := filepath.Join(baseDir, "channel-artifacts")
    
    // 检查文件是否存在
    if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
        return "", fmt.Errorf("脚本文件不存在: %s", scriptPath)
    }
    
    // 直接执行configtxgen命令,而不是通过shell脚本
    cmd := exec.Command(
        binPath,
        "-profile", p.ChannelProfile,
        "-outputCreateChannelTx", filepath.Join(artifactsDir, p.ChannelName+".tx"),
        "-channelID", p.ChannelName,
    )
    
    // 设置环境变量
    cmd.Env = append(os.Environ(),
        "FABRIC_CFG_PATH="+baseDir,
    )
    cmd.Dir = baseDir
    
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr
    
    err = cmd.Run()
    
    if err != nil {
        s.logger.Printf("configtxgen执行失败: %v", err)
        s.logger.Printf("错误输出: %s", stderr.String())
        return "", fmt.Errorf("生成通道配置文件失败: %v", stderr.String())
    }
    
    // 读取生成的文件内容
    generatedFile := filepath.Join(artifactsDir, p.ChannelName+".tx")
    fileContent, err := os.ReadFile(generatedFile)
    if err != nil {
        return "", fmt.Errorf("无法读取生成的文件: %v", err)
    }
    
    return string(fileContent), nil
}

3. 添加调试信息

在调试阶段,添加更多日志信息:

func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (res string, err error) {
    s.logger.Printf("开始创建通道: %s, Profile: %s", p.ChannelName, p.ChannelProfile)
    
    // 打印当前工作目录
    wd, _ := os.Getwd()
    s.logger.Printf("当前工作目录: %s", wd)
    
    // 打印环境变量
    s.logger.Printf("FABRIC_CFG_PATH: %s", os.Getenv("FABRIC_CFG_PATH"))
    s.logger.Printf("PATH: %s", os.Getenv("PATH"))
    
    // ... 其余代码保持不变
}

4. 返回文件内容的完整示例

如果你需要将生成的文件作为HTTP响应返回,可以修改design.go:

// 在design.go中修改Result类型
Method("create", func() {
    Payload(func() {
        Field(1, "channelName", String, "Name of the channel")
        Field(2, "channelProfile", String, "Name of the channel profile")
        Required("channelName", "channelProfile")
    })
    Result(func() {
        Attribute("content", String, "Generated file content")
        Attribute("filename", String, "Generated filename")
    })
    Error("not_found", ErrorResult, "channelProfile not found")
    HTTP(func() {
        POST("/createChannelProfile")
        Response(StatusCreated, func() {
            ContentType("application/octet-stream")
            Header("filename:Content-Disposition")
        })
    })
})

然后在fabric.go中返回文件内容:

func (s *fabricsrvc) Create(ctx context.Context, p *fabric.CreatePayload) (*fabric.CreateResult, error) {
    // ... 执行命令生成文件
    
    // 读取文件内容
    content, err := os.ReadFile(generatedFilePath)
    if err != nil {
        return nil, err
    }
    
    return &fabric.CreateResult{
        Content:  string(content),
        Filename: p.ChannelName + ".tx",
    }, nil
}

关键点总结

  1. 状态码127:确保configtxgen命令在PATH中或使用绝对路径
  2. 环境变量:在Go中执行外部命令时,需要显式设置环境变量
  3. 工作目录:使用cmd.Dir设置正确的工作目录
  4. 错误处理:同时捕获stdout和stderr以便调试
  5. 文件权限:确保脚本文件有执行权限:chmod +x generateChannel.sh

先尝试第一个解决方案,查看详细的错误输出,这应该能帮助你定位具体问题所在。

回到顶部