HOWTO · Bash

Bash で部分文字列を抽出する

このチュートリアルでは、cut コマンド、サブストリング拡張、および内部フィールドセパレーターを使用して bash でサブストリングを抽出するさまざまな方法を示します。

このページの内容

Bash で文字列の一部を取り出すには、入力に合った操作を選びます。cut は区切り文字で分けられたフィールドを選択し、Bash の部分文字列展開は位置を指定して文字を取り出します。また、IFS による分割では、区切られたテキストを個別に処理できるフィールドにします。これらの方法は関連していますが、得られるデータの形は同じではありません。

区切りフィールドを cut で取り出す

値が一定の区切り文字で分けられていて、特定のフィールドが必要な場合は cut を使います。-d オプションで区切り文字を指定し、-f オプションで1から数えるフィールド番号を選びます。この例ではアンダースコアが単語を区切っているため、3番目のフィールドは three です。変数展開を引用符で囲んで printf に渡すと、値を1行の入力として保てます。また、安全でない引用符なしの echo $numbers という書き方も避けられます。

次の例を実行します。表示ラベルと出力は英語のままです。

numbers='one_two_three_four_five'
substring=$(printf '%s\n' "$numbers" | cut -d '_' -f 3)
printf 'substring: %s\n' "$substring"
substring: three

cut が選択するのはフィールドであり、任意の文字範囲ではありません。区切り文字とフィールドの位置が分かっている構造化テキストに適しています。位置で範囲を指定したい場合は、代わりに部分文字列展開を使います。

Bash の部分文字列展開で文字を取り出す

Bash では ${variable:offset:length} の形式で部分文字列を展開できます。オフセットは0から始まり、長さは取り出す文字数です。たとえば one_two_three_four_five では、オフセット 4 は two の t から始まり、長さ 3 でその3文字を選びます。区切り文字を含め、元の文字列にあるすべての文字がオフセットの計算対象です。

次の例を実行します。コード内の表示ラベルと出力は英語のままです。

numbers='one_two_three_four_five'
substring=${numbers:4:3}
printf 'substring: %s\n' "$substring"
substring: two

文字の位置と範囲が分かっている場合に直接使える方法です。オフセットはフィールド番号とは異なります。取り出したい部分より前のテキストの長さが変わると、同じオフセットでも別の文字が選ばれることがあります。名前付きまたは番号付きのフィールドに分かれた値なら、cut または IFS のほうが変更に対応しやすい場合があります。

IFS で文字列をフィールドに分割する

IFS は Bash の内部フィールドセパレーターです。引用符で囲まれていない展開の後に行われる単語分割を制御し、組み込みコマンド read が入力を分ける方法にも影響します。一時的に IFS の値を設定すると、区切り文字を含む文字列を位置パラメーターに分割できます。$1、$2、$3 はそれぞれ1番目、2番目、3番目のパラメーターを指し、配列の添字を表す構文ではありません。

この例では関数内だけで分割し、引用符で囲まれていない展開を行う前にパス名展開を無効にします。単語分割の後にはパス名展開が行われるため、フィールドに *、?、角括弧を使ったパターンが含まれていると、シェルが一致するファイル名に置き換えることがあります。この方法は、区切り文字が分かっている管理された入力に使います。区切り文字が空白文字以外の場合は、read -a で入力を配列に読み込む際に IFS をその文字に設定し、フィールドを使うときは配列展開を引用符で囲みます。既定の IFS では、空白、タブ、改行でフィールドが分割されます。IFS を別の区切り文字に変更すると、その文字で入力が分割されます。ただし、選んだ区切り文字を含むフィールドは保持されず、行単位で処理する read -a ではフィールド内の改行も保持できません。そのような形式には、read -a だけに頼らず、形式に対応したパーサーを使ってください。

次の例を実行します。コード内の表示ラベルと出力は英語のままです。

numbers='one_two_three_four_five'
extract_second() (
  local IFS=_
  set -f
  set -- $numbers
  printf 'second field: %s\n' "$2"
)
extract_second
second field: two

関数内で set -- を実行すると、その関数の位置パラメーターが分割後のフィールドに置き換わります。関数を抜けると、呼び出し元の位置パラメーターに戻ります。区切り文字はローカルに設定されるため、スクリプト全体の IFS は恒久的には変わりません。

用途に合った方法を選ぶ

既知の区切り文字で分けられたテキストから、たとえばアンダースコアで区切られた3番目の値を選ぶなら cut を使います。既知の0始まりのオフセットと長さで文字を取り出すなら、Bash の部分文字列展開を使います。各フィールドを後続の処理で個別のシェル単語または位置パラメーターとして扱うなら、IFS による分割を選びます。

予測可能な入力なら、どの方法も簡潔に書けます。cut に渡す入力は引用符で囲み、部分文字列展開には波括弧を使います。IFS による分割で引用符なしの展開を使うと、パス名展開も起こり得る点に注意してください。区切り文字自体がデータに含まれる場合や、フィールドにシェルパターン文字が含まれる場合は、引用符で囲んだ配列要素としてフィールドを保持できる解析方法を使い、引用符なしの単語分割に頼らないでください。この違いを理解すると、フィールド番号、文字オフセット、位置パラメーターを、同じ意味の「部分文字列」として扱わずに済みます。