在 BASH 中使用 printf 从 ASCII 值到字符的整数

品格对价值作品:

$ printf "%d\n" \'A
65
$

我有两个问题,第一个最重要:

  • 我怎么把65变成 A?
  • A 使用 printf 将 ASCII 字符转换为其值。语法 具体点打印还是在 BASH 的其他地方使用?(这么小的字符串很难在谷歌上搜索到。)
164064 次浏览

For this kind of conversion, I use perl:

perl -e 'printf "%c\n", 65;'

One option is to directly input the character you're interested in using hex or octal notation:

printf "\x41\n"
printf "\101\n"

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}'

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

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

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

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

If you convert 65 to hexadecimal it's 0x41:

$ echo -e "\x41" A

For capital letters:

i=67
letters=({A..Z})
echo "${letters[$i-65]}"

Output:

C

this prints all the "printable" characters of your basic bash setup:

printf '%b\n' $(printf '\\%03o' {30..127})


!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Here is a solution without eval nor $() nor `` :

ord () {
local s
printf -v s '\\%03o' $1
printf "$s"
}


ord 65

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

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.