Skip to content
AldeaCode Logo
Base64 / Bash Format 100% local

Base64 in Bash: the base64 command, the macOS gotcha, and pipes

Bash does not have a Base64 builtin, but every Unix you might run on ships a `base64` command. The trap is that GNU coreutils and BSD (macOS) speak slightly different flags, and one missing newline can ruin your day.

The basic round-trip

Encoding text from a string is one pipe:

echo -n "AldeaCode" | base64
# QWxkZWFDb2Rl

The -n on echo is critical. Without it, echo appends a newline and you encode AldeaCode\n instead of AldeaCode. The result is different from anything a Python or Node snippet would produce, and you spend twenty minutes debugging an "off by one byte" mystery.

Decoding goes through -d:

echo "QWxkZWFDb2Rl" | base64 -d
# AldeaCode

base64 -d ignores trailing whitespace, so the regular echo works on the decode side. The asymmetry is the source of countless StackOverflow answers.

macOS BSD vs GNU coreutils

On Linux, base64 from GNU coreutils wraps lines at 76 chars by default and accepts -w 0 to disable wrapping. On macOS, base64 is the BSD implementation and uses -b 0 for the same thing. The -w flag is silently ignored on macOS, so your script that "works on my machine" emits a different string in CI.

Portable solution: pipe through tr -d '\n' to strip every newline regardless of who emits them.

echo -n "AldeaCode" | base64 | tr -d '\n'

This is the form to put in a Makefile, a CI pipeline, or anything that runs on more than one OS. It is uglier and bulletproof.

Encoding files and binaries

For files, give base64 the path directly:

base64 logo.png > logo.b64
base64 -d logo.b64 > logo.png

The < redirection works too, but the positional argument is shorter and reads better.

A common real-world use: building a Basic auth header for a quick curl:

AUTH=$(echo -n "user:p@ssw0rd" | base64)
curl -H "Authorization: Basic $AUTH" https://api.example.com

Pop quiz: if you forgot the -n here, the resulting credentials carry an embedded newline and the API will reject them with a 401. Welcome to debugging Basic auth.

URL-safe variant by hand

Bash base64 does not have a built-in URL-safe mode. You build it with tr:

echo -n "AldeaCode" | base64 | tr '+/' '-_' | tr -d '='

Standard Base64 to URL-safe is exactly that substitution: + becomes -, / becomes _, and you strip the padding. To decode back, reverse the substitution, re-pad to a multiple of 4 with =, and pipe to base64 -d. Most token formats (JWT in particular) follow this exact convention, codified in RFC 4648 ยง5.

Working example

bash
#!/usr/bin/env bash
set -euo pipefail

# Encode (portable, no trailing newline)
ENCODED=$(echo -n "AldeaCode" | base64 | tr -d '\n')
echo "$ENCODED"
# QWxkZWFDb2Rl

# Decode
echo "$ENCODED" | base64 -d
# AldeaCode

# URL-safe, unpadded
URLSAFE=$(echo -n "AldeaCode" | base64 | tr '+/' '-_' | tr -d '=')
echo "$URLSAFE"

# Encode a file
base64 logo.png > logo.b64

Just need the result?

When you just need a Base64 of a single string and you are not sure if you are on GNU or BSD, the easiest tool is the one that does not care. Paste the text into the browser-based Base64 encoder, copy the result, move on.

Open Base64 Encoder and Decoder โ†’

Frequently asked questions

Why is my Base64 different from what Python gives me?

Almost certainly because echo appended a newline. Use echo -n or printf '%s' '...' to avoid the trailing byte. Compare the byte counts first; the encoded length will differ by exactly 4 chars.

Can I avoid the BSD vs GNU difference entirely?

Yes: pipe through tr to drop newlines from the output, and pass the long form decode flag which both implementations support. Or install coreutils on macOS via Homebrew and call gbase64.

Does base64 leak secrets in shell history?

Yes. Anything you echo as plain text is in your shell history file. For real secrets, read from a file, an env var loaded from a vault, or stdin via heredoc. Never paste a production password on a command line.