Golang中使用sudo权限执行bash命令的方法

Golang中使用sudo权限执行bash命令的方法 是否可以使用 exec.Command 执行包含 sudo 的 bash 命令?例如,下面的代码在执行 Execute(“ls /”) 时运行正常,但在执行 Execute(“sudo mkdir /test”) 时会挂起。

我认为我期望的是,当命令包含 sudo 时,会弹出要求输入密码的对话框。

我是在 Manjaro Linux 上测试的…

func main() {
	//fmt.Println(Execute("ls /"))
	fmt.Println(Execute("sudo mkdir /test"))
}

func Execute(command string) string {
	cmd := exec.Command("bash", "-c", command)
	// 确保创建一个新的独立进程
	// 该进程不会在此进程退出时被杀死
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	output, err := cmd.Output()
	if err!=nil {
		fmt.Println(err.Error())
	}

	return string(output)
}

我之所以这样问,是因为我正在为自己编写一个启动应用程序和脚本的程序。我知道这类应用程序已经存在,但我想自己编写。例如,当我在启动器程序中输入 gimp 时,我希望它能为我启动 Gimp。而当我输入 fstab 时,我希望它运行 sudo xed /etc/fstabsudo nano /etc/fstab。当然,运行 xed /etc/fstab 没问题,但那样我就无法编辑文件了。


更多关于Golang中使用sudo权限执行bash命令的方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

好的,这是一个可以接受的解决方案。它会打开一个 gnome-terminal 并执行命令 sudo nano /etc/fstab,因此会立即要求你输入 sudo 密码,然后你就可以编辑文件了。有点遗憾的是,你看不到它要求 sudo 密码是为了哪个命令。不过,嘿,我相信我自己……大多数时候……

package main

import (
	"fmt"
	"os/exec"
	"syscall"
)

func main() {
	fmt.Println(Execute("gnome-terminal -x sudo nano /etc/fstab"))
}

func Execute(command string) string {
	commands := append([]string{"-c"}, command)
	cmd := exec.Command("bash", commands...)
	// 确保创建一个新的独立进程
	// 该进程不会在此进程退出时被杀死
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	output, err := cmd.Output()
	if err!=nil {
		fmt.Println(err.Error())
	}

	return string(output)
}

更多关于Golang中使用sudo权限执行bash命令的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中通过exec.Command执行包含sudo的命令时,程序会挂起是因为sudo需要从标准输入读取密码,而默认情况下Go不会为命令提供标准输入。以下是几种解决方案:

方案1:使用-S参数从标准输入读取密码

package main

import (
    "bytes"
    "fmt"
    "os/exec"
    "syscall"
)

func ExecuteWithSudo(command, password string) (string, error) {
    // 使用 -S 参数让 sudo 从标准输入读取密码
    cmd := exec.Command("sudo", "-S", "bash", "-c", command)
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    
    // 创建包含密码的输入缓冲区(末尾需要换行符)
    var stdin bytes.Buffer
    stdin.Write([]byte(password + "\n"))
    cmd.Stdin = &stdin
    
    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("执行失败: %v", err)
    }
    
    return string(output), nil
}

func main() {
    // 注意:硬编码密码有安全风险,仅用于演示
    output, err := ExecuteWithSudo("mkdir /test", "yourpassword")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("输出:", output)
}

方案2:配置免密码sudo(推荐用于自动化脚本)

首先配置/etc/sudoers文件,允许特定命令免密码执行:

# 编辑sudoers文件
sudo visudo

# 添加以下行(将username替换为你的用户名)
username ALL=(ALL) NOPASSWD: /usr/bin/mkdir
username ALL=(ALL) NOPASSWD: /usr/bin/xed
username ALL=(ALL) NOPASSWD: /usr/bin/nano

然后Go代码可以正常执行:

func Execute(command string) (string, error) {
    cmd := exec.Command("bash", "-c", command)
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    
    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("执行失败: %v", err)
    }
    
    return string(output), nil
}

func main() {
    // 现在可以正常执行sudo命令
    output, err := Execute("sudo mkdir /test")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("输出:", output)
}

方案3:使用expect风格的交互式输入

package main

import (
    "bufio"
    "fmt"
    "io"
    "os/exec"
    "strings"
    "syscall"
)

func ExecuteInteractive(command string) (string, error) {
    cmd := exec.Command("bash", "-c", command)
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    
    // 获取标准输入和输出管道
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return "", err
    }
    
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return "", err
    }
    
    stderr, err := cmd.StderrPipe()
    if err != nil {
        return "", err
    }
    
    // 启动命令
    if err := cmd.Start(); err != nil {
        return "", err
    }
    
    // 读取输出并检测密码提示
    go func() {
        scanner := bufio.NewScanner(stdout)
        for scanner.Scan() {
            line := scanner.Text()
            fmt.Println("输出:", line)
            
            // 检测到密码提示时输入密码
            if strings.Contains(line, "[sudo] password for") {
                io.WriteString(stdin, "yourpassword\n")
            }
        }
    }()
    
    // 读取错误输出
    go func() {
        scanner := bufio.NewScanner(stderr)
        for scanner.Scan() {
            fmt.Println("错误:", scanner.Text())
        }
    }()
    
    // 等待命令完成
    err = cmd.Wait()
    if err != nil {
        return "", err
    }
    
    return "命令执行完成", nil
}

方案4:使用pkexec或图形化密码提示

对于桌面应用程序,可以使用pkexec

func ExecuteWithGUI(command string) (string, error) {
    // 使用pkexec显示图形化密码输入对话框
    cmd := exec.Command("pkexec", "bash", "-c", command)
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    
    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("执行失败: %v", err)
    }
    
    return string(output), nil
}

func main() {
    // 这会弹出图形化密码输入对话框
    output, err := ExecuteWithGUI("xed /etc/fstab")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("输出:", output)
}

针对你的启动器程序的完整示例

package main

import (
    "fmt"
    "os/exec"
    "strings"
    "syscall"
)

func LaunchApplication(app string) error {
    var cmd *exec.Cmd
    
    // 检查是否需要sudo权限
    if strings.HasPrefix(app, "sudo ") {
        // 使用pkexec处理需要权限的命令
        actualCommand := strings.TrimPrefix(app, "sudo ")
        cmd = exec.Command("pkexec", "bash", "-c", actualCommand)
    } else {
        cmd = exec.Command("bash", "-c", app)
    }
    
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    
    // 对于GUI应用,不需要等待
    if strings.Contains(app, "gimp") || strings.Contains(app, "xed") {
        return cmd.Start()
    }
    
    // 对于命令行工具,等待完成
    return cmd.Run()
}

func main() {
    // 启动GIMP(不需要sudo)
    if err := LaunchApplication("gimp"); err != nil {
        fmt.Println("启动GIMP失败:", err)
    }
    
    // 编辑fstab(需要sudo,会弹出密码对话框)
    if err := LaunchApplication("sudo xed /etc/fstab"); err != nil {
        fmt.Println("编辑fstab失败:", err)
    }
}

安全提醒:方案1和方案3中硬编码密码存在安全风险,建议在生产环境中使用方案2(配置免密码sudo)或方案4(使用图形化密码提示)。对于桌面应用程序,pkexec是最合适的解决方案。

回到顶部