Bash 移植性ガイド は何をしますか?
ポータブルなPOSIX互換スクリプトを作成し、bash固有の機能をいつ活用すべきかを理解するためのガイダンス。
Shebang 選択
Bash スクリプトには #!/usr/bin/env bash を使用
#!/usr/bin/env bash
理由: PATH から bash を検索するため、bash が異なる場所にあるシステム間でも動作します。
POSIX スクリプトには #!/bin/sh を使用
#!/bin/sh
理由: bash 機能が不要な場合に最大の移植性を提供します。多くのシステムでは、/bin/sh は dash や別の POSIX シェルです。
必要に応じて絶対パスを使用
#!/bin/bash
使用すべき場合: システム要件が bash の場所を保証している場合、またはセキュリティポリシーで絶対パスが必要な場合のみ。
POSIX と Bash の機能マトリックス
| 機能 | POSIX | Bash | 推奨事項 |
|---|---|---|---|
[[ ]] | 不可 | 可 | POSIX では [ ] を使用 |
(( )) | 不可 | 可 | [ ] と -eq などを使用 |
| 配列 | 不可 | 可 | 位置パラメータまたはファイルを使用 |
local | 一部 | 可 | 一般的には安全 |
${var,,} | 不可 | 4+ | POSIX では tr を使用 |
<<< | 不可 | 可 | `echo |
=~ 正規表現 | 不可 | 可 | grep または expr を使用 |
source | 不可 | 可 | .(ドット)コマンドを使用 |
function f() | 不可 | 可 | f() のみを使用 |
$'...' | 不可 | 可 | printf を使用 |
{1..10} | 不可 | 可 | seq または while ループを使用 |
POSIX 互換パターン
条件分岐
# POSIX - [ ] を適切な引用符で使用
if [ -f "$file" ]; then
echo "ファイルが存在します"
fi
# 文字列比較
if [ "$var" = "value" ]; then
echo "一致"
fi
# 数値比較
if [ "$num" -gt 10 ]; then
echo "より大きい"
fi
# 複合条件
if [ -f "$file" ] && [ -r "$file" ]; then
echo "読み取り可能なファイル"
fi
大文字小文字変換(POSIX)
# 小文字
lower=$(echo "$string" | tr '[:upper:]' '[:lower:]')
# 大文字
upper=$(echo "$string" | tr '[:lower:]' '[:upper:]')
部分文字列操作(POSIX)
# 部分文字列を取得 - expr または cut を使用
substr=$(expr "$string" : '.\(...\).*') # 文字列の最初の3文字
substr=$(echo "$string" | cut -c1-3)
# 文字列長
length=$(expr length "$string")
length=${#string} # これは実際には POSIX です
ファイル読み取り(POSIX)
# 行ごとに
while IFS= read -r line; do
echo "$line"
done < "$file"
# ファイル全体を読み取り(cat なし)
content=$(cat "$file") # cat は POSIX です
コマンド置換
# 現代的な構文(POSIX でも推奨)
result=$(command)
# レガシー構文(避ける)
result=`command`
# ネスト(現代的な構文の利点)
result=$(echo $(date)) # 明確
result=`echo \`date\`` # エスケープの悪夢
使用する価値のある Bash 固有機能
移植性が必要ない場合、これらの bash 機能はコード品質を向上させます。
拡張テスト [[ ]]
# パターンマッチング
[[ "$file" == *.txt ]]
# 正規表現マッチング
[[ "$input" =~ ^[0-9]+$ ]]
# 単語分割の心配なし
[[ -f $file ]] # 引用符は任意(ただし推奨)
# 内部での論理演算子
[[ -f "$file" && -r "$file" ]]
配列
# インデックス配列
declare -a files=()
files+=("one.txt")
files+=("two.txt")
for f in "${files[@]}"; do
process "$f"
done
# 連想配列(Bash 4+)
declare -A config
config[host]="localhost"
config[port]="8080"
パラメータ展開
# デフォルト値
"${var:-default}"
# 大文字小文字変換(Bash 4+)
"${var,,}" # 小文字
"${var^^}" # 大文字
# 部分文字列
"${var:0:10}" # 最初の10文字
"${var: -5}" # 最後の5文字
# 検索/置換
"${var//old/new}"
ヒアドキュメント(Here Strings)
# Bash
read -r var <<< "入力文字列"
# POSIX 同等
var=$(echo "入力文字列")
プロセス置換
# Bash - 2つのコマンド出力を比較
diff <(sort file1) <(sort file2)
# POSIX 同等(一時ファイル使用)
sort file1 > /tmp/sorted1
sort file2 > /tmp/sorted2
diff /tmp/sorted1 /tmp/sorted2
シェルタイプの検出
# bash で実行されているか確認
if [ -n "${BASH_VERSION:-}" ]; then
echo "Bash で実行中"
fi
# 機能のために bash バージョンを確認
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
echo "Bash 4+ 利用可能"
fi
# 汎用シェル検出
case "${SHELL##*/}" in
bash) echo "bash" ;;
zsh) echo "zsh" ;;
*) echo "その他" ;;
esac
移植性のあるユーティリティ関数
移植性判断ガイド
POSIX を使用すべき場合:
- スクリプトが最小限のシステム(コンテナ、組み込み)で動作する場合
- ターゲットに dash、ash、busybox sh が含まれる場合
- 最大の互換性が必要な場合
- スクリプトがシステム初期化の一部である場合
Bash を使用すべき場合:
- ターゲットシステムに bash が必ず存在する場合
- 配列、連想配列、正規表現が必要な場合
- 複雑な文字列操作が必要な場合
- コードの可読性が大幅に向上する場合
- インタラクティブ機能が必要な場合
一般的な移植性の落とし穴
echo vs printf
# 問題あり - 動作が異なる
echo -n "改行なし"
echo -e "タブ\tあり"
# 移植性あり
printf '%s' "改行なし"
printf 'タブ\tあり\n'
変数代入
# どこでも動作
var="value"
# 一部のシェルで失敗する可能性あり
var = "value" # = の周りにスペースがあると誤り
代入を伴う export
# POSIX - 別々のコマンド
var="value"
export var
# Bash/モダン - 組み合わせ(ほとんどの場所で動作)
export var="value"
配列のない配列的な操作
# 位置パラメータを使用
set -- "item1" "item2" "item3"
for item in "$@"; do
echo "$item"
done
# または IFS ベースの分割
items="item1:item2:item3"
IFS=':' read -r item1 item2 item3 <<EOF
$items
EOF