# Put the payload in a file, pass the path

Each layer of interpretation — your shell, `ssh`, the remote shell, `-c`, an
interpreter's own string parser — reparses the text and consumes one level of
escaping. Two layers is the practical limit for a human or a model to get right.
One is safe. So do not nest; write a file and reference it.

```sh
# instead of this
ssh host-a "python3 -c \"print('a\\tb')\""

# do this
scp script.py host-a:/tmp/script.py
ssh host-a 'python3 /tmp/script.py'
```

The second form has exactly one layer of quoting and it is single quotes, which
interpret nothing.

## The three quoting rules that cover almost everything

| Form | `$var` | backtick / `$( )` | backslash | Use for |
| --- | --- | --- | --- | --- |
| `'single'` | literal | literal | literal | Anything you want sent verbatim |
| `"double"` | expands | expands | escape char | Only when you *want* expansion |
| `<<'EOF'` | literal | literal | literal | Multi-line payloads |
| `<<EOF` | expands | expands | escape char | Templates, deliberately |

The quoted heredoc delimiter is the one people forget. `<<EOF` will happily
expand a `$PATH` or execute a backticked command inside what you thought was
inert text:

```sh
cat <<'EOF' > /srv/app/notes.md      # safe: nothing inside is interpreted
Costs $100 and uses `backticks` and ${braces}.
EOF
```

For a heredoc inside an `ssh`, quote both the outer command and the delimiter:

```sh
ssh host-a 'bash -s' <<'EOF'
set -euo pipefail
printf '%s\n' 'literal $HOME stays literal'
EOF
```

The payload goes over stdin rather than through the remote shell's argument
parser, so it is never reparsed. This is the single most useful trick here.

## When it truly must be inline: base64

Binary-safe, quote-free, survives any number of layers:

```sh
b64=$(base64 -w0 < payload.txt)                 # -w0: no line wrapping (GNU)
ssh host-a "printf %s '$b64' | base64 -d > /srv/app/payload.txt"
```

Only base64 characters cross the boundary, so no shell, on either side, can
find anything to interpret. On macOS/BSD `base64` wraps by default — use
`base64 | tr -d '\n'`.

## Never build source code with a shell one-liner

`sed -i`, `perl -pe`, `node -e "…fs.writeFileSync…"` and `python -c` on a file
that itself contains quotes, backticks or `$` fail in two ways, both quiet:

- **Match nothing, report success.** The pattern got mangled by a layer of
  quoting, matched zero lines, and `sed` exited `0`. Nothing changed and nothing
  said so.
- **Match too much.** A greedy pattern ate a function body. The file still
  parses. The tests still pass, because the eaten part was not covered. This one
  ships.

Use a real editor/writer for source files. If a scripted edit is genuinely
unavoidable, it is not finished until you have run **both** of these:

```sh
node --check file.js        # or: bash -n f.sh, python -m py_compile f.py,
                            #     jq . f.json, yamllint f.yml
sed -n '40,60p' file.js     # and actually read the region you changed
```

"The script printed `true`" is not evidence. See
[[skills/verifying-a-claim]] — an edit tool that cannot report zero matches is
a check that cannot fail.

## Layers you may not have counted

- **Your agent harness.** A tool call encodes the command as JSON; `\n` and `\"`
  are consumed there before the shell ever sees them.
- **`bash -lc`** on the far side of a tool, adding a shell you did not write.
- **`make`**, which eats `$` — write `$$` for a shell variable in a recipe.
- **CI YAML**, which parses the string before the shell does. A `:` or a leading
  `*` in an unquoted YAML scalar changes the meaning entirely.
- **`docker exec sh -c '…'`** and `kubectl exec -- sh -c '…'`, which are two
  more shells stacked on yours.

Count the layers out loud before writing the command. If the answer is more
than two, switch to a file.

## PowerShell is a different language, not a dialect

If the outer shell is PowerShell, the bash instincts are wrong:

- The escape character is a backtick, not a backslash.
- `"double"` interpolates `$var`; `'single'` does not. Same idea, different
  parser.
- A single-quoted here-string is `@' … '@`, and the closing `'@` **must be at
  column zero**. Indent it and you get a parse error.
- Arguments beginning with `-` or `@` may be parsed as operators before reaching
  the program; `--%` stops PowerShell parsing the rest of the line.
- Redirection and `Set-Content` write UTF-16 or add a BOM depending on version,
  and add CRLF. That is how a `.sh` file stops working — see
  [[skills/line-endings-and-encodings]].

## A quick self-test

If you can answer these without running anything, the command is probably right:

```
[ ] How many parsers see this string before the bytes are used?
[ ] Which of them expands $ and backticks?
[ ] If the payload contained a single quote, would it still work?
[ ] If the pattern matched zero times, would I find out?
```

See also [[skills/atomic-file-writes]] and [[machinery/what-does-not-render]].

[[skills/index]]
