while IFS= read -r line
do
LOCATION=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $1 }')
USER=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $2 }')
MD5=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $3 }')
FILE=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $4 }')
CHECK=$(md5sum "$FILE" | awk '{ print $1 }')
FILENAME="${FILE##*/}"
echo "$FILENAME"
REMOTECHECK=$(ssh server md5sum filelocation/"${FILENAME}" < /dev/null | awk '{ print $1 }')
if [[ "$CHECK" == "$MD5" ]]; then
echo "Local File MD5: "
echo "$CHECK"
echo "Remote File MD5: "
echo "$REMOTECHECK"
fi
done < _path to file_
空白のないファイル名にはスクリプトが正しく機能しますが、ファイル名に空白が含まれていると問題が発生します。
ファイル名に空白があるときに出力されます。
md5sum: path_to_file/File: No such file or directory
md5sum: Name: No such file or directory
md5sum: With: No such file or directory
md5sum: Spaces.mp4: No such file or directory
私が知る限り、問題はこのコード行にあります。
REMOTECHECK=$(ssh server md5sum filelocation/"${FILENAME}" < /dev/null | awk '{ print $1 }')
上記のスクリプトは、空白のないファイル名に対して機能し、問題は空白のあるファイル名でのみ発生します。
どんなアドバイスでも提供していただければとても役に立ちます。
答え1
ssh
実行されない注文するリモートホストは、リモートユーザーのログインシェルが解釈できるようにコードを送信します。したがって、リモートシェルに指定された引数リストを使用して特定のコマンドを実行させるには、次の手順を実行する必要があります。それシェル構文は、シェルにこれらのパラメータを使用してコマンドを実行させるようにします。
シェルはコマンドラインソルバーです。主な目的は、コマンドラインから与えられたコマンドを実行することです(コマンドラインは言い換えれば、シェル構文のコード)あなたはそれを提供します。値$FILENAME
が Korn のようなシェルでは、コマンドラインは次のようFile Name With Spaces.mp4
になります。
ssh server md5sum filelocation/"${FILENAME}"
シェルの作業は、次のパラメータを使用して名前付き$PATH
ファイルを実行することです。ssh
/usr/bin/ssh
argv[0]
:ssh
argv[1]
:server
argv[2]
:md5sum
argv[3]
:filelocation/File Name With Spaces.mp4
シェル言語構文では、$xxx
スペースはコマンドパラメータを区切ってパラメータ拡張をトリガします。
その後、アクションはssh
受け取った引数のリストから連結server
し、残りの引数を空白で連結し、結果をリモートユーザーのログインシェルに渡すことです(それらは変更されたデフォルトのシェルを使用できますが、、、、chsh
...)パラメータを使用します。で実行します。zsh
tcsh
fish
yash
bash
rc
argv[0]
: そのシェルの名前argv[1]
:-c
argv[2]
:結果、ここ:md5sum filelocation/File Name With Spaces.mp4
ここではすべてのシェルの構文が異なりますが、コマンドラインはほとんどの人が同じように解釈するのと同じくらい簡単です。つまり、/path/to/md5sum
次のパラメーターを使用してコマンドを実行します。
argv[0]
:md5sum
argv[1]
:filelocation/File
argv[2]
:Name
argv[3]
:With
argv[4]
:Spaces.mp4
md5sum
1つの引数で実行されるコマンドでは、filelocation/File Name With Spaces.mp4
これらのスペースが引数区切り文字と見なされないことをリモートシェルに知らせる必要があります。これは引用/エスケープを通じて行われます。そして引用構文はシェルによって大きく異なります。
とにかく、空白だけが問題を引き起こす唯一の文字ではありません。リモートシェル構文の特殊文字も問題になります。たとえば、ファイル名が$(reboot).mp4
またはその場合、blah;rm -rf ~;blah.mp4
より大きな問題があります。
リモートシェルがBourneに似ていることがわかっている場合は、次のことができます。
#! /bin/zsh -
while IFS=, read -ru3 location user md5 file rest; do
md5sum -- $file | read check rest
filename=$file:t
print -r -- $filename
ssh -n server "md5sum filelocation/${(qq)filename}" | read remotecheck rest
if [[ $md5 = $check ]]; then
printf '%s File MD5: %s\n' Local "$check" Remote "$remotecheck"
fi
done 3< $path_to_file
${(qq)file}
次に見積もり一重引用符は、Bourneのようなシェルから内容を引用する最も安全な方法です。。したがって、あなたの場合はにFile Name With Spaces.mp4
転送されます'File Name With Spaces.mp4'
。そうでFile Name With Quote's.mp4
あれば、引用されたもの自体を除いてすべてが'File Name With Quote'\''s.mp4'
引用されます。'...'
'
\
Bourneに似たリモートシェルが保証されていない場合は、以下を参照してください。リモートユーザーのログインシェルを知らず、SSHを介して任意の単純なコマンドをどのように実行できますか?より多くの選択のために。
md5sum
ここで特定のユースケースでは、ローカルとリモートのチェックサムを比較するためのもう1つのオプションは、確認モード(と一緒に)を使用することです-c
。
#! /bin/zsh -
while IFS=, read -ru3 location user md5 file rest; do
(cd -P -- $file:h && md5sum -- $file:t) |
ssh -n server 'cd ./filelocation && md5sum -c'
done 3< $path_to_file
今回はファイル名がローカルに作成され、md5sum
リモートファイルの標準入力から読み取られるため、リモートシェルでそれを参照する必要はありません。このコマンドラインはほとんどのシェルで理解されていますcd ./filelocation && md5sum -c
(接頭辞は、./
非対話式またはsshを介して呼び出されたときにrcファイルを読み書きできるcsh / tcsh / bashでの影響を避けるためです)。$cdpath
$CDPATH
答え2
問題は、この行と2つのシェルがそれらを解釈する方法にあります。
REMOTECHECK=$(ssh server md5sum filelocation/"${FILENAME}" < /dev/null | awk '{ print $1 }')
ファイル名が"happy Monday"であると仮定し、ssh
コマンドを具体的に見てみましょう。
変数の値を評価したら、ローカルシェルで次のことを確認します。
ssh server md5sum 'filelocation/happy monday' < /dev/null
特に、引用符が削除され、シェルは内容を単一の単語として扱いますfilelocation/happy monday
。
結果は、ssh
リモートシェル(何でも)に渡されたコマンドライン引数によって実行されます。引用符が削除されたので、次はリモートで実行されることに注意してください。
md5sumファイルの場所/ハッピーマンデー
現在md5sum
2つのファイルを探してfilelocation/happy
いますmonday
。
引用符の欠落を防ぐには、コマンド全体を別のセットにまとめる必要があります。
ssh server "md5sum 'filelocation/happy monday'"
これを元のコードに再適用します。
REMOTECHECK=$(ssh -n server "md5sum 'filelocation/$FILENAME'" | awk '{ print $1 }')
答え3
最も簡単なアプローチは、リモートシステムが次のようにPOSIX sh構文をサポートするシェルを使用していることを知っていると仮定することです。
#!/bin/sh
while IFS= read -r line
do
LOCATION=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $1 }')
USER=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $2 }')
HASH=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $3 }')
FILE=$(echo "$line" | awk 'BEGIN { FS = "," } ; { print $4 }')
CHECK=$(b2sum "$FILE" | awk '{ print $1 }')
FILENAME="${FILE##*/}"
REMOTECHECK=$(printf '%s\0' "$FILE" |
ssh castro xargs -0 -I{} b2sum "remotefile/{}" |
cut -d' ' -f1)
echo "Local File hash: "
echo "$CHECK"
echo "Remote File hash: "
echo "$REMOTECHECK"
done
参考にするいくつかの点があります。
まず、xargs -I
リモートシステムで単一の特定のパス名を指定するために使用します。これは、リモートシステムがパス名をリモート側に渡し、正しく引用することを可能にする最も簡単な方法です。ファイル名に引用符が含まれている場合は、それを引用しようとすると興味深い例外が発生する可能性があります。git rev-parse --sq-quote
ローカルでGitを使用できる場合は、これらの方法を使用してこれらの問題を解決できますが、この方法はより簡単で強力です。私たちのxargs
使用法は厳密に移植可能ではありませんが(一部のシステムでは別々{}
のパラメータが必要なため)、Linuxや他のほとんどの一般的なシステムではこの動作を実装しています。
第二に、リモートシステムで実行する必要があるスループットを最小限に抑え、これをあまりにも多くの前提にする必要はありません。保証することはできませんが、リモート側でPOSIX shを使用しない場合でも、この構文が機能する可能性があります。もちろん、追加の出力を提供しないリモート側に依存しますが、これは事実上避けられません。
第三に、私たちはMD5を使用しません。 CERT CCを引用すると、「ソフトウェア開発者は... MD5アルゴリズムを何らかの方法で使用しないでください。私のシステムにMD5と競合するファイルがあるため、クイックチェックには適していません」ここでは、MD5よりも安全で高速なBLAKE2bを使用したか、b2sum
SHA-256を使用できます(sha256sum
または使用できない場合)。shasum -a 256