通过 JSch shell 执行多个命令

Posted

技术标签:

【中文标题】通过 JSch shell 执行多个命令【英文标题】:Multiple commands through JSch shell 【发布时间】:2011-08-15 10:52:17 【问题描述】:

我试图使用 JSch 库通过 SSH 协议执行多个命令。但我似乎卡住了,找不到任何解决方案。 setCommand() 方法在每个会话中只能执行单个命令。但我想像 android 平台上的 connectbot 应用程序一样按顺序执行命令。到目前为止,我的代码是:

package com.example.ssh;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class ExampleSSH extends Activity 
    /** Called when the activity is first created. */
    EditText command;
    TextView result;
    Session session;
    ByteArrayOutputStream baos;
    ByteArrayInputStream bais;
    Channel channel;

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bais = new ByteArrayInputStream(new byte[1000]);
        command = (EditText) findViewById(R.id.editText1);
        result  = (TextView) findViewById(R.id.terminal);
    

    public void onSSH(View v)
        String username = "xxxyyyzzz";
        String password = "aaabbbccc";
        String host     = "192.168.1.1"; // sample ip address
        if(command.getText().toString() != "")
            JSch jsch = new JSch();
            try 
                session = jsch.getSession(username, host, 22);
                session.setPassword(password);

                Properties properties = new Properties();
                properties.put("StrictHostKeyChecking", "no");
                session.setConfig(properties);
                session.connect(30000);

                channel = session.openChannel("shell");
                channel.setInputStream(bais);
                channel.setOutputStream(baos);
                channel.connect();

             catch (JSchException e) 
                // TODO Auto-generated catch block
                Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
            
        
        else
            Toast.makeText(this, "Command cannot be empty !", Toast.LENGTH_LONG).show();
        
    

    public void onCommand(View v)
        try 
            bais.read(command.getText().toString().getBytes());
         catch (IOException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        

        baos = new ByteArrayOutputStream();
        channel.setOutputStream(baos);
        result.setText(baos.toString());

    

代码似乎已连接到服务器,但我认为输入和输出数组缓冲区存在一些问题,因为根本没有输出。有人可以指导我如何正确处理服务器的输入和输出以获得所需的输出吗?

【问题讨论】:

【参考方案1】:

该命令是一个字符串,可以是远程 shell 接受的任何内容。试试

cmd1 ; cmd2 ; cmd3

依次运行多个命令。或者

cmd1 && cmd2 && cmd3

运行命令直到一个失败。

即使这样也行:

cmd1
cmd2
cmd3

或在 Java 中:

channel.setCommand("cmd1\ncmd2\ncmd3");

旁注:不要将密码和用户名放入代码中。将它们放入属性文件中,并使用系统属性来指定属性文件的名称。这样,您甚至可以将文件保留在项目之外,并确保密码/用户名不会泄露。

【讨论】:

【参考方案2】:

如果您不必区分各个命令的输入或输出,Aaron 的答案(连续给出所有命令,以\n; 分隔)就可以了。

如果你必须分开处理,或者在前面的命令完成之前不知道后面的命令:你可以在同一个Session(即连接)上打开多个exec-Channels,一个接一个(即在之前的一个已关闭)。每个人都有自己的命令。 (但他们不共享环境,所以第一个中的cd 命令对后面的没有影响。)

您只需要注意周围有Session 对象,而不是为每个命令创建一个新对象。

另一个选项是shell channel,然后将各个命令作为输入(即通过流)传递给远程 shell。但是,您必须注意不要将一个命令的输入与下一个命令混合(即只有当您知道命令在做什么时,或者如果您有一个交互式用户可以同时为命令和下一个命令,并且知道何时使用哪个命令。)

【讨论】:

【参考方案3】:

设置一个 SCPInfo 对象来保存用户名、密码、端口:22 和 ip。

    List<String> commands = new ArrayList<String>();
    commands.add("touch test1.txt");
    commands.add("touch test2.txt");
    commands.add("touch test3.txt");
    runCommands(scpInfo, commands);

public static void runCommands(SCPInfo scpInfo, List<String> commands)
    try 
        JSch jsch = new JSch();
        Session session = jsch.getSession(scpInfo.getUsername(), scpInfo.getIP(), scpInfo.getPort());
        session.setPassword(scpInfo.getPassword());
        setUpHostKey(session);
        session.connect();

        Channel channel=session.openChannel("shell");//only shell  
        channel.setOutputStream(System.out); 
        PrintStream shellStream = new PrintStream(channel.getOutputStream());  // printStream for convenience 
        channel.connect(); 
        for(String command: commands) 
            shellStream.println(command); 
            shellStream.flush();
        

        Thread.sleep(5000);

        channel.disconnect();
        session.disconnect();
     catch (Exception e)  
        System.err.println("ERROR: Connecting via shell to "+scpInfo.getIP());
        e.printStackTrace();
    


private static void setUpHostKey(Session session) 
    // Note: There are two options to connect
    // 1: Set StrictHostKeyChecking to no
    //    Create a Properties Object
    //    Set StrictHostKeyChecking to no
    //    session.setConfig(config);
    // 2: Use the KnownHosts File
    //    Manually ssh into the appropriate machines via unix
    //    Go into the .ssh\known_hosts file and grab the entries for the hosts
    //    Add the entries to a known_hosts file
    //    jsch.setKnownHosts(khfile);
    java.util.Properties config = new java.util.Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);

【讨论】:

这仅在命令不接受任何输入时才有效,否则以下命令将被解释为第一个命令的输入。 太棒了!正是我想要的。【参考方案4】:

尽量避免使用“shell”通道。 “shell”通道旨在实现交互式会话,而不是自动执行命令。使用“shell”通道,您将面临许多不必要的副作用。

要自动执行命令,请使用“exec”通道。


通常,您可以根据需要打开任意数量的“执行”通道,使用每个通道来执行您的一个命令。您可以按顺序打开通道,甚至可以并行打开。

有关“exec”通道使用的完整示例,请参阅JSch Exec.java example。

这样每个命令都在一个隔离的环境中执行。这可能是一种优势,但在某些情况下也可能是不可取的。


如果您需要以先前命令影响以后命令的方式执行命令(例如更改工作目录或设置环境变量),则必须在同一通道中执行所有命令。为此,请使用适当的服务器外壳构造。在大多数系统上,您可以使用分号:

execChannel.setCommand("command1 ; command2 ; command3");

在*nix服务器上,你也可以使用&amp;&amp;使以下命令仅在前面的命令成功时才执行:

execChannel.setCommand("command1 && command2 && command3");

另见Execute a list of commands from an ArrayList using JSch exec in Java。

在许多情况下,您甚至不需要使用多个命令。

例如,您可以在交互式使用 shell 时代替这个序列:

cd /path
ls

你可以这样做:

ls /path

最复杂的情​​况是,当命令相互依赖时,您需要在继续执行其他命令之前处理先前命令的结果。

当你有这样的需求时,它通常表明一个糟糕的设计。仔细想想,如果这真的是解决您问题的唯一方法。或者考虑实现一个服务器端的 shell 脚本来实现逻辑,而不是从 Java 代码远程执行。 Shell 脚本具有更强大的技术来检查先前命令的结果,然后您可以在 JSch 中使用 SSH 接口。

无论如何,请参阅JSch Shell channel execute commands one by one testing result before proceeding。


旁注:不要使用StrictHostKeyChecking=no。见JSch SFTP security with session.setConfig("StrictHostKeyChecking", "no");。

【讨论】:

以上是关于通过 JSch shell 执行多个命令的主要内容,如果未能解决你的问题,请参考以下文章

使用java中的jsch在linux中通过sudo命令执行shell脚本以启动服务

使用jsch连接到linux上,然后执行shell命令,返回的结果中存在乱码,各位大大怎么解决?

通过 JSch shell 通道向服务器发送命令

Jsch使用SSH协议连接到远程Shell执行脚本

JSch 在一个会话中执行多个 linux 命令

使用java JSch库一个接一个地执行多个命令