Я не думаю, що будь-яка реалізація ssh
має власний спосіб передавати команду від клієнта до сервера без залучення оболонки.
Тепер все може бути простішим, якщо ви можете сказати віддаленій оболонці запускати лише певний інтерпретатор (наприклад sh
, для якого ми знаємо очікуваний синтаксис) і дати код виконувати іншим способом.
Це інше значення може бути, наприклад, стандартним входом або змінною середовища .
Коли жодне з них не може бути використане, я пропоную прискіпливе третє рішення нижче.
Використання stdin
Якщо вам не потрібно подавати дані у віддалену команду, це найпростіше рішення.
Якщо ви знаєте, що віддалений хост має xargs
команду, яка підтримує -0
параметр, і команда не надто велика, ви можете:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Цей xargs -0 env --
командний рядок інтерпретується однаково з усіма цими сімействами оболонок. xargs
читає список обмежених нулем аргументів на stdin та передає їх як аргументи env
. Це передбачає, що перший аргумент (назва команди) не містить =
символів.
Або ви можете використовувати sh
на віддаленому хості після цитування кожного елемента, використовуючи sh
синтаксис цитування.
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
Використання змінних середовища
Тепер, якщо вам потрібно подати деякі дані від клієнта до stdin віддаленої команди, вищевказане рішення не працюватиме.
Деякі ssh
розгортання сервера дозволяють передавати довільні змінні середовища від клієнта до сервера. Наприклад, багато відкритих розгортань в системах на базі Debian дозволяють передавати змінні, ім'я яких починається з LC_
.
У цих випадках у вас може бути LC_CODE
змінна, наприклад, що містить кодований sh
код, як зазначено вище, і запустити sh -c 'eval "$LC_CODE"'
на віддаленому хості після того, як ви сказали своєму клієнтові передати цю змінну (знову ж таки, це командний рядок, який інтерпретується однаково у кожній оболонці):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Побудова командного рядка, сумісного для всіх сімей оболонок
Якщо жоден із наведених вище варіантів не є прийнятним (тому що вам потрібен stdin, а sshd не приймає змінну або тому, що вам потрібне загальне рішення), вам доведеться підготувати командний рядок для віддаленого хоста, сумісний з усіма підтримувані снаряди.
Це особливо хитро, тому що всі ці оболонки (Bourne, csh, rc, es, fish) мають свій різний синтаксис, зокрема різні механізми цитування, а деякі з них мають обмеження, які важко обійти.
Ось рішення, яке я придумав, описую його далі:
#! /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;
Це perl
сценарій обгортки навколо ssh
. Я це називаю sexec
. Ви називаєте це так:
sexec [ssh-options] user@host -- cmd and its args
так у вашому прикладі:
sexec user@host -- "${cmd[@]}"
І обгортка перетворюється cmd and its args
на командний рядок, який усі оболонки в кінцевому підсумку інтерпретують як виклик cmd
своїми арг (не зважаючи на їх зміст).
Обмеження:
- Преамбула та те, як цитується команда, означають, що віддалений командний рядок стає значно більшим, а значить, швидше буде досягнуто обмеження на максимальний розмір командного рядка.
- Я протестував його лише з: оболонкою Борна (від heirloom toolchest), тире, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, рибою, як знайдено в останній системі Debian та / bin / sh, / usr / bin / ksh, / bin / csh та / usr / xpg4 / bin / sh на Solaris 10.
- Якщо
yash
це оболонка віддаленого входу, ви не можете передавати команду, аргументи якої містять недійсні символи, але це обмеження в yash
тому, що ви ніяк не можете обійти.
- Деякі оболонки, такі як csh або bash, читають деякі файли запуску, коли викликаються через ssh. Ми припускаємо, що вони не змінюють поведінку кардинально, так що преамбула все ще працює.
- Крім того
sh
, він також передбачає, що віддалена система має printf
команду.
Щоб зрозуміти, як це працює, потрібно знати, як працює цитування в різних оболонках:
- Борн:
'...'
сильні цитати без особливого характеру. "..."
є слабкими котируваннями, куди "
можна уникнути зворотної косої риси.
csh
. Те саме, що Борн, за винятком того, що "
всередину не можна уникнути "..."
. Також символ нового рядка повинен бути введений з попередньою косою рисою. І !
викликає проблеми навіть всередині одиничних цитат.
rc
. Єдині цитати '...'
(сильні). Єдина пропозиція в межах однієї лапки вводиться як ''
(як '...''...'
). Подвійні котирування чи зворотні риси не особливі.
es
. Так само, як і rc, за винятком того, що зовнішні котирування, зворотна косої риси може уникнути єдиної цитати.
fish
: Такий же , як Bourne винятком того, що зворотний слеш '
всередині '...'
.
Зі всіма цими протипоказаннями легко помітити, що не можна надійно цитувати аргументи командного рядка, щоб він працював з усіма оболонками.
Використання одинарних лапок як:
'foo' 'bar'
працює у всіх, крім:
'echo' 'It'\''s'
не працював би в rc
.
'echo' 'foo
bar'
не працював би в csh
.
'echo' 'foo\'
не працював би в fish
.
Однак ми повинні бути в змозі працювати навколо більшості з цих проблем , якщо нам вдасться зберегти ці проблемні символи в змінних, як зворотна коса риса $b
, одинарні лапки в $q
, символ нового рядка в $n
(і !
в $x
для розширення історії CSH) в оболонці незалежний чином.
'echo' 'It'$q's'
'echo' 'foo'$b
працював би у всіх оболонках. Це все одно не працює для нового рядка, csh
хоча. Якщо він $n
містить новий рядок, в csh
, ви повинні написати його, $n:q
щоб розширити його до нового рядка, і це не буде працювати для інших оболонок. Отже, те, що ми в кінцевому підсумку робимо натомість тут, - це закликати sh
і sh
розширити їх $n
. Це також означає, що потрібно виконати два рівні котирування, один для оболонки віддаленого входу та один для sh
.
У $preamble
цьому коді є найскладнішою частиною. Це робить використання різних різного котирування правил у всіх оболонках , щоб мати деякі ділянки коду витлумачений лише однією з оболонок ( в той час як закоментовані для інших) , кожного з яких тільки що визначають ті $b
, $q
, $n
, $x
змінних для їх відповідної оболонки.
Ось код оболонки, який інтерпретується оболонкою для входу віддаленого користувача host
для вашого прикладу:
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
Цей код в кінцевому підсумку виконує ту саму команду при інтерпретації будь-якої з підтримуваних оболонок.
cmd
аргумент був,/bin/sh -c
ми б у 99% всіх випадків опинилися з оболонкою posix, чи не так? Звичайно, втеча спеціальних персонажів трохи болючіше таким чином, але чи вирішить це початкову проблему?