ssh tiene una característica molesta cuando ejecutas:
ssh user@host cmd and "here's" "one arg"
En lugar de ejecutar eso cmdcon sus argumentos host, concatena eso cmdy argumentos con espacios y ejecuta un shell hostpara interpretar la cadena resultante (supongo que por eso se llama sshy 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 userni siquiera se garantiza que sea Bourne, ya que todavía hay personas que lo usan tcshcomo su shell de inicio de sesión y fishestá en aumento.
¿Hay alguna manera de evitar eso?
Supongamos que tengo un comando como una lista de argumentos almacenados en una bashmatriz, cada uno de los cuales puede contener cualquier secuencia de bytes no nulos, hay alguna manera de tener que ejecutar en hostcomo userde una manera consistente independientemente de la shell de entrada de que useren 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 shcomando hostdisponible $PATHque 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/ yashas:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
o
env "${cmd[@]}"
o
xterm -hold -e "${cmd[@]}"
...
¿Cómo iba a ejecutarlo en hostlo usermá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 %qen el shell local lo produce) y se ejecuta en la misma configuración regional.
fuente

cmdargumento fuera/bin/sh -cque 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 cmdlínea de comando. Necesitamos escribir el comando en la sintaxis correcta para ese shell (y no sabemos cuál es) para queshse llame allí con-cysome cmdargumentos.sh -c 'some cmd'ysome cmdse 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/shes una solución que di en mi respuesta, pero eso significa que no puede alimentar datos al stdin de ese comando remoto.cmdes asícmd=(echo "foo bar"), la línea de comando de shell pasadasshdebería ser algo así como la línea de comando '' echo '' foo bar '. The *first* space (the one beforeecho) 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
sshtenga 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
xargscomando que admite la-0opció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.xargslee 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
shen el host remoto después de haber citado cada elemento utilizando lashsintaxis 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á.
sshSin 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_CODEvariable que, por ejemplo, contenga el código entrecomilladoshcomo 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
perlguión envolventessh. Yo lo llamosexec. Lo llamas así:entonces en tu ejemplo:
Y el contenedor se convierte
cmd and its argsen una línea de comando que todos los shells terminan interpretando como llamadascmdcon sus argumentos (independientemente de su contenido).Limitaciones:
yashes 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 deyashque no puede evitarlo de todos modos.sh, también supone que el sistema remoto tiene elprintfcomando.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$xexpansió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$ncontiene nueva línea, encsh, debe escribirlo$n:qpara que se expanda a una nueva línea y eso no funcionará para otros shells. Entonces, lo que terminamos haciendo aquí es llamarshyshexpandirlos$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
$preambleen 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,$xvariables 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
hostpara 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-
bashshells. Pero suponiendo que estébashen 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
printfel trabajo usted mismo, si sabe lo que está haciendo:fuente
bashestará 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.bashproyectiles. 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 labashcosa.ssh_run user@host "${cmd[@]}"). Aún te faltan algunas citas.printf %qno 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.