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
不运行命令在远程主机上,但发送代码供远程用户的登录 shell 进行解释,因此如果您希望远程 shell 使用给定的参数列表执行给定的命令,则需要在那shell 语法将导致该 shell 使用这些参数执行该命令。
shell 是一个命令行解释器。它的主要目的是执行给定命令行的命令(命令行是另一种说法shell语法中的代码)你给它。在像您这样的类似 Korn 的 shell 中,其值为$FILENAME
being File Name With Spaces.mp4
,命令行如下:
ssh server md5sum filelocation/"${FILENAME}"
shell 的工作是使用以下参数执行一个$PATH
名称为ssh
(类似)的文件:/usr/bin/ssh
argv[0]
:ssh
argv[1]
:server
argv[2]
:md5sum
argv[3]
:filelocation/File Name With Spaces.mp4
在 shell 语言语法中,空格分隔命令参数、$xxx
触发参数扩展,并且此处的引号用于防止该扩展时出现 split+glob。
然后ssh
的工作是,从它收到的参数列表中连接到server
,用空格连接剩余的参数,并将结果传递到远程用户的登录 shell(他们可以使用 更改的首选 shell chsh
,zsh
对我来说,但可以是tcsh
, fish
, yash
, bash
... rc
) 通过使用参数执行它:
argv[0]
: 那个外壳的名字argv[1]
:-c
argv[2]
:结果,所以这里:md5sum filelocation/File Name With Spaces.mp4
在这里,虽然所有 shell 都有不同的语法,但该命令行足够简单,大多数人都会对其进行相同的解释。也就是说,它将执行/path/to/md5sum
带有以下参数的命令:
argv[0]
:md5sum
argv[1]
:filelocation/File
argv[2]
:Name
argv[3]
:With
argv[4]
:Spaces.mp4
对于md5sum
使用一个参数运行的命令filelocation/File Name With Spaces.mp4
,我们需要告诉远程 shell 这些空格不被视为参数分隔符。这是通过引用/转义来完成的。而且 shell 之间的引用语法差异很大。
无论如何,空格并不是唯一会导致问题的字符。远程 shell 语法中的任何特殊字符也会成为问题。例如,如果文件名是$(reboot).mp4
或blah;rm -rf ~;blah.mp4
,您就会遇到更大的问题。
如果您知道远程 shell 与 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 的 shell 中引用内容的最安全的方式。所以在你的情况下,File Name With Spaces.mp4
将被传递为'File Name With Spaces.mp4'
.如果是的话File Name With Quote's.mp4
,那么除了用 引用的自身之外,'File Name With Quote'\''s.mp4'
所有内容都被引用了。'...'
'
\
如果您不能保证远程 shell 与 Bourne 类似,请参阅如何在不知道远程用户的登录 shell 的情况下通过 ssh 执行任意简单命令?以获得更多选择。
在这里,对于您的特定用例,要比较本地和远程校验和,另一个选择是使用 的md5sum
检查模式(带有-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
并由远程文件在其标准输入上读取,因此我们不需要为远程 shell 引用它。大多数 shell都可以理解该cd ./filelocation && md5sum -c
命令行(前缀是为了避免 csh/tcsh/bash 中/./
的影响,这些 shell 在非交互式或通过 ssh 调用时会读取或可以读取其 rc 文件)。$cdpath
$CDPATH
答案2
问题在于这一行以及两个 shell 解释它的方式。
REMOTECHECK=$(ssh server md5sum filelocation/"${FILENAME}" < /dev/null | awk '{ print $1 }')
假设文件名是“happy Monday”,我们将ssh
具体查看该命令
评估变量值后,本地 shell 会看到这个
ssh server md5sum 'filelocation/happy monday' < /dev/null
特别是,引号被删除,并且 shell 将内容视为单个单词filelocation/happy monday
。
现在,结果由ssh
传递到远程 shell(无论是什么)的命令行参数执行。请记住,引号已被删除,因此这是远程执行的内容:
md5sum 文件位置/周一快乐
此时md5sum
正在寻找两个文件,filelocation/happy
和monday
。
为了防止丢失引号,有必要将整个命令包装在另一组中
ssh server "md5sum 'filelocation/happy monday'"
将此重新应用到您的原始代码中,
REMOTECHECK=$(ssh -n server "md5sum 'filelocation/$FILENAME'" | awk '{ print $1 }')
答案3
最简单的方法是,假设您知道远程系统正在使用支持 POSIX sh 语法的 shell,如下所示:
#!/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 冲突的文件。我在这里使用了 BLAKE2b(通过b2sum
),它比 MD5 更安全且更快,或者如果不可用,您也可以使用 SHA-256(sha256sum
或)。shasum -a 256