📝

シェル変数の長さの求め方

2021/04/29に公開
4

まとめ

シェル変数 VAR の長さを出力したい場合、

  • echo ${#VAR}

とすれば良い。

ただし、これで出力されるのは文字数であり、マルチバイト文字を含む場合はバイト数とは一致しない。バイト数を出力したい場合は、

  • LC_ALL=C とした上で echo ${#VAR}

とする。

解説

${#VAR} によりシェル変数 ${VAR} の長さを求めることができる。

$ VAR=foobar
$ echo ${VAR}
foobar
$ echo ${#VAR}
6

この例のようにASCII文字だけを含む場合には、ここで出力される「長さ」は文字数であり、かつ、バイト数でもある。

一方で、マルチバイト文字を含む場合には ${#VAR} は文字数であり、バイト数とは異なる。

$ echo ${LANG}
en_US.UTF-8
$ env | grep LC_
$ VAR=🍺
$ printf '%s' "${VAR}" | xxd
00000000: f09f 8dba                                ....
$ echo ${#VAR}
1

文字数をカウントする際にはロケールに沿ったエンコーディングが使われており、ロケールをCにするとバイト数になる。

$ LC_ALL=C
$ echo ${#VAR}
4

シェルのロケールを変更したくない場合はサブシェルで実行すればよい

$ (LC_ALL=C; echo ${#VAR})
4

ロケールのことを気にせずバイト数を求めたい場合には wc -c でカウントすれば良い

$ printf '%s' "${VAR}" | wc -c
4

補足1

macOS などでは wc -c の出力に空白が入る。それを除去して整数値のみを出力するには xargs を使うとよい。

$ VAR=🍺
$ printf '%s' "${VAR}" | wc -c
       4
$ printf '%s' "${VAR}" | wc -c | xargs
4

補足2

最初、printf ではなく echo の出力をパイプしていたが、それだと変数値によってはうまく機能しない。

問題ない場合:

$ VAR=🍺
$ echo -n ${VAR} | wc -c
4

クォートすれば何とかなる場合:

$ VAR="-n 🍺"
$ echo -n ${VAR} | wc -c
4
$ echo -n "${VAR}" | wc -c
7

クォートしても駄目な場合:

$ VAR="-n"
$ echo -n "${VAR}" | wc -c
0

確認環境

$ bash --version
GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ zsh --version
zsh 5.8 (x86_64-ubuntu-linux-gnu)
$ wc --version
wc (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Paul Rubin and David MacKenzie.

Discussion

ko1nksmko1nksm

変数に入ってる文字列のサイズを知りたいのであれば LC_ALL=C + ${#VAR} だけで十分です、

bash の場合、変数に -n-e という文字列が入ってる場合に正しく機能しません。macOS の bash は POSIX モード (set -o posix または /bin/sh ) の echo -n-n という文字列を出力します。また bash 以外のシェルはエスケープシーケンスの解釈が異なり移植性がありません。printf を使えば解決しますが、それに加えて wcxargs を使うのであれば遅いだけなので LC_ALL=C + ${#VAR} の方が優れています。

Yoichi NakayamaYoichi Nakayama

コメントありがとうございます。考慮が足りてなかったですね。
ちなみにシェルスクリプト内で LC_ALL の状態を維持したい場合には以下のような感じでしょうか?

VAR=🍺
LC_ALL=C VAR="${VAR}" bash -c 'echo ${#VAR}'
ko1nksmko1nksm

より短く書くとしたらこうでしょうか。サブシェルも遅いですが bash を起動するよりマシですし。

VAR=🍺
(LC_ALL=C; echo ${#VAR})

速度を気にするなら関数とlocal を使うのが良いかもしれません。

VAR=🍺
count() {
  local LC_ALL=C
  echo ${#VAR}
}
count

なお bash 以外も考慮するなら ksh では local が使えない(代わりに typeset を使う)という問題があったり、改めて調べてみたら yash や busybox 1.32.0以降 では ${#VAR} ではうまく動きませんでした。もし他のシェルを考慮するなら printf '%s' "$VAR" | wc -c の方が良いかもしれません。十分調べてないので追試が必要です。

Yoichi NakayamaYoichi Nakayama

サブシェルを使った書き方はシンプルですね。
関数と local の組み合わせであれば fork のオーバーヘッドすらかからないってことですね。
いただいた情報を元に本文を修正しました。
色々勉強になりました。ありがとうございます。