Передача декількох файлів через stdin (понад ssh)


15

У мене є програма на віддаленому хості, виконання якого мені потрібно автоматизувати. Команда, що виконує цю програму на одній машині, виглядає приблизно так:

/path/to/program -a file1.txt -b file2.txt

В цьому випадку, file1.txtі file2.txtвикористовуються для абсолютно різних речей , в рамках програми, так що я не можу просто catїх разом. Однак у моєму випадку, file1.txtі file2.txtте, що я хочу перейти в програму, існує лише на моєму пристрої, а не на хості, де мені потрібно виконати програму. Я знаю, що я можу подати принаймні один файл через SSH, передавши його через stdin:

cat file1.txt | ssh host.name /path/to/program -a /dev/stdin -b file2.txt

але, оскільки мені заборонено зберігати файли на хості, мені потрібен спосіб, щоб також перейти file2.txtтуди. Я думаю, що це можливо через зловживання змінними середовища та творчим використанням catта sedразом, але я не знаю інструментів настільки добре, щоб зрозуміти, як би я їх використав для цього. Це можливо, і як?


2
catі sedтут не рішення.
Slyx

Можливо, мені вдалося б встановити, але, враховуючи проксі та обмеження безпеки, я не впевнений, що зможу втекти від цього.
Зелений плащ Гай

У вас є дозвіл на віддаленій машині монтувати ssh папку?
Slyx

1
якщо ви можете відкрити сеанс ssh з віддаленої машини на ваш локальний, тож на рівні мережі не виникає проблем для монтажу папки SSH.
Slyx

Ви можете робити експедиції? Які системи та оболонки у вас є на локальному та віддаленому кінці?
mosvy

Відповіді:


18

Якщо файли, наведені в якості аргументів вашій програмі, є текстовими файлами, і ви можете контролювати їх вміст (ви знаєте рядок, який не зустрічається всередині них), ви можете використовувати кілька тут-документів:

{
    echo "cat /dev/fd/3 3<<'EOT' /dev/fd/4 4<<'EOT' /dev/fd/5 5<<'EOT'"
    cat file1
    echo EOT
    cat file2
    echo EOT
    cat file3
    echo EOT
} | ssh user@host sh

Ось catприкладна команда, яка приймає назви файлів як аргументи. Це може бути замість цього:

echo "/path/to/prog -a /dev/fd/3 3<<'EOT' -b /dev/fd/4 4<<'EOT'

Замініть кожного EOTтим, що не відбувається у кожному з файлів відповідно.


1
Цей підхід досить розумний! Якщо файли не текстові, ви можете кодувати / декодувати їх чимось на зразок base64 або навіть гарним ol 'uuencode ... Дуже приємно!
filbranden

3
Зауважте, що кілька (більшість) sh-реалізацій реалізують тут документи, використовуючи тимчасові файли, тому це може не працювати для ОП, якщо їм не дозволяється зберігати файли на віддаленому хості.
Стефан Шазелас

2
dash( /bin/shВ Debian, Ubuntu, і т.д.), busybox sh, то /bin/shз FreeBSD і yashне використовувати тимчасові файли тут-документів. Я фактично перевірив це за допомогою chroot лише для читання. Звичайно, /dev/fd/файли повинні бути доступними - лише на складі система FreeBSD /dev/fd/0-2є, тому fdescfsїї потрібно монтувати /dev/fd.
mosvy

1
Я прихильник розділювачів інформації ASCII , які призначені для розмежування полів без перешкод. U+001Cє "Сепаратор інформації чотири" (роздільник файлів, FS) і ідеально підходить для цього випадку, хоча двійкові файли можуть використовувати його випадково. Тому я пропоную EOT="$(printf $'\x1c')"в передових оболонках або ще EOT="$(awk 'BEGIN{printf"%c",28}')"для сумісності. Вам потрібно буде цитувати його, коли ви поставите його на місце, наприклад echo "$EOT$EOT$EOT"(втричі зменшити шанси на збіг двійкового файлу).
Адам Кац

Якщо ви збираєтесь використовувати tty, чому б не скористатися ttyкомандою? Якщо я щось не пропускаю - що, напевно, можливо?
Прифтан

14

Можливо, не зовсім те, що ви хочете ... Але, можливо, подумайте надсилати тарбол через трубу, відкриту ssh?

Ти сказав це:

Мені заборонено зберігати файли на хості.

Можливо, у вас немає домашнього каталогу, що можна записати, або іншого зручного місця для зберігання файлів на тривалий термін, але я б сказав, що навряд чи у вас немає місця для запису, навіть якщо тимчасове, tmpfsяке буде доступне лише для вашої конкретний зв'язок.

У багатьох програмах (і навіть підпрограми libc) потрібна можливість запису /tmp, тому, швидше за все, одна буде доступна для вас.

Тоді ви можете використовувати скрипт, який розпакує tarball у тимчасовий каталог, запустить вашу програму та очистить через ssh-з'єднання.

Щось на зразок:

$ tar cf - file1.txt file2.txt |
  ssh host.name '
      set -e
      tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX)
      cleanup () { rm -rf "$tmpdir"; }
      trap cleanup EXIT
      cd "$tmpdir"
      tar xf -
      /path/to/program -a file1.txt -b file2.txt
  '

Для цього може знадобитися додатковий догляд із шляхами до файлів, і слід розглядати деякі кутові випадки (перевірити їх), але загальний підхід повинен працювати.

Якщо каталог, що не використовується для запису, недоступний, можливим підходом буде модифікація, programщоб взяти тарбол як єдиний вхід і розпакувати його вміст у пам'ять. Наприклад, якщо programце сценарій Python, то використання вбудованого tarfileмодуля легко здійснить щось подібне.


3
Навіть не переживаючи проблему тарболінгу - я вирішив більш-менш просто написати файл /tmp/і безпосередньо використовувати його.
Зелений плащ Гай

2
Підхід tarball має деякі переваги, оскільки ви використовуєте одне ssh-з'єднання (не потрібно тестувати на успіх / збій), і кілька одночасних запусків, швидше за все, не натраплять один на одного (використовувати та / або видаляти файли в / tmp, призначені для окремих прогонів .) Але я думаю, що це найкраще, що ти отримаєш від ssh, як це існує зараз. Удачі!
filbranden

3
@GreenCloakGuy Отже, зрештою, ви можете добре зберігати файли на сервері. Будь ласка, змініть відповідне запитання.
mosvy

1
@mosvy може , так, але не хочу . Питання стоїть: "Чи можна це зробити без збереження жодних файлів", відповідь на які видається "можливо, але не в моєму випадку"
Зелений плащ Гай

5

Якщо ви можете встановити слухач TCP (може бути більш високим портом), тоді ви можете використовувати другий сеанс SSH для встановлення другого вхідного джерела nc.

Приклад:

Цей скрипт на сервері ( ~/script.bash):

#!/usr/bin/env bash
cat "$1" | tr a 1
nc localhost "$2" | tr '[:lower:]' '[:upper:]'

І є ці два файли локально:

$ cat a 
aaa
aaa
aaa
$ cat b
bbb
bbb
bbb

Тепер спочатку запустіть друге джерело ( $servце сервер):

ssh "$serv" nc -l -p 2222 -q1 <b &

І виконайте команду належним чином:

$ ssh "$serv" ./script.bash - 2222 <a
111
111
111
BBB
BBB
BBB
[1]+  Done                    ssh "$serv" nc -l -p 2222 -q1 < b

2

Це було встановлено в коментарі, який /tmpможна записати, тому просто скопіюйте попередньо один із файлів:

scp -p file2.txt host.name:/tmp/
ssh host.name "/path/to/program -a /dev/stdin -b /tmp/file2.txt && rm /tmp/file2.txt" < file1.txt

Це також очищає скопійований файл після успішного запуску (змініть &&на a, ;якщо ви хочете видалити його незалежно від успіху, але потім зауважте, що ви втратите значення виходу).


Якщо це не прийнятно, я б запропонував попрацювати з ним /path/to/programабо обгорткою, яка може відокремити два файли від одного вхідного потоку, наприклад:

awk 'FNR == 1 && NR > 1 { printf "%c%c%c", 28, 28, 28 } 1' file1.txt file2.txt \
  | ssh host.name /path/to/tweaked_program

При цьому використовується роздільник інформації ASCII чотири (роздільник файлів, FS), і він потроївся, щоб ми мінімізували шанс того, що бінарний файл збігся з цією строкою. Ви tweaked_programб потім розділити на вхід даний сепаратор , а потім працювати на двох збережених файлів в якості змінних.

Звичайно, якщо ви використовуєте мову, яка має бібліотеки для боротьби з тарболами, більш безпечним і чистішим підходом буде просто введення tarкоду в такий код ssh:

tar -zpc file1.txt file2.txt |ssh host.name /path/to/tweaked_program

І ваш tweaked_programрозпакує і відкриє архів, збереже кожен файл в іншій змінній, а потім запустить programлогіку оригіналу на змінні.


1

Якщо вам дозволяється пересилати порти ssh, і у вас є доступ до wgetвіддаленої машини та до busyboxлокальної машини, ви можете зробити щось на кшталт:

mkdir /tmp/test; cd /tmp/test
echo 1st_file > 1st_file
echo 2nd_file > 2nd_file

busybox httpd -f -p 127.0.0.1:11080 &
ssh USER@HOST -R 10080:127.0.0.1:11080 '
        cat <(wget -q -O- http://localhost:10080/1st_file) \
            <(wget -q -O- http://localhost:10080/2nd_file)
'
kill $!

(використовуючи catв якості прикладу програму, яка бере два аргументи файлу).

Важливим є лише можливість переадресації портів за допомогою -R- замість того, щоб робити http, ви можете використовувати інші методи, наприклад. якщо ваш netcatпідтримує -dта -Nваріанти:

nc -Nl localhost 11001 < 1st_file &
nc -Nl localhost 11002 < 2nd_file &
ssh USER@HOST -R 10001:localhost:11001 -R 10002:localhost:11002 '
        cat <(nc -d localhost 10001) <(nc -d localhost 10002)

Можливо, існують способи заміни <(...)технологічних підстановок, якщо оболонка входу на віддаленій машині не схожа на ksh- або bash-.

Загалом, це не надто велике - кращі знання про точну систему / оболонки / конфігурації / дозволи (які ви не надали) могли б забезпечити розумніші рішення.


-1

ОНОВЛЕННЯ: Це насправді не працює, ssh має дуже задане уявлення про те, що означають stdin та stdout / stderr і насправді не дозволяє узурпувати stderr для читання з нього. Я скасую цю відповідь через кілька днів, оскільки вона не працює. Дякую за дуже цікаву дискусію !!!


На жаль, це не кращий спосіб зробити це безпосередньо, так як sshклієнт буде передавати тільки три файлових дескрипторів ( stdin, stdoutі stderr) на сервер , і він не має положення передати додаткові дескриптори файлів (які були б корисні для даного конкретного випадку використання .)

(Також зауважте, що у протоколі SSH є положення про передачу додаткових дескрипторів файлів, лише sshклієнт не реалізує спосіб використання цієї функції. Теоретично, розширення клієнта з патчем було б достатньо для викриття цієї функції.)

Один з химерних способів досягти того, що ви шукаєте, - використовувати дескриптор файлів 2 ( stderrдескриптор файлу) для передачі другого файлу. Щось на зразок:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/stderr \
      <file1.txt 2<file2.txt

Це має спрацювати, це просто може спричинити проблему, якщо programнамагається записати на stderr. Ви можете обійти це, повторно жонглюючи дескрипторами файлів на віддаленому кінці перед запуском програми. Ви можете перейти file2.txtдо дескриптора 3 та повторно відкрити stderr до дзеркального stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt

Я ще не дуже перевіряв це (набираючи по телефону), але, принаймні, теоретично я би очікував, що це спрацює. Дайте мені знати, якщо з якихось причин цього немає. Ура!
filbranden

Це взагалі не буде працювати. Навіть із зламаним ssh. AFAIK stderr використовує не окремий канал, а якесь особливе повідомлення. Але так, можливість доступу до ssh-каналів у вигляді труб або неназваних unix доменних розеток (замість того, щоб робити переадресацію сокетів) було б чудово, це просто не здається можливим у будь-якому виді чи формі.
mosvy

@mosvy, fd 0, 1 і 2 у віддаленій команді будуть 3 різні труби. Дані пересуваються через 3 різні канали, мультиплексовані в ssh-з'єднанні, але вони є однонаправленими (від клієнта до розриву для fd 0 і від сервера до клієнта для 1 і 2), тому навіть у системах, де труби двосторонні, виграли ' t працювати. В Linux відкриття / dev / stderr у режимі читання лише на іншому кінці труби, тому воно також ніколи не працюватиме.
Stéphane Chazelas

Зверніть увагу, що останній передбачає оболонку входу користувача на remote.name - подібний до Bourne ( 3<&2є оператором оболонки Bourne, а sshd запускає оболонку входу користувача для інтерпретації команди, що надсилається клієнтом)
Stéphane Chazelas

2
@ StéphaneChazelas Nope, для stdin / stdout / stderr використовується один ssh-канал, і дані stderr передаються через SSH_MSG_CHANNEL_EXTENDED_DATAповідомлення з типом, встановленим на SSH_EXTENDED_DATA_STDERR. Ви можете прочитати всю справу тут
mosvy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.