![SSH find コマンドがローカル変数に格納されているディレクトリを認識しない](https://rvso.com/image/170291/SSH%20find%20%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%8C%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%A4%89%E6%95%B0%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92%E8%AA%8D%E8%AD%98%E3%81%97%E3%81%AA%E3%81%84.png)
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 経由で渡す必要はほとんどありません (必要になったとしても)。