Use remote environment variable's value in scp

2019-09-14 18:19发布

问题:

I am having some trouble with not expanding a parameter I am passing in.

My shell script (scp_run.ksh) runs in an environment with 3 servers- the goal of the script is to move files from the archive folders on the servers to the archive folder of the server running this script.

I have tested this in an example like ./scp_run.ksh /server/path/to/archive and the shell script successfully looks across the servers and scp's the files found in each /server/path/to/archive and moves them to the current servers /server/path/to/archive

Here is where things get messy: I want to not use a hardcoded path but an environment var like $SERVER_ARCHIVE. This environment variable is set up on each server - though for each server it is a slightly different path (out of my control). Ex.

Server 1 $SERVER_ARCHIVE= /reports_1/archive
Server 2 $SERVER_ARCHIVE= /reports_2/archive
Server 3 $SERVER_ARCHIVE= /reports_3/archive

When I call my script like ./scp_run.ksh $SERVER_ARCHIVE it seems to instantly evaluate the parameter to /reports_x/archive and so when calling the SCP command it is looking for the folder path set from the environment variable on the current node not on the one it is looking to scp from.

ex: Running this from Server 1 I see that it is executing the scp command for Server 2 like /usr/bin/scp -p server_2_name:/reports_1/archive/my_file.dat /reports_1/archive/.

SOURCE_DIR=${1}
...
for server in "${prod_servers_array[@]}"
do
   if [[ ${server} != ${currserver} ]]
   then 
        /usr/bin/scp -p ${server}:${SOURCE_DIR}/${SOURCE_FILES} ${SOURCE_DIR}/.
    fi
done

What I am hoping is for that SCP command to actually pass/get the $SERVER_ARCHIVE (or whatever env varible I pass) from the actual server to use. So then the correct execution/evaluation of

/usr/bin/scp -p ${server}:${SOURCE_DIR}/${SOURCE_FILES} ${SOURCE_DIR}/.

when running on Server 1 and looking at Server 2 looks like

/usr/bin/scp -p server_2_name:/reports_2/archive/my_file.dat /reports_1/archive/.

Is there a way to pass $SERVER_ARCHIVE as a parameter to ${1} and have it not evaluate to the full path?

My thought is that right before my scp command I need to SSH over to the $server and echo ${SOURCE_DIR} but I am having trouble getting $SOURCE_DIR to not evaluate the $SERVER_ARCHIVE right away.

I think it would be something like remote_var=$(ssh myuser@server_2_name 'printf $SOURCE_DIR') but I think in my current setup this $SOURCE_DIR is already evaluated to /reports_1/archive and NOT set as $SERVER_ARCHIVE

回答1:

Unlike SFTP, SCP is largely implementation-defined -- meaning that there is no formal protocol document or specification that would determine what is and isn't supported in a forward-compatible way.

If we're trying to write something that isn't going to break in the future, it would be safer not to depend on it.

One of your attempts was in the right direction:

# this only evaluates SOURCE_DIR on the remote end due to the outer single-quotes
remote_var=$(ssh myuser@server_2_name 'printf "%s\n" "$SOURCE_DIR"')

Another approach is not to use SCP at all, but to use SSH running your own code for the transfer:

source_dir=$1
source_files=( a.txt b.txt ) # set this yourself
source_files_q=$(printf '%q ' "${source_files[@]}")

copy_remote_files() {
  local server=$1
  # important: unlike a <<EOF heredoc, <<'EOF' prevents any local expansion
  ssh "$server" "ksh -s $source_files_q" <<'EOF'
    # source dotfiles, to ensure we pick up SOURCE_DIR
    # modify for your actual dotfiles
    for f in ~/.profile ~/.rc; do . "$f"; done
    [[ $SOURCE_DIR ]] || { echo 'Unable to find remote $SOURCE_DIR' >&2; exit 1; }

    cd "$SOURCE_DIR" || { echo "Unable to cd to $SOURCE_DIR" >&2; exit 1; }

    exec tar -c -- "$@"
EOF
}

cd "$SOURCE_DIR" || { echo "Unable to cd to local SOURCE_DIR" >&2; exit 1; }
for server in "${prod_servers_array[@]}"; do
  if [[ ${server} != "${currserver}" ]]; then
    copy_remote_files "$server" | tar -x
  fi
done