
Eu tenho um script local que desejo executar em uma máquina remota sem copiá-lo para a máquina, então estou usando: ssh user@remote <local-script.sh
Isso funciona, mas se eu adicionar uma instrução de origem em local-script.sh para originar outro arquivo, por exemplo source ./another-local-script.sh
, quando local-script.sh é executado no controle remoto, ele procura o arquivo de origem no controle remoto. Existe alguma maneira de resolver isso para que os arquivos de origem sejam resolvidos localmente primeiro?
Responder1
Você não pode fazer isso de forma transparente.
Você pode alterar seu script bash para obter arquivos de sua máquina local usando túnel reverso, antes de obtê-los, mas não é muito limpo.
Muito melhor é transferir todos os arquivos necessários e executá-los assim:
scp local-script.sh another-script.sh user@remote
ssh user@remote local-script.sh
Responder2
Com um conjunto restrito de arquivos de entrada cujo conteúdo você controla, você pode usar awk
ou similar para substituir o source
comando no fluxo stdin pelo arquivo de origem. Por exemplo,
desource <local-script.sh | ssh user@remote
onde desource é o script
#!/bin/sh
awk '$1=="source" && NF>=2 {
file = $2; while((getline <file)>0)print $0
close(file); next
}
{ print }' "$@"
Isso apenas corresponde às linhas cuja primeira palavra é "fonte" e considera a segunda palavra como o arquivo a ser inserido. getline
lê uma linha desse arquivo (em $0) e retorna 0 no final do arquivo. A print
linha apenas copia as linhas não correspondentes.
Obviamente, isso é altamente limitado em sua aplicação e, por exemplo, precisará de algum trabalho para ser recursivo se o arquivo incluído também tiver
source
comandos.
getline alternativo com uma variável em vez de $0:
while((getline inp <file)>0)print inp
script alternativo usando sed
. Ele lê o arquivo duas vezes, então o uso precisa do nome do arquivo (descarte o "<" ou seja 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
Isso usa sed uma primeira vez para combinar as source
linhas, imprime o número da linha (=) e deixa apenas o nome do arquivo (s/// reutiliza o mesmo padrão). O segundo sed pega o número da linha, anexa a próxima linha (N) e substitui a nova linha e o nome do arquivo seguinte (.* é o resto da linha) pelo que se tornará um comando sed para ler o arquivo desejado e excluir a linha original. O tr
converte o ponto e vírgula do comando em novas linhas. O comando resultante é dado a um terceiro sed no arquivo original.
Responder3
Obrigado pela ajuda @meuh, mas como não consegui fazer seus exemplos funcionarem no Mac OS X, criei uma função que faz o trabalho, embora tenha certeza que muitos de vocês poderiam melhorar isso:
# 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"
}
Basicamente ele lê o arquivo linha por linha inserindo cada linha em uma variável chamada tmp. Se a linha contiver uma instrução source, ela obterá o nome do arquivo, resolvendo o caminho absoluto para o arquivo (e assumindo que quaisquer caminhos relativos sejam relativos ao script passado). Ele verifica se os arquivos existem e, em seguida, adiciona o conteúdo desses arquivos à variável tmp no lugar das instruções de origem.