bash、awk、sed、tr で小文字、大文字への変換方法と、それぞれのベンチマーク結果を紹介します。

検証環境

検証した環境は以下のとおりです。

  • OS
    • Ubuntu 20.04.1 LTS
  • bash
    • GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
  • gawk
    • GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.2.0)
  • mawk
    • 1.3.4 20200120
  • sed
    • sed (GNU sed) 4.7
  • tr
    • tr (GNU coreutils) 8.30

bash

ドキュメントに以下のように書かれています。

The ‘^’ operator converts lowercase letters matching pattern to uppercase; the ‘,’ operator converts matching uppercase letters to lowercase. The ‘^^’ and ‘,,’ expansions convert each matched character in the expanded value;

# to upper
$ val="AAA"
$ echo ${val,,}
aaa

# to lower
$ val="aaa"
$ echo ${val^^}
AAA

awk

ドキュメントに tolowertoupper という関数があると書かれています。

# to lower
$ echo AAA | awk '{print tolower($0)}'
aaa

# to upper
$ echo aaa | awk '{print toupper($0)}'
AAA

sed

ドキュメント に以下のように書かれています。

\L Turn the replacement to lowercase until a \U or \E is found,

\U Turn the replacement to uppercase until a \L or \E is found,

# to lower
$ echo AAA | sed -e 's/\(.*\)/\L\1/'
aaa

# to upper
$ echo aaa | sed -e 's/\(.*\)/\U\1/'
AAA

ただし、BSD sed では \L\U は使えません。

tr

ドキュメント に以下のように書かれています。

[:lower:] all lower case letters

[:upper:] all upper case letters

# to lower
$ echo AAA | tr '[:upper:]' '[:lower:]'
aaa

# to upper
$ echo aaa | tr '[:lower:]' '[:upper:]'
AAA

以上が、小文字、大文字への変換方法です。

ベンチマーク

小文字、大文字への変換方法を紹介したので、次はそれぞれの実行速度をベンチマークします。ベンチマークには hyperfine を使います。

スクリプトの生成

色々な長さの文字列に対して変換処理するために、以下のようなスクリプトを書いて任意の長さの文字列に対してコマンドを実行するスクリプトを生成します。

#!/bin/bash

n=${1}

str=$(perl -e "print 'a' x ${n}")

cat <<EOF > bash-n${n}.sh
v=${str}; echo \${v^^}
EOF

cat <<EOF > tr-n${n}.sh
echo ${str} | tr '[:lower:]' '[:upper:]'
EOF

cat <<EOF > sed-n${n}.sh
echo ${str} | sed -e 's/\(.*\)/\U\1/'
EOF

cat <<EOF > gawk-n${n}.sh
echo ${str} | gawk '{print toupper(\$0)}'
EOF

cat <<EOF > mawk-n${n}.sh
echo ${str} | mawk '{print toupper(\$0)}'
EOF

cat <<EOF > benchmark-n${n}.sh
hyperfine --warmup 3 --min-runs 1000 "bash bash-n${n}.sh" "bash tr-n${n}.sh" "bash sed-n${n}.sh" "bash gawk-n${n}.sh" "bash mawk-n${n}.sh" --export-markdown benchmark-result-n${1}.md
EOF

上記のスクリプトを gen-scripts.sh としたとき、gen-scripts.sh 10 のように実行すれば、aaaaaaaaaa(length = 10) に対してコマンドを実行するスクリプトと hyperhine でそれらをベンチマークするスクリプトを生成することができます。面倒 小文字への変換と大文字への変換で結果が変わることはないと思われるので、大文字への変換でのみベンチマークをします。

結果

文字列の長さは 10、100、1,000、10,000、100,000、1000,000 でベンチマークしました。*-n10.sh は文字列の長さが 10、*-n100.sh は文字列の長さが 100、… となっています。ベンチマークは Ubuntu 20.04.1 で行いましたが、最初からインストールされている awk が mawk だったので、gawk でも計測しています。

CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n10.sh2.2 ± 0.51.56.31.00
bash tr-n10.sh3.1 ± 0.62.49.61.43 ± 0.44
bash sed-n10.sh3.3 ± 0.52.76.71.55 ± 0.44
bash gawk-n10.sh3.9 ± 0.73.18.51.81 ± 0.54
bash mawk-n10.sh3.3 ± 0.62.56.71.52 ± 0.46
CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n100.sh1.6 ± 0.31.13.31.00
bash tr-n100.sh2.6 ± 0.51.86.11.70 ± 0.45
bash sed-n100.sh2.8 ± 0.52.25.61.79 ± 0.46
bash gawk-n100.sh3.4 ± 0.62.66.82.16 ± 0.55
bash mawk-n100.sh2.7 ± 0.52.06.41.70 ± 0.45
CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n1000.sh2.3 ± 0.51.67.21.00
bash tr-n1000.sh3.2 ± 1.32.419.01.44 ± 0.66
bash sed-n1000.sh4.6 ± 2.22.824.12.02 ± 1.08
bash gawk-n1000.sh4.1 ± 0.93.18.91.83 ± 0.57
bash mawk-n1000.sh3.2 ± 0.42.66.31.40 ± 0.37
CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n10000.sh3.1 ± 0.42.47.31.00
bash tr-n10000.sh3.4 ± 0.62.66.11.11 ± 0.25
bash sed-n10000.sh5.7 ± 26.53.8842.71.84 ± 8.62
bash gawk-n10000.sh4.4 ± 0.73.47.61.44 ± 0.31
bash mawk-n10000.sh3.4 ± 0.42.75.51.10 ± 0.21
CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n100000.sh17.1 ± 1.414.829.41.63 ± 0.20
bash tr-n100000.sh10.5 ± 1.08.915.81.00
bash sed-n100000.sh21.2 ± 1.619.039.22.02 ± 0.24
bash gawk-n100000.sh12.8 ± 1.011.019.11.22 ± 0.15
bash mawk-n100000.sh11.9 ± 0.710.718.31.13 ± 0.13
CommandMean [ms]Min [ms]Max [ms]Relative
bash bash-n1000000.sh144.8 ± 11.2126.3206.81.89 ± 0.19
bash tr-n1000000.sh77.4 ± 6.967.1143.51.01 ± 0.11
bash sed-n1000000.sh172.3 ± 13.6151.1276.52.24 ± 0.23
bash gawk-n1000000.sh84.3 ± 5.475.4103.71.10 ± 0.10
bash mawk-n1000000.sh76.8 ± 5.068.8128.31.00

結果をまとめると、

  • 文字列の長さが 1,000 までは、bash の変数展開による変換が最も速い
  • 文字列の長さが 10,000 になると、bash の変数展開、tr、mawk がほとんど同じ程度の速度になる
  • 文字列の長さが 100,000 以上になると bash の変数展開が遅くなり、tr、mawk、gawk のほうが速くなる
  • tr と mawk は常にほとんど同じ程度の速度になる
  • sed はすべての結果において一番遅い
  • 処理が単純だったからか、gawk と mawk での速度差はあまりなかった

まとめ

コマンドで小文字、大文字に変換する方法とベンチマークの結果を紹介しました。 単純に速度で考えると、文字列の長さに関係なく一貫して速かった tr か mawk が良さそうですが、top と bottom で 2 倍程度の速度差しかないため、実行回数が多くなければ好きなコマンドを使うので問題ないと思います。また、hyperfine では CPU とメモリ使用量は計測されないため、それらも含めて考えるとどれを選択するかが変わりそうです。