Melewati banyak file melalui stdin (over ssh)

15

Saya memiliki program pada host jarak jauh, yang pelaksanaannya perlu sayaotomatiskan. Perintah menjalankan program itu, pada mesin yang sama, terlihat seperti ini:

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

Dalam hal ini, file1.txtdan file2.txtdigunakan untuk hal-hal yang sepenuhnya berbeda dalam program, jadi saya tidak bisa hanya catbersama-sama. Namun, dalam kasus saya, file1.txtdan file2.txtyang ingin saya sampaikan ke program hanya ada di perangkat saya, bukan pada host tempat saya perlu menjalankan program. Saya tahu bahwa saya dapat memberi makan setidaknya satu file melalui SSH dengan mengirimkannya stdin:

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

tetapi, karena saya tidak diizinkan untuk menyimpan file di host, saya perlu cara untuk mendapatkan di file2.txtsana juga. Saya berpikir itu mungkin melalui penyalahgunaan variabel lingkungan dan penggunaan kreatif catdan sedbersama - sama, tapi saya tidak tahu alat cukup baik untuk memahami bagaimana saya akan menggunakannya untuk mencapai ini. Apakah bisa dilakukan, dan bagaimana?

Guy Jubah Hijau
sumber
2
catdan sedbukan solusinya di sini.
Slyx
Mungkin saya bisa me-mount, tetapi diberikan proxy dan kendala keamanan saya tidak yakin saya bisa lolos begitu saja.
Green Cloak Guy
Apakah Anda memiliki izin di mesin jarak jauh untuk memasang folder ssh?
Slyx
1
jika Anda dapat membuka sesi ssh dari mesin jarak jauh ke mesin lokal Anda, jadi tidak ada masalah di tingkat jaringan untuk memasang folder SSH.
Slyx
Bisakah Anda melakukan penerusan? Sistem dan cangkang apa yang Anda miliki di ujung lokal dan remote?
Mosvy

Jawaban:

18

Jika file yang diberikan sebagai argumen untuk program Anda adalah file teks, dan Anda dapat mengontrol kontennya (Anda tahu baris yang tidak muncul di dalamnya), Anda dapat menggunakan beberapa dokumen di sini:

{
    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

Berikut catadalah contoh perintah yang menggunakan nama file sebagai argumen. Ini bisa jadi sebagai gantinya:

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

Ganti masing EOT- masing dengan sesuatu yang tidak terjadi di masing-masing file, masing-masing.

mosvy
sumber
1
Pendekatan ini cukup pintar! Jika file-file tersebut bukan teks, Anda dapat menyandikan / mendekode dengan sesuatu seperti base64 atau bahkan uuencode yang bagus ... Sangat bagus!
filbranden
3
Perhatikan bahwa beberapa (sebagian besar) implementasi mengimplementasikan dokumen di sini menggunakan file sementara, jadi itu mungkin tidak berfungsi untuk OP jika mereka tidak diizinkan untuk menyimpan file pada host jarak jauh.
Stéphane Chazelas
2
dash(yang /bin/shdi debian, ubuntu, dll), busybox sh, yang /bin/shdari FreeBSD dan yashtidak menggunakan file-file sementara untuk di sini-dokumen. Saya sebenarnya sudah mengujinya dengan chroot read-only. Tentu saja, /dev/fd/file - file itu harus tersedia - hanya tersedia sistem FreeBSD /dev/fd/0-2, jadi fdescfsharus dipasang /dev/fd.
Mosvy
1
Saya penggemar pemisah informasi ASCII , yang dirancang untuk membatasi bidang tanpa halangan. U+001Cadalah "Pemisah Informasi Empat" (pemisah file, FS) dan sangat ideal untuk kasus ini, meskipun file biner dapat menggunakannya secara kebetulan. Oleh karena itu, saya sarankan EOT="$(printf $'\x1c')"di shell canggih atau yang lain EOT="$(awk 'BEGIN{printf"%c",28}')"untuk kompatibilitas. Anda harus mengutipnya ketika Anda meletakkannya, seperti echo "$EOT$EOT$EOT"(tiga kali lipat untuk menurunkan kemungkinan mencocokkan file biner).
Adam Katz
Jika Anda akan menggunakan tty mengapa tidak menggunakan ttyperintah? Kecuali saya melewatkan sesuatu - yang saya kira mungkin?
Pryftan
14

Mungkin tidak persis seperti yang Anda inginkan ... Tapi mungkin mempertimbangkan mengirim tarball melalui pipa yang dibuka oleh ssh?

Anda mengatakan itu:

Saya tidak diizinkan menyimpan file di host.

Mungkin saja Anda tidak memiliki direktori home yang dapat ditulisi atau lokasi lain yang nyaman untuk menyimpan file dalam jangka panjang, tetapi saya katakan itu tidak mungkin Anda tidak memiliki lokasi yang dapat ditulisi, meskipun hanya sementara tmpfsyang hanya tersedia untuk Anda koneksi tertentu.

Banyak program (dan bahkan rutin libc) memerlukan program yang dapat ditulisi /tmp, sehingga kemungkinan besar program tersebut akan tersedia untuk Anda.

Kemudian Anda bisa menggunakan skrip yang akan membongkar tarball ke direktori sementara, menjalankan program Anda dan membersihkan melalui koneksi ssh.

Sesuatu seperti:

$ 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
  '

Ini mungkin memerlukan perhatian ekstra dengan jalur file dan ada beberapa kasus sudut untuk dipertimbangkan (uji untuk mereka), tetapi pendekatan umum harus bekerja.

Jika tidak ada direktori yang bisa ditulisi, pendekatan yang mungkin adalah memodifikasi programuntuk mengambil tarball sebagai input tunggal dan membongkar isinya ke memori. Misalnya, jika programadalah skrip Python, maka menggunakan tarfilemodul bawaan akan dengan mudah mencapai sesuatu seperti itu.

filbranden
sumber
3
Bahkan tanpa melalui kesulitan tarballing itu - saya telah memutuskan lebih atau kurang untuk hanya menulis file /tmp/dan menggunakannya secara langsung.
Green Cloak Guy
2
Pendekatan tarball memiliki beberapa keuntungan, karena Anda menggunakan koneksi ssh tunggal (tidak perlu menguji untuk keberhasilan / kegagalan) dan beberapa menjalankan bersamaan tidak mungkin saling bertemu (menggunakan dan / atau menghapus file dalam / tmp dimaksudkan untuk menjalankan terpisah .) Tapi saya pikir itu yang terbaik yang akan Anda dapatkan dari ssh seperti yang ada sekarang. Semoga berhasil!
filbranden
3
@GreenCloakGuy Jadi, Anda dapat menyimpan file di server dengan baik. Harap ubah pertanyaan sesuai.
Mosvy
1
@ Mosvy bisa , ya, tapi tidak mau . Pertanyaannya adalah, dalam "apakah mungkin untuk melakukan ini tanpa menyimpan file", jawaban yang tampaknya "mungkin, tetapi tidak dalam kasus saya"
Green Cloak Guy
5

Jika Anda dapat mengatur pendengar TCP (bisa menjadi port yang lebih tinggi), maka Anda bisa menggunakan sesi SSH kedua untuk menetapkan sumber input kedua nc.

Contoh:

Ada skrip ini di server ( ~/script.bash):

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

Dan ada dua file ini secara lokal:

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

Sekarang pertama mulai sumber kedua ( $servadalah server):

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

Dan jalankan perintah dengan benar:

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

sumber
2

Itu didirikan dalam komentar yang /tmpdapat ditulisi, jadi cukup salin ke salah satu file sebelumnya:

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

Ini juga membersihkan file yang disalin setelah menjalankan yang sukses (ubah &&ke ;jika Anda ingin menghapusnya terlepas dari keberhasilan, tetapi kemudian perhatikan bahwa Anda akan kehilangan nilai keluar).


Jika itu tidak dapat diterima, saya akan mengusulkan bermain-main dengan /path/to/programatau pembungkus untuk itu yang dapat memisahkan dua file dari aliran input tunggal seperti:

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

Ini menggunakan pemisah informasi ASCII empat (pemisah file, FS) dan membuatnya tiga kali lipat sehingga kami meminimalkan kemungkinan file biner yang secara kebetulan berisi string itu. Anda tweaked_programkemudian akan membagi input yang diberikan pemisah dan kemudian beroperasi pada dua file yang disimpan sebagai variabel.

Tentu saja, jika Anda menggunakan bahasa yang memiliki perpustakaan untuk menangani tarbal, pendekatan yang lebih aman dan lebih bersih adalah dengan menyalurkan tarke kode sshseperti ini:

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

Dan Anda tweaked_programakan mendekompresi dan membuka arsip, menyimpan setiap file ke variabel yang berbeda, dan kemudian menjalankan programlogika asli pada variabel.

Adam Katz
sumber
1

Jika Anda diizinkan untuk meneruskan porta ssh, dan Anda memiliki akses ke wgetpada mesin jarak jauh dan ke busyboxmesin lokal, Anda dapat melakukan sesuatu seperti:

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 $!

(menggunakan catsebagai program contoh yang mengambil dua argumen file).

Hanya kemampuan untuk meneruskan porta -Ryang penting - alih-alih melakukan http, Anda dapat menggunakan metode lain, mis. jika Anda netcatmendukung -ddan -Nopsi:

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)

Mungkin ada cara untuk mengganti <(...)proses penggantian jika shell login pada mesin remote tidak seperti ksh atau bash-like.

Secara keseluruhan, ini tidak terlalu bagus - pengetahuan yang lebih baik tentang sistem / shell / config / izin yang tepat (yang belum Anda berikan) dapat memungkinkan solusi yang lebih cerdas.

mosvy
sumber
-1

UPDATE: Ini tidak benar-benar bekerja, ssh memiliki ide yang sangat jelas tentang apa arti stdin dan stdout / stderr dan tidak benar-benar memungkinkan merebut stderr untuk membaca darinya. Saya akan menghapus jawaban ini dalam beberapa hari karena tidak berfungsi. Terima kasih untuk diskusi yang sangat menarik !!!


Sayangnya tidak ada cara yang bagus untuk melakukan ini secara langsung, karena sshklien hanya akan meneruskan tiga deskriptor file ( stdin, stdoutdan stderr) ke server dan tidak memiliki ketentuan untuk meneruskan deskriptor file tambahan (yang akan membantu untuk kasus penggunaan khusus ini .)

(Perhatikan juga bahwa protokol SSH memiliki ketentuan untuk meneruskan deskriptor file tambahan, hanya sshklien yang tidak mengimplementasikan cara untuk menggunakan fitur itu. Secara teoritis, memperluas klien dengan tambalan akan cukup untuk mengekspos fitur ini.)

Salah satu cara hacky untuk mencapai apa yang Anda cari adalah dengan menggunakan file descriptor 2 ( stderrfile descriptor) untuk mengirimkan file kedua. Sesuatu seperti:

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

Ini harusnya berfungsi, itu hanya dapat menyebabkan masalah jika programmencoba menulis ke stderr. Anda dapat mengatasinya dengan menyulap ulang file deskriptor pada ujung jarak jauh sebelum Anda menjalankan program. Anda dapat memindahkan file2.txtke file deskriptor 3 dan membuka kembali stderr ke mirror stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt
filbranden
sumber
Saya belum benar-benar menguji ini (mengetik di telepon) tetapi setidaknya secara teori saya berharap itu berfungsi. Beri tahu saya jika karena alasan tertentu tidak. Bersulang!
filbranden
Itu tidak akan berhasil sama sekali. Bahkan dengan ssh yang diretas. AFAIK stderr tidak menggunakan saluran terpisah, tetapi beberapa jenis pesan khusus. Tapi ya, bisa mengakses saluran ssh sebagai pipa atau soket domain unix yang tidak disebutkan namanya (alih-alih harus melakukan penerusan soket) akan lebih baik, sepertinya tidak mungkin dalam bentuk atau bentuk apa pun.
Mosvy
@mosvy, fd 0, 1 dan 2 pada perintah remote akan menjadi 3 pipa yang berbeda. Data bergerak melalui 3 saluran berbeda yang digandakan dalam koneksi ssh, tetapi itu bersifat uni-directional (dari klien ke putus untuk fd 0 dan dari server ke klien untuk 1 dan 2), sehingga bahkan pada sistem di mana pipa dua arah, yang tidak akan t bekerja. Di Linux, openening / dev / stderr dalam mode readonly diberikan di ujung lain dari pipa, sehingga tidak akan pernah berhasil.
Stéphane Chazelas
Perhatikan bahwa yang terakhir mengasumsikan shell login pengguna di remote.name mirip Bourne ( 3<&2adalah operator shell Bourne, dan sshd menjalankan shell login pengguna untuk menafsirkan perintah yang dikirim oleh klien)
Stéphane Chazelas
2
@ StéphaneChazelas Tidak, saluran ssh tunggal digunakan untuk stdin / stdout / stderr, dan data stderr ditransfer melalui SSH_MSG_CHANNEL_EXTENDED_DATApesan dengan jenis yang disetel ke SSH_EXTENDED_DATA_STDERR. Anda dapat membaca semuanya di sini
mosvy