The Trailing Newline Trap — Read This First
echo "Hello" | base64 does notproduce the same output as Base64-encoding the string "Hello". It Base64-encodes "Hello\n" — echo adds a trailing newline. Result: SGVsbG8K (with K at the end) instead of the correct SGVsbG8=. This one bug wastes more developer hours than any other Bash Base64 mistake.
Three ways to fix it:
printf %s "Hello" | base64— printf with%semits no newline.echo -n "Hello" | base64—-nsuppresses the trailing newline. Works on both GNU and BSD echo.printf "Hello" | base64— printf without a trailing\nin the format string.
Encoding a String — The Standard Patterns
# The three correct ways
printf %s 'Hello' | base64 # SGVsbG8=
echo -n 'Hello' | base64 # SGVsbG8=
printf 'Hello' | base64 # SGVsbG8=
# The classic bug — DO NOT copy this pattern
echo 'Hello' | base64 # SGVsbG8K <- wrong!
# Store into a variable
ENCODED=$(printf %s "$INPUT" | base64)
echo "$ENCODED"
# Encode multi-line input (heredoc)
base64 <<'EOF'
line one
line two
line three
EOF
# Encode Unicode (UTF-8 is the default on modern Linux/macOS)
printf %s 'Hello 世界 👋' | base64 # SGVsbG8g5LiW55WMIPCfkYs=Encoding a File
# Encode a file — output includes line wraps on GNU (Linux)
base64 image.png
# Single-line output on Linux (GNU coreutils)
base64 -w0 image.png
# Single-line output on macOS (BSD userland)
base64 image.png # macOS defaults to single-line already
# For explicit wrap width on macOS:
base64 -b 76 image.png # BSD flag is -b, not -w
# Portable version (works on both):
base64 image.png | tr -d '\n'
# Redirect to a file
base64 -w0 image.png > image.b64
# Read from stdin explicitly
base64 -w0 < image.png
# Capture into a variable
IMAGE_B64=$(base64 -w0 image.png)
echo "${#IMAGE_B64}" # length in chars — roughly 4/3 the file size
# Build a data URL for HTML embedding
DATA_URL="data:image/png;base64,$(base64 -w0 image.png)"
echo "$DATA_URL"URL-Safe Base64 in Bash
The base64 command has no built-in URL-safe mode. Use tr to substitute the two characters that differ:
# URL-safe Base64 with padding
printf %s 'Hello' | base64 -w0 | tr '+/' '-_'
# SGVsbG8=
# JWT-style: URL-safe + no padding (strip trailing =)
printf %s 'Hello' | base64 -w0 | tr '+/' '-_' | tr -d '='
# SGVsbG8
# Reusable shell function
b64url() {
printf %s "$1" | base64 -w0 | tr '+/' '-_' | tr -d '='
}
b64url 'my-secret-payload'
# Modern GNU coreutils 8.31+ has basenc which supports URL-safe directly:
printf %s 'Hello' | basenc --base64url -w0
# On macOS basenc is available via 'brew install coreutils' (as gbasenc)
# Decode URL-safe Base64 back to text
decode_b64url() {
local input="$1"
# Add back the padding
local pad=$(( (4 - ${#input} % 4) % 4 ))
printf '%s%.0s=' "$input" $(seq 0 $pad) | tr '-_' '+/' | base64 -d
}Real-World Shell Patterns
# 1. HTTP Basic Auth header for curl
USER='admin'
PASS='secret123'
TOKEN=$(printf '%s:%s' "$USER" "$PASS" | base64 -w0)
curl -H "Authorization: Basic $TOKEN" https://api.example.com/protected
# 2. Kubernetes Secret from a value
kubectl create secret generic api-key \
--from-literal=token="$(printf %s "$RAW_TOKEN" | base64 -w0)"
# Or generate the manifest directly:
cat <<EOF
apiVersion: v1
kind: Secret
metadata:
name: api-key
data:
token: $(printf %s "$RAW_TOKEN" | base64 -w0)
EOF
# 3. Embed a file in a GitHub Actions secret / environment variable
CERT_B64=$(base64 -w0 tls.crt)
gh secret set TLS_CERT_B64 --body "$CERT_B64"
# 4. Encode JSON for a JWT payload (URL-safe, no padding)
PAYLOAD='{"user_id":42,"role":"admin"}'
ENCODED=$(printf %s "$PAYLOAD" | base64 -w0 | tr '+/' '-_' | tr -d '=')
echo "$ENCODED"
# 5. Terraform variable containing a binary file
export TF_VAR_ssh_pubkey_b64=$(base64 -w0 ~/.ssh/id_rsa.pub)GNU vs BSD (Linux vs macOS) — The Full Comparison
# Task | Linux (GNU) | macOS (BSD)
# ------------------------|--------------------------|--------------------------
# Default line wrap | Wraps at 76 chars | Single line (no wrap)
# Disable wrap | -w0 | (default)
# Custom wrap width | -w N | -b N
# Read from file | base64 filename | base64 -i filename
# Decode | -d or --decode | -D or --decode
# URL-safe mode | (none, use tr) | (none, use tr)
# Version | GNU coreutils | Apple BSD userland
# Detect which one you have:
base64 --help 2>&1 | grep -q 'GNU' && echo "GNU" || echo "BSD"
# Portable wrapper function that works on both:
b64() {
if [ -n "${1:-}" ]; then
# Argument = input string
printf %s "$1" | base64 | tr -d '\n'
else
# No argument = read from stdin
base64 | tr -d '\n'
fi
}
echo 'usage: b64 "hello" OR cat file | b64'Common Pitfalls in Bash Base64 Scripts
echowithout-n— Adds a trailing newline that silently corrupts every Base64 output. Useprintf %sfor maximum portability orecho -nif you accept that-nis a Bash-ism (POSIXechodoesn't define-n).- Assuming
-w0works on macOS— It doesn't. BSD base64 uses-b Nfor wrap width. For portable scripts, always pipe throughtr -d '\n'as a fallback. - Using
base64in cron/systemd without-w0— Multi-line output breaks when embedded in a systemd Environment= line or a cronMAILTO=$(base64 ...). Always force single-line output before storing in structured config. - Piping binary data through variable assignment — Command substitution (
$()) strips trailing newlines but preserves internal ones. That's usually fine for Base64 output, but if you pipe raw binary through a variable Bash may truncate at null bytes. Store binary as Base64 first, then pass around. - Forgetting quotes around
$(base64 ...)— Base64 output contains/and+which are not shell-special, but the whole block should be quoted anyway when substituting into structured formats like JSON orcurl -Harguments.
Key Facts
- Command:
- base64 (GNU coreutils on Linux, BSD userland on macOS)
- Standard encode:
- printf %s "text" | base64
- Single-line (Linux):
- base64 -w0
- Single-line (portable):
- base64 | tr -d '\n'
- Decode:
- base64 -d (Linux) or base64 -D (macOS) or base64 --decode (both)
- URL-safe:
- base64 | tr '+/' '-_' | tr -d '='
- File encoding:
- base64 -w0 file.png > file.b64
- Modern alt:
- basenc --base64url (GNU coreutils 8.31+)
Related Base64 Tools
- Base64 Encode Online — browser encoder when you don't have a terminal
- Base64 Encode in Python — Python 3 equivalent
- Base64 Encode in JavaScript — Node.js and browser
- Base64 Encode in PHP — PHP base64_encode
- URL-Safe Base64 — cross-language URL encoding guide
- Base64 Decode Online — reverse the encoding
- JWT Debugger — inspect JWT tokens