2

I want to create a function locally, echo_a in the example, and pass it with to a remote shell through ssh, here with typeset -f. The problem is that function does not have access to the local variables.

export a=1

echo_a() {
    echo a: $a
}

bash <<EOF

$(typeset -f echo_a)

echo local heredoc:
echo_a
echo

echo local raw heredoc:
echo a: $a
echo

EOF

ssh localhost bash <<EOF

$(typeset -f echo_a)

echo remote heredoc:
echo_a
echo

echo remote raw heredoc:
echo a: $a
echo

EOF

Assuming the ssh connection is automatic, running the above script gives me as output:

local heredoc:
a: 1

local raw heredoc:
a: 1

remote heredoc:
a:

remote raw heredoc:
a: 1

See how the "remote heredoc" a is empty? What can I do to get 1 there?

I tested adding quotes and backslashes everywhere without success.

What am I missing? Would something else than typeset make this work?

ibizaman
  • 3,053
  • 1
  • 23
  • 34
  • 1
    I don’t think the environment is sent over ssh for security. You have to expressly allow it on the server (local host here) – Guy Feb 18 '18 at 00:51

1 Answers1

0

Thanks to @Guy for the hint, it indeed is because ssh disables by default sending the environment variables. In my case, changing the server's setting was not wanted.

Hopefully we can hack around by using compgen, eval and declare.

First we identify added variables generically. Works if variables are created inside a called function too. Using compgen is neat because we don't need to export variables explicitely.

The array diff code comes from https://stackoverflow.com/a/2315459/1013628 and the compgen trick from https://stackoverflow.com/a/16337687/1013628.

# Store in env_before all variables created at this point
IFS=$'\n' read -rd '' -a env_before <<<"$(compgen -v)"
a=1
# Store in env_after all variables created at this point
IFS=$'\n' read -rd '' -a env_after <<<"$(compgen -v)"

# Store in env_added the diff betwen env_after and env_before
env_added=()
for i in "${env_after[@]}"; do
    skip=
    for j in "${env_before[@]}"; do
        [[ $i == $j ]] && { skip=1; break; }
    done
    if [[ $i == "env_before" || $i == "PIPESTATUS" ]]; then
        skip=1
    fi
    [[ -n $skip ]] || env_added+=("$i")
done

echo_a() {
    echo a: $a
}

env_added holds now an array of all names of added variables between the two calls to compgen.

$ echo "${env_added[@]}"
a

I filter out also the variables env_before and PIPESTATUS as they are added automatically by bash.

Then, inside the heredocs, we add eval $(declare -p "${env_added[@]}").

declare -p VAR [VAR ...] prints, for each VAR, the variable name followed by = followed by its value:

$ a = 1
$ b = 2
$ declare -p a b 
declare -- a=1 
declare -- b=2

And the eval is to actually evaluate the declare lines. The rest of the code looks like:

bash <<EOF

# Eval the variables computed earlier
eval $(declare -p "${env_added[@]}")
$(typeset -f echo_a)

echo local heredoc:
echo_a
echo

echo local raw heredoc:
echo a: $a
echo

EOF

ssh rpi_301 bash <<EOF

# Eval the variables computed earlier
eval $(declare -p "${env_added[@]}")
$(typeset -f echo_a)

echo remote heredoc:
echo_a
echo

echo remote raw heredoc:
echo a: $a
echo

EOF

Finally, running the modified script gives me the wanted behavior:

local heredoc:
a: 1

local raw heredoc:
a: 1

remote heredoc:
a: 1

remote raw heredoc:
a: 1
ibizaman
  • 3,053
  • 1
  • 23
  • 34