bashshellssh

How to run functions and local resources over SSH in a shell script


I have a shell script file like this:

#!/bin/bash

CONF_FILE="/tmp/settings.conf" #settings.conf contains OS_NAME="Caine Linux"
source $CONF_FILE

display_os_name() { echo "My OS is:" $OS_NAME }

#using the function locally works fine
display_os_name
#displays: My OS is: Caine Linux

#using the function on the remote host doesn't work
ssh user@host "$(declare -f); display_os_name"
#displays: My OS is:

If I remove the -f and I use just ssh user@host "$(declare); display_os_name" it works but displays these errors and warnings:

bash: line 10: BASHOPTS: readonly variable
bash: line 18: BASH_VERSINFO: readonly variable
bash: line 26: EUID: readonly variable
bash: line 55: PPID: readonly variable
bash: line 70: SHELLOPTS: readonly variable
bash: line 76: UID: readonly variable

If I use ssh user@host "$(declare); display_os_name >/dev/null" to suppress the warnings only the output of the function is suppressed (My OS is: Caine Linux), not the warnings.

Is there a way to run local functions together with sourced local files on a remote SSH host?


Solution

  • An easy approach (if your local side is Linux) is to use set -a to enable automatic export before your source command; copy /proc/self/environ on stdin; and parse it into a set of variables on the remote side.

    Because BASHOPTS, EUID, etc. aren't environment variables, this avoids trying to modify them. (If you were complying with POSIX recommendations and using lowercase names for your own variables, you could even go as far as to ignore all-caps variables entirely).

    set -a # enable export of all variables defined, **before** the source operation
    source /tmp/settings.conf
    
    import_env() {
      while IFS= read -r -d '' item; do
        printf -v "${item%%=*}" "%s" "${item#*=}" && export "$item"
      done
    }
    
    cat /proc/self/environ | ssh user@host "$(declare -f); import_env; display_os_name"
    

    Even easier is to just copy the file you want to source over the wire.

    ssh user@host "$(declare -f); $(</tmp/settings.conf); display_os_name"