ssh
tiene una característica molesta cuando ejecutas:
ssh user@host cmd and "here's" "one arg"
En lugar de ejecutar eso cmd
con sus argumentos host
, concatena eso cmd
y argumentos con espacios y ejecuta un shell host
para interpretar la cadena resultante (supongo que por eso se llama ssh
y no sexec
).
Peor aún, no sabes qué shell se va a usar para interpretar esa cadena, ya que ese shell de inicio de sesión user
ni siquiera se garantiza que sea Bourne, ya que todavía hay personas que lo usan tcsh
como su shell de inicio de sesión y fish
está en aumento.
¿Hay alguna manera de evitar eso?
Supongamos que tengo un comando como una lista de argumentos almacenados en una bash
matriz, cada uno de los cuales puede contener cualquier secuencia de bytes no nulos, hay alguna manera de tener que ejecutar en host
como user
de una manera consistente independientemente de la shell de entrada de que user
en host
(que asumiremos que es una de las principales familias de shell de Unix: Bourne, csh, rc / es, fish)?
Otra suposición razonable que debería poder hacer es que haya un sh
comando host
disponible $PATH
que sea compatible con Bourne.
Ejemplo:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Puedo ejecutarlo localmente con ksh
/ zsh
/ bash
/ yash
as:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
o
env "${cmd[@]}"
o
xterm -hold -e "${cmd[@]}"
...
¿Cómo iba a ejecutarlo en host
lo user
más ssh
?
ssh user@host "${cmd[@]}"
Obviamente no funcionará.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
solo funcionaría si el shell de inicio de sesión del usuario remoto fuera el mismo que el shell local (o entiende las citas de la misma manera que printf %q
en el shell local lo produce) y se ejecuta en la misma configuración regional.
fuente
cmd
argumento fuera/bin/sh -c
que terminaríamos con un shell posix en el 99% de todos los casos, ¿no? Por supuesto, escapar de caracteres especiales es un poco más doloroso de esta manera, pero ¿resolvería el problema inicial?ssh host sh -c 'some cmd'
, lo mismossh host 'sh -c some cmd'
que el shell de inicio de sesión del usuario remoto interpreta esash -c some cmd
línea de comando. Necesitamos escribir el comando en la sintaxis correcta para ese shell (y no sabemos cuál es) para quesh
se llame allí con-c
ysome cmd
argumentos.sh -c 'some cmd'
ysome cmd
se interpretan de la misma manera en todos esos shells. ¿Qué sucede si quiero ejecutar laecho \'
línea de comando Bourne en el host remoto?echo command-string | ssh ... /bin/sh
es una solución que di en mi respuesta, pero eso significa que no puede alimentar datos al stdin de ese comando remoto.cmd
es asícmd=(echo "foo bar")
, la línea de comando de shell pasadassh
debería ser algo así como la línea de comando '' echo '' foo bar '. The *first* space (the one before
echo) is superflous, but doen't harm. The other one (the ones before
' foo bar ') is needed. With
'% q ', we'd pass a
' echo '' foo bar ' ' .Respuestas:
No creo que ninguna implementación
ssh
tenga una forma nativa de pasar un comando del cliente al servidor sin involucrar un shell.Ahora, las cosas pueden ser más fáciles si puede decirle al shell remoto que solo ejecute un intérprete específico (por ejemplo
sh
, para el que conocemos la sintaxis esperada) y le dé al código que se ejecute por otro medio.Esa otra media puede ser, por ejemplo, una entrada estándar o una variable de entorno .
Cuando ninguno de los dos se puede utilizar, propongo una tercera solución hacky a continuación.
Usando stdin
Si no necesita alimentar ningún dato al comando remoto, esa es la solución más fácil.
Si sabe que el host remoto tiene un
xargs
comando que admite la-0
opción y el comando no es demasiado grande, puede hacer lo siguiente:Esa
xargs -0 env --
línea de comando se interpreta de la misma manera con todas esas familias de shell.xargs
lee la lista de argumentos delimitados por nulos en stdin y los pasa como argumentos aenv
. Eso supone que el primer argumento (el nombre del comando) no contiene=
caracteres.O puede usar
sh
en el host remoto después de haber citado cada elemento utilizando lash
sintaxis de cita.Usar variables de entorno
Ahora, si necesita alimentar algunos datos del cliente al stdin del comando remoto, la solución anterior no funcionará.
ssh
Sin embargo, algunas implementaciones de servidor permiten pasar variables de entorno arbitrarias del cliente al servidor. Por ejemplo, muchas implementaciones de openssh en sistemas basados en Debian permiten pasar variables cuyo nombre comienza conLC_
.En esos casos, podría tener una
LC_CODE
variable que, por ejemplo, contenga el código entrecomilladosh
como se indicó anteriormente y ejecutarlash -c 'eval "$LC_CODE"'
en el host remoto después de haberle dicho a su cliente que pase esa variable (de nuevo, esa es una línea de comandos que se interpreta igual en cada shell):Construyendo una línea de comando compatible con todas las familias de shell
Si ninguna de las opciones anteriores es aceptable (porque necesita stdin y sshd no acepta ninguna variable, o porque necesita una solución genérica), deberá preparar una línea de comando para el host remoto que sea compatible con todos Conchas soportadas.
Eso es particularmente complicado porque todos esos shells (Bourne, csh, rc, es, fish) tienen su propia sintaxis diferente y, en particular, diferentes mecanismos de cotización y algunos de ellos tienen limitaciones que son difíciles de solucionar.
Aquí hay una solución que se me ocurrió, la describo más abajo:
Ese es un
perl
guión envolventessh
. Yo lo llamosexec
. Lo llamas así:entonces en tu ejemplo:
Y el contenedor se convierte
cmd and its args
en una línea de comando que todos los shells terminan interpretando como llamadascmd
con sus argumentos (independientemente de su contenido).Limitaciones:
yash
es el shell de inicio de sesión remoto, no puede pasar un comando cuyos argumentos contengan caracteres no válidos, pero esa es una limitación en el sentido deyash
que no puede evitarlo de todos modos.sh
, también supone que el sistema remoto tiene elprintf
comando.Para comprender cómo funciona, necesita saber cómo funciona la cita en los diferentes shells:
'...'
son citas fuertes sin carácter especial."..."
son comillas débiles donde"
se puede escapar con una barra invertida.csh
. Igual que Bourne, excepto que"
no se puede escapar por dentro"..."
. También se debe ingresar un carácter de nueva línea con una barra diagonal inversa. Y!
causa problemas incluso dentro de comillas simples.rc
. Las únicas citas son'...'
(fuertes). Una comilla simple dentro de comillas simples se ingresa como''
(like'...''...'
). Las comillas dobles o las barras invertidas no son especiales.es
. Igual que rc, excepto que las comillas externas, la barra diagonal inversa puede escapar de una comilla simple.fish
: igual que Bourne, excepto que la barra invertida se escapa'
dentro'...'
.Con todas esas restricciones, es fácil ver que uno no puede citar de manera confiable los argumentos de la línea de comandos para que funcione con todos los shells.
Usando comillas simples como en:
funciona en todos pero:
no funcionaría en
rc
.no funcionaría en
csh
.no funcionaría en
fish
.Sin embargo, deberíamos poder solucionar la mayoría de esos problemas si logramos almacenar esos caracteres problemáticos en variables, como la barra diagonal inversa
$b
, la comilla simple$q
, la nueva línea$n
(y!
en la$x
expansión del historial de csh) de una manera independiente del shell.funcionaría en todos los depósitos. Sin embargo, eso todavía no funcionaría para Newline
csh
. Si$n
contiene nueva línea, encsh
, debe escribirlo$n:q
para que se expanda a una nueva línea y eso no funcionará para otros shells. Entonces, lo que terminamos haciendo aquí es llamarsh
ysh
expandirlos$n
. Eso también significa tener que hacer dos niveles de cotización, uno para el shell de inicio de sesión remoto y otro parash
.El
$preamble
en ese código es la parte más complicada. Se hace uso de las distintas reglas diferentes de cotización en todas las cáscaras tenían algunas secciones del código interpretado por una sola de las conchas (mientras se está comentada para los demás) cada uno de los cuales acaba de definir los$b
,$q
,$n
,$x
variables para su respectiva concha.Aquí está el código de shell que sería interpretado por el shell de inicio de sesión del usuario remoto
host
para su ejemplo:Ese código termina ejecutando el mismo comando cuando lo interpretan cualquiera de los shells compatibles.
fuente
tl; dr
Para una solución más elaborada, lea los comentarios e inspeccione la otra respuesta .
descripción
Bueno, mi solución no funcionará con non-
bash
shells. Pero suponiendo que estébash
en el otro extremo, las cosas se vuelven más simples. Mi idea es reutilizarprintf "%q"
para escapar. También en general, es más legible tener un script en el otro extremo, que acepte argumentos. Pero si el comando es corto, probablemente esté bien alinearlo. Aquí hay algunas funciones de ejemplo para usar en scripts:local.sh
:remote.sh
:La salida:
Alternativamente, puede hacer
printf
el trabajo usted mismo, si sabe lo que está haciendo:fuente
bash
estará disponible en la máquina remota. También hay algunos problemas con las comillas faltantes que podrían causar problemas con espacios en blanco y comodines.bash
proyectiles. Pero con suerte la gente lo encontrará útil. Sin embargo, traté de abordar los otros problemas. Siéntase libre de decirme si hay algo que me falta además de labash
cosa.ssh_run user@host "${cmd[@]}"
). Aún te faltan algunas citas.printf %q
no es segura de usar en una configuración regional diferente (y también es bastante defectuosa; por ejemplo, en configuraciones regionales que usan el juego de caracteres BIG5, cita (4.3.48)ε
comoα`
!). Para eso, lo mejor es citar todo y con comillas simples solo como con elshquote()
en mi respuesta.