Копирование вывода по SSH

17 января 2020

Как скопировать вывод команды на удалённый SSH-сервер?

Ниже будут рассмотрены следующие варианты:

  • cat,
  • scp,
  • sftp: curl и lftp.

Все они имеют смысл в том случае, если производимые командой данные имеют слишком большой размер и не помещаются на локальном диске, поэтому их необходимо сразу отправлять на удалённый сервер, без создания промежуточной локальной копии.

Наиболее распространённый пример: виртуальная машина с ограниченными ресурсами, выполняющая резервное копирование большой базы для отправки на удалённый сервер.

Классика жанра:

Наиболее известным методом является вызов cat на сервере:

локальная_команда | ssh remote_user@remote_host "cat - > /remote/file" 

Однако как быть, если SSH-сервер предназначен только для копирования файлов и выполнение команд на нём ограничено через rssh, scponly, ForceCommand или authorized_keys?

Сначала разберёмся с scp:

Согласно официальной документации, scp работает только с обычными файлами и каталогами.

Попытка скопировать с его помощью /dev/stdout или именованный pipe выдаёт ошибку “not a regular file”.

Однако у scp существует недокументированный ключ “-t”:

$ scp -v /tmp/aa.txt localhost:/tmp/bb.txt
Executing: program /usr/bin/ssh host localhost, user (unspecified), command scp -v -t /tmp/bb.txt
OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n  7 Dec 2017
...
debug1: Sending command: scp -v -t /tmp/bb.txt
Sending file modes: C0664 33 aa.txt
Sink: C0664 33 aa.txt
date1.txt        100%   33    92.3KB/s   00:00    
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0

Схема работы scp и всех прочих инструментов для передачи данных через SSH-транспорт (т.е. rsync, git и т.д.) примерно одинакова:

  • локальный scp устанавливает ssh-сессию;
  • запускает “scp -t /remote/file” на удалённой стороне;
  • затем отправляет удалённому процессу scp служебный заголовок (права доступа, размер, локальное имя) и содержимое файла;
  • которое тот читает из ssh-соединения и записывает на диск в /remote/file.

Т.е. запустив удалённый “scp -t” не локальной командой scp, а через ssh, и сформировав заголовок вручную, мы могли бы отправить данные так:

{ echo "C0600 9999999999 test"; наша_команда; } | ssh remote_user@remote_host 'scp -t /remote/file.txt'

На практике данный способ малоприменим, потому что в заголовке требуется указать размер файла, а для вывода команды он заранее неизвестен.

Если указать заведомо большее число, то удалённый scp, во-первых, вернёт ошибку “broken pipe”, и во-вторых, не запишет последнюю часть принятых данных (сохранение производится блоками по 64 килобайта).

Неэлегантно обойти эту ошибку можно, добавив за вызовом команды отправку 64 килобайт нулей:

{ echo "C0600 9999999999 test"; наша_команда; dd status=none if=/dev/zero bs=64K count=1 } | ssh ...

При этом придётся смириться, что (а) файлы на сервере станут занимать больше места за счёт хвостов с нулями, (б) перед дальнейшей обработкой файла этот хвост может потребоваться аккуратно отрезать, причём (в) не всегда имея возможность определить его точную длину.

Поэтому вариант с scp имеет смысл использовать в крайнем случае, когда запрещены и cat (см.выше), и sftp (см.ниже).

Забегая вперёд, следует отметить, что замена scp на curl без замены протокола с scp на sftp не помогает:

$ наша_команда | curl -k -sS -T - -u remote_user scp://remote.host/remote/file.txt
Enter host password for user 'user1': ***
curl: (25) SCP requires a known file size for upload

SFTP:

Если на сервере разрешён SFTP, то у нас появляется определённый выбор.

Например, поддержку SFTP имеет утилита cURL:

наша_команда | curl -k -sS -u username:password -T - sftp://remote.host/remote/file

В этом примере:

  • “-k” отключает проверку подлинности серверного ключа;
  • “-sS” подавляет вывод информационных сообщений, оставляя только сообщения об ошибках;
  • “-u” задаёт логин и пароль для подключения (если «:пароль» отсутствует, curl запросит его интерактивно);
  • “-T -” читает данные для отправки со стандартного входа.

К сожалению, cURL собран с SFTP не во всех дистрибутивах. Проверяется это командой “curl -V”. Например, в CentOS 7 протоколы “sftp” и “scp” имеются в её выводе, а в Ubuntu 18.04 отсутствуют:

$ curl /tmp/aa.txt sftp://localhost/tmp/bb.txt
curl: (3)  malformed
curl: (1) Protocol "sftp" not supported or disabled in libcurl

Однако в Ubuntu с поддержкой SFTP собрана другая замечательная утилита. Скрепный импортозамещённый вариант с её использованием будет выглядеть так:

наша_команда | lftp -u remote_user, -e "put /dev/stdin -o remote/file.txt" sftp://remote_host

Пояснения:

  • Для авторизации по SSH-ключу “remote_user” необходимо указывать через “-u”, а не внутри URL.
    Вариант с “sftp://remote_user@remote_host” никакие настройки из ~/.ssh и /etc/ssh читать не станет.
  • Ключ “-u” используется со значением “remote_user,remote_password”.
    Пароль указывать необязательно, но запятую удалять нельзя!


← Назад в Блог

Подпишитесь на новые статьи: