Saya tidak berpikir implementasi apapun ssh
memiliki cara asli untuk meneruskan perintah dari klien ke server tanpa melibatkan shell.
Sekarang, segala sesuatunya dapat menjadi lebih mudah jika Anda dapat memberi tahu remote shell untuk hanya menjalankan juru bahasa tertentu (seperti sh
, yang kami tahu sintaks yang diharapkan) dan memberikan kode untuk dieksekusi dengan cara lain.
Itu berarti lain dapat misalnya input standar atau variabel lingkungan .
Ketika tidak ada yang dapat digunakan, saya mengusulkan solusi ketiga yang hacky di bawah ini.
Menggunakan stdin
Jika Anda tidak perlu memasukkan data apa pun ke perintah jarak jauh, itu solusi termudah.
Jika Anda tahu host jarak jauh memiliki xargs
perintah yang mendukung -0
opsi dan perintahnya tidak terlalu besar, Anda dapat melakukannya:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Itu xargs -0 env --
baris perintah ditafsirkan sama dengan semua keluarga shell mereka. xargs
membaca daftar argumen tanpa batas pada stdin dan meneruskannya sebagai argumen env
. Itu mengasumsikan argumen pertama (nama perintah) tidak mengandung =
karakter.
Atau Anda dapat menggunakan sh
pada host jarak jauh setelah mengutip setiap elemen menggunakan sh
sintaks mengutip.
shquote() {
LC_ALL=C awk -v q=\' '
BEGIN{
for (i=1; i<ARGC; i++) {
gsub(q, q "\\" q q, ARGV[i])
printf "%s ", q ARGV[i] q
}
print ""
}' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh
Menggunakan variabel lingkungan
Sekarang, jika Anda perlu memberi makan beberapa data dari klien ke stdin perintah jarak jauh, solusi di atas tidak akan berfungsi.
Namun beberapa ssh
penyebaran server memungkinkan berlalunya variabel lingkungan sewenang-wenang dari klien ke server. Sebagai contoh, banyak penyebaran openssh pada sistem berbasis Debian memungkinkan lewat variabel yang namanya dimulai LC_
.
Dalam kasus tersebut, Anda bisa memiliki LC_CODE
variabel misalnya berisi kode shquoted sh
seperti di atas dan dijalankan sh -c 'eval "$LC_CODE"'
pada host jarak jauh setelah menyuruh klien Anda untuk mengirimkan variabel itu (sekali lagi, itu adalah baris perintah yang diartikan sama di setiap shell):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Membangun baris perintah yang kompatibel untuk semua keluarga shell
Jika tidak ada opsi di atas yang dapat diterima (karena Anda perlu stdin dan sshd tidak menerima variabel apa pun, atau karena Anda memerlukan solusi generik), maka Anda harus menyiapkan baris perintah untuk host jarak jauh yang kompatibel dengan semua kerang yang didukung.
Itu sangat sulit karena semua cangkang (Bourne, csh, rc, es, fish) memiliki sintaks yang berbeda, dan khususnya mekanisme kutipan yang berbeda dan beberapa dari mereka memiliki keterbatasan yang sulit untuk dikerjakan.
Berikut adalah solusi yang saya buat, saya jelaskan lebih jauh ke bawah:
#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};
@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
push @ssh, $arg;
}
if (@ARGV) {
for (@ARGV) {
s/'/'\$q\$b\$q\$q'/g;
s/\n/'\$q'\$n'\$q'/g;
s/!/'\$x'/g;
s/\\/'\$b'/g;
$_ = "\$q'$_'\$q";
}
push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}
exec @ssh;
Itu perl
skrip pembungkus di sekitar ssh
. Saya menyebutnya sexec
. Anda menyebutnya seperti:
sexec [ssh-options] user@host -- cmd and its args
jadi dalam contoh Anda:
sexec user@host -- "${cmd[@]}"
Dan pembungkusnya berubah cmd and its args
menjadi baris perintah yang akhirnya ditafsirkan oleh semua shell sebagai panggilan cmd
dengan argumennya (tanpa konten).
Keterbatasan:
- Pembukaan dan cara perintah dikutip berarti garis perintah jarak jauh berakhir secara signifikan lebih besar yang berarti batas ukuran maksimum dari baris perintah akan tercapai lebih cepat.
- Saya hanya mengujinya dengan: Bourne shell (dari heirloom toolchest), tanda hubung, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, ikan seperti yang ditemukan pada sistem Debian baru-baru ini dan / bin / sh, / usr / bin / ksh, / bin / csh dan / usr / xpg4 / bin / sh pada Solaris 10.
- Jika
yash
shell login jarak jauh, Anda tidak bisa meneruskan perintah yang argumennya berisi karakter yang tidak valid, tetapi itu adalah batasan di yash
mana Anda tidak bisa menyelesaikannya.
- Beberapa shell seperti csh atau bash membaca beberapa file startup ketika dipanggil melalui ssh. Kami menganggap itu tidak mengubah perilaku secara dramatis sehingga pembukaan masih berfungsi.
- selain
sh
itu, ia juga menganggap sistem remote memiliki printf
perintah.
Untuk memahami cara kerjanya, Anda perlu tahu cara mengutip bekerja di shell yang berbeda:
- Bourne:
'...'
adalah kutipan kuat tanpa karakter khusus di dalamnya. "..."
adalah tanda kutip yang lemah di mana "
bisa lolos dengan backslash.
csh
. Sama seperti Bourne kecuali itu "
tidak bisa diloloskan ke dalam "..."
. Juga karakter baris baru harus dimasukkan diawali dengan garis miring terbalik. Dan !
menyebabkan masalah bahkan di dalam tanda kutip tunggal.
rc
. Satu-satunya kutipan adalah '...'
(kuat). Kutipan tunggal dalam kutipan tunggal dimasukkan sebagai ''
(seperti '...''...'
). Kutipan ganda atau garis miring tidak spesial.
es
. Sama seperti rc kecuali kutipan luar, backslash dapat lolos dari penawaran tunggal.
fish
: sama seperti Bourne kecuali bahwa backslash lolos '
di dalam '...'
.
Dengan semua hambatan itu, mudah untuk melihat bahwa seseorang tidak dapat dengan baik mengutip argumen baris perintah sehingga ia bekerja dengan semua shell.
Menggunakan tanda kutip tunggal seperti pada:
'foo' 'bar'
bekerja di semua tetapi:
'echo' 'It'\''s'
tidak akan bekerja di rc
.
'echo' 'foo
bar'
tidak akan bekerja di csh
.
'echo' 'foo\'
tidak akan bekerja di fish
.
Namun kita harus dapat mengatasi sebagian besar masalah tersebut jika kita berhasil menyimpan karakter-karakter bermasalah tersebut dalam variabel, seperti backslash in $b
, single quote in $q
, newline in $n
(dan !
in $x
untuk ekspansi sejarah csh) dengan cara independen shell.
'echo' 'It'$q's'
'echo' 'foo'$b
akan bekerja di semua kulit. Itu masih tidak akan bekerja untuk baris baru csh
. Jika $n
berisi baris baru, dalam csh
, Anda harus menulisnya $n:q
agar bisa meluas ke baris baru dan itu tidak akan berfungsi untuk shell lain. Jadi, apa yang akhirnya kami lakukan di sini adalah menelepon sh
dan sh
memperluasnya $n
. Itu juga berarti harus melakukan dua tingkat penawaran, satu untuk shell login jarak jauh, dan satu untuk sh
.
Di $preamble
dalam kode itu adalah bagian tersulit. Itu membuat penggunaan berbagai aturan mengutip berbeda dalam semua kerang untuk memiliki beberapa bagian dari kode ditafsirkan oleh hanya satu dari kerang (sementara itu komentar untuk orang lain) yang masing-masing hanya mendefinisikan mereka $b
, $q
, $n
, $x
variabel untuk shell masing-masing.
Inilah kode shell yang akan ditafsirkan oleh shell login pengguna jarak jauh host
untuk contoh Anda:
printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q
Kode itu akhirnya menjalankan perintah yang sama ketika ditafsirkan oleh salah satu shell yang didukung.
cmd
argumennya adalah/bin/sh -c
kita akan berakhir dengan shell posix di 99% dari semua kasus, bukan? Tentu saja melarikan diri dari karakter khusus sedikit lebih menyakitkan dengan cara ini, tetapi apakah itu akan menyelesaikan masalah awal?