Integer ASCII value to character in BASH using printf
BashAsciiPrintfBash Problem Overview
Character to value works:
$ printf "%d\n" \'A
65
$
I have two questions, the first one is most important:
- How do I take 65 and turn it into A?
- 'A converts an ASCII character to its value using printf. Is the syntax specific to printf or is it used anywhere else in BASH? (Such small strings are hard to Google for.)
Bash Solutions
Solution 1 - Bash
One line
printf "\x$(printf %x 65)"
Two lines
set $(printf %x 65)
printf "\x$1"
Here is one if you do not mind using awk
awk 'BEGIN{printf "%c", 65}'
Solution 2 - Bash
This works (with the value in octal):
$ printf '%b' '\101'
A
even for (some: don't go over 7) sequences:
$ printf '%b' '\'{101..107}
ABCDEFG
A general construct that allows (decimal) values in any range is:
$ printf '%b' $(printf '\\%03o' {65..122})
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz
Or you could use the hex values of the characters:
$ printf '%b' $(printf '\\x%x' {65..122})
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz
You also could get the character back with xxd (use hexadecimal values):
$ echo "41" | xxd -p -r
A
That is, one action is the reverse of the other:
$ printf "%x" "'A" | xxd -p -r
A
And also works with several hex values at once:
$ echo "41 42 43 44 45 46 47 48 49 4a" | xxd -p -r
ABCDEFGHIJ
or sequences (printf is used here to get hex values):
$ printf '%x' {65..90} | xxd -r -p
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Or even use awk:
$ echo 65 | awk '{printf("%c",$1)}'
A
even for sequences:
$ seq 65 90 | awk '{printf("%c",$1)}'
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Solution 3 - Bash
For your second question, it seems the leading-quote syntax (\'A
) is specific to printf
:
> If the leading character is a single-quote or double-quote, the value shall be the numeric value in the underlying codeset of the character following the single-quote or double-quote.
From https://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
Solution 4 - Bash
One option is to directly input the character you're interested in using hex or octal notation:
printf "\x41\n"
printf "\101\n"
Solution 5 - Bash
For this kind of conversion, I use perl:
perl -e 'printf "%c\n", 65;'
Solution 6 - Bash
If you want to save the ASCII value of the character: (I did this in BASH and it worked)
{
char="A"
testing=$( printf "%d" "'${char}" )
echo $testing}
output: 65
Solution 7 - Bash
If you convert 65
to hexadecimal it's 0x41
:
$ echo -e "\x41" A
Solution 8 - Bash
For capital letters:
i=67
letters=({A..Z})
echo "${letters[$i-65]}"
Output:
C
Solution 9 - Bash
Here's yet another way to convert 65 into A (via octal):
help printf # in Bash
man bash | less -Ip '^[[:blank:]]*printf'
printf "%d\n" '"A'
printf "%d\n" "'A"
printf '%b\n' "$(printf '\%03o' 65)"
To search in man bash
for \'
use (though futile in this case):
man bash | less -Ip "\\\'" # press <n> to go through the matches
Solution 10 - Bash
this prints all the "printable" characters of your basic bash setup:
printf '%b\n' $(printf '\\%03o' {30..127})
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Solution 11 - Bash
Here is a solution without eval nor $() nor `` :
ord () {
local s
printf -v s '\\%03o' $1
printf "$s"
}
ord 65
Solution 12 - Bash
The following script prints aaa...zzz
(i.e. aaa > aab > aac > ... > zzx > zzy > zzz
)
for i in $(seq 0 25); do FIRST=$(printf \\$(printf '%03o' $[97+$i]) | tr -d '\n'); for f in $(seq 0 25); do SECOND=$(printf \\$(printf '%03o' $[97+$f]) | tr -d '\n'); for g in $(seq 0 25); do THIRD=$(printf \\$(printf '%03o' $[97+$g]) | tr -d '\n'); echo "${FIRST}${SECOND}${THIRD}"; done; done; done
Solution 13 - Bash
Given that there are not that many 1-byte characters, but only 256, they can quickly be precomputed at your script startup:
declare -ag CHARS=()
for REPLY in {{0..9},{a..f}}{{0..9},{a..f}}; do
printf -v CHARS[${#CHARS[@]}] "\x$REPLY"
done
(I reused the discardable REPLY
variable, but you can local
your own inside an init function
)
Then...
$ echo "${CHARS[65]}"
A
$ i=65
$ echo "${CHARS[i]}"
A
Beware of \x00
, whose value is not properly handled in Bash because it is the string terminator character:
$ var=$'qwe\x00rty'
$ echo ${#var}
3
$ echo "<${var}>"
<qwe>
So, "${CHAR[0]}"
will always be a problem under Bash.
This way, you avoid output capture, inline text substitution and re-parsing once and again for every time you need to process one character; which is even worse for subprocess IPC through xxd
, awk
or perl
.