Ssh 在 bash 中打破 while 循环

我使用 bash 代码将文件上传到远程服务器,对于普通文件,这个工作得很好:

for i in `find devel/ -newer $UPLOAD_FILE`
do
echo "Upload:" $i
if [ -d $i ]
then
echo "Creating directory" $i
ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i"
continue
fi
if scp -Cp $i $USER@$SERVER:$REMOTE_PATH/$i
then
echo "$i OK"
else
echo "$i NOK"
rm ${UPLOAD_FILE}_tmp
fi
done

唯一的问题是,对于名称中带有空格的文件,for 循环将失败,因此我替换了第一行,如下所示:

find devel/ -newer $UPLOAD_FILE | while read i
do
echo "Upload:" $i
if [ -d $i ]
then
echo "Creating directory" $i
ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i"
continue
fi
if scp -Cp $i $USER@$SERVER:$REMOTE_PATH/$i
then
echo "$i OK"
else
echo "$i NOK"
rm ${UPLOAD_FILE}_tmp
fi
done

由于某种奇怪的原因,ssh-command 从 while-loop 中断开,因此第一个缺少的目录被创建得很好,但是所有后续缺少的文件/目录都被忽略。

我猜这与 ssh 向 stdout 写入一些东西有关,这会混淆“ read”命令。注释掉 ssh-command 可以使循环正常工作。

有人知道为什么会发生这种情况以及如何防止 ssh 破坏 while 循环吗?

66935 次浏览

问题是 ssh从标准输入读取,因此它会吃掉剩下的所有行。你可以把它的标准输入连接到任何地方:

ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i" < /dev/null

您还可以使用 ssh -n代替重定向。

另一种方法是循环访问标准输入以外的 FD:

while IFS= read -u 3 -r -d '' filename; do
if [[ -d $filename ]]; then
printf -v cmd_str 'cd %q; mkdir -p %q' "$REMOTE_PATH" "$filename"
ssh "$USER@$SERVER" "$cmd_str"
else
printf -v remote_path_str '%q@%q:%q/%q' "$USER" "$SERVER" "$REMOTE_PATH" "$filename"
scp -Cp "$filename" "$remote_path_str"
fi
done 3< <(find devel/ -newer "$UPLOAD_FILE" -print0)

-u 33<操作符在这里非常关键,使用 FD3而不是默认的 FD0(stdin)。

这里给出的方法——使用 -print0、一个清除的 IFS值等等——也比原始代码和现有答案的错误少,后者不能正确处理有趣的文件名。(Glenn Jackman 的答案很接近,但即使是这样也不能处理带换行符的文件名或带空白尾随的文件名)。

使用 printf %q对于生成不能用于攻击远程计算机的命令至关重要。考虑一下,如果一个名为 devel/$(rm -rf /)/hello的文件没有这种偏执的代码,会发生什么情况。