Linuxシェルスクリプトで文字列の長さを取得する方法

Bashの変数に入った文字列の長さは、パラメーター展開${#variable}で取得できます。外部コマンドを起動せず、空白を含む文字列も扱える基本的な方法です。ただし「長さ」が文字数、バイト数、画面上の文字幅、配列要素数のどれかで答えは変わります。また未設定変数と空文字、改行を含む値、Unicode、秘密情報の検証では注意が必要です。本稿ではBashスクリプトを前提に、用途別の測り方と安全な入力処理を整理します。

目次

基本は${#variable}

変数へ値を代入し、${#text}を算術値として表示します。代入の等号の前後に空白を入れず、表示するときは変数展開をダブルクォートで囲みます。次の例ではASCII文字列の長さは5です。printfは書式と値を分けられ、値をオプションや書式として誤解しにくいため、echoより予測しやすい表示に向きます。

text='Linux'
length=${#text}
printf 'length=%d\n' "$length"

${#text}はBashの構文であり、すべての/bin/shで同じ機能やUnicode処理が保証されるわけではありません。スクリプトのshebangと起動方法を確認し、sh script.shでBashスクリプトを別シェルへ渡しません。POSIXシェルだけを対象にする場合は、その環境の仕様と必要な単位に応じて別実装を検討します。

空白を含む文字列

空白も文字列の一部として数えられます。前後の空白を保持したいなら、代入時と受け渡し時の引用が重要です。引用していない変数展開は単語分割やファイル名展開を受けるため、長さを求める式以外の場所でも"$text"とします。シェル変数はNUL文字を保持できない点も、任意のバイナリデータを扱う設計では重要です。

text='two words'
printf 'characters=%d\n' "${#text}"

未設定と空文字を区別する

空文字を代入した変数の長さは0です。未設定変数も通常の展開では0に見えることがありますが、set -u(nounset)が有効だと未設定参照がエラーになります。入力必須なら長さを測る前に設定有無を確認します。[[ -v name ]]はBashで変数が設定されているかを調べられます。値を表示せず、エラー文にも秘密を含めないようにします。

if [[ -v text ]]; then
  printf 'length=%d\n' "${#text}"
else
  printf '%s\n' 'text is unset' >&2
fi

引数を受け取るスクリプトでは、${1-}で未指定時の安全な既定値を用意できますが、「引数なし」と「空文字を明示」の区別が必要なら$#で個数も確認します。${1:-default}は未設定だけでなく空文字にもdefaultを使うため、ハイフンだけの形と意味が異なります。

コマンドライン引数の長さを検証する

1番目の引数が1〜64文字か確認する例です。先に引数個数を検証し、値そのものはログへ出しません。長さの上限だけでは安全な入力にならないため、用途に応じて許可文字、正規化、一意性、予約語も確認します。ファイル名、ホスト名、ユーザーIDではそれぞれ別の制約があります。

if (( $# != 1 )); then
  printf '%s\n' 'usage: script VALUE' >&2
  exit 2
fi
value=$1
if (( ${#value} < 1 || ${#value} > 64 )); then
  printf '%s\n' 'VALUE must be 1 to 64 characters' >&2
  exit 2
fi

秘密鍵、アクセストークン、パスワードをコマンドライン引数で渡すと、プロセス一覧や履歴へ露出する可能性があります。秘密情報は承認された秘密管理、保護されたファイル記述子、対話入力などを使います。長さだけをログへ出しても、形式や種類を推測する材料になることがあるため、本当に監視に必要かを検討します。

日本語とロケール

Bashの${#text}は、現在のロケールがマルチバイト文字を認識する環境では、日本語を文字単位として数えるのが一般的です。一方、Cロケールではバイトとして数えるような違いが出ます。たとえば「日本語」はUTF-8で画面上3文字ですが9バイトです。実行環境のLANGやLC_CTYPE、Bash版により結果が変わり得るため、入力仕様で「Unicode文字数」か「UTF-8バイト数」かを明記します。

text='日本語'
printf 'current-locale length=%d\n' "${#text}"
LC_ALL=C printf 'bytes in current value=%d\n' "${#text}"

最後の例は、同じシェル内のパラメーター展開がいつロケールを参照するかや環境の実装差を確認するテストとしては注意が必要です。確実にバイト数を求めるなら、値を標準入力へ渡してLC_ALL=C wc -cを使う方法が分かりやすいです。ただし外部コマンドが起動し、コマンド置換が末尾改行を除くため、データの扱いを確認します。

バイト数をwc -cで測る

wc -cはバイト数を数えます。printfで文字列を余計な改行なしに渡します。echoは改行を追加し、-nやバックスラッシュの扱いが実装や設定で異なるため、バイト数計測にはprintfを使います。先頭がハイフンの値も書式%sの引数として安全に渡せます。

text='日本語'
bytes=$(LC_ALL=C printf '%s' "$text" | wc -c)
printf 'bytes=%d\n' "$bytes"

非常に大きな文字列をシェル変数に保持するのはメモリや引数制限の面で不向きです。ファイルのバイト数なら値を変数へ読み込まず、wc -c -- fileやstatで測ります。テキスト全体をコマンド置換に入れるとNULを保持できず、末尾改行も失われます。データがファイルならストリームのまま処理します。

改行、タブ、制御文字

Bash変数は改行とタブを保持できます。${#text}ではそれらも文字として数えます。コマンド置換$(command)は出力末尾の改行を取り除くため、元データの長さをそのまま測れません。ファイルから1行読む場合もreadは区切り文字を値へ含めず、バックスラッシュを扱うため、通常はIFS= read -rを使います。最後の改行がない行も別途扱います。

IFS= read -r text < ./one-line.txt || true
printf 'line content length=%d\n' "${#text}"

この例の|| trueは、最終行に区切り改行がない場合でもreadが値を設定し得るという特殊な状況を単純化しており、一般のエラーまで無視する書き方として使い回してはいけません。実運用ではファイルを開けないエラーとEOFを分けます。複数行ファイルなら一行ずつ処理し、全体を巨大な変数へ入れません。

Unicodeの「見た目の1文字」

絵文字、結合文字、異体字セレクター、ゼロ幅接合子を含む文字列では、Unicodeコードポイント数と利用者が見る書記素数が一致しません。たとえばアクセント付き文字は1コードポイントの場合と、基底文字+結合記号の2コードポイントの場合があります。家族絵文字のように複数コードポイントが一つの絵として表示される例もあります。Bashの長さだけでUIの「最大10文字」を厳密に実装せず、Unicode書記素分割に対応したアプリケーション言語やライブラリを使います。

表示幅はさらに別の値

端末で占める列数は、文字数やバイト数とも異なります。全角文字は2列、結合文字は0列、絵文字は端末ごとに1または2列として扱われる場合があります。表をそろえるために${#text}で空白数を計算すると崩れることがあります。端末幅を扱うライブラリ、ロケール、フォント、絵文字版を考慮します。機械可読出力では空白整形よりタブ区切り、CSV、JSONなど明示形式を使います。

配列の長さと要素の文字列長

Bash配列では式が似ていても意味が変わります。${#array[@]}は要素数、${#array[0]}は添字0の要素の文字列長です。${#array}は添字0相当として扱われるため、要素数を求める式にはしません。疎な添字配列では、最大添字+1と要素数も一致しません。

items=('alpha' '日本語' 'two words')
printf 'elements=%d\n' "${#items[@]}"
printf 'second element characters=%d\n' "${#items[1]}"

連想配列でも${#map[@]}は要素数です。キーや値を反復するときは"${!map[@]}"と"${map[@]}"を引用します。配列展開の引用を外すと、各要素の空白やワイルドカードが再解釈されます。

長さを使う部分文字列処理

文字列長は入力検証だけでなく、Bashの部分文字列展開${text:offset:length}と組み合わせられます。しかし、負のオフセットは:-の既定値構文と曖昧にならないよう空白が必要で、配列や位置パラメーターでは挙動が異なります。Unicode書記素を途中で分割する危険もあります。ユーザー向け文字列の切り詰めはBashだけで行わず、適切なUnicode処理を使います。

テストすべき境界値

  • 未設定、空文字、1文字、上限ちょうど、上限を1超える値。
  • 先頭・末尾空白、複数空白、タブ、改行、バックスラッシュ、先頭ハイフン。
  • ASCII、日本語、結合文字、絵文字、異なる正規化形式。
  • Cロケール、UTF-8ロケール、対象の本番ロケール。
  • 配列の空要素、疎な添字、連想配列、要素内の空白。
  • 非常に大きな入力、ファイル入力、末尾改行あり・なし。

単純なBash文字列なら${#variable}、バイト数ならLC_ALL=C wc -c、画面上の書記素や表示幅なら専用ライブラリというように、求める単位で手段を選びます。未設定と空文字、引用、ロケールを明示すれば、長さ判定の移植性と安全性を大きく改善できます。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次