funwithlinux blog

How to Run a Local Bash Function from a Shell Script on a Remote Server Over SSH

As developers, system administrators, or DevOps engineers, we often find ourselves needing to automate tasks across remote servers. Bash functions are powerful tools for encapsulating reusable logic—they help keep scripts clean, reduce redundancy, and simplify complex workflows. But what if you’ve defined a handy bash function locally and want to run it on a remote server over SSH?

By default, local bash functions live in your current shell session’s memory and aren’t accessible to remote shells spawned by SSH. This creates a challenge: how do you “transfer” a local function to a remote server and execute it there?

In this guide, we’ll demystify this process. We’ll explore four practical methods to run local bash functions on remote servers via SSH, along with common pitfalls, best practices, and real-world examples. Whether you’re automating deployments, managing logs, or configuring remote systems, this guide will help you bridge the gap between local and remote environments.

2026-01

Table of Contents#

  1. Understanding the Challenge: Why Local Functions Don’t Work Remotely
  2. Method 1: Inline Function Definition with SSH Command
  3. Method 2: Using Here-Documents to Pass Functions
  4. Method 3: Base64 Encoding to Avoid Quoting Headaches
  5. Method 4: Process Substitution (Advanced)
  6. Common Pitfalls and How to Avoid Them
  7. Best Practices
  8. Conclusion
  9. References

1. Understanding the Challenge: Why Local Functions Don’t Work Remotely#

Before diving into solutions, let’s clarify why local bash functions aren’t automatically available over SSH:

  • Bash functions are shell-specific: A function defined in your local shell (e.g., my_function() { ... }) exists only in the memory of that shell session. It’s not written to disk or exported to child processes by default.
  • SSH spawns a new remote shell: When you run ssh user@remote "command", SSH connects to the remote server and starts a new, isolated shell session (e.g., /bin/bash or /bin/sh). This remote shell has no knowledge of your local shell’s variables, functions, or environment.

To run a local function remotely, you need to explicitly transfer its definition to the remote shell and then execute it. The methods below achieve this in different ways, depending on your use case.

2. Method 1: Inline Function Definition with SSH Command#

The simplest way to run a local function remotely is to redefine the function inline when invoking SSH. This works well for short, simple functions with no complex logic or special characters.

How It Works#

You pass the function definition directly as a string to the remote shell via SSH. The remote shell parses the function, defines it, and then executes it.

Example#

Suppose you have a local function greet() that prints a welcome message with a name:

# Local function definition
greet() {
  local name="$1"
  echo "Hello, $name! Welcome to the remote server."
}

To run greet "Alice" on a remote server user@remote-server, redefine the function inline with SSH:

ssh user@remote-server '
  greet() {
    local name="$1"
    echo "Hello, $name! Welcome to the remote server."
  };
  greet "Alice"
'

Output#

Hello, Alice! Welcome to the remote server.

Pros#

  • Simple and straightforward for small functions.
  • No external tools or complex syntax required.

Cons#

  • Messy for large functions: Redefining lengthy functions inline bloats the SSH command and reduces readability.
  • Quoting hell: Special characters (e.g., $, ', ", \) in the function will require escaping, leading to errors if not handled carefully.

3. Method 2: Using Here-Documents to Pass Functions#

Here-documents (or “here-docs”) are a shell feature that lets you pass a block of text as input to a command. They’re ideal for transferring multi-line content—like bash functions—to remote servers via SSH.

How It Works#

You’ll use a here-doc to send the function definition to the remote server. The remote shell will read the here-doc, define the function, and execute it.

Key Syntax#

ssh user@remote-server bash -s << 'EOF'
  # Function definition here
  my_function() { ... }
  # Call the function
  my_function arg1 arg2
EOF
  • bash -s: Tells the remote shell to read commands from standard input (instead of a file).
  • << 'EOF': The here-doc delimiter. The single quotes around EOF prevent the local shell from expanding variables or interpreting special characters (critical for preserving the function’s original logic).

Example#

Let’s use the greet() function again, but this time with a here-doc to avoid retyping the function inline:

# Local function definition (for reference)
greet() {
  local name="$1"
  echo "Hello, $name! Welcome to the remote server."
  echo "Current time on remote: $(date)"  # Uses remote `date` command
}
 
# Run the local function remotely via here-doc
ssh user@remote-server bash -s << 'EOF'
  greet() {
    local name="$1"
    echo "Hello, $name! Welcome to the remote server."
    echo "Current time on remote: $(date)"
  }
  greet "Bob"  # Call the function with argument "Bob"
EOF

Output#

Hello, Bob! Welcome to the remote server.
Current time on remote: Wed Oct 11 14:30:00 UTC 2023

Why This Works#

  • The here-doc sends the entire greet() definition to the remote server.
  • The remote shell runs bash -s, reads the here-doc, defines greet(), and executes greet "Bob".
  • The $(date) command runs on the remote server, not locally (thanks to the single quotes around EOF, which prevent local expansion).

Pros#

  • Clean and readable for multi-line functions.
  • Avoids retyping functions inline.
  • Handles special characters (e.g., $, ') safely when using quoted delimiters ('EOF').

Cons#

  • Requires careful handling of variable scoping (local vs. remote variables).
  • If you forget to quote EOF, local variables will expand prematurely (e.g., $name in the function might use your local name variable instead of the remote argument).

4. Method 3: Base64 Encoding to Avoid Quoting Headaches#

For complex functions with nested quotes, loops, or special characters (e.g., |, ;, &), even here-docs can fail due to shell interpretation issues. Base64 encoding solves this by converting the function into a plain text string, which is then decoded and executed on the remote server.

How It Works#

  1. Extract the function definition: Use declare -f my_function to get the full source code of the local function.
  2. Encode as Base64: Convert the function definition to a Base64 string (avoids special character issues).
  3. Decode and execute remotely: Send the Base64 string to the remote server, decode it, and evaluate it with bash.

Example#

Let’s define a more complex local function, backup_logs(), which compresses logs in a remote directory and prints a status:

# Local function with special characters and logic
backup_logs() {
  local log_dir="$1"
  if [ -d "$log_dir" ]; then
    echo "Compressing logs in $log_dir..."
    tar -czf "$log_dir/backup_$(date +%F).tar.gz" "$log_dir"/*.log
    echo "Backup created: $log_dir/backup_$(date +%F).tar.gz"
  else
    echo "Error: Directory $log_dir does not exist!"
  fi
}

To run this remotely, use Base64 encoding:

# Step 1: Extract function definition and encode to Base64
declare -f backup_logs | base64 -w 0 | \
# Step 2: Send to remote, decode, and execute
ssh user@remote-server '
  base64 -d | bash -s;  # Decode and evaluate the function
  backup_logs "/var/log/nginx"  # Call the function remotely
'

Breakdown#

  • declare -f backup_logs: Outputs the full source code of backup_logs().
  • base64 -w 0: Encodes the function to Base64, with no line wrapping (-w 0).
  • base64 -d | bash -s: On the remote server, decodes the Base64 string and pipes it to bash to evaluate the function.
  • backup_logs "/var/log/nginx": Executes the now-remote function with the argument /var/log/nginx.

Output#

Compressing logs in /var/log/nginx...
Backup created: /var/log/nginx/backup_2023-10-11.tar.gz

Pros#

  • Bulletproof for special characters: Base64 encoding eliminates issues with quotes, pipes, or wildcards in functions.
  • No retyping: Uses the local function’s actual source code via declare -f.

Cons#

  • Slightly more complex than here-docs (requires Base64 tools, which are preinstalled on most systems).

5. Method 4: Process Substitution (Advanced)#

Process substitution is a bash/zsh feature that treats the output of a command as a temporary file. It’s useful if you want to pass the function definition to SSH as if it were a local script.

How It Works#

Use process substitution (<(...)) to feed the function definition directly to the remote shell. The remote shell reads the “virtual file” and executes the function.

Example#

Using the greet() function again:

# Local function
greet() {
  local name="$1"
  echo "Hello, $name! Remote hostname: $(hostname)"
}
 
# Run via process substitution
ssh user@remote-server bash -s < <(
  declare -f greet  # Output the function definition
  echo "greet Charlie"  # Call the function
)

Output#

Hello, Charlie! Remote hostname: remote-server-01

How It Works#

  • <(declare -f greet; echo "greet Charlie"): Creates a temporary file-like object containing the function definition and the call to greet Charlie.
  • bash -s < ...: The remote shell reads this temporary file, defines greet(), and runs greet Charlie.

Pros#

  • Clean syntax for bash/zsh users.
  • Avoids here-doc quoting issues.

Cons#

  • Not portable: Process substitution works in bash and zsh but fails in POSIX shells like dash (common on Debian/Ubuntu systems).
  • Requires the local shell to support process substitution (not ideal for scripts intended to run in minimal environments).

6. Common Pitfalls and How to Avoid Them#

Even with the methods above, you may run into issues. Here are the most common pitfalls and fixes:

1. Local Variable Expansion#

If you forget to quote the here-doc delimiter (e.g., << EOF instead of << 'EOF'), the local shell will expand variables in the function before sending it to the remote server.

Fix: Always quote the delimiter (<< 'EOF') to preserve the function’s original logic.

2. Missing Dependencies#

If your function relies on local tools (e.g., jq, aws-cli) or environment variables, the remote server may not have them.

Fix:

  • Check for dependencies upfront: ssh user@remote-server "command -v jq || echo 'jq not found'".
  • Pass required environment variables explicitly: ssh user@remote-server "export API_KEY=$LOCAL_API_KEY; my_function".

3. Function Scope#

If your local function calls other local functions (e.g., funcA() calls funcB()), only funcA will be transferred unless you explicitly include funcB.

Fix: Use declare -f funcA funcB to export all dependent functions.

4. SSH Key/Authentication Issues#

If SSH prompts for a password, automation will fail.

Fix: Set up SSH key-based authentication (run ssh-keygen locally, then ssh-copy-id user@remote-server).

7. Best Practices#

To ensure smooth execution of local functions on remote servers:

  1. Test Locally First: Run the function locally with mock data to confirm it works before sending it remotely.
  2. Keep Functions Simple: Avoid overly complex logic (e.g., nested loops, heavy I/O) in functions intended for remote execution.
  3. Log Output: Redirect output to a file or use set -x (debug mode) to troubleshoot:
    ssh user@remote-server bash -sx << 'EOF'  # -x enables debug output
      my_function() { ... }
      my_function
    EOF
  4. Limit Permissions: Run remote commands with the least privilege (e.g., use a non-root SSH user).

8. Conclusion#

Running local bash functions on remote servers over SSH is a powerful way to automate cross-server workflows. We’ve covered four methods, each with tradeoffs:

  • Inline Definition: Best for tiny, simple functions.
  • Here-Documents: Ideal for readability and moderate complexity (use quoted delimiters!).
  • Base64 Encoding: The go-to for functions with special characters or nested logic.
  • Process Substitution: Advanced option for bash/zsh users (avoid in POSIX scripts).

By choosing the right method and avoiding common pitfalls like variable expansion or missing dependencies, you can seamlessly bridge local and remote environments.

9. References#