Bash 入門

Bash 文字列操作

文字列処理は、Bashスクリプト作成において極めて基礎的かつ重要な要素です。文字列は本質的に文字のシーケンスであり、これらを操作することでテキストデータの処理、ターミナル出力の整形、そして高度に動的なスクリプトの構築が可能になります。本章では、基本的な文字列操作から、定義、アクセス、修正(切り出し/置換)、および比較までを包括的にカバーし、スクリプト内でテキストベースのデータを効率的に扱うためのハードコアなスキルを提供します。

1. 文字列リテラルと変数

1.1 文字列リテラルの定義

Bashにおいて、文字列リテラルはシングルクォートまたはダブルクォートで囲まれた文字のシーケンスです。

シングルクォート ('...'): クォート内の各文字のリテラル値を厳密に保持します(見たままが反映されます)。シングルクォート内では、バリアブル展開(例:$var)やコマンド実行(例:date)は一切行われません。

string1='これは純粋な文字列です'
echo $string1  # 出力: これは純粋な文字列です

string2='変数 x の値は: $x'
echo $string2  # 出力: 変数 x の値は: $x (注意:$x は解析・置換されません)

ダブルクォート ("..."):バリアブル展開やコマンド実行を許可します。$(変数のマーク)やバッククォート `(コマンド置換)などの特殊文字は、Bashによって解析・実行されます。

x=10
string3="変数 x の値は: $x"
echo $string3  # 出力: 変数 x の値は: 10

string4="今日は $(date +%A) です" # バッククォートの代わりに $() を推奨
echo $string4  # 出力: 今日は [曜日] です

1.2 文字列の代入とアクセス

イコール = を使用して、文字列リテラルを変数に代入します。注意:イコールの周囲にスペースを入れてはいけません。

name="John Doe"
message="こんにちは、 $name!"
echo $message  # 出力: こんにちは、 John Doe!

文字列変数の値にアクセスするには、変数名の前に $ を付けるか、${variable_name} を使用します。後者は、変数名の直後に他の文字や数字が続く場合に、Bashが変数名の境界を解析する際の曖昧さを排除するのに非常に役立ちます。

name="Jane"
echo $name       # 出力: Jane
echo ${name}Smith # 出力: JaneSmith ($nameSmith と書くと、Bash は nameSmith という名前の変数を探してしまいます)

2. 文字列の高度な操作

Bashは一連の強力な組み込みパラメータ展開構文を提供しており、awksed などの外部コマンドを呼び出すことなく、日常的な文字列処理のほとんどを完了できます。

2.1 文字列の長さを取得

${#variable_name} を使用して、文字列の文字数を取得できます。

str="Hello"
length=${#str}
echo $length  # 出力: 5

2.2 部分文字列の抽出(スライス)

${variable_name:offset:length} 構文を使用して、長い文字列から部分文字列(サブストリング)を抽出できます。

  • offset (オフセット): 部分文字列の開始位置(インデックスは0から始まります)。
  • length (長さ): 抽出する文字数。このパラメータを省略すると、offset から文字列の末尾までのすべての内容が抽出されます。
str="Bash Scripting"
substring1=${str:0:4}  # インデックス 0 から 4 文字抽出
echo $substring1       # 出力: Bash

substring2=${str:5}    # インデックス 5 から末尾まで抽出
echo $substring2       # 出力: Scripting

substring3=${str:5:6}  # インデックス 5 から 6 文字抽出
echo $substring3       # 出力: Script

2.3 パターンマッチングに基づく部分文字列の削除

Bashは、ワイルドカードパターンに基づいて、文字列の先頭(前方)または末尾(後方)から部分文字列を削除する巧妙な方法を提供しています。これはファイルパスや拡張子の処理によく使われます。

  • ${variable#pattern} (前方一致削除 - 短一致): 文字列の先頭から、pattern に一致する最短部分を削除します。
  • ${variable##pattern} (前方一致削除 - 長一致): 文字列の先頭から、pattern に一致する最長部分を削除します。
  • ${variable%pattern} (後方一致削除 - 短一致): 文字列の末尾から、pattern に一致する最短部分を削除します。
  • ${variable%%pattern} (後方一致削除 - 長一致): 文字列の末尾から、pattern に一致する最長部分を削除します。
file="path/to/my/file.txt.bak"

# 前方一致削除 (最短のプレフィックス一致)
short_prefix=${file#*/}
echo $short_prefix  # 出力: to/my/file.txt.bak (path/ のみが削除されます)

# 前方一致削除 (最長のプレフィックス一致) -> 定番:純粋なファイル名の抽出
long_prefix=${file##*/}
echo $long_prefix  # 出力: file.txt.bak (path/to/my/ が削除されます)

# 後方一致削除 (最短のサフィックス一致) -> 定番:最後の拡張子を削除
short_suffix=${file%.*}
echo $short_suffix  # 出力: path/to/my/file.txt (.bak のみが削除されます)

# 後方一致削除 (最長のサフィックス一致) -> 定番:すべての拡張子を削除
long_suffix=${file%%.*}
echo $long_suffix  # 出力: path/to/my/file (.txt.bak が削除されます)

2.4 文字列の置換

パラメータ展開を使用して、文字列内の特定のコンテンツを置換できます。

  • ${variable/pattern/replacement}: 最初に一致した patternreplacement に置換します。
  • ${variable//pattern/replacement}: 一致したすべての patternreplacement に置換します。
str="apple banana apple"

# 最初に一致した項目を置換
replaced1=${str/apple/orange}
echo $replaced1  # 出力: orange banana apple

# すべての一致項目を置換 (ダブルスラッシュ // に注意)
replaced2=${str//apple/orange}
echo $replaced2  # 出力: orange banana orange

2.5 大文字・小文字の変換

Bashでは文字列の大文字・小文字を簡単に変換できます。
(注意:ネイティブの ^^ および ,, 構文は Bash 4.0 以降でのみ利用可能です。古い macOS デフォルトの Bash を使用している場合は、アップグレードするか代替案を使用してください。)

  • 大文字に変換: ${variable^^}
  • 小文字に変換: ${variable,,}
str="Hello World"

# 大文字に変換
uppercase=${str^^}
echo $uppercase  # 出力: HELLO WORLD

# 小文字に変換
lowercase=${str,,}
echo $lowercase  # 出力: hello world

代替案(旧バージョンの Bash 用):tr コマンドを使用

str="Hello World"
uppercase=$(echo "$str" | tr '[:lower:]' '[:upper:]')
echo $uppercase # 出力: HELLO WORLD

3. 文字列の比較

3.1 一致の確認 (= および ==)

Bashの条件テストステートメント [ ] または [[ ]] 内で、シングルイコール = およびダブルイコール == の両方を使用して、2つの文字列の内容が完全に同一であるかを確認できます。

str1="hello"
str2="world"

# ダブルブラケット [[ ]] の使用を推奨
if [[ "$str1" == "$str2" ]]; then
  echo "文字列は一致しています"
else
  echo "文字列は一致していません"
  # 出力: 文字列は一致していません
fi

3.2 不一致の確認 (!=)

!= オペレーターは、2つの文字列が一致しないことを確認するために使用されます。

str1="hello"
str2="world"

if [[ "$str1" != "$str2" ]]; then
  echo "文字列は確かに一致していません"
  # この行が出力されます
fi

3.3 辞書順・アルファベット順の比較 (<, >)

< および > を使用して、アルファベット順(辞書順)で文字列を比較できます。
極めて重要な警告: これは数値比較(-lt, -gt)のロジックとは完全に異なります。

str1="apple"
str2="banana"

# < や > をリダイレクト記号ではなく比較演算子として正しく解析するために、必ず [[ ]] を使用してください
if [[ "$str1" < "$str2" ]]; then
  echo "辞書順では、apple は banana の前にあります"
  # この行が出力されます
fi

コア・ベストプラクティス:

  1. 文字列比較を実行する際は、変数が空だったりスペースを含んでいたりすることによる構文エラーを防ぐため、必ず変数をダブルクォートで囲んでください(例:"$str1")。
  2. モダンなBashスクリプトでは、文字列の条件判断を行う際、旧式の [ ] ではなく、より安全でパターンマッチングをサポートし、<> のエスケープが不要な [[ ]] を使用することを強く推奨します。

4. 実戦ケーススタディ

4.1 ケース 1:ユーザー入力が空かどうかの迅速な検証

対話型スクリプトを作成する際、ユーザーが何も入力せずにエンターキーを押していないか確認する必要があります。-z (Zero length) または -n (Non-zero length) を使用して迅速にチェックできます。

read -p "パスワードを入力してください: " user_password

if [[ -z "$user_password" ]]; then
  echo "エラー:パスワードは空にできません!"
  exit 1
fi

echo "パスワードを受理しました。"

4.2 ケース 2:正規表現を使用した入力フォーマットの検証

ダブルブラケット [[ ]] を使用すると、=~ オペレーターを使用して文字列を正規表現 (Regular Expressions) と照合できます。これは極めて強力なバリデーション機能です。

read -p "有効なユーザー名を入力してください (英数字のみ): " username

# ^[a-zA-Z0-9]+$ は先頭から末尾まで少なくとも1文字以上の英数字であることを表します
if [[ ! "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
  echo "検証失敗:ユーザー名には英数字のみ使用可能です。"
  exit 1
fi

echo "ユーザー名 '$username' の形式検証を通過しました。"

4.3 ケース 3:ファイル拡張子の一括変更

現在のディレクトリ内にある .old で終わるすべてのファイルを .new に一括リネームしたいとします。ここで、先ほど学習した「後方一致削除」操作が役立ちます。

for file in *.old; do
  # 実際にファイルが見つかったか確認(ディレクトリ内に .old ファイルがない場合のエラー防止)
  if [[ -f "$file" ]]; then
    newfile="${file%.old}.new" # コア操作:.old を切り落とし、.new を結合
    mv "$file" "$newfile"
    echo "ファイルをリネームしました: $file -> $newfile"
  fi
done