Tom Hale Tom Hale - 7 months ago 33
Bash Question

Get exact output of a shell command

The

bash
manual says regarding command substitution:


Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.


Demonstration - 3 characters, newlines first:

$ output="$(printf "\n\nx")"; echo -n "$output" | wc -c
3


Here the newlines are not at the end, and do not get removed, so the count is 3.

Demonstration - 3 characters, newlines last:

$ output="$(printf "x\n\n")"; echo -n "$output" | wc -c
1


Here the newlines are removed from the end, so the count is 1.

TL;DR



What is a robust work-around to get the binary-clean output of a command into a variable?

Bonus points for Bourne shell compatibility.

Answer

The only way to do it in a "Bourne compatible" way is to use external utilities.

Beside writting one in c, you can use xxd and expr (for example):

$ output="$(printf "x\n\n"; printf "X")"         # get the output ending in "X".
$ printf '%s' "${output}" | xxd -p               # transform the string to hex.
780a0a58
$ hexstr="$(printf '%s' "${output}" | xxd -p)"   # capture the hex
$ expr "$hexstr" : '\(.*\)..'                    # remove the last two hex ("X").
780a0a
$ hexstr="$(expr "$hexstr" : '\(.*\)..')         # capture the shorter str.
$ printf "$hexstr" | xxd -p -r | wc -c           # convert back to binary.
3

Shortened:

$ output="$(printf "x\n\n"; printf "X")"
$ hexstr="$(printf '%s' "${output}" | xxd -p )"
$ expr "$hexstr" : '\(.*\)..' | xxd -p -r | wc -c
3

The command xxd is being used for its ability to convert back to binary.

Note that wc will fail with many UNICODE characters (multibyte chars):

$ printf "Voilà" | wc -c
6

$ printf "★" | wc -c
3

It will print the count of bytes, not characters.

The length of a variable ${#var} will also fail in older shells.

Of course, to get this to run in a Bourne shell you must use `…` instead of $(…).