Несколько команд через оболочку JSch

Я пытался выполнить несколько команд по протоколу SSH, используя библиотеку JSch. Но я, кажется, застрял и не нашел никакого решения. Метод setCommand() может выполнять только отдельные команды за сеанс. Но я хочу последовательно выполнять команды так же, как приложение connectbot на платформе Android. Пока мой код:

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

Команда представляет собой строку и может быть любой, что принимает удаленная оболочка. Попробуйте

cmd1 ; cmd2 ; cmd3

для запуска нескольких команд последовательно. Или

cmd1 && cmd2 && cmd3

для запуска команд до тех пор, пока не произойдет сбой.

Даже это может работать:

cmd1
cmd2
cmd3

или в Java:

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

Sidenote: не вводите пароли и имена пользователей в код. Поместите их в файл свойств и используйте системное свойство, чтобы указать имя файла свойства. Таким образом, вы можете сохранить файл даже вне проекта и убедиться, что пароли/имена пользователей не протекают.

Ответ 2

Если вам не нужно различать входы или выходы отдельных команд, ответ от Aaron (дающий все команды в строке, разделенные \n или ;) в порядке.

Если вам приходится обрабатывать их по отдельности или не знать более поздние команды до того, как предыдущие будут завершены: вы можете открыть несколько exec -Channels на той же сессии (т.е. соединение), один за другим (т.е. после того, как она была закрыта). У каждого есть своя команда. (Но они не разделяют среду, поэтому команда cd в первом не влияет на более поздние.)

Вам просто нужно позаботиться о том, чтобы объект Session, а не создавать новый для каждой команды.

Другим вариантом будет канал оболочки, а затем передача отдельных команд удаленной оболочке в качестве входных данных (т.е. через поток). Но тогда вы должны позаботиться о том, чтобы не смешивать входной сигнал с одной командой с помощью следующей команды (т.е. Это работает только в том случае, если вы знаете, что делают команды, или если у вас есть интерактивный пользователь, который может поставлять оба входа в команду и следующую команду и знает, какой из них следует использовать, когда.)

Ответ 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" каналов, сколько вам нужно, используя каждый для выполнения одной из ваших команд. Вы можете открывать каналы последовательно или даже параллельно.

Полный пример использования канала "exec" см. Exec.java примере JSch Exec.java.

Таким образом, каждая команда выполняется в изолированной среде. Что может быть преимуществом, но в некоторых случаях оно также может быть нежелательным.


Если вам нужно выполнить команды таким образом, чтобы предыдущие команды влияли на последующие команды (например, изменение рабочего каталога или установка переменной окружения), вы должны выполнить все команды в том же канале. Для этого используйте соответствующую конструкцию серверной оболочки. В большинстве систем вы можете использовать точки с запятой:

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

На серверах * nix вы также можете использовать && для выполнения следующих команд только тогда, когда предыдущие команды преуспели:

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

См. Также Выполнение списка команд из ArrayList с использованием JSch-exec в Java.


Самая сложная ситуация - когда команды зависят друг от друга, и вам нужно обработать результаты предыдущих команд, прежде чем переходить к другим командам.

Когда у вас есть такая потребность, это обычно указывает на плохой дизайн. Подумайте, если это действительно единственное решение вашей проблемы. Или рассмотрите возможность использования сценария оболочки на стороне сервера для реализации логики, вместо того чтобы делать это удаленно с Java-кода. Сценарии оболочки имеют гораздо более мощные методы для проверки результатов предыдущих команд, а затем у вас есть интерфейс SSH в JSch.

Во всяком случае, см., Что канал JSch Shell выполняет команды по одному результатам тестирования перед продолжением.


Примечание стороны: Не используйте StrictHostKeyChecking=no. См. Безопасность JSch SFTP с session.setConfig("StrictHostKeyChecking", "no"); ,