Chapter 14Lesson 04~105 minutes

Quoting Across Local and Remote Shell Boundaries

Remote shell commands can be parsed twice: once locally and once remotely. Reliable automation begins by deciding which parser owns each expansion and by keeping arbitrary data out of executable shell source.

IntermediateNetworking & remote automationHands-on lab

Learning objectives

By the end of this lesson

  • Identify local and remote parsing boundaries.
  • Control where variable expansion occurs.
  • Pass data as arguments or stdin.
  • Avoid remote command injection.
  • Recognize when nested quoting should be redesigned.

1. Remote commands may pass through multiple parsers

Two-shell quoting boundary
flowchart LR
  L["local Bash parser"] --> S["ssh argument transport"]
  S --> R["remote shell parser"]
  R --> C["remote command"]

A string can be interpreted once by the local shell and again by the remote shell. Most SSH quoting bugs come from forgetting which shell should expand which variable.

2. Decide whether expansion belongs locally or remotely

local_value='from-local'

ssh host "printf '%s\n' '$local_value'"

Here the local shell expands $local_value before SSH runs. That can be correct for trusted local data, but it is easy to create injection bugs with arbitrary input.

3. Single quotes can defer expansion to the remote shell

ssh host 'printf "remote HOME=%s\n" "$HOME"'

The local shell passes the dollar sign literally; the remote shell expands $HOME.

4. Prefer passing data as arguments over constructing remote shell source

service='api worker'

printf -v remote_cmd 'printf "service=%%s\\n" %q' "$service"
ssh host "$remote_cmd"

Bash printf %q can produce shell-escaped text for Bash-like remote shells, but this is still shell-source construction and depends on the remote shell grammar.

5. Send a script over stdin for complex remote logic

ssh -T host 'bash -s -- arg1 arg2' <<'REMOTE'
set -u
first=$1
second=$2

printf 'first=%s second=%s\n' "$first" "$second"
REMOTE

A quoted here-document delimiter prevents the local shell from expanding the script body. Positional arguments provide a cleaner data channel.

6. Environment forwarding is policy-controlled and limited

SSH can forward selected environment variables only when both client and server permit it. Do not assume arbitrary environment state crosses the connection automatically.

7. Arbitrary data must not become remote code

# Dangerous design:
# ssh host "rm -rf /srv/apps/$user_supplied_name"

# Better: validate data and pass it as an argument to a fixed remote program.
ssh host /usr/local/bin/remove-app "$validated_name"
Trust boundary

A variable that becomes part of remote shell source can become command injection. Prefer fixed remote commands plus validated arguments.

8. Nested quotes are a signal to simplify the design

If a command contains multiple layers of backslashes, single quotes, double quotes, command substitutions, and embedded JSON, stop and move logic into a script or structured data file.

9. Structured payloads should stay structured

payload=$(jq -n --arg service "$service" '{service:$service}')

printf '%s\n' "$payload" |
ssh host 'jq -r .service'

This sends JSON as data through stdin rather than embedding it inside remote command syntax.

10. Remote paths with unusual characters deserve conventions

A technically valid remote filename containing quotes or newlines can make SSH command construction extremely difficult. Production environments benefit from constrained naming rules for deployable paths and service identifiers.

11. Hands-on lab: compare local and remote expansion

mkdir -p "$HOME/devops-academy/bash/chapter14/lesson04"
cd "$HOME/devops-academy/bash/chapter14/lesson04"

cat > quoting-notes.sh <<'EOF'
#!/usr/bin/env bash
set -u

host=${1:-}
[[ -n $host ]] || {
  printf 'usage: %s HOST\n' "$0" >&2
  exit 64
}

local_value='LOCAL VALUE'

printf '%s\n' '--- local expansion ---'
ssh -T "$host" "printf '%s\n' '$local_value'"

printf '%s\n' '--- remote expansion ---'
ssh -T "$host" 'printf "remote HOME=%s\n" "$HOME"'

printf '%s\n' '--- script over stdin ---'
ssh -T "$host" 'bash -s -- "arg with spaces"' <<'REMOTE'
set -u
printf 'remote arg=%s\n' "$1"
REMOTE
EOF

chmod u+x quoting-notes.sh
printf 'Run with a reachable SSH host to compare expansion boundaries.\n'

Verification checklist

12. Knowledge check

Question 1. Why are SSH quoting bugs common?

Question 2. How can you defer $HOME expansion to the remote shell?

Question 3. What is safer than embedding arbitrary data into remote shell source?

Question 4. When should nested quoting be replaced by another design?

13. Summary

Remote-shell quoting is about parser ownership. Decide which shell should expand each token, keep arbitrary data out of command source, use positional arguments or stdin for complex workflows, and simplify whenever nested quoting becomes difficult to audit.

14. Further reading

  • GNU Bash Reference Manual — Quoting.
  • OpenSSH ssh(1) manual.
  • OpenSSH environment-forwarding configuration.
  • ShellCheck guidance — quoting and SSH command construction.
Next lesson

Fleet Automation without Turning Bash into a Config Manager

Continue Chapter 14 by making remote networking and automation boundaries safer and more explicit.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.