TL;DR

With no scp/sftp, turn the binary into ASCII text, push it through the terminal, and rebuild it:

1
2
3
4
5
# Source: binary -> text
base64 file.bin             # prints; copy the output

# Destination: text -> binary (paste the block, Ctrl-D ends input)
base64 -d > file.bin

Always verify with a hash on both ends (sha256sum file.bin). If it doesn’t match, the paste dropped bytes.

When you need this

Sometimes all you have is a shell: a Telnet session to an old box, a serial console, an ssh into a jail with no scp, a minimal container with no network tools, or a KVM/IPMI where all you can do is type. No file transfer — but you can write and read text.

The trick: any file — even a binary — can be represented as printable ASCII. You encode it at the source, copy the text through the terminal, and decode it at the destination. Two tools do this: base64 (modern, everywhere) and uuencode (classic, from the UUCP and Usenet days).

base64 ships with coreutils, so it’s on virtually any modern Linux/BSD/macOS.

At the source — encode and print the text:

1
base64 file.bin

For a large file, send it to a .txt and open it, or print it as a single unwrapped line:

1
base64 -w0 file.bin           # -w0 = no wrap, single line (GNU)

Copy the whole output (it’s printable text, safe to paste).

At the destination — paste the text and decode:

1
2
base64 -d > file.bin
# paste the base64 block here, then Enter and Ctrl-D to close input

Ctrl-D signals end of input (EOF) and base64 -d writes the reconstructed binary.

On macOS/BSD the single-line flag isn’t -w0. Use base64 -i file.bin to encode and base64 -D (capital D) to decode. When in doubt, base64 --help.

Option 2: uuencode / uudecode (classic)

uuencode/uudecode ship in the sharutils package. It’s what people used to mail binaries over email and Usenet before MIME. Handy on an old system that already has it and lacks base64.

1
2
3
# install if needed
sudo apt install sharutils        # Debian/Ubuntu
sudo dnf install sharutils        # Fedora/RHEL

At the source — the second argument is the name the file will get on decode:

1
uuencode file.bin file.bin

It prints a block that starts with begin 644 file.bin and ends with end. Copy all of it, including those two lines.

At the destination — paste the block and decode:

1
2
uudecode > /dev/null
# paste the block (begin ... end), then Ctrl-D

uudecode reads the name from the block itself (begin 644 file.bin) and creates file.bin on its own. You don’t pass an output name.

Always verify with a hash

Pasting long text into a terminal is fragile: a truncated buffer, a dropped character, a wrap that injects a space. Confirm the file arrived intact:

1
2
# on both ends
sha256sum file.bin

If the hashes differ, the paste got corrupted. Retry, or split the file (below).

Compress before you encode

base64 inflates the file by ~33%. Compress it first and there’s less text to paste — and you get to bundle a whole directory into one file. Compress, encode, and on the other side reverse the order: decode, decompress.

A single file with gzip (or bzip2 / xz, better ratio, slower):

1
2
3
4
5
6
7
8
9
# source
gzip -c file.bin | base64 -w0           # compress and encode in one go
# bzip2 -c file.bin | base64 -w0        # usually compresses more
# xz   -c file.bin | base64 -w0         # best ratio (slowest)

# destination: decode and decompress
base64 -d | gzip -d > file.bin          # paste the text, Ctrl-D
# base64 -d | bzip2 -d > file.bin
# base64 -d | xz   -d > file.bin

A whole directory with tar (pack + compress + encode):

1
2
3
4
5
6
# source
tar czf - dir/ | base64 -w0             # c=create z=gzip f=-  ->  to stdout
# tar cjf - dir/  = bzip2 ;  tar cJf - dir/  = xz

# destination: decode, decompress and extract
base64 -d | tar xzf -                    # paste the text, Ctrl-D

Verify the tar.gz with sha256sum (not the loose contents) on both ends: comparing one file is easier than comparing a directory.

Large files: split them

Pasting megabytes of text into a terminal is asking for trouble. If it’s still large after compressing, split before encoding:

1
2
3
4
5
6
7
# source
split -b 100k file.bin part_             # creates part_aa, part_ab, ...
base64 part_aa                            # ship each part separately

# destination: rebuild each part and join them
cat part_aa part_ab part_ac > file.bin
sha256sum file.bin                        # verify against the source

What each piece does

CommandWhat it does
base64 fileBinary → ASCII text (76-char lines by default)
base64 -w0 fileSame, but a single line (GNU); handy to paste in one go
base64 -dbase64 text → binary (reads stdin until EOF)
uuencode in outBinary → begin…end block; out = name on decode
uudecodebegin…end block → file (takes the name from the block)
gzip -c / -dCompress / decompress to stdout (also bzip2, xz)
tar czf - dir/Pack and compress a directory to stdout (z=gzip, j=bzip2, J=xz)
tar xzf -Extract a directory from stdin
split -bChop a file into fixed-size pieces
sha256sumHash to verify end-to-end integrity

Traps

  • Always verify with a hash. It’s the only safeguard against a truncated paste. Without it, you think you copied the file and you’ve got garbage.
  • Size grows ~33–35%. base64 adds ~33%, uuencode ~35%. A 3 MB binary becomes ~4 MB of text to type/paste. That’s what split is for.
  • Different flags on macOS/BSD vs GNU. -w0/-d are GNU; on macOS it’s -i/-D. Don’t assume.
  • Watch your terminal client’s auto-wrap. Some Telnet/serial clients insert breaks or cut long lines. If it fails, use base64 with wrap (drop -w0) and smaller blocks.
  • Telnet is cleartext. Anything sensitive sent over Telnet travels unencrypted. Use it only on local/serial consoles or trusted networks; for everything else, SSH.
  • Not for TBs. This is for rescuing a file when there’s no other way, not for moving volumes. If you can use scp/rsync/sftp, use them.

See also