
Tengo un script local que quiero ejecutar en una máquina remota sin copiarlo a la máquina, así que estoy usando: ssh user@remote < local-script.sh
Esto funciona, pero si agrego una declaración de origen en local-script.sh para generar otro archivo, por ejemplo source ./another-local-script.sh
, cuando local-script.sh se ejecuta en el control remoto, busca el archivo de origen en el control remoto. ¿Hay alguna manera de resolver esto para que primero resuelva los archivos de origen local?
Respuesta1
No puedes hacer esto de forma transparente.
Puede modificar su script bash para obtener archivos de su máquina local usando un túnel inverso, antes de obtenerlos, pero no es muy limpio.
Mucho mejor es simplemente transferir todos los archivos que necesita y luego ejecutarlos de esta manera:
scp local-script.sh another-script.sh user@remote
ssh user@remote local-script.sh
Respuesta2
Con un conjunto restringido de archivos de entrada cuyo contenido controla, puede usar awk
o algo similar para reemplazar el source
comando en la secuencia estándar por el archivo de origen. Por ejemplo,
desource <local-script.sh | ssh user@remote
donde desource es el guión
#!/bin/sh
awk '$1=="source" && NF>=2 {
file = $2; while((getline <file)>0)print $0
close(file); next
}
{ print }' "$@"
Esto simplemente coincide con líneas cuya primera palabra es "fuente" y toma la segunda palabra como archivo para insertar. getline
lee una línea de ese archivo (en $0) y devuelve 0 al final del archivo. La print
línea simplemente se copia a través de las líneas que no coinciden.
Obviamente, esto tiene una aplicación muy limitada y, por ejemplo, necesitará algo de trabajo para que sea recurrente si el archivo incluido también tiene
source
comandos.
getline alternativo con una variable en lugar de $0:
while((getline inp <file)>0)print inp
script alternativo usando sed
. Lee el archivo dos veces, por lo que el uso necesita el nombre del archivo (elimine "<", es decir desource local-script.sh | ssh user@remote
:)
#!/bin/bash
file=${1?}
cmd=$( sed -n '/^[ \t]*source[ \t]/{=;s///;p}' <$file |
sed '/^[0-9]/{N;s/\n\(.*\)/{r \1;d;}/}' |
tr ';' '\012' )
sed "$cmd" <$file
Esto usa sed por primera vez para hacer coincidir las source
líneas, imprime el número de línea (=) y deja solo el nombre del archivo (s/// reutiliza el mismo patrón). El segundo sed toma el número de línea, agrega la siguiente línea (N) y reemplaza la nueva línea y el siguiente nombre de archivo (.* es el resto de la línea) por lo que se convertirá en un comando sed para leer el archivo deseado y eliminar la línea original. Convierte tr
los puntos y coma del comando en nuevas líneas. El comando resultante se proporciona a un tercer sed en el archivo original.
Respuesta3
Gracias por la ayuda @meuh, pero como no pude hacer que sus ejemplos funcionaran en Mac OS X, se me ocurrió una función que hace el trabajo, aunque estoy seguro de que muchos de ustedes podrían mejorar esto:
# desource - substitute source statements in file with contents from sourced file
function desource {
if [ ! -f "$1" ]; then
echo "Invalid file: $1"
return
fi
declare tmp
while read -r line; do
if [ "${line:0:1}" == '#' ]; then continue; fi
file=$(echo "$line" | perl -nle 'print $1 if m{(?>source )(.+)}' | sed 's|"||g')
if [ -n "$file" ]; then
case "${file:0:1}" in
'/') ;;
'~') ;;
'$') ;;
*) file="$(cd `dirname $1` && pwd)/$file"
esac
file=$(echo "$file" | sed "s|~|$HOME|" | sed "s|\$HOME|$HOME|")
file="$(cd `dirname $file` && pwd)/$(basename $file)"
if [ -f "$file" ]; then
tmp="$tmp\n$(cat $file)"
else
echo "Invalid file: $file"
return
fi
else
tmp="$tmp\n$line"
fi
done < "$1"
echo -e "$tmp"
}
Básicamente lee el archivo línea por línea insertando cada línea en una variable llamada tmp. Si la línea contiene una declaración fuente, obtiene el nombre del archivo, resolviendo la ruta absoluta al archivo (y asume que cualquier ruta relativa es relativa al script pasado). Tiene comprobaciones para asegurarse de que los archivos existan y luego agrega el contenido de esos archivos a la variable tmp en lugar de las declaraciones fuente.