
У меня есть локальный скрипт, который я хочу запустить на удаленной машине, не копируя его на машину, поэтому я использую: ssh user@remote < local-script.sh
Это работает, но если я добавляю оператор source в local-script.sh для source другого файла, например source ./another-local-script.sh
, когда local-script.sh запускается на удаленном компьютере, он ищет sourced-файл на удаленном компьютере. Есть ли способ решить эту проблему так, чтобы он сначала разрешал sourced-файлы локально?
решение1
Это невозможно сделать прозрачно.
Вы можете изменить свой bash-скрипт так, чтобы он получал файлы с локальной машины с помощью обратного туннеля, прежде чем отправлять их, но это будет не очень чисто.
Гораздо лучше просто перенести все необходимые файлы, а затем запустить их следующим образом:
scp local-script.sh another-script.sh user@remote
ssh user@remote local-script.sh
решение2
С ограниченным набором входных файлов, содержимое которых вы контролируете, вы можете использовать awk
или подобное для замены source
команды в потоке stdin на исходный файл. Например,
desource <local-script.sh | ssh user@remote
где desource — это скрипт
#!/bin/sh
awk '$1=="source" && NF>=2 {
file = $2; while((getline <file)>0)print $0
close(file); next
}
{ print }' "$@"
Это просто сопоставляет строки, первое слово которых "source", и берет второе слово как файл для вставки. getline
считывает строку из этого файла (в $0) и возвращает 0 в конце файла. Строка print
просто копирует несовпадающие строки.
Очевидно, что это весьма ограничено в применении и, например, потребует некоторой работы для рекурсии, если включенный файл также содержит
source
команды.
альтернативная getline с переменной вместо $0:
while((getline inp <file)>0)print inp
альтернативный скрипт с использованием sed
. Он читает файл дважды, поэтому для использования необходимо имя файла (опустите "<", например: 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
Это использует sed в первый раз, чтобы сопоставить source
строки, вывести номер строки (=) и оставить только имя файла (s/// повторно использует тот же шаблон). Второй sed берет номер строки, добавляет следующую строку (N) и заменяет новую строку и следующее имя файла (.* — это остальная часть строки) тем, что станет командой sed для чтения нужного файла и удаления исходной строки. Он tr
преобразует точки с запятой в команде в новые строки. Результирующая команда передается третьему sed для исходного файла.
решение3
Спасибо за помощь @meuh, но поскольку я не смог заставить ваши примеры работать на Mac OS X, я придумал функцию, которая выполняет эту работу, хотя я уверен, что многие из вас могли бы улучшить ее:
# 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"
}
По сути, он читает файл строка за строкой, вставляя каждую строку в переменную с именем tmp. Если строка содержит исходный оператор, он получает имя файла, разрешая абсолютный путь к файлу (и предполагает, что все относительные пути являются относительными к переданному скрипту). Он проверяет, существуют ли файлы, а затем добавляет содержимое из этих файлов в переменную tmp вместо исходных операторов.