如何在Java中以sudo权限执行bash命令?

29

我正在使用ProcessBuilder来执行bash命令:

import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            Process pb = new ProcessBuilder("gedit").start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

但我想制作这样的东西:

Process pb = new ProcessBuilder("sudo", "gedit").start();
如何将超级用户密码传递给bash?
("gksudo", "gedit")不起作用,因为自Ubuntu 13.04以来已删除,我需要使用默认可用的命令来完成这个任务。
编辑
随着最新更新,gksudo回到了Ubuntu 13.04。

1
太好了,取消gksudo是个绝妙的主意!祝贺Ubuntu!作为一种解决方法,我可以建议使用“xterm -e 'sudo -i gedit'”。或者安装“gksu”软件包。甚至可以以root身份运行整个Java程序。 - Aleks-Daniel Jakimenko-A.
@Aleks-DanielJakimenko 使用xterm实际上是个好主意...谢谢。sudo app.jar会像应该的那样工作吗?从打开应用到关闭它? - Vare Zon
是的,“sudo app.jar”应该为它提供sudo权限。只要注意,你所做的一切都将在root下进行,因此如果你从Java程序中创建文件,其他用户将无法看到它,因为用户没有对它的特权。请注意这一点。 - Aleks-Daniel Jakimenko-A.
3
解决方案在这里:http://codeflex.co/java-run-sudo-command-on-remote-linux-host/ - JavaGoPro
6个回答

42

我认为你可以使用这个,但我有点犹豫是否要发布它。所以我只想说:

请自行承担风险,在不推荐使用的情况下,不要起诉我等等...

public static void main(String[] args) throws IOException {

    String[] cmd = {"/bin/bash","-c","echo password| sudo -S ls"};
    Process pb = Runtime.getRuntime().exec(cmd);

    String line;
    BufferedReader input = new BufferedReader(new InputStreamReader(pb.getInputStream()));
    while ((line = input.readLine()) != null) {
        System.out.println(line);
    }
    input.close();
}

4
好的,现在它正在运行 :){"/bin/bash","-c","echo \"密码\"| sudo -S ls"};谢谢。 - Vare Zon
1
是的,我也刚刚再次测试了一下!对我来说,它能够/不能够工作是因为我的sudo记住了我。使用sudo -K取消缓存。你可以点赞以便让其他人知道这就是答案吗? - Erik Pragt
顺便说一下,我不需要转义引号,不确定是否需要。很高兴它能正常工作! - Erik Pragt
1
这个 String[] cmd = {"/bin/bash","-c","echo password| sudo -S ls"}; 对我来说完美无缺!谢谢!! - user5587563
2
这段代码运行得很完美,但不是一个好的解决方案,因为它在命令行中暴露了密码,其他进程可以看到它。 - kato2
显示剩余6条评论

14

使用visudo编辑/etc/sudoers文件,为您的用户授予特定脚本的NOPASSWD权限:

username ALL=(ALL) NOPASSWD: /opt/yourscript.sh


1
这是唯一可行的选择。 - Ivo Limmen
2
或者按照armnotstrong的建议将文件添加到/etc/sudoers.d中:https://dev59.com/NGMl5IYBdhLWcg3wFThP#42090432 - gillesB

9

我的解决方案不会在命令行中显示密码,而是将密码提供给该进程的输出流。这是一种更灵活的解决方案,因为它允许你在需要时向用户请求密码。

public static boolean runWithPrivileges() {
    InputStreamReader input;
    OutputStreamWriter output;

    try {
        //Create the process and start it.
        Process pb = new ProcessBuilder(new String[]{"/bin/bash", "-c", "/usr/bin/sudo -S /bin/cat /etc/sudoers 2>&1"}).start();
        output = new OutputStreamWriter(pb.getOutputStream());
        input = new InputStreamReader(pb.getInputStream());

        int bytes, tryies = 0;
        char buffer[] = new char[1024];
        while ((bytes = input.read(buffer, 0, 1024)) != -1) {
            if(bytes == 0)
                continue;
            //Output the data to console, for debug purposes
            String data = String.valueOf(buffer, 0, bytes);
            System.out.println(data);
            // Check for password request
            if (data.contains("[sudo] password")) {
                // Here you can request the password to user using JOPtionPane or System.console().readPassword();
                // I'm just hard coding the password, but in real it's not good.
                char password[] = new char[]{'t','e','s','t'};
                output.write(password);
                output.write('\n');
                output.flush();
                // erase password data, to avoid security issues.
                Arrays.fill(password, '\0');
                tryies++;
            }
        }

        return tryies < 3;
    } catch (IOException ex) {
    }

    return false;
}

通过稍微修改代码,我认为这是最好的解决方案。我特别喜欢这个解决方案解决了安全问题,因为你不会直接在控制台打印密码。 - Cedric

7
不要试图直接在文件中明文编写系统密码,特别是针对拥有sudo权限的用户。正如@jointEffort所回答的,应该由系统管理员而不是应用程序编写者来解决授予权限的问题。 sudo允许您为特定命令授予特定用户权限,这足够精确了,详见本篇文章 如果您想要管理特权,请选择在一个单独的文件中而非主sudoers文件中进行。只需在主/etc/sudoers文件中添加#includedirs /etc/sudoers.d/即可(大多数Linux发行版已经这样做了),并创建一个名为ifconfig-user的文件,其中包含以下内容:
 USER_NAME ALL=(ALL) NOPASSWD: /sbin/ifconfig

另外一件事是,请记得用visudo编辑配置文件,以防在语法出错时失去对系统的控制。

2

我知道这是一个旧的线程,但我想在这里加一句:

你可以使用sudo -S *command*作为传递给创建进程实例的命令。然后获取输出流并将密码写入其中,在其末尾添加一个新行和c.返回(\n\r)。可能不需要返回,但我加上了以防万一。另外,刷新流是个好主意,确保所有内容都被写入。我已经做过几次了,它能完美运行。请不要忘记关闭流:)。


1

一旦您生成一个进程,您可以提取输入和输出流。只需将密码提供给输出流(将其输出到进程的输入中)。因此,代码看起来像这样 -

Process pb = new ProcessBuilder("gedit").start();
OutputStream out = pb.getOutputStream();
out.write(password);

1
实际上,那样行不通。sudo命令需要从实际键盘获取命令,而不是从输入流获取... - Erik Pragt
@ErikPragt 你确定吗?我从未听说过这样的事情。据我所知,sudo命令不应该知道它来自哪里,它只是从输入流中获取并执行。 - David says Reinstate Monica
2
如果你看一下Sudo中的-S选项,我认为这有点暗示了:“-S(stdin)选项会导致sudo从标准输入读取密码,而不是终端设备。密码必须跟随一个换行符。”也就是说,除非我理解错了。但是,我不确定。 - Erik Pragt
1
@Dgrin91,我在我的Mac上尝试了你的解决方案,但它不起作用,密码没有传递给进程。请参见此处:https://gist.github.com/bodiam/6502781 - Erik Pragt
@VareZon 不太确定。试着在末尾加一个换行符。Erik 说的很有可能是对的。 - David says Reinstate Monica
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接