TechEarl

How to Base64-Encode or Decode a File From the Command Line

Base64-encode and decode a file from the terminal with the built-in base64 command. The macOS-vs-GNU flag gotcha (-D vs -d), the line-wrapping difference, and building a data URI from an image.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Base64-encode and decode a file from the terminal with the built-in base64 command, handle the macOS-vs-GNU decode-flag difference, and build a data URI from an image.

The quickest way to Base64-encode a file from the command line is the built-in base64 tool, which ships with both macOS and every Linux distribution. Encode a file to a text blob, decode it back, or one-line it straight into a data URI:

bash
# Encode a file to Base64 (writes the text to out.txt):
base64 in.png > out.txt

# Decode it back to the original binary (GNU/Linux):
base64 -d out.txt > in.png

# Decode it back to the original binary (macOS):
base64 -D out.txt > in.png

That -d vs -D split is the single thing that trips people up, so I am putting it up front. Everything else below is the detail: the line-wrapping difference between the two base64 implementations, building a complete data: URI from an image, encoding a short string instead of a file, and why none of this is encryption.

Base64 is encoding, not encryption. It scrambles nothing. Anyone with the blob can run base64 -d and get your file back. It exists to carry binary data safely through text-only channels (email, JSON, a CSS file, an env var), not to hide it. If you need secrecy, encrypt first (gpg, age, openssl) and Base64 the ciphertext if you also need it text-safe.

The macOS vs GNU decode flag (the cross-platform gotcha)

There are two base64 programs in the wild and they disagree on how you ask for a decode:

TaskGNU coreutils (Linux)BSD (macOS)
Encode a filebase64 in.pngbase64 in.png or base64 -i in.png
Decodebase64 -d (or --decode)base64 -D (or --decode)
One unwrapped linebase64 -w 0 in.pngwrapping is off by default

Encoding is identical on both, so a plain base64 in.png works everywhere. Decoding is where a copied command breaks. On Linux the flag is a lowercase -d; on macOS the BSD tool wants an uppercase -D. The nasty part is what lowercase -d does on macOS: it is not an error, it is the --debug flag (verbose log messages), so base64 -d out.txt on a Mac silently encodes the file with extra logging instead of decoding it. A copied-from-Linux decode command on macOS therefore fails quietly, which is worse than a hard error. The good news: --decode (the long form) works on both, so if you want one command that runs unchanged on a Mac and a Linux box, write it with the long flag:

bash
# Portable decode, works on macOS and GNU/Linux:
base64 --decode out.txt > in.png

macOS also gives you explicit input and output flags, which read nicely in a script and avoid shell redirection:

bash
# macOS only: -i input file, -o output file
base64 -i in.png -o out.txt          # encode
base64 -D -i out.txt -o in.png       # decode

Those -i/-o flags are a BSD thing and are not present on GNU base64, so keep them out of anything you need to run on Linux.

The line-wrapping difference

Run the same encode on a Mac and on a Linux box and you can get visibly different output. GNU base64 wraps the encoded text at 76 columns by default, inserting a newline every 76 characters. BSD base64 on macOS does not wrap at all by default, so you get one long line.

The wrapping matters when you feed the Base64 somewhere that hates newlines, such as a data URI, an HTTP header, a Kubernetes secret, or an env var. To force a single unbroken line:

bash
# GNU/Linux: disable wrapping with -w 0
base64 -w 0 in.png

# macOS BSD: already one line by default; if you want to be explicit, -b 0
base64 -b 0 -i in.png

Note the flag letters differ again: GNU uses -w (wrap), BSD uses -b (break). Going the other way, if you have a wrapped blob and need it on one line, strip the newlines with tr:

bash
base64 in.png | tr -d '\n'

That tr -d '\n' trick is the portable equalizer. It works regardless of which base64 produced the input, so I reach for it in scripts that have to run on both platforms.

Build a data URI from an image

A data URI inlines an image directly into HTML or CSS as data:<mime>;base64,<data>, which saves a request for small assets. The whole thing is one line once you have the unwrapped Base64:

bash
# GNU/Linux:
echo "data:image/png;base64,$(base64 -w 0 in.png)"

# macOS (already unwrapped, so no -w needed):
echo "data:image/png;base64,$(base64 -i in.png)"

Mind the MIME type: image/png for PNG, image/jpeg for JPEG, image/svg+xml for SVG, image/webp for WebP. The MIME prefix is not optional. A browser will not sniff it from the bytes inside a data URI, so data:image/png;... on a JPEG renders nothing.

Keep data URIs for genuinely small images. Base64 inflates the byte count by roughly 33 percent, and inlining a large image bloats the HTML or CSS that carries it (and that file usually is not cached as aggressively as a standalone image would be). For anything beyond an icon or a tiny background, ship the real file. If you are converting images on the command line anyway, convert an image to WebP from the command line first, then inline the smaller result.

Encode or decode a short string

For a quick string rather than a file, pipe echo into base64. There is one caveat that bites everyone at least once: echo appends a trailing newline, and base64 encodes that newline as part of the data. So echo "hello" | base64 is encoding hello\n, not hello. Use echo -n (or printf) to suppress it:

bash
# Wrong: encodes "hello\n" (the trailing newline is included)
echo "hello" | base64            # aGVsbG8K

# Right: encodes exactly "hello"
echo -n "hello" | base64         # aGVsbG8=
printf '%s' "hello" | base64     # aGVsbG8=  (printf adds no newline at all)

I prefer printf '%s' because echo -n is not portable to every shell, whereas printf behaves the same everywhere. Decoding a string is the same --decode story as files:

bash
echo "aGVsbG8=" | base64 --decode    # hello   (no trailing newline)

If you are encoding a string only to move it onto your clipboard, pipe it straight through: on macOS, printf '%s' "hello" | base64 | pbcopy puts the encoded text on the clipboard ready to paste. See copy to the clipboard from the command line with pbcopy and pbpaste for that workflow.

Verify a round-trip

Because Base64 is lossless, a decode of an encode should give you back the exact original bytes. After moving a binary through a text channel, confirm nothing got mangled by comparing checksums of the original and the decoded copy:

bash
base64 in.png > out.txt
base64 --decode out.txt > roundtrip.png
# Compare hashes; identical means a clean round-trip
shasum -a 256 in.png roundtrip.png

If the two hashes differ, something stripped or rewrote a character in transit (a common cause is a tool that wrapped or unwrapped the lines, or mangled the trailing = padding). See verify a file checksum on macOS for the full hashing flow.

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

Tagsbase64CLImacOSLinuxdata URIencodingterminal

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Normalize Audio Volume From the Command Line

Normalize MP3 and audio volume from the command line: ffmpeg's loudnorm filter for EBU R128 loudness (one-pass and the accurate two-pass), rsgain for ReplayGain tags across a whole library, and why loudness beats peak normalization.