Non credo che nessuna implementazione di ssh
abbia un modo nativo per passare un comando dal client al server senza coinvolgere una shell.
Ora, le cose possono diventare più facili se puoi dire alla shell remota di eseguire solo un interprete specifico (come sh
, per il quale conosciamo la sintassi attesa) e dare al codice l'esecuzione con un altro mezzo.
L'altra media può essere, ad esempio , l'input standard o una variabile di ambiente .
Quando nessuno dei due può essere utilizzato, propongo di seguito una terza soluzione caotica.
Usando lo stdin
Se non è necessario fornire alcun dato al comando remoto, questa è la soluzione più semplice.
Se sai che l'host remoto ha un xargs
comando che supporta l' -0
opzione e il comando non è troppo grande, puoi fare:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Quella xargs -0 env --
riga di comando viene interpretata allo stesso modo con tutte quelle famiglie di shell. xargs
legge l'elenco di argomenti delimitato da null su stdin e li passa come argomenti aenv
. Ciò presuppone che il primo argomento (il nome del comando) non contenga =
caratteri.
Oppure è possibile utilizzare sh
sull'host remoto dopo aver quotato ciascun elemento utilizzando la sh
sintassi di quotazione.
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
Utilizzando le variabili di ambiente
Ora, se è necessario alimentare alcuni dati dal client allo stdin del comando remoto, la soluzione sopra non funzionerà.
Alcune ssh
distribuzioni di server consentono tuttavia il passaggio di variabili di ambiente arbitrarie dal client al server. Ad esempio, molte distribuzioni openssh su sistemi basati su Debian consentono il passaggio di variabili il cui nome inizia conLC_
.
In quei casi potresti avere una LC_CODE
variabile per esempio contenente il codice shquot sh
come sopra ed eseguire sh -c 'eval "$LC_CODE"'
sull'host remoto dopo aver detto al tuo client di passare quella variabile (di nuovo, quella è una riga di comando che viene interpretata allo stesso modo in ogni shell):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Creazione di una riga di comando compatibile con tutte le famiglie di shell
Se nessuna delle opzioni sopra è accettabile (perché hai bisogno di stdin e sshd non accetta alcuna variabile o perché hai bisogno di una soluzione generica), dovrai preparare una riga di comando per l'host remoto compatibile con tutti shell supportate.
Ciò è particolarmente complicato perché tutte quelle conchiglie (Bourne, csh, rc, es, fish) hanno una propria sintassi diversa, e in particolare diversi meccanismi di quotazione e alcuni di essi hanno limitazioni che sono difficili da aggirare.
Ecco una soluzione che mi è venuta in mente, la descrivo più in basso:
#! /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;
Questo è uno perl
script wrapper in giro ssh
. Io lo chiamo sexec
. Lo chiami come:
sexec [ssh-options] user@host -- cmd and its args
quindi nel tuo esempio:
sexec user@host -- "${cmd[@]}"
E il wrapper si trasforma cmd and its args
in una riga di comando che tutte le shell finiscono per interpretare come una chiamata cmd
con i suoi arg (indipendentemente dal loro contenuto).
limitazioni:
- Il preambolo e il modo in cui viene citato il comando indicano che la riga di comando remota finisce per essere significativamente più grande, il che significa che il limite sulla dimensione massima di una riga di comando verrà raggiunto prima.
- L'ho provato solo con: Bourne shell (dal cimelio di cimelio), trattino, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish come si trova su un recente sistema Debian e / bin / sh, / usr / bin / ksh, / bin / csh e / usr / xpg4 / bin / sh su Solaris 10.
- Se
yash
è la shell di accesso remoto, non è possibile passare un comando i cui argomenti contengono caratteri non validi, ma questa è una limitazione in yash
quanto non è possibile aggirare comunque.
- Alcune shell come csh o bash leggono alcuni file di avvio quando invocate su ssh. Partiamo dal presupposto che quelli non cambiano drasticamente il comportamento in modo che il preambolo funzioni ancora.
- accanto
sh
, essa assume anche il sistema remoto ha il printf
comando.
Per capire come funziona, devi sapere come funziona la quotazione nelle diverse shell:
- Bourne:
'...'
sono virgolette forti senza caratteri speciali. "..."
sono virgolette deboli dove "
può essere evitato con la barra rovesciata.
csh
. Lo stesso di Bourne, tranne per il fatto che "
non può essere evitato all'interno "..."
. Inoltre, è necessario inserire un carattere di nuova riga con il prefisso con una barra rovesciata. E !
causa problemi anche all'interno di virgolette singole.
rc
. Le uniche virgolette sono '...'
(forti). Una virgoletta singola tra virgolette singole viene inserita come ''
(come '...''...'
). Le virgolette doppie o le barre rovesciate non sono speciali.
es
. Come in rc, ad eccezione delle virgolette esterne, la barra rovesciata può sfuggire a una singola virgoletta.
fish
: uguale a Bourne, tranne per il fatto che la barra rovesciata fuoriesce '
all'interno '...'
.
Con tutte queste contraddizioni, è facile capire che non è possibile citare in modo affidabile argomenti della riga di comando in modo che funzioni con tutte le shell.
Utilizzando virgolette singole come in:
'foo' 'bar'
funziona in tutto ma:
'echo' 'It'\''s'
non funzionerebbe rc
.
'echo' 'foo
bar'
non funzionerebbe csh
.
'echo' 'foo\'
non funzionerebbe fish
.
Tuttavia dovremmo essere in grado di aggirare la maggior parte di questi problemi se riusciamo a memorizzare quei caratteri problematici in variabili, come backslash in $b
, single quote in $q
, newline in $n
(e !
in $x
per l'espansione della storia di csh) in modo indipendente dalla shell.
'echo' 'It'$q's'
'echo' 'foo'$b
funzionerebbe in tutte le conchiglie. csh
Tuttavia, ciò non funzionerebbe ancora per Newline . Se $n
contiene newline, in csh
, devi scriverlo in modo che $n:q
si espanda a una nuova riga e che non funzionerà per altre shell. Quindi, ciò che finiamo per fare invece qui è chiamare sh
e sh
ampliarli $n
. Ciò significa anche dover fare due livelli di quotazione, uno per la shell di accesso remoto e uno persh
.
Il $preamble
codice in quel codice è la parte più difficile. Si fa uso di varie regole di quoting differenti in tutte le shell di avere alcune sezioni del codice interpretato da uno solo dei gusci (mentre è commentata per gli altri) ciascuno dei quali definire soltanto quelli $b
, $q
, $n
, $x
variabili per le rispettive coperture.
Ecco il codice della shell che verrebbe interpretato dalla shell di accesso dell'utente remoto su host
per il tuo esempio:
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
Quel codice finisce per eseguire lo stesso comando quando interpretato da una qualsiasi delle shell supportate.
cmd
argomento fosse/bin/sh -c
finiremmo con una shell posix nel 99% di tutti i casi, no? Ovviamente sfuggire a personaggi speciali è un po 'più doloroso in questo modo, ma risolverebbe il problema iniziale?