SSH find コマンドがローカル変数に格納されているディレクトリを認識しない

SSH find コマンドがローカル変数に格納されているディレクトリを認識しない

SSHでローカル変数に保存されているディレクトリに移動することはできます。しかし、なぜコマンドが機能しないの$out_dirかわかりません。find | headトライ#2コマンドの最後に 2 番目のバックスラッシュを追加しましたfind

試してください #1 エラー: find: `/dir/on/remote/server': No such file or directory

ssh $user@$ip << EOF
    discard=$( find $out_dir -exec basename {} \; | head -n -1 )
    for f in $discard; do
        echo "rm $f"
    done
    logout
EOF

試行#2エラー: syntax error near unexpected token `|'

ssh $user@$ip << EOF
    discard=$( find $out_dir -exec basename {} \\; | head -n -1 )
    for f in $discard; do
        echo "rm $f"
    done
    logout
EOF

答え1

試行 #1 で「そのようなファイルまたはディレクトリはありません」というエラーが表示される理由は、ヒアドキュメント ( <<EOF) のすべての変数とコマンドの補間が、リモート ホストに送信される前にローカルで実行されるためです。

ご指摘のとおり、$out_dirは補間されています。つまり、表示したいディレクトリが表示されます。これは、ssh 呼び出しを行う前にローカル マシンで行われています。コマンド置換 ( $()) もローカルで行われています (ただし、これはリモートで行う予定です)。言い換えると、 find 全体がリモート マシンに送信される前にローカルで処理されています。つまり、 で囲まれたものはすべて$()heredoc によって処理されます。したがって、 は${out_dir}ローカル マシンに存在しないようです (「そのようなファイルまたはディレクトリはありません」)。

これをもっとよく理解したいなら、例を簡略化してみましょう。次のように試してください。

$ ssh foo@localhost <<EOF
  echo "using account: $(whoami)"
EOF                            
Pseudo-terminal will not be allocated because stdin is not a terminal.
<truncated>
using account: vagrant

私のローカル アカウントは 'vagrant' なので、$() の内容は明らかにローカルで実行されています。正しく動作していれば、 を指定したためアカウントは 'foo' になっているはずですssh foo@localhost

試行 #2 は、';' ではなく '\' をエスケープしたために壊れています。Find は-exec終了する必要がありますが、現在は終了していません。代わりに、コマンドは突然終了し、ぶら下がっている ';' によって bash コマンドが終了しています。'|' は入力を転送することを期待していますが、何も指定されていません。実際には、次のものを作成しました。

$ | cat
-bash: syntax error near unexpected token `|'

それで、実用的な解決策は何でしょうか?

さて、find削除はできます:

ssh $user@$ip <<EOF
  find $out_dir -delete
EOF

シンプルで直接的です。一部のファイルを選択して他のファイルを除外することに不安がある場合は、 を修正してくださいfind

find現在のバージョンでは、エッジケースの処理をしようとしているように見えるため、「修正する」というアドバイスをします。

  • ベース名の使用
  • の用法head -n -1

しかし、この処理が意図したとおりに機能するとは期待していません。

まず、 を呼び出すとbasenameパスの大部分が削除され、ssh コマンドではディレクトリを変更する操作は行われません。ssh の実行では、${user} のホームに相対するすべてのものを削除しようとします。また、${out_dir} を指定すると、ホーム以外のディレクトリを使用する必要があるようです。繰り返しますが、find削除は に任せてください。

2 番目に、これは的外れかもしれませんが、head -n -1ターゲット ファイルのリストから ${out_dir} を削除するために使用しているのではないかと思います。

$ find junk/
junk/
junk/one
junk/two
junk/three

当然ですが、 を削除したくはありませんjunk/

ただし、これを試してください:

$ seq 1 4
1
2
3
4
$ seq 1 4 | head -n -1
1
2
3

これは最初の行を保存せず、最後の行を保存します。tail を試してください:

$ seq 1 4 | tail -n +2
2
3
4

しかし、もう一度言いますが、find削除はお任せください。

ファイルを削除するだけのために find が必要な場合は、 を参照してください-type f。ただし、より高度なフィルタリングを行うための exclude パターンと include パターンもあります。

答え2

これを試して:

ssh "$user@$ip" "out_dir=$out_dir;" '
    discard=$( find $out_dir -exec basename {} \; | head -n -1 )
    for f in $discard; do
        echo "rm $f"
    done
'

注記:意図したリモート コマンドはよりシンプルかつ安全にできるはずですが、それについて文句を言うと、要点が混乱するだけです。

基本的には、a)すべきまた拡大される地元機械とb)すべきであるのみ拡大されるリモート機械としてssh への引数。前者は二重引用符で囲み、後者は一重引用符で囲みます。

Ssh は、すべての「コマンド」引数をスペースで結合してから、それらを単一の引数として-cリモート マシン上のユーザーのログイン シェルのオプションに渡します。

リモート コマンド自体に一重引用符が含まれている場合は、コマンド置換 + here doc の組み合わせを使用できます。また、「local」変数の値にスペースやその他の特殊文字が含まれている場合は、それらをエスケープする必要があります。

out_dir='/path/to/ * happiness * '
out_dir=$(printf %s "$out_dir" | sed 's/[^a-zA-Z0-9_/.]/\\&/g')
ssh localhost out_dir="$out_dir;" "$(cat <<'EOT'
  echo '<$out_dir>' = "<$out_dir>"
EOT
)"

;内の一重引用符を<<'EOF'省略すると、$out_dirヒアドキュメント内の が(ローカルマシン上で)展開されることに注意してください。

最近の bash バージョンでは という形式が採用されているため、その見苦しい の代わりに${var@Q}を使用するだけで済みます。"out_dir=${outdir@Q};"echo | sed

リモート スクリプトを stdin 経由で渡す必要はほとんどありません (必要になったとしても)。

関連情報