shell script でも ord と chr したい!!



たまたま必要になった、
shell script で `ord` と `chr` を使う方法です。

できるだけ環境に依存しないようにしました。(`zsh` は単語展開が他と違うため注意。どうやら `${=val}` とするらしいが環境がないため未検証。該当部参照)

エラーチェックの機構は設けてないので必要であれば if 文等で行ってください。

# 忙しい人向け

## 一番シンプルな書き方

```sh
ord () {
    printf '%d' "'$1"; 
}

chr () {
    echo -e "\\U$(printf '%x' $1)"
    # もしくは
    # printf "\\U$(printf "%x" $1)"      # POSIX等、互換性はこの方がいいだろう
}

ord A # 65
chr 65 # A
```

## 複数になると?

```sh
# 与えられた文字列をすべて文字コードにする ords 関数
ords () {      
    str=$1
    args=$(echo "$str" |  sed "s/./'& /g")
    format=$(echo "$str" |  sed "s/./%d /g")
    printf "$format" $args \
    | sed -e "s/^0 /32 /g" -e "s/ 0/ 32/g"  
}

ords 'Hello!, こんにちは'
# 72 101 108 108 111 33 44 32 12371 12435 12395 12385 12399

chrs () {
    str="$1"
    format=$(echo "$str" | sed 's/[0-9]\+/%x/g')
    printf $(echo " $(printf "$format" $str)" | sed 's/[[:space:]]/\\U/g')
}

chrs '72 101 108 108 111 33 44 32 12371 12435 12395 12385 12399'
# Hello!, こんにちは
```

# 解説
## `ord` について

`ord` では `printf` を用いて数値を文字に変換している。shell の `printf` はC言語の `printf` と似たユーザインタフェースを持っているのである。   

しかしこれはよく考えると変である。`ord A` の場合、変数を展開すると `printf '%d' "'A"` である。`%d` と言えば整数を指定するフォーマット指定子である。しかしそのあとにきているのは`A`という文字である。さらによく見ると `'A` とかいう変なクオーテーションがついている。     
このスニペット自体はまあまあ広く知られているようで、`bash ord` とかで検索をすると似たようなものがたくさん出てくる。しかしクオーテーションについては、`printf` の `man` にも記載がなく、なかなかこのクオーテーションの秘密にたどり着くことができない。

だが、 [The Open Group の "Shell & Utilities > `printf`" のページ](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html) に以下のようにあった

> The argument operands shall be treated as strings if the corresponding conversion specifier is b, c, or s, and shall be evaluated as if by the strtod() function if the corresponding conversion specifier is a, A, e, E, f, F, g, or G. Otherwise, they shall be evaluated as unsuffixed C integer constants, as described by the ISO C standard, with the following extensions:
> 
> - A leading `<plus-sign>` or `<hyphen-minus>` shall be allowed.
> 
> - 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.
> 
> - Suffixed integer constants may be allowed.

意訳:
> 指定子が b, c, s (訳注: 文字/文字列を出力するもの) だったら引数は文字列として解釈されるよ。  
> A, e, E, f, F, g, G だったら `strtod()` (訳注: C言語で数値を表す文字列を受け取り、浮動小数点実数を返す) のようにふるまうよ
> それ以外は以下の拡張を含むCの整数型として引数を解釈するよ。:
> - 先頭の `+`, `-` は許容されるよ
> - 先頭がシングルクォーテーションもしくはダブルクォーテーションの場合、そのあとの文字の文字コードの数値として解釈するよ
> - 接尾辞付き整数定数が許される場合があるよ

ここでは指定子は `%d` なので "Cの整数型として引数を解釈" し、その引数の先頭にシングルクォーテーションがあるため、その引数の文字コードが整数として渡されているのである。

## `chr` について

`chr` は簡単に言うと 
1. `printf "%x" <数値>` で16進数に変換 
    1. `%x` は数値を8進数に変換する指定子。 
2. `echo -e "<\U + 8桁16進数表記>"` で8進数をエスケープシーケンスとして解釈
    1. `echo` でエスケープシーケンスを有効にすると `\U` から始まる数字は8桁の16進数表記の文字コードとして解釈される
    2. もしくは `printf "\\U$(printf "%x" <10進数数値>)"` でも良い
        1. これも 16進数でやり取りしている。互換性はこの方がよいだろう。(エスケープシーケンスは互換性の問題が多い)
        2. 参考サイトの多くは `"%o"` つまり8進数でやり取りしていたが、それでは日本語などが扱えない。逆に16進数として扱い、`\U` にすればサロゲートペア(絵文字など `😄` )も扱える.

ということをしている。   
`chr 65` を例に取ると `printf "%x" 65` で `65` を16進数表記の `41` に変換、次に `echo -e "\U41"` で `A` に変換している。

`printf` 1つだけでも行けそうな気がするが、shell の `printf` は `%c` に整数を渡すことができない。

互換性に不安がある場合は `echo -e` ではなく `printf "\\$(printf "%o" <10進数数値>)` のようにしても良いかもしれない


# ちなみに... 複数になると?

ちなみに例えば複数のアルファベットをすべて文字コードに変換したいときには、`printf` のフォーマットが `printf <format> [arguments]` なので、アルファベットの数だけ `%d` を書いて、アルファベットは各々別の引数として渡せばよい。  


```sh
printf "%d %d %d %d" \'A \'B \'C \'D
# 65 66 67 68


# 与えられた文字列をすべて文字コードに(ただしスペースは 0 とされる)
str="Hello!"
args=$(echo "$str" |  sed "s/./'& /g")      # `Hello!` => `'H 'e 'l 'l 'o '!`
format=$(echo "$str" |  sed "s/./%d /g")    # 文字数の数だけ "%d" を作成
printf "$format" $args      # zsh では単語分割がうまくいかないことに注意。`${=args}` のようにするらしい
# 72 101 108 108 111 33

# もし空白も評価したいなら 0 を 32 に変換する(ただしタブと空白の区別はつかない)
str="Hello!   World!"
args=$(echo "$str" |  sed "s/./'& /g")
format=$(echo "$str" |  sed "s/./%d /g")
printf "$format" $args \
| sed -e "s/^0 /32 /g" -e "s/ 0/ 32/g"  # 0 を 32 に変換
```
関数にすると: 
```sh
# 与えられた文字列をすべて文字コードにする ord 関数
ords () {      
    str=$1
    args=$(echo "$str" |  sed "s/./'& /g")
    format=$(echo "$str" |  sed "s/./%d /g")
    printf "$format" $args \
    | sed -e "s/^0 /32 /g" -e "s/ 0/ 32/g"  
}

ords 'Hello!, こんにちは'
# 72 101 108 108 111 33 44 32 12371 12435 12395 12385 12399

# ただしこれらは絵文字などのサロゲートペアには対応できない
```

逆に 空白区切りの数字を文字列に変換するには以下のようにする

```sh
str="72 101 108 108 111 33"

format=$(echo "$str" | sed 's/[0-9]\+/%x/g')  # 数値の数だけ '%x' を作る
unichr=$(echo " $(printf "$format" $str)" |   # 16進数表記
  sed 's/[[:space:]]/\\U/g')                # それぞれの数字の先頭に '\U' をつける 
printf $unichr
```
関数化:
```sh
chrs () {
    str="$1"
    format=$(echo "$str" | sed 's/[0-9]\+/%x/g')
    printf $(echo " $(printf "$format" $str)" | sed 's/[[:space:]]/\\U/g')
}

chrs '72 101 108 108 111 33 44 32 12371 12435 12395 12385 12399'
# Hello!, こんにちは
```



#### 参考文献

- "Bash script to get ASCII values for alphabet - Unix & Linux Stack Exchange".     
	[https://unix.stackexchange.com/questions/92447/bash-script-to-get-ascii-values-for-alphabet](https://unix.stackexchange.com/questions/92447/bash-script-to-get-ascii-values-for-alphabet) 
    - このページがなければ解決できなかった。感謝.

<br>

- "ORD and CHR a file in Bash - Stack Overflow".     
	[https://stackoverflow.com/questions/28476611/ord-and-chr-a-file-in-bash](https://stackoverflow.com/questions/28476611/ord-and-chr-a-file-in-bash) 

<br>

- "Bash script to get ASCII values for alphabet - Unix & Linux Stack Exchange".     
	[https://unix.stackexchange.com/questions/92447/bash-script-to-get-ascii-values-for-alphabet](https://unix.stackexchange.com/questions/92447/bash-script-to-get-ascii-values-for-alphabet) 

 <br>

- "コラム - WSLで始めるUbuntu | 第12回　エスケープシーケンスについて知ろう｜CTC教育サービス 研修/トレーニング".     
	[https://www.school.ctc-g.co.jp/columns/miyazaki/miyazaki12.html](https://www.school.ctc-g.co.jp/columns/miyazaki/miyazaki12.html) 

 <br>

- "printf コマンド – フォーマット形式で文字列を表示  |  Linuxコマンド.NET".     
	[https://linuxcommand.net/printf/](https://linuxcommand.net/printf/) 

<br>

- The Open Group. "printf".     
	[https://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html)