SSH経由でリモートマシン上でスクリプトを実行し、ローカルファイルをソースにするにはどうすればいいですか?

SSH経由でリモートマシン上でスクリプトを実行し、ローカルファイルをソースにするにはどうすればいいですか?

ローカルスクリプトをリモートマシンにコピーせずに実行したいので、次のコマンドを使用します: ssh user@remote < local-script.sh

これは機能しますが、local-script.sh に別のファイルをソースするためのソース ステートメントを追加すると、たとえば、source ./another-local-script.shlocal-script.sh がリモートで実行されるときに、リモート上のソース ファイルを検索します。ソース ファイルを最初にローカルで解決するように、この問題を解決する方法はありますか?

答え1

これを透過的に行うことはできません。

ファイルをソースする前に、リバース トンネルを使用してローカル マシンからファイルを取得するために bash スクリプトを変更することはできますが、あまりきれいではありません。

必要なファイルをすべて転送して、次のように実行する方がはるかに良いでしょう。

scp local-script.sh another-script.sh user@remote
ssh user@remote local-script.sh

答え2

内容を制御できる入力ファイルの制限されたセットでは、awkまたは同様のものを使用して、stdin ストリーム内のコマンドをソースファイルで置き換えることができますsource。たとえば、

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」である行に一致し、2 番目の単語を挿入するファイルとして受け取ります。getlineそのファイルから行を読み取り ($0 に)、ファイルの終わりで 0 を返します。print行は一致しない行をコピーするだけです。

明らかに、これはその適用範囲が非常に限られており、たとえば、インクルードされたファイルに sourceコマンドも含まれている場合は、再帰的に処理する必要があります。


$0 の代わりに変数を使用する代替の getline:

while((getline inp <file)>0)print inp

を使用する代替スクリプトsed。ファイルを 2 回読み取るため、使用にはファイル名が必要です ("<" を削除してください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/// は同じパターンを再利用します)。2 番目の sed は行番号を取得し、次の行 (N) を追加し、改行とそれに続くファイル名 (.* は行の残り) を、必要なファイルを読み取り、元の行を削除する sed コマンドに置き換えます。trコマンド内のセミコロンを改行に変換します。結果のコマンドは、元のファイルに対して 3 番目の sed に渡されます。

答え3

@meuh さん、助けていただきありがとうございます。しかし、あなたの例を Mac OS XI で動作させることができなかったため、目的を果たす関数を思いつきました。ただし、皆さんの多くがこれを改善できると確信しています:

# 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"
}

基本的に、ファイルを 1 行ずつ読み取り、各行を tmp という変数に挿入します。行にソース ステートメントが含まれている場合は、ファイル名を取得し、ファイルへの絶対パスを解決します (相対パスは渡されたスクリプトに対する相対パスであると想定します)。ファイルが存在するかどうかを確認し、それらのファイルの内容をソース ステートメントの代わりに tmp 変数に追加します。

関連情報