Convert small base64 encoded string to hexadecimal

1. Reducing forks

Accepted answer do offer a solution with a lot of forks! I hate useless forks!

There is my short base64 to hexadecimal converter:

b64toHex() {
    local _arLines
    mapfile -t _arLines < <(base64 -d <<< "$2" | xxd -p)
    printf -v "${1:-hexString}" %s "${_arLines[@]}"
}

In order to avoid fork like myVar=$(myFunc args), this function will only populate a variable and won't print out anything.

b64toHex myVar "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
echo $myVar
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Then you could use:

b64toHex iv_hex "${iv}"
b64toHex key_hex "${key}"

See at bottom of this for execution time comparison.

2. Pure bash way:

This don't depend on xxd or base64 to be installed. (And without forks, will be significantly quicker than running 3 forks! Keep in mind, if you plan to run this repetitively!)

From this: Bash script - decode encoded string to byte array, on my website: base64decoder.sh.txt, base64decoder.sh, with a very small modification**:

**At line 41:
- ((_ar==0)) && printf -v _res %b "${_res[@]/#/\\x}"
+ ((_ar==0)) && printf -v _res %s "${_res[@]}"

First prepare a read-only array as:

declare -a B64=( {A..Z} {a..z} {0..9} + / '=' )
declare -Ai 'B64R=()'
for i in "${!B64[@]}"; do B64R["${B64[i]}"]=i%64; done
declare -r B64R
unset B64

Then b64ToHex function:

b64ToHex() {
    local _4B _Tail _hVal _v _opt OPTIND
    local -i iFd _24b _ar
    while getopts "av:" _opt; do case $_opt in
        a) _ar=1;; v) _v=${OPTARG};; *) return 1;; esac; done
    shift $((OPTIND-1))
    if [[ $_v ]];then local -n _res=${_v}; else local _res; fi
    if [[ $1 ]]; then    exec {iFd}<<<"$1"          # Open Input FD from string
    else                 exec {iFd}<&0        ; fi  # Open Input FD from STDIN
    _res=()
    while read -rn4 -u $iFd _4B; do
        if [[ "$_4B" ]]; then
            _Tail=$_4B
            _24b=" B64R['${_4B::1}'] << 18 | B64R['${_4B:1:1}'] << 12 |
                   B64R['${_4B:2:1}'] << 6 | B64R['${_4B:3:1}'] "
            printf -v _hval %02x\  $((_24b>>16)) $((_24b>>8&255)) $((_24b&255))
            read -ra _hval <<<"$_hval"
            _res+=("${_hval[@]}")
        fi
    done
    exec {iFd}<&-
    _Tail=${_Tail##*([^=])}
    while [[ $_Tail ]]; do
        unset "_res[-1]"
        _Tail=${_Tail:1}
    done
    ((_ar==0)) && printf -v _res %s "${_res[@]}" && _res=("${_res[0]}")
    [[ -z $_v ]] && echo "${_res[@]}"
}

If bash loop are known to be slow, doing a loop over only 32 byte will be significantly quicker and less system expansive than running four forks!

Usage from STDIN:

b64ToHex <<<"OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

From an argument:

b64ToHex "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Assign a variable:

b64ToHex -v someVar "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
echo "$someVar"
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Then from, to variables:

b64ToHex -v iv_hex "${iv}"
b64ToHex -v key_hex "${key}"

Note: this is done without any fork.

For fun: retrieving original base64 string, with bash V5.1+, you could:

shopt -s extglob
printf %b ${someVar//??/\\x& } | base64
OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw=

3. Pure bash way, but usign bash V5.2+

Same script, but by using patsub_replacement and mapfile, I could do this without any bash loop!

declare -a B64=( {A..Z} {a..z} {0..9} + / '=' )
printf -v _b64_tstr '["\44{B64[%d]}"]=%%d%%%%64 ' {0..64}
# shellcheck disable=SC2059  # format is variable.
printf -v _b64_tstr "$_b64_tstr" {0..64}
declare -Ai "B64R=($_b64_tstr)"
unset B64 _b64_tstr
declare -r B64R
b64ToHex52() {
    local _line _Tail _v _opt OPTIND _resArry
    local -i iFd _ar
    while getopts "av:" _opt; do case $_opt in
        a) _ar=1;; v) _v=${OPTARG};; *) return 1;; esac; done
    shift $((OPTIND-1))
    if [[ $_v ]];then local -n _res=${_v}; else local _res; fi
    if [[ $1 ]]; then    exec {iFd}<<<"$1"     # Open Input FD from string
    else                 exec {iFd}<&0   ; fi  # Open Input FD from STDIN
    mapfile -tu $iFd _lines
    read -ra _resArry <<<"${_lines[*]//?/& }"
    printf -v _tmpStr '"B64R[%s]<<18|B64R[%s]<<12|B64R[%s]<<6|B64R[%s]" ' \
           "${_resArry[@]}"
    local -ia "_tmpArry=($_tmpStr)"
    printf -v _tmpStr '%06x' "${_tmpArry[@]}"
    read -ra _res <<<"${_tmpStr//??/& }"
    exec {iFd}<&-
    _Tail=${_lines[-1]##*([^=])}
    _res=("${_res[@]::${#_res[@]}-${#_Tail}}")
    ((_ar==0)) && printf -v _res %s "${_res[@]}" && _res=("${_res[0]}")
    [[ -z $_v ]] && echo "${_res[@]}"
}

4. Execution time comparison

Well, now a little comparison test by doing repetitively same conversion to compute execution time.

I now have 4 functions:

  1. b64toHex My version using base64 and xxd
  2. b64ToHex My pure bash version (notice upper T)
  3. b64ToHex52 My pure bash using bash version 5.2+
  4. base64_to_hex from accepted answer.

Here's my little test function:

testB64decoders(){ 
    local TIMEFORMAT='r %3lR,  u %3lU,  s %3lS, p %P' bunch \
        inString="${2:-SGVsbG8gd29ybGQhIFRoaXMgaXMgYSB0ZXN0IHN0cmluZy4=}"
    printf -v bunch '%*s' ${1:-100} ''
    mapfile -t bunch <<<"${bunch// /$'\n'}"
    printf ' - %-29s: ' "3 fork (base64 | xxd)";
    time for i in "${bunch[@]}"; do b64toHex hx "$inString"; done
    printf ' - %-29s: ' "Pure bash";
    time for i in "${bunch[@]}"; do b64ToHex -v Hx "$inString"; done;
    printf ' - %-29s: ' "Pure bash V5.2+";
    time for i in "${bunch[@]}"; do b64ToHex52 -v Hx5 "$inString"; done;
    printf ' - %-29s: ' "5 fork =\$(echo| base64 | xxd)";
    time for i in "${bunch[@]}"; do hex=$(base64_to_hex "$inString"); done;
    [[ $hx == "$hex" ]] && [[ $Hx == "$hx" ]] && [[ $Hx5 == "$hx" ]] &&
        printf -v inString '%b' ${hx//??/\\x&} &&
        printf 'Hopefully result strings are same (%s).\n' "${inString@Q}"
}

Let's show with a small thousand of operation:

testB64decoders 1000

Could produce something like:

 - 3 fork (base64 | xxd)        : r 0m2.032s,  u 0m2.315s,  s 0m0.699s, p 148.32
 - Pure bash                    : r 0m0.965s,  u 0m0.735s,  s 0m0.213s, p 98.14
 - Pure bash V5.2+              : r 0m0.571s,  u 0m0.477s,  s 0m0.088s, p 98.91
 - 5 fork =$(echo| base64 | xxd): r 0m2.433s,  u 0m3.242s,  s 0m0.989s, p 173.92

Where r for real, u: user, s: system time and p: cpu percentage is: 100 * ( u + s ) / r

  • pure bash method is quicker,
  • pure bash method using bash 5.2+ is significantly quicker,
  • version using xxd and base64 with only two fork will even be a little quicker than
  • version using four forks (a subshell to run three more forks to xxd, base64 and tr). They are the slowest, and yes: two more forks do have system footprint.

Note: on a multicore system, user time is bigger than real time, thanks to parallelization

On my Raspberry-Pi II model B, I had to reduce my test down to 20 loops.

testB64decoders 20

Did produce on my RPi II:

 - 3 fork (base64 | xxd)      …
Answer from F. Hauri - Give Up GitHub on Stack Overflow
🌐
Ubuntu
manpages.ubuntu.com › jammy › man(1)
Ubuntu Manpage: xxd - make a hexdump or do the reverse.
Reverse operation: convert (or patch) hexdump into binary. If not writing to stdout, xxd writes into its output file without truncating it. Use the combination -r -p to read plain hexadecimal dumps without line number information and without a particular column layout.
🌐
Rob Allen
akrabat.com › use-xxd-to-convert-to-hex
Use xxd to convert to hex – Rob Allen
July 23, 2024 - $ xxd foo.txt 00000000: 5468 6973 2069 7320 736f 6d65 2074 6578 This is some tex 00000010: 7421 0a t!. ... ASCII representation, with a . for escape characters, like the 0A on the second line · There are some options that I find useful. Firstly, we can use -u to get uppercase hex letters which I find more “normal”.
Discussions

bash - Decode a base64 string and encode it as hex using xxd - Stack Overflow
xxd -p may have \n chars in the output so you need to remove them: Copybase64_to_hex() { echo "$1" | base64 --decode | xxd -p | tr -d '\n' } More on stackoverflow.com
🌐 stackoverflow.com
bash - Can xxd be used to output the binary representation of hex number , not a string? - Unix & Linux Stack Exchange
I would like to generate a raw binary packet from hex in the command line for the purpose of testing a protocol. I'm hoping its simpler to do it this way than writing an aplication. Here is what More on unix.stackexchange.com
🌐 unix.stackexchange.com
Conversion hex string into ascii in bash command line - Stack Overflow
I have a lot of this kind of string and I want to find a command to convert it in ascii, I tried with echo -e and od, but it did not work. 0xA7.0x9B.0x46.0x8D.0x1E.0x52.0xA7.0x9B.0x7B.0x31.0xD2 More on stackoverflow.com
🌐 stackoverflow.com
linux - How to get specific format using hexdump or xxd? - Stack Overflow
I require the hexdumps of a number of files in a specific format: 00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 However, using hexdump or xxd I can only manage to get the above with a c... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Reddit
reddit.com › r/bash › help need to decipher this command: xxd
r/bash on Reddit: Help need to decipher this command: xxd
March 25, 2022 -

First and foremost, I just want to say that I'm relatively new to Bash and I'm taking a pen testing course. This is the command that I need help to decipher because I don't understand how it works:

echo '4f537b32393736656137393964633562333261343431353535313735303937346230317d0a0a' | xxd -r -p

I think that this command pipes in hex code to xxd, which is a utility used to make a hexdump or do the reverse (as explainshell says). The -r option tells xxd to convert the hexdump into binary and the -p option tells it to output the binary string in plain hexdump style. However, the output is not in binary. It seems to be in (I don't know the technical term for it) regular language (OS) and contains a hex string ({2976ea799dc5b32a4415551750974b01}):

OS{2976ea799dc5b32a4415551750974b01}

Why is the output not in binary?

Top answer
1 of 4
5
Greetings, I have used xxd along with the same parameters to pipe binary characters to a binary file. The reason you are seeing ascii characters is because all the command with those parameters displays the result in ascii and all of your characters from the binary conversion result in regular ascii characters. I found this from a quick search to confirm: https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ If you try this one from terminal you will see it outputs special characters because it cannot display them from a terminal. echo 0ee221ef9e00dfa69efb3b1112bfbb2f | xxd -r -p The sample I listed is part of this script (I'm the author) and used with file encryption. Basically it converts hex to binary and appends the binary bytes to a binary file. Near the bottom of the document I list step by step example of encryption and how to use the commands. For this script xxd is critical because it's the only way to convert to and from hex from shell without installing additional tools. https://fastsitephp.com/en/documents/file-encryption-bash # Append IV to the end of the file. # Typically in secure applications and sites the IV is saved with the # encrypted data while only the key is required to be secret. echo 0ee221ef9e00dfa69efb3b1112bfbb2f | xxd -r -p >> crypto_test_10mb.enc # Now view the end of the file after adding IV tail -c 32 crypto_test_10mb.enc | hexdump -C -v
2 of 4
2
The output is in binary. The command outputs the binary values corresponding to the hexadecimal values 4f, 53, 7b, 32 etc. When sent to the terminal those binary values are interpreted as characters: hex 4f (aka decimal 79, binary 01001111, etc) is a capital O, 53 is a capital S, 7b is an open curly brace, 32 is the digit "2", etc. xxd is doing its job - you just for some reason have a hex string representing the text OS{2976ea799dc5b32a4415551750974b01}. I have no idea what that text represents.
Trying to find hex in bin file Mar 29, 2023
r/bash
3y ago
Decoding hex string Feb 26, 2024
r/bash
2y ago
vi + xxd = hex editor Mar 4, 2008
r/programming
18y ago
How did people decipher this? Feb 19, 2026
r/codes
7mo ago
More results from reddit.com
🌐
SysTutorials
systutorials.com › convert-hex-to-ascii-using-xxd
Converting Hex To ASCII With Xxd - SysTutorials
April 12, 2026 - Not all hex represents text. If your data contains null bytes or non-printable characters, xxd -r -p handles them correctly — you just won’t see them in terminal output:
🌐
Hexmos
hexmos.com › freedevtools › c › cmd › xxd
Xxd - Hex Dump Utility for Data Conversion Cheatsheet | cmd Reference | Online Free DevTools
Conversely, xxd can also reverse this process, taking a hexadecimal string and converting it back into its original binary or text form. This is crucial for decoding data that has been encoded in hexadecimal.
Top answer
1 of 2
4

Convert small base64 encoded string to hexadecimal

1. Reducing forks

Accepted answer do offer a solution with a lot of forks! I hate useless forks!

There is my short base64 to hexadecimal converter:

b64toHex() {
    local _arLines
    mapfile -t _arLines < <(base64 -d <<< "$2" | xxd -p)
    printf -v "${1:-hexString}" %s "${_arLines[@]}"
}

In order to avoid fork like myVar=$(myFunc args), this function will only populate a variable and won't print out anything.

b64toHex myVar "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
echo $myVar
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Then you could use:

b64toHex iv_hex "${iv}"
b64toHex key_hex "${key}"

See at bottom of this for execution time comparison.

2. Pure bash way:

This don't depend on xxd or base64 to be installed. (And without forks, will be significantly quicker than running 3 forks! Keep in mind, if you plan to run this repetitively!)

From this: Bash script - decode encoded string to byte array, on my website: base64decoder.sh.txt, base64decoder.sh, with a very small modification**:

**At line 41:
- ((_ar==0)) && printf -v _res %b "${_res[@]/#/\\x}"
+ ((_ar==0)) && printf -v _res %s "${_res[@]}"

First prepare a read-only array as:

declare -a B64=( {A..Z} {a..z} {0..9} + / '=' )
declare -Ai 'B64R=()'
for i in "${!B64[@]}"; do B64R["${B64[i]}"]=i%64; done
declare -r B64R
unset B64

Then b64ToHex function:

b64ToHex() {
    local _4B _Tail _hVal _v _opt OPTIND
    local -i iFd _24b _ar
    while getopts "av:" _opt; do case $_opt in
        a) _ar=1;; v) _v=${OPTARG};; *) return 1;; esac; done
    shift $((OPTIND-1))
    if [[ $_v ]];then local -n _res=${_v}; else local _res; fi
    if [[ $1 ]]; then    exec {iFd}<<<"$1"          # Open Input FD from string
    else                 exec {iFd}<&0        ; fi  # Open Input FD from STDIN
    _res=()
    while read -rn4 -u $iFd _4B; do
        if [[ "$_4B" ]]; then
            _Tail=$_4B
            _24b=" B64R['${_4B::1}'] << 18 | B64R['${_4B:1:1}'] << 12 |
                   B64R['${_4B:2:1}'] << 6 | B64R['${_4B:3:1}'] "
            printf -v _hval %02x\  $((_24b>>16)) $((_24b>>8&255)) $((_24b&255))
            read -ra _hval <<<"$_hval"
            _res+=("${_hval[@]}")
        fi
    done
    exec {iFd}<&-
    _Tail=${_Tail##*([^=])}
    while [[ $_Tail ]]; do
        unset "_res[-1]"
        _Tail=${_Tail:1}
    done
    ((_ar==0)) && printf -v _res %s "${_res[@]}" && _res=("${_res[0]}")
    [[ -z $_v ]] && echo "${_res[@]}"
}

If bash loop are known to be slow, doing a loop over only 32 byte will be significantly quicker and less system expansive than running four forks!

Usage from STDIN:

b64ToHex <<<"OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

From an argument:

b64ToHex "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Assign a variable:

b64ToHex -v someVar "OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw="
echo "$someVar"
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c

Then from, to variables:

b64ToHex -v iv_hex "${iv}"
b64ToHex -v key_hex "${key}"

Note: this is done without any fork.

For fun: retrieving original base64 string, with bash V5.1+, you could:

shopt -s extglob
printf %b ${someVar//??/\\x& } | base64
OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw=

3. Pure bash way, but usign bash V5.2+

Same script, but by using patsub_replacement and mapfile, I could do this without any bash loop!

declare -a B64=( {A..Z} {a..z} {0..9} + / '=' )
printf -v _b64_tstr '["\44{B64[%d]}"]=%%d%%%%64 ' {0..64}
# shellcheck disable=SC2059  # format is variable.
printf -v _b64_tstr "$_b64_tstr" {0..64}
declare -Ai "B64R=($_b64_tstr)"
unset B64 _b64_tstr
declare -r B64R
b64ToHex52() {
    local _line _Tail _v _opt OPTIND _resArry
    local -i iFd _ar
    while getopts "av:" _opt; do case $_opt in
        a) _ar=1;; v) _v=${OPTARG};; *) return 1;; esac; done
    shift $((OPTIND-1))
    if [[ $_v ]];then local -n _res=${_v}; else local _res; fi
    if [[ $1 ]]; then    exec {iFd}<<<"$1"     # Open Input FD from string
    else                 exec {iFd}<&0   ; fi  # Open Input FD from STDIN
    mapfile -tu $iFd _lines
    read -ra _resArry <<<"${_lines[*]//?/& }"
    printf -v _tmpStr '"B64R[%s]<<18|B64R[%s]<<12|B64R[%s]<<6|B64R[%s]" ' \
           "${_resArry[@]}"
    local -ia "_tmpArry=($_tmpStr)"
    printf -v _tmpStr '%06x' "${_tmpArry[@]}"
    read -ra _res <<<"${_tmpStr//??/& }"
    exec {iFd}<&-
    _Tail=${_lines[-1]##*([^=])}
    _res=("${_res[@]::${#_res[@]}-${#_Tail}}")
    ((_ar==0)) && printf -v _res %s "${_res[@]}" && _res=("${_res[0]}")
    [[ -z $_v ]] && echo "${_res[@]}"
}

4. Execution time comparison

Well, now a little comparison test by doing repetitively same conversion to compute execution time.

I now have 4 functions:

  1. b64toHex My version using base64 and xxd
  2. b64ToHex My pure bash version (notice upper T)
  3. b64ToHex52 My pure bash using bash version 5.2+
  4. base64_to_hex from accepted answer.

Here's my little test function:

testB64decoders(){ 
    local TIMEFORMAT='r %3lR,  u %3lU,  s %3lS, p %P' bunch \
        inString="${2:-SGVsbG8gd29ybGQhIFRoaXMgaXMgYSB0ZXN0IHN0cmluZy4=}"
    printf -v bunch '%*s' ${1:-100} ''
    mapfile -t bunch <<<"${bunch// /$'\n'}"
    printf ' - %-29s: ' "3 fork (base64 | xxd)";
    time for i in "${bunch[@]}"; do b64toHex hx "$inString"; done
    printf ' - %-29s: ' "Pure bash";
    time for i in "${bunch[@]}"; do b64ToHex -v Hx "$inString"; done;
    printf ' - %-29s: ' "Pure bash V5.2+";
    time for i in "${bunch[@]}"; do b64ToHex52 -v Hx5 "$inString"; done;
    printf ' - %-29s: ' "5 fork =\$(echo| base64 | xxd)";
    time for i in "${bunch[@]}"; do hex=$(base64_to_hex "$inString"); done;
    [[ $hx == "$hex" ]] && [[ $Hx == "$hx" ]] && [[ $Hx5 == "$hx" ]] &&
        printf -v inString '%b' ${hx//??/\\x&} &&
        printf 'Hopefully result strings are same (%s).\n' "${inString@Q}"
}

Let's show with a small thousand of operation:

testB64decoders 1000

Could produce something like:

 - 3 fork (base64 | xxd)        : r 0m2.032s,  u 0m2.315s,  s 0m0.699s, p 148.32
 - Pure bash                    : r 0m0.965s,  u 0m0.735s,  s 0m0.213s, p 98.14
 - Pure bash V5.2+              : r 0m0.571s,  u 0m0.477s,  s 0m0.088s, p 98.91
 - 5 fork =$(echo| base64 | xxd): r 0m2.433s,  u 0m3.242s,  s 0m0.989s, p 173.92

Where r for real, u: user, s: system time and p: cpu percentage is: 100 * ( u + s ) / r

  • pure bash method is quicker,
  • pure bash method using bash 5.2+ is significantly quicker,
  • version using xxd and base64 with only two fork will even be a little quicker than
  • version using four forks (a subshell to run three more forks to xxd, base64 and tr). They are the slowest, and yes: two more forks do have system footprint.

Note: on a multicore system, user time is bigger than real time, thanks to parallelization

On my Raspberry-Pi II model B, I had to reduce my test down to 20 loops.

testB64decoders 20

Did produce on my RPi II:

 - 3 fork (base64 | xxd)      …
2 of 2
3

xxd -p may have \n chars in the output so you need to remove them:

base64_to_hex()
{
    echo "$1" | base64 --decode | xxd -p | tr -d '\n'
}

Use your base64 string as example:

( base64_to_hex OQbb8rXnj/DwvglW018uP/1tqldwiJMbjxBhX7ZqwTw= )
$ echo $hex
3906dbf2b5e78ff0f0be0956d35f2e3ffd6daa577088931b8f10615fb66ac13c
Top answer
1 of 3
15

echo '0A' produces three characters: 0 A NL; xxd -b will then print those three characters in binary. If you wanted just the single byte whose value is 10 (i.e. hexadecimal A), you could write (in bash):

echo -n $'\x0A'
      ^ ^  ^
      | |  |
      | |  +-- `\x` indicates a hexadecimal escape
      | +----- Inside a $' string, escapes are interpreted
      +------- -n suppresses the trailing newline

A better alternative would be printf '\x0A'; printf interprets escape sequences in the format string, and does not output implicit newlines. (For a completely Posix-compatible solution, you would need an octal escape: printf '\012'. printf should work on any Posix-compatible shell but hexadecimal escapes are an extension.) Yet another bash possibility is echo -n -e '\x0A'; the (non-standard) -e flag asks echo to interpret escape sequences.

echo '0A' | xxd -b won't output the equivalent of hex 0A, because xxd doesn't know that you intend 0A to be a hex number rather than two characters. It just takes its input as a series of characters, regardless of what those characters are.

Endianness does not affect bytes. The order of bits inside a byte is entirely conceptual until the byte is transmitted over a serial line and even then it is only visible with an oscilloscope or something similar.

2 of 3
8

If you want the binary output for a string of hex digits, xxd -r -p. E.g.:

 $ echo '0A0B0C0D' | xxd -r -p | xxd -b
 0000000: 00001010 00001011 00001100 00001101                    ....

converts 0A0B0C0D into a four bytes of binary (first call to xxd), and then converts it back to be printable (second call). You say you want a binary output, but the examples you're trying for are a printable representation.

I don't know of anything where endianness is ambiguous at the nibble level, as you imply in your second example. The conversion in xxd is a pure byte at a time, not assuming they represent any particular multi-byte number.

Find elsewhere
Top answer
1 of 3
30
hexdump -ve '1/1 "%02x"'
xxd -p | tr -d '\n'

If you get tired of writing this every time, create an alias.

2 of 3
11

How to easily convert to/from plain machine-readable hexadecimal data

In brief.

$ xxd -plain test.txt > test.hex
$ xxd -plain -revert test.hex test2.txt
$ diff test.txt test2.txt
$

Explanation:

$ xxd -plain test.txt > test.hex

This writes a hex encoding of the data in test.txt into new file test.hex. The -p or -plain option makes xxd use "plain" hex format with no spaces between pairs of hex digits (i.e. no spaces between byte values). This converts "abc ABC" to "61626320414243". Without the -p it would convert the text to a 16-bit word oriented traditional hexdump format, which is arguably easier to read but less compact and therefore less suitable as a transmission format and slightly harder to reverse.

$ xxd -plain -revert text.hex test2.txt

This uses the -r or -revert option for reverse operation. The -plain option is used again to indicate that the input hex file is in plain format.

I make the output filename different from the original filename so we can later compare the results with the original file.

$ diff test.txt test2.txt
$ 

The diff command outputs nothing - this means there is no difference between the original and reconstituted file contents.


I'm tired of digging of some special format strings

Use alias or declare functions in your .profile to create mnemonics so you don't have to remember or dig about in man pages.

or just remember -plain and -revert.


Wrapped output

Yes, there are new-line characters in the output. You want to avoid that. You can use the -c or -cols option to specify how long you want the output lines to be to attempt to avoid line-wrapping of the output. -c 0 gives the default length and the man page suggests 256 is the limit but it seems to work beyond that.

$ xxd -plain -cols 9999 test.txt > test.hex
$ wc test.txt test.hex
  121   880  4603 test.txt
    1     1  9207 test.hex

The wc wordcount command tells us how many lines, words and characters are in each file.

So 121 lines (880 words, 4603 bytes) of ASCII text were encoded as 1 line of hex digits.

🌐
Computer Hope
computerhope.com › unix › xxd.htm
Linux Xxd Command
June 1, 2025 - The xxd program takes a file or standard input and outputs a hex dump that uses only ASCII or EBCDIC (Extended Binary Coded Decimal Interchange Code) characters. This output hex dump can be safely emailed and reconstructed at the destination.
🌐
daily.dev
daily.dev › home › john hammond › decode hex on linux
Decode Hex on Linux | daily.dev
October 19, 2024 - The process involves piping standard input into xxd and using various flags like '-p' for printing and '-r -p' for reversing and decoding hexadecimal strings.
🌐
SysTutorials
systutorials.com › docs › linux › man › 1-xxd
Xxd (1) Linux Manual Page - SysTutorials
May 19, 2026 - Reverse operation: convert (or patch) hexdump into binary. If not writing to stdout, xxd writes into its output file without truncating it. Use the combination -r -p to read plain hexadecimal dumps without line number information and without a particular column layout.
🌐
cheat.sh
cheat.sh › xxd
cheat.sh/xxd
cheat:xxd # To convert bin/string to hex: echo '42 is the solution' | xxd -p # output: 34322069732074686520736f6c7574696f6e0a # To convert hex to bin/string: echo '34322069732074686520736f6c7574696f6e0a' | xxd -r -p # output: 42 is the solution tldr:xxd # xxd # Create a hexadecimal representation ...
Top answer
1 of 2
4

hexdump is able to produce this output, and provides much more options, once you figure out the somewhat arcane, yet powerful, format syntax.

$ hexdump -v -e '"%08_ax" 16/1 " %02X" "\n"' en.gif 
00000000 47 49 46 38 37 61 10 00 0A 00 A1 03 00 CC 00 00
00000010 44 44 CC FF FF FF FF BB 66 2C 00 00 00 00 10 00
00000020 0A 00 00 02 25 8C 20 61 A8 97 BA 92 8B 10 BA CB
00000030 AA 45 FC 59 01 82 C0 18 96 E3 89 A6 64 79 AC 6C
00000040 E5 74 DA 26 D6 F3 84 67 41 01 00 3B

The format in detail:

  • "%08_ax" is the offset in hexadecimal, 8 digits with leading zeroes.
  • 16/1 " %02X" displays 16 bytes per iteration (line), one byte per block, each block formatted as 2 uppercase hexadecimal digits with leading zeroes and preceeding space.
  • "\n" finishes each iteration (line) with a newline.
2 of 2
2

od is one way:

After creating an example file via

perl -e 'print map { chr hex } @ARGV' 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 > foo.bin

getting a hex dump of it:

$ od -Ax -t x1 foo.bin
0000000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00
0000010

The key here is the -t FORMAT argument. In the format, x uses base 16, and 1 means to print one byte per block. The -Ax says to print out the offsets in base 16 instead of the default base 8.

It does print out the offset of the end of the file as the last line, but that's trivial to get rid of with head -n -1 if not needed. There doesn't seem to be a way to make it use upper-case hex digits, but that's also easily fixable if you prefer them:

$ od -Ax -t x1 foo.bin | head -n -1 | tr a-f A-F
000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
🌐
Iasg
iasg.github.io › puzzlers › spoilers › 2017-03-21.html
Hex and Base64 Encoding Puzzler | Information Assurance Student Group
xxd -p file simply creates a plain hex encoding of the file xxd -r -p file.hex decodes plain hex xxd -p file | xxd -r -p encodes then decodes the file - full cycle
🌐
Man Pages
manpages.org › xxd
man xxd (1): make a hexdump or do the reverse.
xxd creates a hex dump of a given file or standard input. It can also convert a hex dump back to its original binary form. Like uuencode(1) and uudecode(1) it allows the transmission of binary data in a `mail-safe' ASCII representation, but has the advantage of decoding to standard output.
🌐
Simplilearn
simplilearn.com › home › resources › software development › xxd command in linux: a must-know for system administrators
XXD Command in Linux: A Must-Know for System Administrators
1 month ago - Unlock the secrets of XXD Command in Linux! This quick guide offers insider tips for mastering this powerful tool. Boost your Linux skills now!
Address: 5851 Legacy Circle, 6th Floor, Plano, TX 75024 United States
🌐
O'Reilly
oreilly.com › library › view › linux-pocket-guide › 9781449332969 › re25.html
xxd - Linux Pocket Guide, 2nd Edition [Book]
March 20, 2012 - By default, xxd outputs three columns: file offsets, the data in hex, and the data as text (printable characters only).
Author: Daniel J. Barrett
Published: 2012
Pages: 230
🌐
Linux Journal
linuxjournal.com › content › doing-reverse-hex-dump
Doing a Reverse Hex Dump | Linux Journal
June 2, 2008 - # xxd bdata | xxd -r >bdata2 # cmp bdata bdata2 # xxd bdata2 0000000: 0001 0203 0405 ...... Note that when doing reverse conversions with xxd, the data needs to look like a hex dump: there needs to be an offset and the data needs to be formatted correctly.