📝
シェル変数の長さの求め方
まとめ
シェル変数 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
変数に入ってる文字列のサイズを知りたいのであれば
LC_ALL=C
+${#VAR}
だけで十分です、bash の場合、変数に
-n
や-e
という文字列が入ってる場合に正しく機能しません。macOS の bash は POSIX モード (set -o posix
または/bin/sh
) のecho -n
は-n
という文字列を出力します。また bash 以外のシェルはエスケープシーケンスの解釈が異なり移植性がありません。printf
を使えば解決しますが、それに加えてwc
やxargs
を使うのであれば遅いだけなのでLC_ALL=C
+${#VAR}
の方が優れています。コメントありがとうございます。考慮が足りてなかったですね。
ちなみにシェルスクリプト内で LC_ALL の状態を維持したい場合には以下のような感じでしょうか?
より短く書くとしたらこうでしょうか。サブシェルも遅いですが bash を起動するよりマシですし。
速度を気にするなら関数と
local
を使うのが良いかもしれません。なお bash 以外も考慮するなら ksh では
local
が使えない(代わりにtypeset
を使う)という問題があったり、改めて調べてみたら yash や busybox 1.32.0以降 では${#VAR}
ではうまく動きませんでした。もし他のシェルを考慮するならprintf '%s' "$VAR" | wc -c
の方が良いかもしれません。十分調べてないので追試が必要です。サブシェルを使った書き方はシンプルですね。
関数と local の組み合わせであれば fork のオーバーヘッドすらかからないってことですね。
いただいた情報を元に本文を修正しました。
色々勉強になりました。ありがとうございます。