Bashシェルスクリプトで文字列を分割(split)する方法いろいろ

2023-02-09Bash,Linux

はじめに

最近、Bash で特定の文字 ( あるいは文字列 ) で連結されている文字列を分割して配列に代入したい場合の方法について聞かれることがあったのでまとめました。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

大きく 2 つのケースを考えてみる

文字列の分割 ( split ) について、大きく 2 つのケースを考えてみます。

  • 単一の文字で分割
  • 複数の文字 ( 文字列 ) で分割

単一の文字で分割する方法

1.変数参照時に置換する

Bash では、スペースを区切り文字として取り扱います。
したがって、区切り文字をスペースに置換し、 (..) で囲んで配列にします。
最も簡潔に実現できます ( 問題点については後述 ) 。

$ STR="I,am,a,pen"

# split
$ ARR=(${STR//,/ })

# 配列の1番目の要素
$ echo ${ARR[0]}
I

# 配列の2番目の要素
$ echo ${ARR[1]}
am

# ループで1要素ずつ出力
$ for S in "${ARR[@]}"; do echo "$S"; done
I
am
a
pen

※ただし、この方法には問題があります。本来区切り文字でないスペースが含まれている場合、スペースで区切られてしまいます。

# penの部分が先程と異なり、"p e n" とスペースを含んでいる
$ STR="I,am,a,p e n"

# split
$ ARR=(${STR//,/ })

# 配列の4番目の要素
$ echo "${ARR[3]}"
p

# ループで1要素ずつ出力
$ for S in "${ARR[@]}"; do echo "$S"; done
I
am
a
p
e
n

2.一時的に環境変数 IFS を変更する

環境変数 IFS ( Internal Filed Separator ) には、「スペース」「タブ」「改行」 ( \t\n )といった値が初期設定されています。これらが文字の区切りとして認識されます。

IFS に区切り文字としたい値を設定することで、単語の区切りを任意に変更できます。

# "p e n" とスペースを含んでいる
$ STR="I,am,a,p e n"

# 区切り文字として","を指定
$ IFS=, ARR=(${STR})

# 配列の1番目の要素
$ echo ${ARR[0]}
I

# 配列の4番目の要素
$ echo ${ARR[3]}
p e n

# ループで1要素ずつ出力
$ for S in "${ARR[@]}"; do echo "$S"; done
I
am
a
p e n

3.一時的に環境変数 IFS を変更し、 read で配列にロード

こちらは先の IFS 環境変数を一時的に書き換える方法の亜種です。
別解と思っていただければよいでしょう。

$ STR="I,am,a,p e n"

# readコマンドで変数の文字列を標準入力として`read`に渡す
$ IFS=, read -ra ARR <<< "${STR}"

# ループで1要素ずつ出力
$ for S in "${ARR[@]}"; do echo "$S"; done
I
am
a
p e n

複数の文字 ( 文字列 ) で分割

こちらは先の 単一の文字で分割する方法 を応用することで簡単に実現できます。

以下の例は、 , カンマの代わりに abc という 文字列 で連結された文字列を分割する例になります。

$ STR="Iabcamabcaabcp e n"

# 区切り文字として","を指定 (区切り文字は,である必要はない)
$ IFS=, ARR=(${STR//abc/,})

# ループで1要素ずつ出力
$ for S in "${ARR[@]}"; do echo "$S"; done
I
am
a
p e n

区切り文字列 を一時的に単一の文字 ( ここでは , ) に変換し、あとは先程見てきたやり方を利用します。

ひとこと

もっと良い方法があれば、ぜひコメントください。

2023-02-09Bash,Linux