Base64 encode on the command line

printf '%s' 'hello' | base64

printf '%s' 'hello' | base64 prints aGVsbG8=, on Linux and macOS alike. printf '%s' writes the text exactly as given, with no newline after it.

printf '%s' 'hello' | base64
# aGVsbG8=
printf '%s' 'aGVsbG8=' | base64 --decode
# hello

The tool below shows the same aGVsbG8= for hello.

The echo trap

echo hello | base64
# aGVsbG8K

echo adds a newline, so 6 bytes are encoded and the result ends in K instead of =. Press Enter after hello in the tool and it shows aGVsbG8K too. echo -n drops the newline in bash and zsh, but POSIX makes -n implementation-defined, and macOS’s /bin/sh prints it as text:

/bin/sh -c 'echo -n hello | base64'    # on macOS
# LW4gaGVsbG8K                           that's "-n hello" and a newline

GNU and macOS flags

Linux has GNU coreutils’ base64; macOS has its own, and they disagree:

  • Line wrapping. GNU wraps the output at 76 characters, so a long value comes out on several lines. Pass -w 0 for one line. macOS doesn’t wrap unless you ask with -b 76.
  • Files. GNU takes the file name as an argument: base64 photo.png. macOS answers that with base64: invalid argument photo.png and wants -i photo.png. On GNU, -i means --ignore-garbage, a decoding option. base64 < photo.png works on both.
  • Decoding. GNU decodes with -d; macOS takes -d or -D. --decode works on both.
head -c 60 /dev/zero | tr '\0' x | base64        # GNU
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
# eHh4

Missing padding

Base64 without its = padding, as in base64url and JWTs, decodes differently on each system:

printf '%s' 'aGVsbG8' | base64 -d
# GNU coreutils 9.6:  hello
# GNU coreutils 9.1:  hello, plus "base64: invalid input" and exit status 1
# macOS 27:           hel, exit status 0

macOS drops the last partial group without an error. Pad the string first:

s=aGVsbG8; while [ $(( ${#s} % 4 )) -ne 0 ]; do s="$s="; done
printf '%s' "$s" | base64 -d
# hello

URL-safe Base64

GNU has basenc --base64url, which still writes the padding. On any system, swap the two characters and strip the = (with -w 0 on GNU):

printf '%s' '<<???>>' | basenc --base64url
# PDw_Pz8-Pg==
printf '%s' '<<???>>' | base64 | tr '+/' '-_' | tr -d '='
# PDw_Pz8-Pg

Try it in the Base64 Encoder & Decoder

Nothing leaves your browser: the conversion happens here, in the page.

Base64
aGVsbG8=

5 bytes of UTF-8, written as 8 Base64 characters.

Runs in your browser. Nothing you enter is sent anywhere.