ssh
doesn't run a command on the remote host, but sends code for the login shell of the remote user to interpret, so if you want that remote shell to execute a given command with a given list of arguments, you need to construct a command line in that shell syntax that will cause that shell to execute that command with those arguments.
A shell is a command line interpreter. Its primary purpose is to execute commands given the command lines (command lines being another way to say code in the shell syntax) you give it. In a Korn-like shell like yours with a value of $FILENAME
being File Name With Spaces.mp4
and a command line like:
ssh server md5sum filelocation/"${FILENAME}"
The shell's job is to execute a file found in $PATH
whose name is ssh
(something like /usr/bin/ssh
) with these arguments:
argv[0]
: ssh
argv[1]
: server
argv[2]
: md5sum
argv[3]
: filelocation/File Name With Spaces.mp4
In the shell language syntax, white space separate command arguments, $xxx
triggers parameter expansion, and quotes here are used to prevent split+glob upon that expansion.
Then ssh
's job is, from that list of argument it receives, to connect to server
, join the remaining arguments with spaces, and pass the result to the login shell of the remote user (their preferred shell which they can change with chsh
, zsh
for me, but could be tcsh
, fish
, yash
, bash
, rc
...) by executing it with as arguments:
argv[0]
: that shell's name
argv[1]
: -c
argv[2]
: that-result, so here: md5sum filelocation/File Name With Spaces.mp4
Here, while all shells have different syntaxes, that command line is simple enough that it will be interpreted the same by most. That is, it will execute a /path/to/md5sum
command with these arguments:
argv[0]
: md5sum
argv[1]
: filelocation/File
argv[2]
: Name
argv[3]
: With
argv[4]
: Spaces.mp4
For the md5sum
command to be run with one filelocation/File Name With Spaces.mp4
argument instead, we'd need to tell that remote shell that those spaces are not to be taken as argument separators. And that's done via quoting/escaping. And the quoting syntax varies significantly between shells.
In any case, space is not the only character that would cause problem. Any character that is special in the remote shell syntax would be a problem as well. For instance, if the filename was $(reboot).mp4
or blah;rm -rf ~;blah.mp4
, you'd have bigger problems.
If you know that remote shell is Bourne-like, you could do:
#! /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
That ${(qq)file}
quotes with single quotes which is the safest way to quote things in Bourne-like shells. So in your case, File Name With Spaces.mp4
would be passed as 'File Name With Spaces.mp4'
. If it was File Name With Quote's.mp4
, it would be 'File Name With Quote'\''s.mp4'
, where everything is quoted with '...'
except for the '
itself which is quoted with \
.
If you cannot guarantee that the remote shell be Bourne-like, see How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user? for more options.
Here, for your particular use-case, to compare the local and remote checksums, another option is to use md5sum
's check mode (with -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
This time, the name of the file is written by the local md5sum
and read by the remote one on its stdin, so we don't need to quote it for the remote shell. And that cd ./filelocation && md5sum -c
command line is understood by most shells (the ./
prefix is to avoid the effect of $cdpath
/$CDPATH
in csh/tcsh/bash, the shells that do or can read their rc file when invoked non-interactively or over ssh).