Skip to content

Bash チートシート

スクリプティングと自動化のための Unix シェルおよびコマンド言語。

01

変数と文字列

変数と代入

Bash 変数はデフォルトで型なしの文字列です。値が数値の場合は算術演算が機能します。代入で = の周りにスペースを決して置かないでください — 'name = Alice' は name というコマンドを実行しようとします。コマンド置換には $(...) を使用します(バックティックより推奨)。export は変数を子プロセスで利用可能にし、readonly は不変にします。環境変数(export されたもの)は起動したスクリプトやプログラムに継承されます。

bash
# no spaces around = in assignments
name="Alice"
age=30
PI=3.14
active=true

# use variables with $ prefix
echo "Hello, $name!"
echo "Age: $age"

# command substitution: capture command output
today=$(date +%Y-%m-%d)
files=$(ls | wc -l)
echo "Today is $today, $files files"

# readonly and environment variables
readonly MAX=100
export PATH="$PATH:/opt/bin"
env | grep PATH

文字列のパラメータ展開

パラメータ展開は Bash の最も強力な機能の1つで、${...} で囲みます。${#var} は長さを返し、${var:offset:length} は部分文字列を抽出します(負のオフセットは末尾から数えます — - の前にスペースが必要)。,, と ^^ は大文字小文字を変換します(Bash 4+)。:- は var を設定せずにデフォルトを提供し、:= は副作用として設定し、:? はエラーで中止します — スクリプトでの必須引数チェックに最適です。

bash
s="Hello World"
echo ${#s}                # length: 11
echo ${s:0:5}             # substring: Hello
echo ${s:6}               # from index 6: World
echo ${s: -5}             # last 5 chars: World

# case conversion (Bash 4+)
echo ${s,,}               # lowercase: hello world
echo ${s^^}               # uppercase: HELLO WORLD
echo ${s^l}               # capitalize first if 'l': Hello World

# default values
echo ${name:-Guest}       # use Guest if name unset
echo ${name:=Guest}       # assign Guest if unset, then use
echo ${name:?required}    # error and exit if unset

文字列の検索と置換

/ パターンは置換し、// はすべて置換し、# は先頭でマッチし、% は末尾でマッチします。## と %% は貪欲(最長マッチ)で、# と % は非貪欲です。これらはパス操作に不可欠です:${path##*/} は basename を抽出し(basename コマンドのように)、${path%/*} はディレクトリを抽出します(dirname のように)。これらを習得すると、sed や cut のような外部コマンドの多くの呼び出しを回避できます。

bash
s="Hello World Hello"

# replace first occurrence
echo ${s/Hello/Hi}        # Hi World Hello

# replace all occurrences
echo ${s//Hello/Hi}       # Hi World Hi

# replace at beginning / end
echo ${s/#Hello/Hi}       # Hi World Hello (prefix)
echo ${s/%Hello/Hi}       # Hello World Hello (no suffix match)

# delete patterns (replace with empty)
echo ${s//Hello/}         #  World 
path="/usr/local/bin/app"
echo ${path##*/}          # app (longest prefix)
echo ${path%/*}           # /usr/local/bin (shortest suffix)

# extract filename and extension
file="report.tar.gz"
echo ${file%.*}           # report.tar (remove ext)
echo ${file##*.}          # gz (keep ext)

クォーティングと特殊文字

単一引用符はすべてを文字通りに保持します — 変数展開もエスケープ処理もありません(単一引用符内に単一引用符を含めることもできません)。二重引用符は $、バックティック、 の展開を許可しながらスペースと特殊文字を保持します — 変数の周りにはほぼ常に二重引用符を優先して、単語分割とグロブのバグを防ぎます。\n や \t のような ANSI-C エスケープには $'...' を使用します。

bash
# single quotes: literal, no expansion
echo 'Hello $USER'         # Hello $USER

# double quotes: expansion happens
echo "Hello $USER"         # Hello alice

# escape special chars with backslash
echo "Price: \\$5.00"      # Price: $5.00
echo 'It\'s a test'       # It's a test (escape inside single)

# concatenate strings
a="Hello"
b="World"
c="$a, $b!"
echo $c                    # Hello, World!

# multi-line string
msg="Line 1
Line 2
Line 3"
echo "$msg"

read とユーザー入力

read はユーザー入力を取得します。-p はプロンプトを表示し、-t はタイムアウトを設定し(非ゼロを返す)、-a は配列に読み込みます。ファイルを1行ずつ読み込む場合、常に 'IFS= read -r' を使用してください — IFS= は前後の空白を保持し、-r はバックスラッシュエスケープを無効にしてバックスラッシュを文字通りに保持します。これはファイルを1行ずつ処理する標準的な安全なパターンです。

bash
# read a single value
echo -n "Enter your name: "
read name
echo "Hi, $name!"

# read multiple values
read -p "First and last: " first last

# read with timeout (seconds)
read -t 5 -p "Quick! " answer || echo "too slow"

# read into an array
echo "Enter colors:"
read -a colors
echo "First: ${colors[0]}"

# read line by line from a file
while IFS= read -r line; do
    echo ">>> $line"
done < file.txt
02

配列

インデックス配列

Bash 配列はゼロベースのインデックスです。@ と * はすべての要素に展開します — スペースを含む要素を正しく処理するには常に引用符で囲んでください("${arr[@]}")。${#arr[@]} は数を返します。+= は追加します。unset はインデックスにギャップを残します。再インデックスするには arr=("${arr[@]}") を使用します。${arr[@]:start:count} でスライスします。

bash
# create and populate
fruits=("apple" "banana" "cherry")
fruits[3]="date"

# access elements
echo ${fruits[0]}         # apple
echo ${fruits[@]}         # all: apple banana cherry date
echo ${fruits[*]}         # same as @ in most contexts

# count and slice
echo ${#fruits[@]}        # count: 4
echo ${fruits[@]:1:2}     # elements 1-2: banana cherry

# append
fruits+=("elderberry")

# iterate
for f in "${fruits[@]}"; do
    echo "- $f"
done

# delete an element
unset fruits[1]            # removes "banana" (gap remains)

連想配列(マップ)

連想配列(declare -A)は他の言語の辞書のように文字列キーを値にマップします — Bash 4+ で利用可能です。${!arr[@]} でキーを、${arr[@]} で値を取得します。-v テストでキーが存在するか確認します。インデックス配列とは異なり、キーは任意の文字列なので、スペースを含むキーの引用符が不可欠です。これらは Bash 4+ が必要です(macOS はデフォルトで Bash 3 を同梱)。

bash
# must declare before use (Bash 4+)
declare -A ages

# key-value assignment
ages[alice]=30
ages[bob]=25
ages["carol smith"]=28

# access by key
echo ${ages[alice]}       # 30
echo ${ages[bob]}         # 25

# all keys and values
echo ${!ages[@]}          # keys: alice bob carol smith
echo ${ages[@]}           # values: 30 25 28
echo ${#ages[@]}          # count: 3

# iterate keys
for name in "${!ages[@]}"; do
    echo "$name is ${ages[$name]}"
done

# check if key exists
if [[ -v ages[alice] ]]; then
    echo "alice exists"
fi

配列の反復パターン

3つの主な反復スタイルがあります:値による(for x in "${arr[@]}")、インデックスによる(for i in "${!arr[@]}")、C スタイル。インデックス形式は位置が必要な場合に便利です。スペースを含む要素が単一アイテムとして保持されるよう、常に "${arr[@]}" を引用符で囲んでください。ループ内で += で配列を構築するのが結果を蓄積する慣用的な方法です。

bash
arr=(one two three four five)

# iterate by value
for item in "${arr[@]}"; do
    echo "$item"
done

# iterate by index
for i in "${!arr[@]}"; do
    echo "[$i] = ${arr[$i]}"
done

# C-style indexed loop
for ((i=0; i<${#arr[@]}; i++)); do
    echo "${arr[$i]}"
done

# map/transform: build a new array
upper=()
for w in "${arr[@]}"; do
    upper+=("$(echo $w | tr a-z A-Z)")
done
echo "${upper[@]}"

コマンド出力からの配列

mapfile/readarray(Bash 4+)は1回で配列に行を読み込みます — 大きなファイルでは while ループよりはるかに高速です。区切り文字列を分割するには、IFS を設定して read -ra を使用します。ファイル名(スペースや改行を含む可能性あり)には、常に find -print0 を read -d '' と組み合わせて null バイトで分割します — ファイル名の唯一の安全な区切り文字です。< <(...) プロセス置換はサブシェルなしでループに供給します。

bash
# split a string into an array
csv="a,b,c,d"
IFS=',' read -ra parts <<< "$csv"
echo ${parts[2]}          # c

# read lines of a file into an array
mapfile -t lines < file.txt
echo "${lines[0]}"        # first line
echo "${#lines[@]}"       # line count

# alternative: readarray (same as mapfile)
readarray -t lines2 < file.txt

# from command output (words split on IFS)
files=($(ls *.txt))
echo "${files[@]}"

# safer: read null-delimited output
while IFS= read -r -d '' f; do
    files+=("$f")
done < <(find . -name "*.txt" -print0)

特殊変数

これらの特殊変数はスクリプティングに不可欠です。$0 はスクリプトパス、$1-$9 は位置引数(2桁は ${10} を使用)。スペースを含む引数を保持するには常に "$@" を引用符で囲んでください — "$*" は1つの文字列に結合します。$? は終了ステータス(0 = 成功)で、すべてのエラーチェックの基礎です。$$ と $! は PID ファイルとプロセス管理に便利です。shift は $1 を破棄し残りを下にずらします。

bash
# positional parameters
echo $0       # script name
echo $1       # first argument
echo $#       # argument count
echo $@       # all arguments (separate words)
echo "$@"     # all arguments (preserves quoting)
echo $*       # all arguments (single string)

# process and shell info
echo $$       # current shell PID
echo $!       # PID of last background command
echo $?       # exit status of last command (0 = success)
echo $-       # current shell option flags
echo $_       # last argument of previous command

# shift arguments
echo "$1 $2"  # a b
shift
echo "$1 $2"  # b c

# iterate all args safely
for arg in "$@"; do
    echo "arg: $arg"
done
03

制御フローとテスト

If / Elif / Else

[ ] は POSIX テストコマンドです(ポータブルだが制限あり)。[[ ]] は Bash の拡張版で、パターンマッチング(ワイルドカード付き ==)、正規表現(=~)、&& / || 演算子、変数の引用符が不要をサポートします。Bash スクリプトでは [[ ]] を優先してください。[ ] では空やスペースを含む値での構文エラーを避けるため、常に変数を引用符で囲んでください。-f、-r、-d、-e テストはファイルの存在と属性をチェックします。

bash
# basic if
if [ "$age" -ge 18 ]; then
    echo "adult"
elif [ "$age" -ge 13 ]; then
    echo "teen"
else
    echo "child"
fi

# modern [[ ]] test (Bash-specific, safer)
if [[ $name == "Alice" ]]; then
    echo "hi Alice"
fi

# pattern matching in [[ ]]
if [[ $file == *.txt ]]; then
    echo "text file"
fi

# regex matching
if [[ $email =~ ^[a-z]+@[a-z]+.[a-z]+$ ]]; then
    echo "valid email"
fi

# multiple conditions
if [[ -f file.txt && -r file.txt ]]; then
    echo "readable file"
fi

テスト演算子

ファイルテスト(-f、-d、-r など)はファイルシステム属性をチェックします。文字列テストは = と !=(または [[ ]] では ==)を使用し、-z は空、-n は非空をチェックします。整数比較には -eq、-ne、-lt、-gt、-le、-ge を使用します — [ ] で整数に < > を使用しないでください(リダイレクトになります!)。[[ ]] と (( )) ではおなじみの < > <= >= 演算子を使用できます。これらの演算子を暗記することが正しい条件分岐を書くために不可欠です。

bash
# file tests
[ -f file ]       # exists and is regular file
[ -d dir ]        # exists and is directory
[ -e path ]       # exists (any type)
[ -r file ]       # readable
[ -w file ]       # writable
[ -x file ]       # executable
[ -s file ]       # exists and is non-empty
[ file1 -nt file2 ]  # newer than
[ file1 -ot file2 ]  # older than

# string tests
[ -z "$s" ]       # empty string
[ -n "$s" ]       # non-empty string
[ "$a" = "$b" ]   # equal (use == in [[ ]])
[ "$a" != "$b" ]  # not equal

# integer tests
[ $n -eq 5 ]      # equal
[ $n -ne 5 ]      # not equal
[ $n -lt 5 ]      # less than
[ $n -gt 5 ]      # greater than
[ $n -le 5 ]      # less or equal
[ $n -ge 5 ]      # greater or equal

case 文

case は Bash の switch に相当します — 値を glob パターンに対してマッチします。| は代替を区切ります。;; はブランチを終了します(break のように)。*) パターンがデフォルトです。パターンはワイルドカード(*、?、[abc])をサポートしますが、完全な正規表現はサポートしません。case は単一の値でのディスパッチに長い if-elif チェーンよりクリーンで、コマンドラインサブコマンド(start/stop/restart)を構築する標準的な方法です。

bash
case $1 in
    start)
        echo "Starting service..."
        systemctl start app
        ;;
    stop)
        echo "Stopping service..."
        systemctl stop app
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        systemctl status app
        ;;
    --help|-h)
        echo "Usage: $0 {start|stop|restart|status}"
        ;;
    *)
        echo "Unknown command: $1" >&2
        exit 1
        ;;
esac

# pattern matching in case
case $file in
    *.jpg|*.png)    echo "image" ;;
    *.mp3|*.wav)    echo "audio" ;;
    *.txt)          echo "text" ;;
    *)              echo "other" ;;
esac

算術と (( ))

$(( )) は算術式を評価して結果を返します。(( )) 内では変数名に $ は不要 — 名前のみを使用します。コマンドとしての (( )) は結果が非ゼロの場合に終了ステータス0(真)を返し、数値条件に理想的です。Bash は整数算術のみを行います。浮動小数点には bc や awk を使用してください。演算子:+ - * / % ** と C スタイルの比較・ビット演算子がすべて動作します。

bash
# arithmetic expansion
x=5
y=$((x + 3))
echo $y              # 8
echo $((x * 2))      # 10
echo $((2 ** 10))    # 1024 (exponent)
echo $((17 % 5))     # 2 (modulo)
echo $((17 / 5))     # 3 (integer division)

# (( )) condition: no $ needed, returns exit status
x=10
if (( x > 5 && x < 20 )); then
    echo "in range"
fi

# increment / decrement
((x++))             # post-increment
((x--))             # post-decrement
((++x))             # pre-increment

# bitwise operators
echo $((5 & 3))     # 1 (AND)
echo $((5 | 2))     # 7 (OR)
echo $((5 << 1))    # 10 (left shift)

ショートサーキットと三項

&& と || はショートサーキット演算子で、簡潔な制御フローとしても機能します:'cmd1 && cmd2' は cmd1 が成功した場合のみ cmd2 を実行し、'cmd1 || cmd2' は cmd1 が失敗した場合のみ cmd2 を実行します。これは1行でシンプルな if-else を置き換えます。'A && B || C' パターンは三項を模倣しますが、B が失敗する可能性がある場合は微妙なバグがあります — 堅牢なコードには実際の if 文を使用してください。コロン(:)は空のブランチに便利な no-op コマンドです。

bash
# && and || as short-circuit control flow
[[ -f file.txt ]] && echo "exists"
[[ -f file.txt ]] || echo "missing"

# combined: do this OR fail
mkdir -p build && cd build && make || exit 1

# ternary-like (Bash doesn't have a real ternary)
result=$([[ $x -gt 0 ]] && echo "positive" || echo "non-positive")

# default value via &&
echo ${name:-"anonymous"}

# run command, fallback on failure
ping -c1 host && echo "up" || echo "down"

# null command as no-op
if condition; then
    :    # do nothing
fi
04

ループと反復

For ループ

for ループは単語のリストを反復します。ブレース展開 {1..5} はシーケンスを生成します(オプションのステップ {start..end..step} 付き)。C スタイルの for ((init; cond; update)) は数値カウンタに最適です。*.txt でファイルを反復する場合、引用符なしの glob は安全に展開しますが、ファイルがマッチしない場合はリテラル '*.txt' になります — shopt -s nullglob を有効にして空のリストを得てください。

bash
# iterate a list
for fruit in apple banana cherry; do
    echo "$fruit"
done

# iterate an array
colors=("red" "green" "blue")
for c in "${colors[@]}"; do
    echo "Color: $c"
done

# range with brace expansion
for i in {1..5}; do
    echo "Number: $i"
done

# range with step
for i in {0..20..2}; do echo $i; done    # 0 2 4 ... 20

# C-style for loop
for ((i=0; i<5; i++)); do
    echo "Iteration $i"
done

# iterate command output
for file in *.txt; do
    echo "Processing $file"
done

While と Until ループ

while は条件が成功する(exit 0)限り実行し、until は成功するまで実行します — これらは対です。'while read' パターンはファイルを1行ずつ処理する標準的な方法です。常に 'IFS= read -r' を使用してください。while ループにパイプするとサブシェルで実行されるため、変数の変更が持続しません — 変数を保持する必要がある場合はプロセス置換 '< <(cmd)' を使用してください。

bash
# while: runs while condition is true
count=0
while [ $count -lt 5 ]; do
    echo "Count: $count"
    ((count++))
done

# read lines from a file
while IFS= read -r line; do
    echo "Line: $line"
done < input.txt

# infinite loop with break
while true; do
    read -p "Quit? (y/n) " ans
    [[ $ans == "y" ]] && break
done

# until: runs until condition is true (opposite of while)
n=0
until [ $n -ge 3 ]; do
    echo "n=$n"
    ((n++))
done

# while reading from a pipeline
seq 1 5 | while read n; do
    echo "got $n"
done

Break、Continue と select

break はループを終了し(break N は N 個のネストしたループを終了)、continue は次の反復にジャンプします。select はリストからインタラクティブな番号付きメニューを作成し — break されるまでループします。これらの制御文は for、while、until ループで動作します。break N 形式は深くネストしたループから脱出するのに不可欠ですが、コードが追いにくくなるため控えめに使用してください。

bash
# break exits the loop
for i in {1..10}; do
    [[ $i -eq 5 ]] && break
    echo $i       # prints 1 2 3 4
done

# continue skips to next iteration
for i in {1..6}; do
    (( i % 2 == 0 )) && continue
    echo $i       # prints 1 3 5 (odd only)
done

# break out of nested loops
for i in 1 2 3; do
    for j in a b c; do
        [[ $j == "b" ]] && break 2   # break both loops
        echo "$i$j"
    done
done

# select menu
select opt in "Start" "Stop" "Quit"; do
    case $opt in
        Start) echo "starting" ;;
        Stop)  echo "stopping" ;;
        Quit)  break ;;
        *)     echo "invalid" ;;
    esac
done

ファイルの安全なループ

ls 出力の解析は古典的なバグです — スペース、改行、特殊文字を含むファイル名が壊します。for ループでシェル glob(*.txt)を直接使用し、変数を引用符で囲んでください。再帰的または複雑な検索には、find -print0 を 'read -d ''' にパイプします — null バイトがファイル名の唯一の安全な区切り文字です。マッチしない glob がリテラルパターンではなく空のリストを生成するよう nullglob を有効にしてください。

bash
# BAD: breaks on filenames with spaces
for f in $(ls *.txt); do
    cat "$f"
done

# GOOD: glob expands safely
for f in *.txt; do
    [[ -f "$f" ]] || continue
    cat "$f"
done

# BEST: handle spaces, newlines, and no matches
shopt -s nullglob dotglob
for f in *.txt; do
    echo "Processing: $f"
done

# find with -print0 and while read -d ''
while IFS= read -r -d '' f; do
    echo "Found: $f"
done < <(find . -type f -name "*.txt" -print0)

# process files modified today
for f in $(find . -mtime -1 -print); do
    echo "Recent: $f"
done

範囲とシーケンス生成

ブレース展開 {a..b} は解析時にシーケンスを生成します — 高速で組み込みです。文字、数字、ゼロパディング、ステップ({start..end..step})をサポートします。seq はより多くのフォーマットオプション(-f で printf スタイル)を持つ外部コマンドです。ブレース展開は複数の引数も作成します:echo file{1..3}.{txt,log} は6つのファイル名を生成します。シンプルな範囲にはブレース展開を優先し、カスタムフォーマットが必要な場合は seq を使用してください。

bash
# brace expansion (not a loop, but generates lists)
echo {1..10}                 # 1 2 3 4 5 6 7 8 9 10
echo {a..e}                  # a b c d e
echo {01..10}                # zero-padded: 01 02 ... 10
echo file{1..3}.txt          # file1.txt file2.txt file3.txt

# seq command
seq 1 5                      # 1 2 3 4 5
seq 1 2 10                   # 1 3 5 7 9 (step 2)
seq -f "%03d" 1 5            # 001 002 003 004 005

# use seq in a for loop
for i in $(seq 1 5); do
    echo $i
done

# generate a list of dates
for d in $(seq -f "2024-01-%02g" 1 31); do
    echo "$d"
done
05

関数

関数の定義と呼び出し

Bash の関数は name() または function name で定義します。スペース区切りの引数で名前で呼び出します(括弧なし)。グローバルスコープの汚染を避けるため、関数内の変数には常に 'local' を使用してください — これがないと、代入が漏れ出します。Bash 関数は直接値を返せません(return は終了ステータス0-255)。文字列を返すには echo して $() で取得します。

bash
# two equivalent syntaxes
greet() {
    echo "Hello, $1!"
}
function greet2() {
    echo "Hi, $1!"
}

# call with arguments (no parens)
greet "Alice"               # Hello, Alice!
greet2 "Bob"                # Hi, Bob!

# local variables (function-scoped)
counter() {
    local count=0
    ((count++))
    echo $count
}

# return a value via echo + command substitution
add() {
    echo $(($1 + $2))
}
result=$(add 3 4)           # 7

引数とパラメータ

関数内では $1、$@、$# はスクリプトの引数ではなく関数の引数を参照します — スクリプトレベルのパラメータをシャドウします。$FUNCNAME は関数名を保持します(デバッグに便利)。関数内の shift は関数のパラメータにのみ影響します。スクリプトの引数を関数に渡すには func "$@" を使用します。このシャドウイングが関数が再利用可能なビルディングブロックである理由です。

bash
# all positional params work inside functions
show_args() {
    echo "Function name: $FUNCNAME"
    echo "First arg:  $1"
    echo "Second arg: $2"
    echo "All args:   $@"
    echo "Arg count:  $#"
}

show_args a b c
# Function name: show_args
# First arg:  a
# Second arg: b
# All args:   a b c
# Arg count:  3

# pass all script args to a function
process "$@"

# shift within a function (local to it)
parse() {
    while (($#)); do
        echo "arg: $1"
        shift
    done
}

戻り値と終了ステータス

Bash の 'return' は終了ステータス(0-255)のみを設定するため、関数はブールテストとしても機能します。文字列を返すには echo して $() で取得します。複数の値には nameref(local -n)— Bash 4.3+ — を使用します。これにより関数が呼び出し元の変数に名前で代入できます。これは複雑なデータを返す最もクリーンな方法です。関数を再入可能でなくするため、戻り値にグローバル変数を使用しないでください。

bash
# return sets exit status (0-255), not a value
is_even() {
    if (($1 % 2 == 0)); then
        return 0    # true/success
    else
        return 1    # false/failure
    fi
}

# use as a condition
if is_even 4; then
    echo "4 is even"
fi

# chain with && and ||
is_even 4 && echo "yes" || echo "no"

# return a string via echo (capture with $())
to_upper() {
    echo "${1^^}"
}
UP=$(to_upper hello)        # HELLO

# return multiple values via global or nameref
set_pair() {
    local -n _r=$1
    _r=(10 20)
}
set_pair result
echo "${result[@]}"       # 10 20

変数スコープ

Bash の変数はデフォルトでグローバルです — 関数内で代入されても!これはバグの一般的な原因です。関数内部の変数には常に 'local' を使用してください。'declare -g' は関数内から明示的にグローバルを作成します。再帰は動作しますが Bash では遅い(各呼び出しが $() のためにサブシェルをフォーク)。控えめに使用してください。階乗の例がパターンを示します:ローカル変数 + 再帰呼び出し + 算術。

bash
# global by default
x=1
modify() {
    x=2          # changes the GLOBAL x!
    local y=3    # local to function
    echo "inside: x=$x y=$y"
}

modify
echo "outside: x=$x"       # x=2 (modified!)
echo "outside: y=$y"       # empty (y is local)

# declare -g for global inside a function
set_global() {
    declare -g NEW_VAR=42
}
set_global
echo $NEW_VAR              # 42

# recursion with local
factorial() {
    local n=$1
    if (( n <= 1 )); then
        echo 1
    else
        local prev=$(factorial $((n - 1)))
        echo $((n * prev))
    fi
}
echo $(factorial 5)        # 120

ライブラリとソース

source(または .)は現在のシェルでファイルを実行し、関数と変数を利用可能にします — これが再利用可能なライブラリの構築方法です。一般的なパターンは、スクリプトが source するヘルパー関数を含む utils.sh です。スクリプトの実行(サブシェルで実行)とは異なり、ソースされたコードは呼び出し元の環境を変更できます。これが .bashrc と .bash_profile の動作方法でもあります — シェル起動時にソースされます。

bash
# utils.sh — a reusable library
#!/bin/bash
log() {
    echo "[$(date +%H:%M:%S)] $*" >&2
}

die() {
    log "ERROR: $*"
    exit 1
}

confirm() {
    read -p "$1 [y/N] " ans
    [[ $ans =~ ^[Yy]$ ]]
}

# main.sh — use the library
source ./utils.sh        # or . ./utils.sh

log "Starting script"
confirm "Continue?" || die "aborted"
log "Done"

# source vs execute
# source: runs in current shell (shares variables)
# ./script.sh: runs in a subshell (isolated)
06

テキスト処理

grep — パターン検索

grep はパターンにマッチする行を見つけます。-i は大文字小文字を無視、-v は反転、-n は行番号表示、-r は再帰、-E は拡張正規表現(+、|、{} など)を使用します。-A/-B/-C はマッチの周囲のコンテキスト行を表示し — エラーの理解に不可欠です。再帰検索中にファイルタイプでフィルタするには --include を使用します。grep はマッチが見つかると終了ステータス0を返すため、条件で便利です:if grep -q pattern file; then...

bash
# basic search
grep "error" app.log
grep -i "error" app.log          # case-insensitive
grep -v "debug" app.log          # invert (non-matching lines)
grep -c "error" app.log          # count matches
grep -n "error" app.log          # line numbers
grep -w "error" app.log          # whole word only

# recursive search
grep -rn "TODO" src/             # recursive + line numbers
grep -rl "TODO" src/             # files with matches only

# extended regex (or use egrep)
grep -E "error|warn|fatal" log
grep -E "^[0-9]{4}-" log         # lines starting with date

# context lines
grep -A 2 "error" log            # 2 lines after
grep -B 2 "error" log            # 2 lines before
grep -C 2 "error" log            # 2 lines before and after

# search multiple files
grep "function" *.js --include="*.js" -r .

sed — ストリームエディタ

sed はテキストストリームを非対話的に編集します。s コマンドは置換し、g はグーバルにします。-i はインプレース編集です(常に最初に -i なしでテストしてください!)。区切り文字は任意の文字にできます — スラッシュのエスケープを避けるためパスには | や # を使用します。sed は行ごとに処理するため、複数行操作には N コマンドやホールドスペースが必要です。複雑な編集には awk や perl の方が明確かもしれません。常に sed スクリプトを引用符で囲んでシェル展開を防いでください。

bash
# substitute (replace)
sed 's/old/new/' file.txt           # first occurrence per line
sed 's/old/new/g' file.txt          # all occurrences (global)
sed 's/old/new/3' file.txt          # 3rd occurrence only
sed 's/old/new/gi' file.txt         # global + case-insensitive

# in-place editing
sed -i 's/foo/bar/g' file.txt       # edit file in place
sed -i.bak 's/foo/bar/g' file.txt   # keep a backup

# delete lines
sed '/^#/d' file.txt                # delete comment lines
sed '/^$/d' file.txt                # delete blank lines
sed '5d' file.txt                   # delete line 5
sed '5,10d' file.txt                # delete lines 5-10

# print specific lines
sed -n '10,20p' file.txt            # print lines 10-20
sed -n '/pattern/p' file.txt        # print matching lines

# use a different delimiter for paths
sed 's|/usr/local|/opt|g' paths.txt

# multiple commands
sed -e 's/a/A/g' -e 's/b/B/g' file.txt

awk — 列処理

awk は列データのためのミニプログラミング言語です。$1、$2... はフィールド、$0 は行全体、$NF は最後のフィールドです。-F は入力区切り文字を設定し、OFS は出力を設定します。BEGIN は処理前に、END は処理後に実行されます。NR はレコード(行)番号、NF はフィールド数です。awk は CSV/TSV 処理、ログ分析、レポート生成に理想的です — 単純な抽出を超えると cut よりはるかに強力です。

bash
# print columns (default separator: whitespace)
awk '{print $1}' file.txt           # first column
awk '{print $1, $3}' file.txt       # columns 1 and 3
awk '{print $NF}' file.txt          # last column
awk -F: '{print $1}' /etc/passwd    # split on :

# filter and print
awk '$3 > 100' file.txt             # lines where col 3 > 100
awk '$1 == "ERROR" {print $2}' log  # col 2 of ERROR lines
awk 'NR == 5' file.txt              # 5th line only
awk 'NR >= 10 && NR <= 20' file.txt # lines 10-20

# BEGIN and END blocks
awk 'BEGIN {print "Start"} {sum += $1} END {print sum}' nums.txt

# field separator and output
awk -F, 'BEGIN {OFS="|"} {print $1, $2}' csv.txt

# count lines matching a pattern
awk '/error/' file.txt              # like grep
awk '/error/ {count++} END {print count}' log

# built-in variables: NR (row), NF (fields), FS, OFS
awk '{print NR, NF, $0}' file.txt

cut、tr、sort と uniq

cut はシンプルなフィールド/文字抽出です — 高速だが制限あり(引用符サポートなし)。tr は文字を変換(大文字小文字変換や区切り文字の入れ替えに便利)し、-d は削除します。sort は行をソートします(-n は数値、-r は逆順、-k はフィールド)。uniq は隣接する重複のみを削除するため、常に最初に sort にパイプしてください。uniq -c と sort -rn が頻度分析の古典的パターンです:'sort | uniq -c | sort -rn' で最も一般的な行を表示します。

bash
# cut: extract fields or characters
cut -d: -f1 /etc/passwd            # field 1, delimiter :
cut -c1-5 file.txt                 # characters 1-5
cut -d, -f2,3 data.csv             # fields 2 and 3

# tr: translate or delete characters
echo "Hello" | tr 'a-z' 'A-Z'      # HELLO
echo "a,b,c" | tr ',' ' '          # a b c
echo "hello" | tr -d 'l'           # heo (delete)
tr -s ' ' < file.txt               # squeeze repeated spaces

# sort lines
sort file.txt                      # alphabetical
sort -n nums.txt                   # numeric
sort -rn nums.txt                  # reverse numeric
sort -t: -k3 -n /etc/passwd        # by 3rd field, numeric
sort -u file.txt                   # sort + unique

# uniq: filter or count adjacent duplicates
sort file.txt | uniq               # unique lines
sort file.txt | uniq -c            # count occurrences
sort file.txt | uniq -d            # only duplicates
sort file.txt | uniq -u            # only unique lines

パイプとリダイレクト

パイプは stdout を stdin に接続し、強力なパイプラインを構築します。> は stdout をリダイレクト(>> は追記)、2> は stderr、&> は両方を取得します。ヒアドキュメント(<<EOF)は複数行文字列を供給し、区切り文字を引用符で囲む('EOF')と変数展開を無効にします。プロセス置換 <(cmd) はコマンド出力を一時ファイルとして扱います — サブシェルなしで while ループに供給し、diff のようにファイルを期待するコマンドに不可欠です。

bash
# pipe: chain commands (stdout of one -> stdin of next)
cat file | grep "x" | sort | uniq -c | sort -rn | head

# redirect stdout
echo "hello" > file.txt            # overwrite
echo "world" >> file.txt           # append

# redirect stderr
command 2> error.log               # stderr to file
command 2>&1                       # stderr to stdout
command > all.log 2>&1             # both to same file
command &> all.log                 # both (Bash shortcut)

# stdin from file
command < input.txt

# here-string
grep "x" <<< "some text with x"

# here-document (multi-line input)
cat << EOF
Line 1
Line 2 with $VAR expansion
EOF

# quoted delimiter disables expansion
cat << 'EOF'
Literal $VAR no expansion
EOF

# process substitution
diff <(ls dir1) <(ls dir2)         # compare outputs
while read line; do echo "$line"; done < <(grep x file)
07

ファイルとディレクトリ操作

find — ファイル検索

find は最も強力なファイル検索ツールです。-name は glob にマッチ(大文字小文字を無視するには -iname を使用)、-type は f/d/l でフィルタ、-mtime/-mmin は変更時間でフィルタ(- は以内、+ はより古い)、-size はサイズでフィルタします。-exec は各結果でコマンドを実行し、{} はファイル名、; はファイルごと、+ はバッチ化します。-delete は削除に -exec rm より安全です。常に最初に -delete なしで find をテストしてください!

bash
# search by name
find . -name "*.js"
find . -iname "*.JS"               # case-insensitive
find / -name "*.conf" 2>/dev/null  # suppress permission errors

# search by type
find . -type f -name "*.txt"       # files only
find . -type d -name src           # directories only
find . -type l                     # symlinks

# search by time and size
find . -mtime -7                   # modified < 7 days ago
find . -mtime +30                  # modified > 30 days ago
find . -mmin -60                   # modified < 60 min ago
find . -size +10M                  # larger than 10MB
find . -size -1k                   # smaller than 1KB
find . -empty                      # empty files/dirs

# act on results (-exec)
find . -name "*.log" -exec rm {} \;
find . -name "*.js" -exec wc -l {} +
find . -type f -exec chmod 644 {} \;

# safe deletion with -delete
find /tmp -type f -mtime +7 -delete

パーミッションと所有権

パーミッションは3つの三重項です:所有者、グループ、その他。各桁は r(4)+w(2)+x(1) なので、755 = rwxr-xr-x です。chmod はパーミッションを変更し、chown は所有者/グループを変更します。シンボリック表記(u+x、g-w)は段階的な変更に明確です。umask は新規ファイルのデフォルトパーミッションを設定します(ファイルは666から、ディレクトリは777から減算)。Web サーバーでは、ディレクトリは755、ファイルは644が標準です。本番で777は決して使用しないでください。

bash
# view permissions
ls -l file.txt
# -rw-r--r-- 1 user group 1024 Jan 1 10:00 file.txt
#  ^^^ ^^^ ^^^
#  owner group others

# chmod: change permissions
chmod 755 script.sh        # rwxr-xr-x
chmod 644 file.txt         # rw-r--r--
chmod +x script.sh         # add execute for all
chmod u+x,g-w file.txt     # symbolic: user +x, group -w
chmod -R 755 directory/    # recursive

# chown: change owner
chown alice file.txt
chown alice:developers file.txt
chown -R alice:developers project/

# umask: default permissions for new files
umask                      # show current (e.g. 022)
umask 077                  # new files: 600, dirs: 700

# numeric permission reference:
# 7=rwx 6=rw 5=rx 4=r 3=wx 2=w 1=x 0=none

コピー、移動、削除とリンク

cp -r はディレクトリを再帰的にコピーし、-i は上書き前にプロンプト(より安全)、-p はタイムスタンプとパーミッションを保持します。mv は移動とリネームの両方です。rm -rf は危険です — プロンプトなしで再帰的に削除します。常にパスを再確認してください。ハードリンクは同じ inode を指し(同じファイル、ファイルシステムを越えられない、ディレクトリをリンクできない)、シンボリックリンクはパス参照(何でもリンクできるが、ターゲットが移動すると壊れる)です。シンボリックリンクには ln -s を使用してください。

bash
# copy
cp file.txt backup.txt
cp -r src/ dest/            # recursive (directories)
cp -i file.txt dest/        # interactive (prompt before overwrite)
cp -u file.txt dest/        # update (only if newer)
cp -p file.txt dest/        # preserve attributes

# move / rename
mv old.txt new.txt
mv file.txt /other/dir/
mv -i file.txt dest/        # prompt before overwrite

# remove
rm file.txt
rm -r directory/            # recursive
rm -f file.txt              # force (no error if missing)
rm -rf node_modules/        # force + recursive (DANGER)

# links
ln target hardlink          # hard link (same inode)
ln -s target symlink        # symbolic link (path reference)
ln -sf target symlink       # force recreate symlink

# view link target
readlink symlink
readlink -f symlink         # canonical absolute path

アーカイブと圧縮

tar はファイルを1つのアーカイブにバンドルし、gzip/bzip2/xz が圧縮します。フラグ:c=作成、x=展開、t=リスト、f=ファイル、z=gzip、j=bzip2、J=xz、v=詳細。.tar.gz が Unix 標準で、.zip は Windows で一般的です。bzip2 は gzip より良く圧縮しますが遅く、xz は最良ですが最遅です。特定のディレクトリに展開するには -C を使用します。-k フラグは圧縮時に元を保持します。

bash
# tar + gzip (most common)
tar -czf archive.tar.gz dir/      # create
tar -xzf archive.tar.gz           # extract
tar -xzf archive.tar.gz -C /opt/  # extract to /opt
tar -tzf archive.tar.gz           # list contents

# tar + bzip2 (better compression)
tar -cjf archive.tar.bz2 dir/
tar -xjf archive.tar.bz2

# zip / unzip
zip -r archive.zip dir/
unzip archive.zip
unzip archive.zip -d /target

# gzip / gunzip (single files)
gzip file.txt           # -> file.txt.gz
gunzip file.txt.gz      # -> file.txt
gzip -k file.txt        # keep original

# xz (best compression)
tar -cJf archive.tar.xz dir/
tar -xJf archive.tar.xz

# mnemonic: eXtract, Create, List, File, gZip, bZip2, xz

ファイル内容と比較

cat はファイル全体をダンプし、head/tail は末尾を表示し、tail -f はライブ更新をストリーミングします(ログに不可欠)。less は検索(/)とナビゲーション付きのインタラクティブページャです。diff はファイルを比較し、-u は patch が使用する unified 形式を生成します。comm は2つのソート済みファイルを比較し、それぞれに固有の行または両方に共通の行を表示します — リストの比較に便利です。comm はソート済みファイルを必要とするため、常に前もって入力をソートしてください。

bash
# view file contents
cat file.txt               # entire file
head -n 20 file.txt        # first 20 lines
tail -n 20 file.txt        # last 20 lines
tail -f app.log            # follow (live updates)
less file.txt              # pager (q to quit, / to search)

# line/word/char count
wc file.txt                # lines words bytes
wc -l file.txt             # lines only
wc -w file.txt             # words only

# compare files
diff file1.txt file2.txt
diff -u file1.txt file2.txt    # unified format (for patches)
diff -r dir1/ dir2/            # recursive

# patch
diff -u old new > change.patch
patch < change.patch
patch -R < change.patch        # reverse

# find duplicate lines across files
comm -12 <(sort a) <(sort b)   # lines in both
comm -23 <(sort a) <(sort b)   # only in a
comm -13 <(sort a) <(sort b)   # only in b
08

プロセス管理とシグナル

バックグラウンドジョブとジョブ制御

末尾に & を付けるとコマンドをバックグラウンドで実行し、即座に戻ります。jobs はアクティブなジョブをリストし、fg/bg はフォアグラウンドとバックグラウンド間で移動します。Ctrl+Z はフォアグラウンドジョブを一時停止します(SIGTSTP を送信)。wait はバックグラウンドジョブが終了するまでブロックします — 並列作業を起動するスクリプトに不可欠です。disown はジョブをシェルのジョブテーブルから削除し、ログアウト後も実行を続けます(nohup とは異なり、実行中のジョブにも機能します)。

bash
# run in background (append &)
sleep 100 &
# [1] 12345   (job 1, PID 12345)

# list jobs
jobs
jobs -l            # with PIDs

# bring to foreground / send to background
fg                 # foreground the current job
fg %1              # foreground job 1
bg %2              # background job 2 (continue running)

# suspend a running foreground job: Ctrl+Z
# then resume in background:
bg

# wait for background jobs
sleep 5 &
wait                # wait for all background jobs
wait $!             # wait for the last background job
wait 12345          # wait for specific PID

# disown: detach from shell (survives logout)
long_task &
disown %1

kill とシグナル

kill はプロセスにシグナルを送信します。SIGTERM(デフォルト)はプロセスに正常終了を要求します — クリーンアップできます。SIGKILL(-9)は強制的で即時です。プロセスはキャッチや無視できないため、リソースを不良状態のままにする可能性があります。常に最初に SIGTERM を試し、待って、必要な場合のみ SIGKILL を使用してください。killall/pkill は名前で kill します。pkill -f は完全なコマンドラインにマッチ(より柔軟)。kill -l ですべてのシグナル名をリストします。

bash
# send signals to processes
kill 12345              # SIGTERM (graceful, default)
kill -15 12345          # SIGTERM explicitly
kill -9 12345           # SIGKILL (force, cannot be caught)
kill -HUP 12345         # SIGHUP (reload config)

# kill by name
killall nginx           # kill all nginx processes
pkill -f "python app.py"  # match full command line

# common signals
# SIGTERM (15) - graceful termination (default)
# SIGKILL (9)  - force kill (cannot be caught/ignored)
# SIGINT (2)   - interrupt (Ctrl+C)
# SIGHUP (1)   - hangup (often reload config)
# SIGSTOP (19) - pause (cannot be caught)
# SIGCONT (18) - resume
# SIGTSTP (20) - terminal stop (Ctrl+Z)

# list all signals
kill -l

# priority: try SIGTERM first, SIGKILL as last resort
kill 12345; sleep 2; kill -9 12345 2>/dev/null

trap — シグナル処理

trap はスクリプトがシグナルを受信した時に実行するコマンドを登録します — クリーンアップに不可欠です。EXIT 疑似シグナルは任意の終了(正常、エラー、kill)で発生し、テンポラリファイルの削除に最適です。常にクリーンアップするリソースを作成する前に trap を設定してください。一般的なパターン:trap cleanup EXIT INT TERM。trap '' SIGNAL は無視し、trap - SIGNAL はデフォルトに戻します。これが堅牢なスクリプトが中断されてもクリーンアップを保証する方法です。

bash
# clean up on exit (EXIT signal)
trap 'rm -f $TMPFILE' EXIT
TMPFILE=$(mktemp)
echo "working with $TMPFILE"
# when script exits (normally or via error), TMPFILE is removed

# handle Ctrl+C (SIGINT)
trap 'echo "Caught Ctrl+C, exiting"; exit 130' INT

# handle multiple signals
cleanup() {
    echo "Cleaning up..."
    rm -rf "$WORKDIR"
    exit
}
trap cleanup INT TERM EXIT

# ignore a signal
trap '' INT              # ignore Ctrl+C
trap - INT               # restore default behavior

# reload on SIGHUP
trap 'echo "reloading config"; source config.sh' HUP

# list current traps
trap -p

プロセス検査

ps はプロセスのスナップショットを表示し、top/htop はリアルタイムを表示します。pstree は親子階層を表示します。pgrep は名前で PID を見つけます(ps|grep より安全)。lsof -i :PORT はどのプロセスがポートを使用しているかを見つけます — 'ポート使用中'エラーのデバッグに不可欠です。ss(ソケット統計)は netstat のモダンな代替です。ps aux | grep パターンは至る所にありますが pgrep の方がクリーンです。リソースホッグを見つけるには --sort を使用してください。

bash
# list processes
ps aux                   # all processes (BSD style)
ps -ef                   # all processes (System V style)
ps -ef | grep nginx      # find nginx processes

# interactive viewer (like Task Manager)
top                      # real-time, all processes
htop                     # nicer interactive viewer (install separately)

# process tree
pstree                   # tree view
pstree -p                # with PIDs

# find process by name/port
pgrep -f "node app"      # print PIDs matching
pgrep -fl "node"         # full command line

# who's listening on a port
lsof -i :8080            # process using port 8080
ss -tlnp                 # all listening TCP ports
netstat -tlnp            # legacy alternative

# process resource usage
top -b -n 1 | head       # snapshot
ps aux --sort=-%mem | head  # top memory users

nohup、disown と tmux

nohup はプロセスに SIGHUP を無視させ、ログアウト後も生き残らせます — 出力は nohup.out に行きます。disown は実行中のバックグラウンドジョブに同じことを達成します。setsid は新しいセッションでプロセスを開始し、完全にデタッチします。長時間実行されるインタラクティブ作業には、tmux や screen の方が良いです。これらは後で再アタッチできる完全なターミナルセッションを保持し、テキストエディタでさえ切断を生き延びます。これがシステム管理者がリモートサーバーを管理する方法です。

bash
# nohup: run immune to hangups (survives logout)
nohup ./long_script.sh &
# output goes to nohup.out by default
nohup ./server.sh > server.log 2>&1 &

# disown: detach an already-running job
./long_task &
disown %1

# setsid: start in a new session
setsid ./daemon.sh &

# tmux: terminal multiplexer (persistent sessions)
tmux                    # start new session
tmux new -s work        # named session
tmux ls                 # list sessions
tmux attach -t work     # reattach
# inside tmux: Ctrl+B then D to detach
# processes keep running after detach

# screen: alternative to tmux
screen -S work
screen -ls
screen -r work

# check if a process is running
pgrep -x nginx && echo "running" || echo "stopped"
09

スクリプティングと高度なトピック

シバンとスクリプト構造

シバン(#!)はカーネルにどのインタプリタを使用するかを伝えます。#!/usr/bin/env bash が最もポータブルです。よく構造化されたスクリプトは安全性のために 'set -euo pipefail' で始まり、usage() を定義し、getopts(短いフラグ用)または手動ループ(長いフラグ用)で引数を解析します。OPTIND が次の引数を追跡し、解析したオプションを過ぎて shift して $1 が最初の位置引数になるようにします。この構造によりスクリプトが堅牢でユーザーフレンドリーになります。

bash
#!/usr/bin/env bash
# ^ portable shebang (finds bash via PATH)
# #!/bin/bash          # absolute path (common)
# #!/usr/bin/bash       # on some systems
# #!/bin/sh             # POSIX sh (most portable)

set -euo pipefail        # strict mode (see below)

# script metadata
readonly PROGNAME=$(basename "$0")
readonly VERSION="1.0.0"

usage() {
    cat << EOF
Usage: $PROGNAME [OPTIONS] FILE
Options:
  -h, --help     Show this help
  -v, --version  Show version
  -o OUTPUT      Output file
EOF
}

# parse options with getopts
while getopts "hvo:" opt; do
    case $opt in
        h) usage; exit 0 ;;
        v) echo $VERSION; exit 0 ;;
        o) output=$OPTARG ;;
        *) usage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

厳格モード(set -euo pipefail)

set -e はコマンドが失敗(非ゼロを返す)すると即座に終了し — エラーを早期にキャッチします。set -u は未設定変数の参照をエラーとして扱います。pipefail はパイプライン内の任意のコマンドが失敗すると非ゼロを返します(デフォルトでは最後のコマンドのステータスのみが重要)。これらを組み合わせてほとんどのバグをキャッチします。予想される失敗を許可するには 'cmd || true' を使用し、明示的なチェックには 'if ! cmd' を使用します。一部のコマンド(grep、test)は正当に非ゼロを返すため、ラップしてください。

bash
# the 'strict mode' trio
set -e             # exit on error (any non-zero status)
set -u             # error on unset variable
set -o pipefail    # pipeline fails if any command fails
# combined: set -euo pipefail

# example: without -e, errors are silently ignored
mkdir /root/nope   # fails (permission denied)
echo "still running"  # this runs anyway!

# with set -e, the script exits at the failure
set -e
mkdir /root/nope   # script exits here
echo "never reached"

# handle expected failures
set -e
if ! grep -q "x" file; then
    echo "not found"   # grep returning 1 won't kill script
fi

# temporarily disable -e
set +e
risky_command
set -e

# || true to ignore a command's failure
mkdir -p dir || true

デバッグテクニック

set -x(xtrace)は実行前に各コマンドを出力します — #1 デバッグツールです。PS4 はプロンプトをカスタマイズし(行番号を追加すると問題の特定に役立ちます)。bash -n は実行せずに構文をチェックします。trap ERR はエラー発生時にコマンドを実行し、どこで問題が起きたかをログに記録するのに最適です。複雑なスクリプトでは、疑わしい関数を set -x/+x でラップしてその部分のみトレースします。set -e と組み合わせて最初のエラーで停止して検査します。

bash
# trace execution: print each command before running
set -x              # turn on
set +x              # turn off
bash -x script.sh   # run with tracing from the start

# verbose: print input lines as read
set -v

# combine: -xv for full detail
bash -xv script.sh

# print commands with line numbers
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x

# check syntax without executing
bash -n script.sh   # syntax check only

# step through interactively
bash -x script.sh 2>&1 | less

# debug a specific function
debug_func() {
    set -x
    actual_func "$@"
    set +x
}

# use trap ERR to catch errors
trap 'echo "Error on line $LINENO" >&2' ERR

算術と数学

Bash は $(( )) で整数算術のみを行います。浮動小数点には、式を bc にパイプ(scale=N で小数点以下桁数を設定)するか awk を使用します。bc -l は数学ライブラリ(sqrt、sin、cos など)をロードします。$RANDOM は0-32767の疑似乱数を返します。暗号論的ランダム性には /dev/urandom を使用します。(( )) コマンドは +=、-=、*=、/=、%= と C のような ++/-- をサポートします。本格的な数学には Python や awk の方が良い選択です。

bash
# integer arithmetic
echo $((5 + 3))         # 8
echo $((10 / 3))        # 3 (integer division)
echo $((10 % 3))        # 1 (modulo)
echo $((2 ** 8))        # 256 (exponent)

# floating point with bc
echo "scale=2; 10/3" | bc       # 3.33
echo "3.14 * 2" | bc            # 6.28
result=$(echo "scale=4; $a / $b" | bc)

# floating point with awk
awk 'BEGIN {printf "%.2f\n", 10/3}'   # 3.33
awk "BEGIN {print $a * $b}"

# math functions with bc -l
echo "scale=4; s(0)" | bc -l    # 0 (sine, radians)
echo "scale=4; sqrt(16)" | bc -l # 4.0000

# random numbers
echo $RANDOM              # 0-32767
echo $((RANDOM % 100))    # 0-99
head -c 4 /dev/urandom | od -An -tu4   # 32-bit random

# increment/decrement
n=5
((n++)); echo $n          # 6
((n+=10)); echo $n        # 16

正規表現とパターンマッチング

Bash には3つのパターンシステムがあります:ファイル名用の glob(*.txt、?、[abc])、より複雑なマッチング用の拡張 glob(extglob:!()、@()、*())、ERE 正規表現([[ ]] 内の =~)。=~ はグループを BASH_REMATCH にキャプチャ(インデックス0 = 完全マッチ、1+ = グループ)。正規表現は ERE 構文(grep -E のように)を使用します。ファイル名パターンの強力な否定と交替には extglob を有効にしてください。正規表現は入力検証のための Bash の最も有用な機能の1つです。

bash
# glob patterns (filename matching)
*.txt          # any .txt file
file?.txt      # single char: file1.txt, fileA.txt
file[0-9].txt  # digit: file0.txt ... file9.txt
file[!0-9].txt # NOT a digit

# extended globs (shopt -s extglob)
shopt -s extglob
echo !(*.bak)       # everything except .bak files
echo @(*.jpg|*.png) # jpg or png
echo *(foo)         # zero or more "foo"

# regex with =~ in [[ ]]
if [[ $email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
    echo "valid email"
fi

# capture groups with BASH_REMATCH
if [[ $date =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
    echo "Year: ${BASH_REMATCH[1]}"
    echo "Month: ${BASH_REMATCH[2]}"
    echo "Day: ${BASH_REMATCH[3]}"
fi

# case-insensitive regex
shopt -s nocasematch
if [[ $s =~ ^hello ]]; then echo "starts with hello"; fi
10

grep の深掘り

基本的な grep パターン

grep はパターンを使用してテキストを検索します。-i は大文字小文字を無視、-w は単語全体にマッチ('error' 検索時に 'errors' にマッチするのを防止)、-v は反転(非マッチ行)、-c はカウント、-n は行番号表示、-l はマッチしたファイル名のみリスト、-h は複数ファイル検索時のファイル名プレフィックスを抑制。デフォルトで grep は基本正規表現(BRE)を使用し、メタ文字はバックスラッシュエスケープが必要です。拡張正規表現には -E(クリーンな構文)、固定文字列には -F(正規表現なし、リテラル検索で高速)を使用します。

bash
# Basic search (literal string)
grep "error" logfile.txt

# Case-insensitive
grep -i "error" logfile.txt

# Whole word match
grep -w "error" logfile.txt

# Invert match (lines NOT matching)
grep -v "debug" logfile.txt

# Count matches
grep -c "error" logfile.txt

# Show line numbers
grep -n "error" logfile.txt

# Multiple files with filename prefix
grep "error" *.log

# Suppress filename prefix (single file behavior)
grep -h "error" *.log

# Only show filenames with matches
grep -l "error" *.log

正規表現付き grep

grep -E(または egrep)はクリーンな構文の拡張正規表現を使用します:+、?、|、() はエスケープなしで動作します。^ と $ は行の先頭/末尾にアンカーします。[] は文字クラスを定義し、{} は繰り返しを指定します。-P は \d、\w、ルックアヘッドなどの機能を持つ Perl 互換正規表現(PCRE)を有効にします — ただし GNU 固有でポータブルではありません。複雑な正規表現には、デフォルトで PCRE のような構文を使用し高速な ripgrep(rg)を検討してください。$ や * のような特殊文字のシェル解釈を防ぐため、常にパターンを引用符で囲んでください。

bash
# Extended regex (-E or egrep)
grep -E "error|warning|fatal" logfile.txt

# Anchors: ^ start, $ end
grep -E "^ERROR:" logfile.txt      # lines starting with ERROR:
grep -E "completed$" logfile.txt   # lines ending with completed

# Character classes
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" dates.txt  # date format
grep -E "[A-Z][a-z]+" names.txt                  # Capitalized words

# Quantifiers
grep -E "ab+c" file.txt    # one or more b's
grep -E "ab*c" file.txt    # zero or more b's
grep -E "ab?c" file.txt    # zero or one b

# Alternation and grouping
grep -E "(cat|dog) food" file.txt

# PCRE (-P, GNU grep only)
grep -P "\d{3}-\d{4}" phones.txt  # \d for digits

grep のコンテキストと出力制御

コンテキストフラグ(-B、-A、-C)は周囲の行を表示し、ログエントリの理解に不可欠です。-o はマッチした部分のみ出力(URL、数値などの抽出に便利)。--color はターミナルでマッチをハイライトします。-r は再帰検索(シンボリックリンクを除外)、-R はシンボリックリンクに従います。--include/--exclude/--exclude-dir で検索するファイルをフィルタ — 大規模コードベースに不可欠(常に node_modules、.git、vendor を除外)。-a はバイナリファイルをテキストとして強制処理します。コード検索には、ripgrep(rg)が合理的なデフォルトを持つモダンで高速な代替です。

bash
# Show context: lines before/after match
grep -B 2 "error" log.txt    # 2 lines Before
grep -A 3 "error" log.txt    # 3 lines After
grep -C 2 "error" log.txt    # 2 lines Context (both)

# Only show matched part (not full line)
grep -o "https?://[^ ]+" urls.txt

# Color highlight matches
grep --color=auto "error" log.txt

# Recursive search
grep -r "TODO" ./src/
grep -R "TODO" ./src/        # follows symlinks

# Include/exclude file patterns
grep -r --include="*.py" "import" .
grep -r --exclude="*.test.js" "TODO" .
grep -r --exclude-dir=node_modules "TODO" .

# Binary files
grep -a "pattern" binary_file  # treat as text

stdin とパイプラインの grep

grep はパイプラインで最も強力です。[n]ginx トリックは grep が自身のプロセスにマッチするのを防ぎます:ブラケットによりパターンがプロセスリストのリテラル 'grep' 文字列にマッチしません。zgrep は手動解凍なしで圧縮ファイルを検索します。pgrep は特殊なプロセスファインダー(ps | grep より良い)。grep -rl はパターンを含むファイルをリストし、xargs grep にパイプして別のパターンを検索 — 一般的なコード考古学テクニック。インタラクティブなコード検索には、ソースコード用に設計された ripgrep や ack を使用してください。

bash
# Search command output
ps aux | grep nginx
ps aux | grep "[n]ginx"  # trick: prevents matching grep itself

# Search compressed logs
zcat log.gz | grep "error"
zgrep "error" log.gz     # direct

# Chain multiple greps
cat log.txt | grep "error" | grep -v "timeout" | grep -c

# Extract and filter
grep -oE "[0-9.]+" response.txt | sort -n | uniq

# Find processes excluding grep
pgrep -f "node server.js"  # better than ps | grep

# Search command history
history | grep "git rebase"

# Find files containing pattern, then search more
grep -rl "config" . | xargs grep "database"

grep の終了ステータスとスクリプティング

grep の終了ステータスはスクリプティングに理想的です:0(マッチあり)、1(マッチなし)、2(エラー)。-q(クワイエット)は純粋な条件チェックのための出力を抑制します。set -e スクリプトでは、grep が1(マッチなし)を返すとスクリプトが終了します — '|| true' でこれを防止します。while-read パターンはマッチした行を一度に1つ処理します。grep -c はカウント(マッチなしは0)を返し、数値で比較できます。このスクリプティング機能により grep はログ監視、検証スクリプト、CI/CD チェックのビルディングブロックになります。

bash
# grep returns exit codes for scripting
# 0 = match found, 1 = no match, 2 = error

if grep -q "error" /var/log/syslog; then
    echo "Errors found!"
    # send alert, etc.
fi

# Silent check (-q), no output
grep -q "^$" file.txt && echo "has blank lines"

# Use in while loop
grep "pattern" file.txt | while read -r line; do
    echo "Found: $line"
done

# Count and branch
errors=$(grep -c "ERROR" log.txt)
if [ "$errors" -gt 10 ]; then
    echo "Too many errors: $errors"
fi

# grep with set -e (exits on non-zero)
set -e
grep "missing" file.txt || true  # prevent exit
11

sed の深掘り

sed の置換

sed(ストリームエディタ)はテキストを行ごとに変換します。s コマンドはテキストを置換します:s/pattern/replacement/flags。g(グローバル)は1行のすべての出現を置換し、これがないと最初のマッチのみ置換されます。操作を特定の行(番号、範囲、またはパターン)に制限できます。-i はファイルをインプレース編集します(危険 — 常に最初に -i なしでテスト、または -i.bak でバックアップ)。sed は各行を独立して処理します。区切り文字は / である必要はありません — パターンにスラッシュが含まれる場合(ファイルパスなど)は s|old|new|g を使用します。

bash
# Basic substitution (first occurrence per line)
sed 's/old/new/' file.txt

# Replace all occurrences
sed 's/old/new/g' file.txt

# Case-insensitive (GNU sed)
sed 's/old/new/gi' file.txt

# Replace Nth occurrence
sed 's/old/new/2' file.txt  # 2nd occurrence only

# Only on lines matching pattern
sed '/error/s/old/new/' file.txt

# Only on specific line numbers
sed '5s/old/new/' file.txt      # line 5 only
sed '5,10s/old/new/g' file.txt  # lines 5-10
sed '$s/old/new/' file.txt      # last line

# In-place edit (modifies file)
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt  # keeps backup

正規表現とキャプチャグループ付き sed

sed -E(または -r)はクリーンな構文の拡張正規表現を使用します(括弧/ブレースの前にバックスラッシュ不要)。キャプチャグループは置換で \1、\2 などとして参照されます。& は完全マッチを表します。日付再フォーマットの例が威力を示します:年、月、日を個別にキャプチャして並べ替えます。複数のコマンドをセミコロンでチェーンできます(s/.../.../;s/.../.../)。常にパターンの特殊文字(.、*、[ など)をエスケープし、置換で / をエスケープ(または別の区切り文字を使用)してください。

bash
# Extended regex (-E or -r)
sed -E 's/[0-9]+/NUMBER/g' file.txt

# Capture groups with \1, \2 (BRE)
sed 's/\(foo\)\(bar\)/\2\1/' file.txt  # swap: foobar -> barfoo

# Capture groups with () (ERE, cleaner)
sed -E 's/(foo)(bar)/\2\1/' file.txt

# Reformat date: 2025-01-15 -> 15/01/2025
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' file.txt

# Add prefix to lines
sed 's/^/>> /' file.txt

# Add suffix
sed 's/$/ <<</' file.txt

# Remove leading/trailing whitespace
sed 's/^[ \t]*//;s/[ \t]*$//' file.txt

# Use & for full match
sed 's/[0-9]\{3\}/[&]/g' file.txt  # wrap 3 digits in brackets

sed の削除と印刷

d コマンドは行を削除し、p コマンドは行を印刷します。-n(自動印刷なし)では、明示的な p コマンドのみが出力を生成します — これにより sed が選択的プリンターになります(grep のように)。空行の削除(sed '/^$/d')は一般的なクリーンアップです。sed -n '5,10p' は head/tail の組み合わせと同等です。~ 構文(0~3p)は3行おきに印刷します(GNU 拡張)。覚えておいてください:-n なしでは sed はすべての行を印刷し(変更されている可能性あり)、-n では p を使用しない限り何も印刷しません。この二重性により sed はエディターとフィルターの両方になります。

bash
# Delete lines
sed '5d' file.txt              # delete line 5
sed '5,10d' file.txt           # delete lines 5-10
sed '/pattern/d' file.txt      # delete matching lines
sed '/^$/d' file.txt           # delete blank lines
sed '/^[ \t]*$/d' file.txt    # delete whitespace-only lines
sed '$d' file.txt              # delete last line

# Print only specific lines (like head/tail)
sed -n '5p' file.txt           # print line 5 only
sed -n '5,10p' file.txt        # print lines 5-10
sed -n '/pattern/p' file.txt   # print matching lines (like grep)
sed -n '5,${/pattern/p}' file.txt  # from line 5 to end

# Print with line numbers
sed '=' file.txt | sed 'N;s/\n/\t/'

# Print every Nth line
sed -n '0~3p' file.txt  # every 3rd line (GNU)

sed の複数行とホールドスペース

sed のホールドスペースは複数行操作を可能にします — パターンスペース(現在行)をホールドスペースに保存し、後で取得できます。h/H はホールドにコピー/追加し、g/G はホールドからコピー/追加し、x は交換します。行結合トリック(:a;N;$!ba;s/\n/ /g)はファイル全体をパターンスペースに読み込み、改行を置換します。N は次の行をパターンスペースに追加します。これらの高度な機能により sed はチューリング完全ですが、非常に暗号化されます。複雑な複数行変換には awk や Perl の方が読みやすいです。単純な行ベースの編集には sed を、フィールドベースや複数行ロジックには awk を使用してください。

bash
# Join lines (replace newline with space)
sed ':a;N;$!ba;s/\n/ /g' file.txt

# Join every 2 lines
sed 'N;s/\n/ /' file.txt

# Print paragraph (blocks separated by blank lines)
sed '/./{H;d;};x;s/\n/ /g' file.txt

# Reverse line order (tac alternative)
sed '1!G;h;$!d' file.txt

# Double-space a file
sed 'G' file.txt

# Remove last line of each paragraph
sed -n '/^$/{p;h;};/./{H;};/^$/{x;s/\n.*//;p;}' file.txt

# Hold space (h,H,g,G,x) stores lines between cycles
# h: copy pattern space to hold
# H: append pattern space to hold
# g: copy hold to pattern space
# G: append hold to pattern space
# x: exchange pattern and hold

パイプラインとスクリプトの sed

sed はテキスト変換のパイプラインで優れています。変数を置換する場合、特殊文字(& と )をエスケープして解釈を防いでください。複数の -e フラグが複数のコマンドを順番に適用します。-f はファイルからコマンドを読み込みます(複雑なスクリプトに便利)。CSV-to-TSV 変換が実用的な使用例を示します。スクリプトで設定ファイルを編集する場合、常に変更を検証し(sed 後に grep)、専用ツール(JSON には jq、YAML には yq)の使用を検討してください。sed の強みはシンプルで高速な行ベースの編集です — grep や awk と並ぶ Unix テキスト処理の定番です。

bash
# Clean up output in pipeline
cat messy.txt | sed 's/[ \t]*$//' | sed '/^$/d' | sort

# Extract and transform
echo "name=John;age=30" | sed 's/;/\n/g' | sed 's/=/: /'

# Modify config files in scripts
sed -i "s/PORT=8080/PORT=$NEW_PORT/" config.env

# Escape special chars for safe substitution
ESCAPED=$(printf '%s\n' "$URL" | sed 's/[&\]/\\&/g')
sed "s|URL|$ESCAPED|g" template.txt

# Multiple substitutions
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt

# Read commands from file
sed -f script.sed input.txt

# Convert CSV to TSV
sed 's/,/\t/g' data.csv
12

awk の深掘り

awk の基礎とフィールド

awk は各行を自動的にフィールド($1、$2、...、$NF)に分割します。-F は入力フィールド区切り文字を設定し、OFS は出力区切り文字を設定します。$0 は行全体です。NR(Number of Records)は行番号、NF(Number of Fields)は現在行のフィールド数です。awk は各行をパターン-アクションペアで処理します:pattern { action }。パターンなしならアクションはすべての行で実行されます。アクションなしなら行が印刷されます。これにより awk は CSV、TSV、/etc/passwd、ログファイルからの列ベースのデータ抽出に理想的です。

bash
# Print specific fields (default delimiter: whitespace)
awk '{print $1}' file.txt        # first field
awk '{print $1, $3}' file.txt    # first and third
awk '{print $NF}' file.txt       # last field
awk '{print $(NF-1)}' file.txt   # second-to-last

# Custom field separator
awk -F: '{print $1, $7}' /etc/passwd   # colon-separated
awk -F',' '{print $2}' data.csv        # CSV
awk -F'\t' '{print $1}' data.tsv      # TSV

# Print entire line ($0)
awk '{print}' file.txt
awk '{print NR, $0}' file.txt  # with line number

# NR = record (line) number
# NF = number of fields in current record
# NR and NF are built-in variables

# Change output separator
awk 'BEGIN{OFS="|"} {print $1, $2, $3}' file.txt

awk のパターンと条件

awk パターンは正規表現(/pattern/)、比較($3 > 100)、行番号(NR == 5)、または範囲(/start/,/end/)にできます。~ と !~ は特定のフィールドに正規表現を適用します。条件は &&、||、! で組み合わせできます。これにより awk は強力なフィルターになります — フィールド値を数値または文字列として比較できるため grep より表現力があります。範囲パターン(/start/,/end/)は2つのマーカー間のすべての行を印刷します(両端を含む)。awk は行ごとに条件を評価し、真ならアクションを実行します。アクションなしならデフォルトは {print} です。

bash
# Pattern: only process matching lines
awk '/error/ {print}' log.txt          # like grep
awk '$3 > 100' data.txt                # 3rd field > 100
awk 'NR == 5' file.txt                 # line 5 only
awk 'NR >= 10 && NR <= 20' file.txt    # lines 10-20
awk 'NF == 0' file.txt                 # empty lines
awk '$1 == "ERROR"' log.txt            # first field equals ERROR

# Range pattern: from start to end
awk '/start/,/end/' file.txt

# Combine conditions
awk '$3 > 50 && $3 < 100 {print $1, $3}' data.txt
awk '$1 == "GET" || $1 == "POST" {print}' access.log

# Negation
awk '!/debug/ {print}' log.txt  # lines NOT containing debug

# Field comparison with regex
awk '$2 ~ /^[0-9]+$/' file.txt   # 2nd field is numeric
awk '$2 !~ /pattern/' file.txt   # 2nd field doesn't match

awk の BEGIN/END と変数

BEGIN は入力が読み込まれる前に実行され(初期化、ヘッダー、変数セットアップに理想的)、END はすべての入力処理後に実行されます(サマリー、合計に理想的)。ユーザー変数は宣言不要 — デフォルトは0(数値)または空(文字列)。-v で外部変数を awk に渡します。これにより awk はデータ処理のミニプログラミング言語になります:合計、平均、最小、最大、カウント — すべて1パスで。パターン 'NR == 1 {max = $1}' が最初の行から max を初期化し、以降の行が更新します。これは複数の grep/sort/cut パイプラインよりはるかに効率的です。

bash
# BEGIN: runs before processing (setup)
# END: runs after processing (summary)
awk 'BEGIN {print "Total:"} {sum += $1} END {print sum}' nums.txt

# Count lines
awk 'END {print NR " lines"}' file.txt

# Sum a column
awk '{sum += $2} END {print "Total:", sum}' sales.csv

# Average
awk '{sum += $2; count++} END {print sum/count}' data.txt

# Find max value
awk 'NR == 1 {max = $1} $1 > max {max = $1} END {print max}' nums.txt

# User-defined variables (no declaration needed)
awk '{total += $1; if ($1 > max) max = $1}
     END {print "Sum:", total, "Max:", max, "Avg:", total/NR}'

# Pass variables with -v
awk -v threshold=50 '$1 > threshold {print}' data.txt
awk -v date="$(date +%Y-%m-%d)" '{print date, $0}' file.txt

awk の制御フロー

awk は完全な制御フローを持ちます:if/else、for、while、連想配列(キー-値)。これにより完全なデータ処理言語になります。単語カウントの例(words[$1]++)が古典的な awk のユースケースです — 列内の各値の出現をカウントし、頻度でソートします。awk の配列は連想(辞書のように)で、文字列または数値でインデックスされます。for (key in array) はキーを反復します(順不同 — sort にパイプ)。awk は cut、sort、uniq、wc のパイプライン全体を1つの効率的なパスで置き換えられます。複雑なデータ処理には、チェーンされた Unix コマンドより awk の方が明確なことがよくあります。

bash
# if-else
awk '{
  if ($3 >= 90) grade = "A"
  else if ($3 >= 80) grade = "B"
  else if ($3 >= 70) grade = "C"
  else grade = "F"
  print $1, grade
}' students.txt

# for loop
awk 'BEGIN {
  for (i = 1; i <= 5; i++) print i, i*i
}'

# for-in (iterate array)
awk '{count[$1]++} END {
  for (key in count) print key, count[key]
}' access.log | sort -rnk2

# while loop
awk '{
  i = 1
  while (i <= NF) {
    print i, $i
    i++
  }
}' file.txt

# Arrays (associative)
awk '{words[$1]++} END {
  for (w in words) print words[w], w
}' file.txt | sort -rn | head

awk の実践例

これらの実践例は awk の現実世界の威力を示します。IP 頻度カウントはログ分析に不可欠です。拡張子ごとのファイルサイズサマリーは split() で拡張子を抽出します。列値による CSV フィルタリングは複雑な grep/sed パイプラインを置き換えます。パーセンタイル計算は awk の数学能力を実演します(asorti は配列インデックスをソート)。列の再フォーマット(区切り文字の変更とフィールド選択)は一般的な ETL タスクです。awk は構造化テキスト処理の go-to ツールです — データに列/フィールドがある場合、awk はほぼ常に正しい選択です。JSON には jq を、CSV には awk または csvkit を使用してください。

bash
# Top 10 IP addresses from access log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
# Or entirely in awk:
awk '{ip[$1]++} END {for (i in ip) print ip[i], i}' access.log | sort -rn | head

# Sum file sizes by extension
ls -l | awk '{split($NF, a, "."); ext=a[2]; size[ext]+=$5}
  END {for (e in size) print e, size[e]}' | sort -rnk2

# Filter CSV by column value
awk -F',' '$3 == "active" {print $1, $2}' users.csv

# Calculate response time percentiles from log
awk '{times[NR] = $2} END {
  n = asorti(times, sorted)
  print "p50:", sorted[int(n*0.5)]
  print "p95:", sorted[int(n*0.95)]
  print "p99:", sorted[int(n*0.99)]
}' response_times.txt

# Reformat columns
awk -F',' 'BEGIN{OFS="|"} {print $1, $2, $4}' data.csv
13

find と xargs

名前とタイプで find

find は様々な条件でファイルシステムを検索します。-name はファイル名にマッチ(大文字小文字区別)、-iname は大文字小文字無視。-type f/d/l はファイルタイプでフィルタ。-path は完全パスにマッチ、-regex は完全パス正規表現を使用。-o(または)で条件を組み合わせ、グループ化には \( \) を使用。-maxdepth は再帰深度を制限(大きなファイルシステムでパフォーマンスに重要)。find はパスを出力し、-exec や xargs と組み合わせて結果にアクションを実行します。常にシェルの glob 展開を防ぐためパターンを引用符で囲んでください。コード検索には ripgrep(rg)が高速ですが、ファイルシステム操作には find の方が柔軟です。

bash
# Find by name (exact)
find . -name "config.txt"

# Case-insensitive name
find . -iname "config.txt"

# Wildcard pattern
find . -name "*.py"
find . -name "test_*"

# Find by type
find . -type f -name "*.js"   # files only
find . -type d -name "node_modules"  # directories
find . -type l                # symlinks

# Find by path (full path match)
find . -path "*/src/*.test.js"

# Find by regex
find . -regex ".*\.\(py\|js\)$"

# Multiple name patterns
find . \( -name "*.py" -o -name "*.js" \)

# Max depth (don't recurse too deep)
find . -maxdepth 2 -name "*.txt"

時間とサイズで find

find の時間ベース検索はクリーンアップと監査に不可欠です。-mtime(変更)、-atime(アクセス)、-ctime(メタデータ変更)は日単位、-mmin/-amin/-cmin は分単位。-(未満)と +(超過)が値の前に付きます。サイズはサフィックスを使用:c(バイト)、k(KB)、M(MB)、G(GB)。-empty はゼロ長ファイルや空ディレクトリを見つけます。-perm はパーミッションをチェック:完全一致(644)、すべてのビットセット(-u+x)、または任意のビットセット(/4000)。SUID ファイル(/4000)の検索はセキュリティ監査テクニックです。これらの条件は -a(and、デフォルト)と -o(or)で組み合わせできます。

bash
# Find by modification time
find . -mtime -1    # modified in last 24 hours
find . -mtime +7    # modified more than 7 days ago
find . -mtime 7     # modified exactly 7 days ago

# Access time (-atime) and change time (-ctime)
find . -atime -1    # accessed in last 24h
find . -ctime -1    # status changed in last 24h

# Minutes instead of days
find . -mmin -30    # modified in last 30 minutes

# Find by size
find . -size +100M   # larger than 100MB
find . -size -1k     # smaller than 1KB
find . -size 10M     # exactly 10MB

# Find empty files/directories
find . -empty -type f
find . -empty -type d

# Find by permissions
find . -perm 644     # exactly 644
find . -perm -u+x    # has execute for user
find / -perm /4000   # SUID files (security audit)

find -exec と -delete

-exec は各結果でコマンドを実行します。{} はファイル名のプレースホルダ、\; はコマンドを終了(ファイルごとに1回実行)、+ はファイルをバッチ化(すべてのファイルで1回実行 — より効率的)。-delete はマッチしたファイルを削除(-exec rm より高速だが、最初に -print でテスト)。chmod パターン(ディレクトリ755、ファイル644)は一般的な Web サーバーセットアップです。複数ファイルを受け入れるコマンド(grep、ls、wc)には -exec と + を優先してください。削除の場合、常に最初に -print で何が削除されるかを検証し、その後 -delete に置き換えてください。-ok は -exec の代わりにファイルごとに確認を求めます。

bash
# Execute command on each result
find . -name "*.log" -exec ls -lh {} \;

# Execute with + (batch, more efficient)
find . -name "*.log" -exec ls -lh {} +

# Delete files (DANGEROUS - test first!)
find /tmp -name "*.tmp" -type f -delete
# Safer: use -exec rm
find /tmp -name "*.tmp" -type f -exec rm -i {} \;

# Execute a complex command
find . -name "*.py" -exec wc -l {} \; | sort -rn | head

# Change permissions
find . -type d -exec chmod 755 {} \;  # dirs to 755
find . -type f -exec chmod 644 {} \;  # files to 644

# Find and grep
find . -name "*.py" -exec grep "TODO" {} +

# Print before deleting (confirmation)
find . -name "*.bak" -print -delete

xargs の基礎

xargs は stdin をコマンド引数に変換します。find 出力と stdin を読まないコマンドの橋渡しです。-n はコマンドごとの引数を制限し、-I {} はカスタム配置のプレースホルダを定義します。-0(find -print0 と組み合わせ)はスペース/改行を含むファイル名を正しく処理します — 安全のために常にこのペアを使用してください。-P は並列実行を有効にします(画像変換のような CPU バウンドタスクに最適)。-t(トレース)は実行前にコマンドを表示し、-p は確認を求めます。-exec が遅すぎる場合(ファイルごとに1プロセス)に xargs は不可欠です — xargs は引数を効率的にバッチ化します。

bash
# Basic: take stdin and pass as arguments
echo "file1 file2 file3" | xargs rm

# One argument per line
find . -name "*.bak" | xargs rm

# Control arguments per command (-n)
echo "1 2 3 4 5" | xargs -n 2 echo  # echo 1 2; echo 3 4; echo 5

# Show commands before running (-t, verbose)
find . -name "*.log" | xargs -t rm

# Prompt before each command (-p)
find . -name "*.tmp" | xargs -p rm

# Handle filenames with spaces (-0 with find -print0)
find . -name "*.txt" -print0 | xargs -0 grep "pattern"

# Replace string (-I)
find . -name "*.py" | xargs -I {} cp {} /backup/

# Parallel execution (-P)
find . -name "*.png" | xargs -P 4 -I {} convert {} {}.thumb.png

# Limit with -L (lines per command)
cat urls.txt | xargs -L 1 curl -O

find + xargs パターン

find + xargs はバッチファイル操作の古典的 Unix パターンです。-print0 | xargs -0 はスペースや特殊文字を含むファイル名の安全な組み合わせです。バルク grep パターンは -exec grep よりはるかに高速です(多数の grep プロセス vs 1つ)。並列 xargs(-P N)は音声/動画変換のような CPU バウンドタスクを劇的に高速化します。アーカイブパターン(古いログを見つけて tar にする)は一般的なログローテーションテクニックです。堅牢性のために常に -print0/-0 を使用してください — これがないと、スペース、引用符、改行を含むファイル名がパイプラインを壊します。この組み合わせは Unix システム管理の基礎です。

bash
# Find and bulk grep (efficient)
find . -name "*.py" -print0 | xargs -0 grep -l "import django"

# Find large files and show sizes
find . -type f -size +100M -print0 | xargs -0 ls -lhS

# Find and archive
find . -name "*.log" -mtime +30 -print0 |
  xargs -0 tar -czf old_logs.tar.gz

# Find and batch rename
find . -name "*.JPG" | xargs -I {} mv {} {}.bak
# Or with rename command:
find . -name "*.JPG" -exec rename 's/\.JPG$/.jpg/' {} +

# Find empty directories and remove
find . -type d -empty -print0 | xargs -0 rmdir

# Find recently modified files and copy
find . -name "*.py" -mtime -1 -print0 |
  xargs -0 -I {} cp {} /tmp/recent/

# Parallel processing
find . -name "*.wav" -print0 |
  xargs -0 -P 8 -I {} ffmpeg -i {} {}.mp3
14

高度な Bash テクニック

ヒアドキュメントとヒア文字列

ヒアドキュメント(<< DELIMITER)は複数行テキストをコマンドの stdin として供給します — 設定ファイル、SQL クエリ、任意の複数行入力の生成に便利です。引用符なしのデリミタは変数展開を許可し、引用符('EOF')は内容を文字通りに扱います。ヒア文字列(<<<)は単一の文字列を stdin として供給します — シンプルなケースで echo | command よりクリーンです。デリミタは任意の単語(EOF、END、DONE)にできます。慣例として大文字です。ヒアドキュメントはファイルを生成したりインタラクティブプログラム(mysql、psql、ssh)とやり取りするスクリプトに不可欠です。インデントされたデリミタ(<<-)は可読性のために先頭のタブを削除します。

bash
# Here document: multi-line input to a command
cat << EOF
Line 1
Line 2 with $VARIABLE expansion
Line 3
EOF

# Quoted delimiter: no variable expansion
cat << 'EOF'
This $won't expand
\n stays literal
EOF

# Write to file
cat > config.txt << EOF
host=localhost
port=8080
EOF

# Here string: single string as stdin
grep "pattern" <<< "search this text"
read -r line <<< "hello world"

# With variables
name="Alice"
greeting=$(cat <<< "Hello, $name!")

# Pipe multi-line to command
mysql << EOF
USE mydb;
SELECT * FROM users;
EOF

パラメータ展開

パラメータ展開は Bash の組み込み文字列操作です — 単純な操作に sed/awk/cut は不要です。:- はデフォルトを提供し、# と ## はプレフィックスを削除し、% と %% はサフィックスを削除します。ファイル拡張子の例は非常に一般的です:${file##*.} で拡張子を取得し、${file%.*} で拡張子なしのベース名を取得します。/ は最初のマッチを置換し、// はすべて置換します。^^ と ,, は大文字小文字を変換します(Bash 4+)。これらの操作は外部コマンドの起動より高速です。パラメータ展開を習得して、不要なサブシェルなしで効率的で読みやすい Bash スクリプトを書いてください。

bash
# Default values
echo ${name:-"default"}    # use "default" if name unset/empty
echo ${name:="default"}    # set name to "default" if unset/empty

# String length
echo ${#var}

# Substring extraction
var="Hello World"
echo ${var:0:5}    # Hello (start:length)
echo ${var:6}      # World (start to end)

# Remove from beginning (# shortest, ## longest)
file="archive.tar.gz"
echo ${file#*.}     # tar.gz (remove up to first .)
echo ${file##*.}    # gz (remove up to last .)

# Remove from end (% shortest, %% longest)
echo ${file%.*}     # archive.tar (remove from last .)
echo ${file%%.*}    # archive (remove from first .)

# Replace
echo ${var/World/Bash}    # Hello Bash (first match)
echo ${var//l/L}          # HeLLo WorLd (all matches)

# Case conversion (Bash 4+)
echo ${var^^}   # HELLO WORLD (uppercase)
echo ${var,,}   # hello world (lowercase)

trap とシグナル処理

trap はシグナルとイベントのハンドラを登録します。EXIT はスクリプト終了時(正常、エラー、kill)に発生し — クリーンアップ(テンポラリファイル、ロック)に理想的です。INT(Ctrl-C)、TERM(kill)、HUP(ターミナルクローズ)が一般的なシグナルです。テンポラリファイルパターン(trap 'rm -f' EXIT)はスクリプトが失敗してもクリーンアップを保証します。DEBUG はすべてのコマンド前に発生し — トレースに便利です。常に trap コマンドを引用符で囲んでください(単一引用符は即時展開を防止)。trap は堅牢なスクリプトに不可欠です。これがないと、テンポラリファイルが蓄積し、ロックが失敗時に解放されない可能性があります。常に後始末をしてください。

bash
# Clean up on exit (any reason)
cleanup() {
  echo "Cleaning up..."
  rm -f /tmp/myapp.lock
  exit 0
}
trap cleanup EXIT

# Handle specific signals
trap 'echo "Interrupted!"; cleanup' INT TERM
trap 'echo "Ctrl-C pressed"' INT

# Ignore a signal
trap '' INT  # ignore Ctrl-C

# Reset trap
trap - INT  # restore default behavior

# Common pattern: temp file cleanup
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
echo "data" > "$TMPFILE"
# ... use TMPFILE ...
# File is removed when script exits (even on error)

# Debug trap (trace execution)
trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG

Bash スクリプトのデバッグ

set -x は実行をトレース(+ プレフィックス付きで各コマンドを印刷)— 主要なデバッグツール。set -euo pipefail(厳格モード)はエラーを早期にキャッチします:-e は非ゼロで終了、-u は変数名のタイプミスをキャッチ、pipefail はパイプを正しく失敗させます(これがないと最後のコマンドの終了コードのみが重要)。bash -n は実行せずに構文をチェック。PS4 はトレースプレフィックスをカスタマイズ(file:line の表示が非常に役立つ)。trap ERR は(-e で)任意のエラーで発生し、行番号コンテキストを提供。複雑なスクリプトでは、グローバルではなく疑わしいセクションに set -x を追加してください。本番スクリプトでは常に厳格モードを使用してください。

bash
# Trace execution: print each command before running
bash -x script.sh
# Or within script:
set -x   # enable tracing
set +x   # disable tracing

# Verbose: print input lines as read
bash -v script.sh

# Strict mode (recommended for all scripts)
set -euo pipefail
# -e: exit on error
# -u: error on undefined variable
# -o pipefail: pipe fails if any command fails
# -o pipefail: pipe fails if any command fails

# Print specific debug info
echo "DEBUG: var=$var" >&2  # to stderr

# Check syntax without executing
bash -n script.sh

# PS4: customize trace prompt
export PS4='+${BASH_SOURCE}:${LINENO}: '
bash -x script.sh

# Trap ERR for error handling
trap 'echo "Error on line $LINENO"' ERR

curl と wget

curl と wget は不可欠な HTTP クライアントです。curl は API テスト用です(すべての HTTP メソッド、ヘッダー、JSON をサポート)。-d は POST データを送信、-H はヘッダーを設定、-X はメソッドを指定、-L はリダイレクトに従う、-O はリモートファイル名で保存、-s はサイレント(スクリプティング用)。wget はダウンロードに最適化(-c でレジューム、-r で再帰)。REST API テストには、curl と -H 'Content-Type: application/json' および JSON ボディ用の -d が標準です。-w '%{http_code}' でステータスコードのみを抽出してスクリプティングに使用します。複雑な API テストには、httpie(よりシンプルな構文)や postman を検討してください。

bash
# Basic HTTP request
curl https://api.example.com/data
curl -O https://example.com/file.zip  # save to file

# HTTP methods
curl -X POST https://api.example.com/users
curl -X PUT -d '{"name":"Alice"}' https://api.example.com/users/1
curl -X DELETE https://api.example.com/users/1

# Headers and JSON
curl -H "Content-Type: application/json" \
     -H "Authorization: Bearer TOKEN" \
     -d '{"key":"value"}' \
     https://api.example.com/data

# Download with wget
wget https://example.com/file.zip
wget -q https://example.com/file.zip  # quiet
wget -c https://example.com/file.zip  # continue/resume

# Follow redirects
curl -L https://short.url/abc

# Save response, show headers
curl -s -o response.json -w "%{http_code}" https://api.example.com

# Download with progress bar
curl --progress-bar -O https://example.com/largefile.iso

# POST form data
curl -d "name=Alice&[email protected]" https://api.example.com/form
15

シグナルと trap

trap クリーンアップハンドラ

trap はシグナルのハンドラを登録します。EXIT は特別で — 任意の理由でシェル終了時に発生し、クリーンアップに最適です。常に trap でロックファイル、テンポラリディレクトリ、子プロセスをクリーンアップしてください。トラップする一般的なシグナル:INT(Ctrl-C)、TERM(デフォルト kill)、HUP(ターミナルクローズ)、EXIT。

bash
cleanup() {
  echo "Cleaning up..."
  rm -f /tmp/myapp.lock
  exit 0
}

# catch INT (Ctrl-C), TERM, EXIT
trap cleanup INT TERM EXIT

# normal exit also triggers EXIT trap
echo "Working..."
sleep 30
# cleanup runs automatically

# remove trap
trap - EXIT                      # disable
trap - INT TERM EXIT             # disable all

クリーンアップ付きテンポラリファイル

$$ や $RANDOM でテンポラリファイル名を手作りしないでください — 予測可能なパスはシンボリックリンク攻撃を招きます。mktemp は一意の名前のファイル/ディレクトリを原子的に作成します。EXIT の trap と組み合わせて、エラーや中断時でもクリーンアップを保証してください。場所を制御するには TMPDIR 環境変数を設定します。

bash
TMPDIR=$(mktemp -d)
TMPFILE=$(mktemp)

cleanup() {
  rm -rf "$TMPDIR" "$TMPFILE"
}
trap cleanup EXIT

# use temp files safely
echo "data" > "$TMPFILE"
process_data < "$TMPFILE"

# mktemp is safer than $RANDOM — avoids symlink attacks
# -d creates a directory
# -p /var/tmp specifies parent
# mktemp -t myapp.XXXXXX   # use TMPDIR env

スクリプトでのシグナル処理

長時間実行されるスクリプトでは、シグナルハンドラでフラグ変数を反転させてメインループから正常に脱出します。これにより現在の反復を終了し、状態を永続化し、クリーンに終了できます。trap ハンドラ内で重い作業をしないでください — フラグを設定するだけにします。

bash
#!/bin/bash
# graceful shutdown for a long-running script

RUNNING=1
stop() {
  echo "Stopping..."
  RUNNING=0
}
trap stop INT TERM

while [ $RUNNING -eq 1 ]; do
  echo "Tick $(date)"
  sleep 1
done

echo "Done."

# send signals: kill -TERM <pid> or Ctrl-C

timeout と watch

timeout はコマンドを実行し、期間後に kill します(デフォルトは SIGTERM)。終了コード124はタイムアウトを意味します。プロセスが SIGTERM を無視する場合、--kill-after で SIGKILL にエスカレートします。ループと組み合わせてヘルスチェックを構築します。watch は人間向けの定期表示用で、スクリプト用ではありません。

bash
# timeout kills a command after N seconds
timeout 30 slow_command
timeout 5s curl https://slow.example.com
timeout --signal=KILL 60 build.sh  # escalate to SIGKILL
timeout -k 5 30 command            # kill -9 after 5s grace

# exit status: 124 if timed out
timeout 2 sleep 10
echo $?                            # 124

# watch reruns a command
watch -n 1 date
watch -n 5 'kubectl get pods'

# tlimit-style loops
while true; do
  timeout 5 curl -s https://api.example.com/health || echo "down"
  sleep 60
done

無視と再送出

trap '' SIGNAL はシグナルを無視します(中断できないクリティカルセクションに便利)。子にシグナルを転送するには、$! で PID を取得して再送信します。ERR trap は set -e 有効時にコマンド失敗で発生し — $LINENO で失敗行番号をログに記録するのに便利です。

bash
# ignore a signal entirely
trap '' INT                       # ignore Ctrl-C
trap - INT                        # restore default

# re-raise / propagate to children
trap 'kill -TERM $childpid; wait $childpid' TERM

# forward signals to a subprocess
childpid=
run_child() {
  some_server &
  childpid=$!
  wait $childpid
}
trap 'kill -TERM $childpid' TERM INT
run_child

# exit on any error
set -e
trap 'echo "Failed at line $LINENO"' ERR
16

関数とライブラリ

関数の定義

関数は再利用可能なコマンドをグループ化します。2つの構文:name() {...} と function name {...}。関数内の引数は $1、$2 など — $0 はまだスクリプト名です。$@ はすべての引数に展開します(スペースを含む引数を保持するには常に "$@" として引用符で囲む)。$# は数です。

bash
# two equivalent syntaxes
greet() {
  echo "Hello, $1!"
}

function greet {                  # also valid
  echo "Hello, $1!"
}

greet Alice                       # Hello, Alice!
greet "Bob Smith"                 # quoted args

# arguments: $1, $2, ... $@, $#, $*
echo_args() {
  echo "Count: $#"
  echo "All: $@"
  echo "First: $1"
  for arg in "$@"; do echo "- $arg"; done
}

戻り値とローカル変数

return は終了ステータスを設定します(0=成功、1-255=失敗)— 値を返しません。値を取得するには echo して $() で取得します。グローバルスコープの汚染を避けるため関数内の変数には local を使用してください。再帰関数には local が不可欠です。

bash
# return is exit status (0-255), not a value
is_even() {
  if (( $1 % 2 == 0 )); then return 0; else return 1; fi
}

if is_even 4; then echo "even"; fi

# capture stdout as a value
get_date() { date +%Y-%m-%d; }
today=$(get_date)

# local variables
counter() {
  local count=0                  # scoped to function
  ((count++))
  echo $count
}

ライブラリのソース

source(または .)は現在のシェルでファイルを実行し — 関数、変数、エイリアスが利用可能になります。これが再利用可能なライブラリの構築方法です。BASH_SOURCE[0] と $0 でソースと実行を区別 — ライブラリでも実行可能スクリプトでもあるファイルに便利です。

bash
# library file: lib/utils.sh
log() {
  echo "[$(date +%H:%M:%S)] $*" >&2
}

CONFIG_PATH="/etc/myapp"

# main script
source lib/utils.sh              # or: . lib/utils.sh
log "Starting..."
echo "Config: $CONFIG_PATH"

# check if sourced vs executed
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  echo "Running directly"
else
  echo "Sourced"
fi

再帰関数

Bash は再帰をサポートしますが、遅く(各呼び出しが $() のためにサブシェルをフォーク)、スタック深度が限られています(約1000)。計算量の多い作業には awk、Python、または外部ツールを優先してください。再帰関数では常にローカル変数を使用してください — そうでないと呼び出し間で互いに上書きします。

bash
# factorial
factorial() {
  local n=$1
  if (( n <= 1 )); then
    echo 1
  else
    local prev=$(factorial $((n - 1)))
    echo $((n * prev))
  fi
}
factorial 5                      # 120

# fibonacci
fib() {
  if (( $1 < 2 )); then echo $1; return; fi
  echo $(( $(fib $(($1 - 1))) + $(fib $(($1 - 2))) ))
}

# caution: bash recursion is slow and limited depth
# for heavy work, use awk, python, or external tools

デフォルト引数とオプション引数

${1:-default} は $1 が未設定または空の場合にデフォルトを代入します。${1:?message} は欠落時にエラーで終了 — 必須引数に最適。shift は $1 を引数リストから削除します。case + shift の組み合わせが bash 関数でフラグと位置引数を解析する慣用的な方法です。

bash
# default values
greet() {
  local name="${1:-World}"        # default if unset or empty
  local greeting="${2:-Hello}"
  echo "$greeting, $name!"
}
greet                            # Hello, World!
greet Alice                      # Hello, Alice!
greet Alice Hi                   # Hi, Alice!

# required argument
require_arg() {
  : "${1:?Usage: require_arg <name>}"   # exits with message if missing
  echo "Got: $1"
}

# shift through args
process() {
  while (( $# )); do
    case "$1" in
      -v) verbose=1; shift ;;
      *)  files+=("$1"); shift ;;
    esac
  done
}
17

配列と連想配列

インデックス配列

インデックス配列は0ベースの整数キーを使用します。反復時にはスペースを含む要素を正しく処理するため常に "${arr[@]}" を引用符で囲んでください。${#arr[@]} が数です。${!arr[@]} でインデックスを取得(スパース配列に便利)。unset 'arr[i]'(引用符付き)で要素を削除します。

bash
# declare and assign
fruits=("apple" "banana" "cherry")
fruits+=("date")                 # append
fruits[10]="kiwi"                # sparse

# access
echo "${fruits[0]}"             # first
echo "${fruits[@]}"             # all values
echo "${#fruits[@]}"            # count
echo "${!fruits[@]}"            # indices

# iterate
for f in "${fruits[@]}"; do
  echo "$f"
done

# slice
echo "${fruits[@]:1:2}"         # 2 items starting at index 1

# delete
unset 'fruits[1]'

連想配列(マップ)

連想配列(declare -A)は bash 4+ の機能です(macOS はデフォルトで bash 3 を同梱 — brew install bash を使用)。キーは文字列です。-v テストでキーが存在するか確認します。${!arr[@]} でキーを反復します。macOS のデフォルト /bin/bash は3.2です — -A を使用するスクリプトは #!/usr/bin/env bash でより新しい bash を指す必要があります。

bash
# requires bash 4+
declare -A ages
ages["alice"]=30
ages["bob"]=25
ages["carol"]=42

# access
echo "${ages["alice"]}"
echo "${ages[@]}"               # all values
echo "${!ages[@]}"              # all keys

# iterate keys
for name in "${!ages[@]}"; do
  echo "$name => ${ages[$name]}"
done

# check key exists
if [[ -v ages["alice"] ]]; then echo "yes"; fi

# count
echo "${#ages[@]}"

# delete
unset 'ages[bob]'

配列への読み込み

mapfile(別名 readarray)が配列に行を読み込む最速の方法です — 常に -t で末尾の改行を削除してください。while-read ループはポータブルですが遅いです。区切り文字列を分割するには、IFS を設定して read -ra を使用します。< <(cmd) プロセス置換でサブシェルの変数スコープ問題を回避します。

bash
# read lines into array
mapfile -t lines < file.txt      # bash 4+
echo "${lines[0]}"

# alternative (portable)
lines=()
while IFS= read -r line; do
  lines+=("$line")
done < file.txt

# split string into array
IFS=',' read -ra csv <<< "a,b,c,d"
echo "${csv[2]}"                # c

# from command
mapfile -t files < <(find . -name "*.js")

# process each
for f in "${files[@]}"; do
  echo "Processing $f"
done

配列操作

Bash 配列には多くの組み込み操作がありません(.indexOf、.reverse、.unique なし)。これらにはループを書くか sort/uniq にパイプしてください。ユニーク配列トリックは連想配列をセットとして使用します。複雑な配列操作には、awk または本物のプログラミング言語を検討してください — bash 配列はシンプルなリストに最適です。

bash
# append
arr=(a b c)
arr+=(d e)                       # (a b c d e)

# length
echo "${#arr[@]}"

# slice
echo "${arr[@]:1:2}"            # (b c)

# find index (no built-in; loop)
idx=-1
for i in "${!arr[@]}"; do
  if [[ "${arr[$i]}" == "b" ]]; then idx=$i; break; fi
done

# reverse
n=${#arr[@]}
for ((i=n-1; i>=0; i--)); do
  echo "${arr[$i]}"
done

# unique (preserve order)
declare -A seen
unique=()
for x in "${arr[@]}"; do
  [[ -v seen[$x] ]] && continue
  seen[$x]=1
  unique+=("$x")
done

配列でのネストされたデータ

Bash にはネイティブのネストされた配列がありません。回避策:(1) レコードを区切り文字結合文字列として保存し IFS で分割、(2) "r,c" のような複合キーで連想配列を使用、(3) ${!name} で変数間接参照を使用。本物のネストされたデータには、jq + JSON または本物の言語に切り替えてください。

bash
# array of "records" (delimiter-based)
users=("alice:30:admin" "bob:25:user")
for u in "${users[@]}"; do
  IFS=':' read -r name age role <<< "$u"
  echo "$name ($age) - $role"
done

# simulate 2D with name-mangled vars
declare -A grid
grid["0,0"]=1; grid["0,1"]=2
grid["1,0"]=3; grid["1,1"]=4
for key in "${!grid[@]}"; do
  IFS=',' read -r r c <<< "$key"
  echo "[$r,$c] = ${grid[$key]}"
done

# arrays of arrays (not directly supported)
# workaround: use indirection
rows=("row0" "row1")
row0=(a b c)
row1=(d e f)
ref="${rows[0]}[@]"
echo "${!ref}"                   # a b c
18

文字列操作

パラメータ展開

パラメータ展開は bash の最も強力な文字列ツールです。# と ## は先頭から削除(最短/最長マッチ)、% と %% は末尾から削除。/ は最初を置換、// はすべて置換。これらはサブシェルの起動を回避し — 単純な操作で sed にパイプするよりはるかに高速です。

bash
path="/var/log/app.log"

# length
echo "${#path}"                 # 16

# substring (offset:length)
echo "${path:5}"                # log/app.log
echo "${path:5:3}"              # log

# remove from front (shortest/longest)
echo "${path#*/}"               # var/log/app.log
echo "${path##*/}"              # app.log (basename)

# remove from end
echo "${path%/*}"               # /var/log (dirname)
echo "${path%%.*}"              # /var/log/app

# replace
echo "${path/log/LOG}"          # /var/LOG/app.log (first)
echo "${path//log/LOG}"         # all occurrences

大文字小文字変換

Bash 4+ で ,,(小文字)、^^(大文字)、^(先頭大文字)、,(先頭小文字)が追加されました。declare -u/-l は代入を自動変換します。古い bash(macOS デフォルト)には tr や awk を使用してください。大文字小文字変換はユーザー入力の正規化や識別子の構築で一般的です。

bash
s="Hello World"

# bash 4+
echo "${s,,}"                   # hello world (lower)
echo "${s^^}"                   # HELLO WORLD (upper)
echo "${s^}"                    # Hello World (capitalize first)
echo "${s,}"                    # hello World (lowercase first)

# pattern-based
echo "${s^^[aeiou]}"            # hEllO wOrld (vowels upper)

# portable (pre-bash 4)
echo "$s" | tr 'a-z' 'A-Z'
echo "$s" | awk '{print toupper($0)}'

# declare typed variables
declare -u UPPER="hello"         # HELLO
declare -l LOWER="WORLD"         # world

分割と結合

分割:IFS を設定して read -ra を使用。結合:IFS を設定した ${arr[*]} は動作しますが扱いにくい — join_by 関数の方が信頼できます。printf '%s' "$d%s" トリックは残りの各引数の前にデリミタを付加し、クリーンな結合を生成します。tr でデリミタと改行を変換できます。

bash
# split on delimiter
csv="a,b,c,d"
IFS=',' read -ra parts <<< "$csv"
echo "${parts[2]}"              # c
for p in "${parts[@]}"; do echo "$p"; done

# join with delimiter (bash 4+)
arr=(a b c d)
joined=$(IFS=,; echo "${arr[*]}")
echo "$joined"                   # a,b,c,d

# custom joiner
join_by() {
  local d=$1; shift
  local first=$1; shift
  printf '%s' "$first"
  printf '%s' "$d%s" "$@"
}
join_by " | " a b c              # a | b | c

# split into lines
tr ',' '\n' <<< "a,b,c"

検索とテスト

パターンマッチング:ワイルドカード(* ? [..])付き == は glob マッチングを行います。=~ は拡張正規表現を行い、キャプチャグループは BASH_REMATCH に入ります。shopt -s nocasematch で [[ ]] 比較を大文字小文字無視にします。expr はレガシー — 新しいコードには [[ ]] を優先してください。

bash
s="Hello World"

# substring search
if [[ "$s" == *"World"* ]]; then echo "contains"; fi
if [[ "$s" == Hello* ]]; then echo "starts with"; fi
if [[ "$s" == *World ]]; then echo "ends with"; fi

# regex match
if [[ "$s" =~ ^H[a-z]+\sW[a-z]+$ ]]; then echo "match"; fi
echo "${BASH_REMATCH[0]}"       # whole match
echo "${BASH_REMATCH[1]}"       # first group

# case-insensitive
shopt -s nocasematch
[[ "$s" == *world* ]] && echo "yes"
shopt -u nocasematch

# index (no built-in; use expr)
expr "$s" : ".*World"            # length up to match

printf フォーマット

printf は echo より強力です — C スタイルのフォーマット指定子(%s、%d、%f、%x)、幅/精度、配置をサポートします。%()T は date コマンドを起動せずに時間をフォーマットします。printf -v で結果を変数に保存します。printf '=%.0s' {1..40} は文字を N 回繰り返す便利なトリックです。

bash
# basic
printf '%s\n' "hello" "world"   # one per line
printf '%s = %d\n' "count" 42

# width and padding
printf '%10s\n' "right"         # right-aligned, width 10
printf '%-10s|\n' "left"        # left-aligned
printf '%05d\n' 42              # 00042 (zero-padded)
printf '%.2f\n' 3.14159         # 3.14

# date formatting
printf 'Today: %(%Y-%m-%d)T\n' -1

# to variable
printf -v today '%(%Y%m%d)T' -1
echo "$today"                    # 20260621

# repeat
printf '=%.0s' {1..40}; echo     # 40 equals signs
19

正規表現

=~ を使った bash 正規表現

=~ は ERE(拡張正規表現)を使用します。キャプチャグループは BASH_REMATCH に入ります(インデックス0 = 完全マッチ、1+ = グループ)。文字列は引用符で囲みますが正規表現は引用符で囲まないでください(正規表現を引用符で囲むとリテラルになります)。大文字小文字を無視するマッチングには shopt -s nocasematch を使用します。正規表現は POSIX ERE です — \d、\w はありません。[0-9]、[A-Za-z0-9_] を使用してください。

bash
s="user_42"

if [[ "$s" =~ ^([a-z]+)_([0-9]+)$ ]]; then
  echo "Match!"
  echo "Name: ${BASH_REMATCH[1]}"   # user
  echo "ID: ${BASH_REMATCH[2]}"     # 42
fi

# email-ish check
email="[email protected]"
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
  echo "valid"
fi

# case-insensitive
shopt -s nocasematch
[[ "$s" =~ USER ]] && echo "yes"
shopt -u nocasematch

grep 正規表現パターン

grep はデフォルトで BRE(基本正規表現)を使用し、+、?、|、() はバックスラッシュが必要です。-E は ERE を有効にし(クリーンな構文)。-P は \d、\w、\b、ルックアラウンドを持つ PCRE を有効にします — 最も強力ですがポータブル性が低い(GNU grep のみ)。macOS/BSD で実行する必要があるスクリプトでは -E を優先してください。

bash
# basic (BRE) — default
grep 'foo' file
grep 'a\.' file                 # literal dot (escape in BRE)
grep 'a*b' file                  # zero or more a

# extended (ERE) — -E
grep -E 'a+b' file               # one or more
grep -E 'a|b' file               # alternation
grep -E '(ab)+' file             # grouping
grep -E '[0-9]{3}' file          # quantifier

# perl (PCRE) — -P
grep -P '\d{3}' file            # digits
grep -P '\bword\b' file        # word boundary
grep -P '(?<=foo)bar' file       # lookbehind

# anchors
grep -E '^Error' file            # line start
grep -E 'done$' file             # line end
grep -E '^Error.*timeout$' file

sed 正規表現置換

sed はデフォルトで BRE を使用します(キャプチャグループは \( \)、バックリファレンスは \1)。-E は ERE に切り替えます(クリーンな () と +、?、|)。I フラグで置換を大文字小文字無視にします(GNU sed)。パターンにスラッシュが含まれる場合は別の区切り文字(s|...|...|)を使用してください。[[:space:]] は空白のポータブル表現です。

bash
# BRE (default)
sed 's/foo/bar/' file
sed 's/a\.b/x/' file            # literal dot
sed 's/a\(b\)c/\1/' file      # capture with backrefs

# ERE with -E
sed -E 's/a+b/x/' file
sed -E 's/(foo|bar)/X/g' file
sed -E 's/([0-9]{4})/Year: \1/' file

# case-insensitive
sed 's/foo/bar/I' file
sed 's/[fF][oO][oO]/bar/g' file

# common recipes
sed -E 's/^[[:space:]]+//' file  # trim leading whitespace
sed -E 's/[[:space:]]+$//' file  # trim trailing
sed -E 's/ +/ /g' file           # collapse spaces
sed -E 's#https?://[^ ]+##g' file # strip URLs

awk 正規表現

awk は ERE を使用します。~ 演算子は文字列を正規表現に対してテストします。/pat1/, /pat2/ は範囲パターンです(pat1 から pat2 までを含めてマッチ)。gsub はインプレースのグローバル置換を行います。gawk の match() は第3引数でグループを配列にキャプチャ — 便利ですが mawk/POSIX awk にはポータブルではありません。

bash
# match operator ~
awk '$1 ~ /^[0-9]+/' file        # field 1 is numeric
awk '$0 !~ /debug/' file         # line doesn't contain
awk '/^Error/,/^$/' file         # from Error to blank line

# case-insensitive
awk 'BEGIN { IGNORECASE=1 } /error/' file
awk 'tolower($0) ~ /error/' file

# gsub (global substitute)
awk '{ gsub(/foo/, "bar"); print }' file
awk '{ gsub(/[0-9]+/, "N"); print }' file

# match with capture (gawk)
awk 'match($0, /id=([0-9]+)/, m) { print m[1] }' file

# split on regex
awk '{ n = split($0, parts, /[,;\t]/); print n }' file

一般的な正規表現レシピ

これらのレシピは一般的なテキスト抽出タスクをカバーします。-o はマッチした部分のみ印刷します。正規表現は HTML、JSON、XML の解析には適していないことに注意してください — 適切なパーサーを使用してください(JSON には jq、XML には xmllint)。IPv4 の場合、この正規表現はフォーマットにマッチしますが有効な範囲にはマッチしません(255.255.255.255 vs 999.999.999.999)。

bash
# IPv4
grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

# URL
grep -Eo 'https?://[^[:space:]]+' file

# email
grep -Eo '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' file

# date YYYY-MM-DD
grep -E '[0-9]{4}-[0-9]{2}-[0-9]{2}' file

# hex color
grep -Eo '#[0-9A-Fa-f]{6}' file

# strong password (8+, upper, lower, digit)
grep -E '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,}$' file

# trim whitespace
sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' file

# extract domain from URL
sed -E 's#https?://([^/]+).*#\1#' file
20

デバッグ

set オプション

set -e はコマンド失敗で終了します(スクリプトに必須)。set -u は変数名のタイプミスをキャッチします。set -o pipefail はパイプの任意の部分が失敗するとパイプを失敗させます(これがないと最後のコマンドの終了コードのみが重要)。set -x は実行をトレースします。-euo pipefail として組み合わせると、これが「厳格モード」です — 最も安全なデフォルトです。

bash
#!/bin/bash
set -e                           # exit on error
set -u                           # error on unset variable
set -o pipefail                  # pipe fails if any command fails
set -x                           # print commands (trace)
# or combined:
set -euo pipefail

# shorthand
set -Eeuxo pipefail              # -E: ERR trap inherits

# strict mode for robust scripts
set -euo pipefail
IFS=$'\n\t'

# trace specific section
set -x
complex_logic
set +x

# debug a script
bash -x script.sh
bash -xv script.sh               # verbose + trace

-x でのトレース

set -x は実行前に各コマンドを印刷します(デフォルトのプレフィックスは +(PS4))。PS4 を file:line:function を含むようにカスタマイズすると、トレースがデバッグにはるかに役立ちます。BASH_XTRACEFD はトレースを別の FD にリダイレクトし、stdout/stderr からトレース出力を分離できます。

bash
# trace entire script
bash -x script.sh

# trace inside script
set -x
echo "tracing on"
set +x

# customize PS4 (the trace prompt)
export PS4='+ ${BASH_SOURCE:-shell}:${LINENO}: ${FUNCNAME[0]:-main}() '
set -x
my_function
set +x

# trace to file
exec 5> /tmp/trace.log
BASH_XTRACEFD=5
set -x
# ... commands ...
set +x
exec 5>&-

エラー処理と ERR trap

ERR trap はコマンド失敗時に発生します(set -e と共に)。trap 内の $? は失敗した終了コードを返します。$LINENO は失敗場所を示します。caller ビルトインは file:line:function を印刷し — ループするとスタックトレースを生成します。set -E は関数内でも ERR trap を発生させます(そうでないと関数コンテキストで無効化されます)。

bash
set -e
set -E                           # ERR trap inherits functions

on_error() {
  local exit_code=$?
  local line=$1
  echo "ERROR: command failed at line $line with exit code $exit_code" >&2
  echo "Stack:" >&2
  local i=0
  while caller $i >&2; do ((i++)); done
  exit $exit_code
}
trap 'on_error $LINENO' ERR

# now any failing command triggers the handler
false                            # triggers on_error

# caller builtin gives file:line:func
# stack trace via: while caller $i; do ((i++)); done

shellcheck リンター

shellcheck はシェルスクリプトの事実上のリンターです — クォーティングの問題、未使用変数、一般的な落とし穴をキャッチし、イディオムを提案します。すべてのスクリプトで実行してください。最も一般的な修正は SC2086 です:単語分割とグロブを防ぐため変数を引用符で囲んでください。多くの CI パイプラインが shellcheck の通過を要求します。

bash
# install: apt install shellcheck (or brew, or use shellcheck.net)
# run:
shellcheck script.sh
shellcheck -x script.sh          # trust includes (no SC1090 warnings)
shellcheck -S warning script.sh  # only warnings+
shellcheck --exclude=SC2086 *.sh # exclude specific rules

# common warnings:
# SC2086: double-quote to prevent globbing (use "$var")
# SC2046: quote $(cmd) to prevent word splitting
# SC2034: unused variable
# SC2155: declare and assign separately (masks return code)
# SC2004: $/${} unnecessary in arithmetic

# in script: disable inline
# shellcheck disable=SC2086
echo $unquoted_var

# disable for next line
# shellcheck disable=SC2086
echo $args

ロギングと診断

タイムスタンプとカラー付きのレベル別ロガー(DEBUG/INFO/WARN/ERROR)によりスクリプトのデバッグがはるかに容易になります。常に stderr にログして stdout をデータ用にクリーンに保ちます。--verbose フラグ(set -x を有効化)と --dry-run(実行せずにコマンドを印刷)をサポートしてください。ANSI カラーコード:31=赤、33=黄、32=緑、90=グレー。

bash
# logging function with levels
log() {
  local level=$1; shift
  local msg="$*"
  local ts=$(date '+%Y-%m-%d %H:%M:%S')
  local color
  case $level in
    DEBUG) color='\033[90m' ;;
    INFO)  color='\033[32m' ;;
    WARN)  color='\033[33m' ;;
    ERROR) color='\033[31m' ;;
  esac
  printf "%b[%s] [%s] %s\033[0m\n" "$color" "$ts" "$level" "$msg" >&2
}

log INFO "Starting process"
log WARN "Disk space low"
log ERROR "Connection failed"

# verbose flag
[[ $VERBOSE ]] && set -x

# dry-run mode
if [[ $DRY_RUN ]]; then
  echo "Would: $cmd"
else
  eval "$cmd"
fi
21

セキュリティ

クォーティングとインジェクション

bash セキュリティの#1ルール:すべての変数展開を引用符で囲むこと。引用符なしの $var は単語分割とグロブを受け、バグとインジェクションを引き起こします。動的引数のコマンドには、文字列の代わりに配列を使用します(cmd=(ls "$dir"); "${cmd[@]}")。ユーザー入力をシェル文字列に eval や $() で決して入れないでください。

bash
# ALWAYS quote variables
file="my file.txt"
rm $file                         # BAD: rm my file.txt (3 args!)
rm "$file"                       # GOOD: rm "my file.txt"

# use arrays for command + args
files=("a.txt" "b file.txt" "c.txt")
rm "${files[@]}"                # each file as separate arg, safely

# avoid eval
eval "echo $user_input"          # DANGEROUS
echo "$user_input"               # safe

# avoid command substitution in unsafe places
# BAD: filename from user, executed
cmd="ls $userdir"
$cmd                             # word-splitting + globbing

# GOOD: use array
cmd=(ls "$userdir")
"${cmd[@]}"

安全なテンポラリファイルとシークレット

mktemp は予測不可能な名前と600パーミッションのファイルを作成します — シンボリックリンク競合から安全です。常に trap でクリーンアップしてください。シークレットは環境変数から読み込み、ハードコードしないでください。シークレットをコマンドライン引数として渡すのを避けてください(ps で見える) — stdin、環境変数、または600パーミッションの設定ファイルを使用してください。

bash
# safe temp files
tmp=$(mktemp)                    # atomic, unpredictable name
trap 'rm -f "$tmp"' EXIT

# safe temp dir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

# secrets: read from env, never hardcode
api_key="${API_KEY:?Set API_KEY env var}"

# don't leak secrets to process list (ps)
# BAD: curl -H "Authorization: Bearer $TOKEN" ...
# GOOD: pass via stdin or config file
curl -H @- <<EOF https://api.example.com
Authorization: Bearer $TOKEN
EOF

# restrict file perms for secrets
umask 077
echo "$api_key" > ~/.secrets/api
chmod 600 ~/.secrets/api

入力検証

使用前にすべてのユーザー入力を検証してください。正規表現はフォーマットをチェックし、算術は範囲をチェックします。ファイルパスの場合、明示的に許可されない限り ..(トラバーサル)と絶対パスを拒否してください。tr -cd は許可セットにない文字を削除し — 識別子のサニタイズに便利です。許可されるアクションにはブラックリストよりホワイトリスト(case)の方が安全です。

bash
# validate numeric input
read -p "Enter a number: " n
if ! [[ "$n" =~ ^[0-9]+$ ]]; then
  echo "Not a number" >&2; exit 1
fi

# validate range
if (( n < 1 || n > 100 )); then
  echo "Out of range" >&2; exit 1
fi

# validate file path (no traversal)
path="$1"
case "$path" in
  *..*|/*) echo "Invalid path" >&2; exit 1 ;;
esac

# sanitize filename
safe=$(echo "$name" | tr -cd 'A-Za-z0-9._-')

# whitelist allowed values
case "$action" in
  start|stop|restart) ;;
  *) echo "Invalid action" >&2; exit 1 ;;
esac

権限ドロップと setuid

setuid シェルスクリプトは安全ではありません — ほとんどのシステムはスクリプトの setuid ビットを無視します。代わりに厳格な sudoers エントリ(特定のコマンド、パスワードなし)で sudo を使用してください。exec sudo -u でできるだけ早く root をドロップします。(ポート80のバインドのような)機能には、root として実行する代わりに setcap を使用してください。chmod 700 でスクリプトを所有者のみにプライベートに保ちます。

bash
# drop privileges in a script running as root
if [[ $EUID -eq 0 ]]; then
  exec sudo -u appuser "$0" "$@"
fi

# check for root
if [[ $EUID -ne 0 ]]; then
  echo "Requires root" >&2; exit 1
fi

# setuid scripts are DANGEROUS — avoid them
# instead, use sudoers entries
# /etc/sudoers.d/myapp:
#   appuser ALL=(root) NOPASSWD: /usr/local/bin/myapp.sh

# restrict script permissions
chmod 700 script.sh              # only owner can run
chown appuser:appuser script.sh

# use capsh instead of full root for fine-grained caps
# setcap 'cap_net_bind_service=+ep' /usr/bin/myapp

署名と整合性

ダウンロードしたスクリプト、特に root として実行されるインストーラのチェックサムと署名を常に検証してください。sha256sum -c は既知の良好なハッシュと比較します。GPG 署名は整合性と真正性の両方を証明します(署名者の身元)。curl を sha256sum にパイプすると実行前にハッシュを目視できます — しかしダウンロードして検証してから実行するパターンの方が安全です。

bash
# verify checksum
sha256sum -c file.sha256
# file.sha256 contains: <hash>  <filename>

# generate checksums
sha256sum release.tar.gz > release.tar.gz.sha256
sha256sum *.iso > isos.sha256

# GPG sign and verify
gpg --sign file.txt              # creates file.txt.gpg
gpg --verify file.txt.gpg
gpg --detach-sig --armor file.tar.gz
gpg --verify file.tar.gz.asc file.tar.gz

# download + verify in one go
curl -fsSL https://example.com/install.sh | sha256sum
curl -fsSL https://example.com/install.sh -o install.sh
sha256sum -c <(echo "expected_hash  install.sh")
22

ネットワーキング

curl 高度

curl は URL 経由でデータを転送します。-X はメソッドを設定し、-d はデータを送信し、-H はヘッダーを設定します。-O はリモートファイル名で保存します。-I はヘッダーのみ取得します。-L はリダイレクトに従います。-u user:pass で認証。-v で詳細。

bash
# POST with data
curl -X POST -d "name=Alice" https://api.example.com
# JSON POST
curl -H "Content-Type: application/json" \
     -d '{"name":"Alice"}' https://api.example.com
# Download with progress
curl -O https://example.com/file.zip
# Headers only
curl -I https://example.com

wget

wget は非対話的にファイルをダウンロードします。-m でサイトをミラー。-c で中断されたダウンロードをレジューム。-r で再帰、-l で深度制限。-b でバックグラウンド実行。再帰ダウンロードには curl より優れています。

bash
# Download file
wget https://example.com/file.zip
# Mirror website
wget -m https://example.com
# Resume download
wget -c https://example.com/largefile.zip
# Recursive download
wget -r -l 2 https://example.com
# Background
wget -b https://example.com/file.zip

ssh と scp

ssh はリモートマシンに接続します。-i でキーファイルを指定。-L でローカルポートフォワーディングを作成。scp は SSH 経由でファイルをコピーします。-r でディレクトリを再帰。ssh-copy-id でキーをインストール。エイリアスのために ~/.ssh/config を設定してください。

bash
# SSH connect
ssh user@host
# SSH with key
ssh -i ~/.ssh/id_rsa user@host
# Port forwarding
ssh -L 8080:localhost:80 user@host
# Copy file
scp file.txt user@host:/path/
# Copy directory
scp -r dir/ user@host:/path/

netstat と ss

netstat と ss はネットワーク接続を表示します。-t TCP、-u UDP、-l リスニング、-n 数値、-p プロセス。ss は netstat より高速で詳細です。どのプロセスがポートを使用しているかを見つけたり、接続問題を診断するために使用します。

bash
# List listening ports
netstat -tlnp
ss -tlnp
# Show all connections
netstat -an
ss -tunap
# Show routing table
netstat -r
# Show interface stats
netstat -i

ping と traceroute

ping は到達性とレイテンシをテストします(-c でカウント制限)。traceroute はホストへのパスを表示します。dig は DNS レコード(A、MX、NS、TXT)を照会します。+short で簡潔な出力。ネットワーク診断と DNS トラブルシューティングに使用します。

bash
# Check connectivity
ping -c 4 google.com
# Trace route
traceroute google.com
# DNS lookup
dig example.com
dig +short example.com
# Get DNS records
dig MX example.com
dig NS example.com
23

シェルスクリプティングの深掘り

関数

関数はコマンドをグループ化します。local で関数スコープの変数を作成します。$1、$2 は引数です。return は終了ステータス(0-255)を設定します。$() で出力を取得します。関数は使用前に定義する必要があります。source でファイルから読み込みます。

bash
greet() {
    local name="$1"
    echo "Hello, $name"
    return 0
}
greet "Alice"
# Return value via $?
result=$(greet "Bob")

条件分岐

[ ] はテストコマンドです。-f ファイル、-d ディレクトリ、-z 空文字列、-n 非空。文字列は =、数値は -eq/-ne/-gt/-lt。スペースを処理するため常に変数を引用符で囲んでください。[[ ]] は bash 拡張で正規表現をサポートします。

bash
if [ -f "/path/file" ]; then
    echo "File exists"
elif [ -d "/path" ]; then
    echo "Directory exists"
else
    echo "Not found"
fi
# String comparison
if [ "$str" = "hello" ]; then ... fi
# Numeric
if [ "$num" -gt 10 ]; then ... fi

ループ

for はリストを反復します。C スタイルの for は (( )) を使用します。while は条件が失敗するまで読み込みます。while read はファイルを1行ずつ安全に処理します。常に変数を引用符で囲んでください。再帰的なファイル反復には find を使用します。

bash
# For loop
for i in 1 2 3; do
    echo $i
done
# C-style
for ((i=0; i<5; i++)); do echo $i; done
# While loop
while read line; do
    echo "$line"
done < file.txt
# Iterate files
for f in *.txt; do process "$f"; done

配列

配列は括弧を使用します。${arr[@]} はすべての要素に展開します。${#arr[@]} は長さです。スペースを処理するため常に "${arr[@]}" を引用符で囲んでください。declare -A で連想配列を作成します(bash 4+)。read -a で配列に分割します。

bash
# Declare array
fruits=("apple" "banana" "cherry")
# Access
echo ${fruits[0]}      # apple
echo ${fruits[@]}      # all
echo ${#fruits[@]}     # length
# Iterate
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done
# Associative array
declare -A config
config[key]="value"

エラー処理

set -e はエラーで終了、-u は未定義変数、pipefail はパイプ失敗をキャッチします。trap ERR でエラーを処理します。trap EXIT でクリーンアップを実行します。command -v でコマンドが存在するかチェックします。常に set -euo pipefail でスクリプトを開始してください。

bash
set -e  # Exit on error
set -u  # Error on undefined variable
set -o pipefail  # Pipeline fails if any command fails
set -euo pipefail  # All three (recommended)
trap 'echo "Error on line $LINENO"' ERR
trap 'cleanup' EXIT
# Check command result
if ! command -v docker &> /dev/null; then
    echo "docker not installed"
fi
24

ファイル操作の深掘り

find 高度

find は条件でファイルを検索します。-type f/d でファイル/ディレクトリ。-size +1M で1MBより大きい。-mtime -7 で7日以内に変更。-exec で各マッチでコマンドを実行。{} がファイル名、\; がコマンド終了です。

bash
# By name
find . -name "*.txt"
# By type and size
find . -type f -size +1M
# By modification time
find . -mtime -7  # Modified in last 7 days
# Execute command
find . -name "*.log" -exec gzip {} \;
# Delete
find . -name "*.tmp" -delete

tar と圧縮

tar はファイルを結合し、-z で gzip 圧縮します。-c 作成、-x 展開、-t リスト、-v 詳細、-f ファイル名。.tar.bz2 には -j を使用。zip/unzip は ZIP 形式を処理します。特定のディレクトリに展開するには -C を使用します。

bash
# Create tar.gz
tar -czvf archive.tar.gz dir/
# Extract
tar -xzvf archive.tar.gz
# List contents
tar -tzvf archive.tar.gz
# zip/unzip
zip -r archive.zip dir/
unzip archive.zip

rsync

rsync は差分のみ転送して効率的にファイルを同期します。-a アーカイブモード(属性保持)、-v 詳細、-z 圧縮。--delete でソースにないファイルを削除。src/ の末尾のスラッシュが重要:あると内容をコピー、ないとディレクトリをコピーします。

bash
# Sync local
rsync -av src/ dest/
# Sync to remote
rsync -avz src/ user@host:/path/
# Delete files not in source
rsync -av --delete src/ dest/
# Dry run
rsync -av --dry-run src/ dest/
# Exclude
rsync -av --exclude='*.log' src/ dest/

ファイルパーミッション

パーミッション:r=4、w=2、x=1。755 = rwxr-xr-x(所有者フル、その他読み/実行)。644 = rw-r--r--(ファイル)。u/g/o/a = ユーザー/グループ/その他/すべて。chmod -R で再帰。chown は所有権を変更します。

bash
# Symbolic
chmod u+x file      # User execute
chmod g-w file      # Group write off
chmod a=r file      # All read only
# Numeric
chmod 755 file      # rwxr-xr-x
chmod 644 file      # rw-r--r--
# Change owner
chown user:group file
# Recursive
chmod -R 755 dir/

ln とシンボリックリンク

ハードリンクは同じ inode を指します(同じファイルシステムのみ)。シンボリックリンク(-s)はパスを指します。ハードリンクは元の削除後も生き残ります。シンボリックリンクはターゲットが移動すると壊れます。readlink でシンボリックリンクを解決します。

bash
# Hard link
ln file.txt hardlink.txt
# Symbolic link
ln -s /path/to/target symlink
# Find broken symlinks
find . -type l ! -exec test -e {} \; -print
# Copy symlink target
cp -L symlink dest/
25

よくある落とし穴

引用符なしの変数

引用符なしの変数はスペースで分割され glob されます。$(ls) はスペースを含むファイル名で壊れます。glob(*.txt)または find -print0 と read -d を使用してください。常に変数を引用符で囲んでください:"$var"。安全な行読み込みには IFS= read -r を使用します。

bash
# BUG: breaks with spaces
for f in $(ls); do echo "$f"; done
# GOOD: handle spaces
for f in *.txt; do echo "$f"; done
# Or with find
find . -name "*.txt" -print0 | while IFS= read -r -d '' f; do
    echo "$f"
done

cp vs mv

cp はコピー、mv は移動します。両方とも警告なしで上書きできます。上書き前にプロンプトするには -i を使用してください。上書きしないには -n を使用します。mv は同じファイルシステム上で原子的で、ロックや原子的更新に便利です。

bash
# cp: copy (original remains)
cp file.txt backup.txt
# mv: move/rename (original removed)
mv file.txt newname.txt
# Common mistake: overwriting
cp important.txt /tmp/  # If /tmp/important.txt exists, overwritten!
# Use -i for safety
cp -i file.txt /tmp/

rm の危険性

rm -rf は危険です、特に変数付きの場合。空の変数は rm -rf / を引き起こします。常に変数を引用符で囲みチェックしてください。回復可能な削除には trash-cli を検討してください。rm -rf / や sudo で慎重でない実行は決してしないでください。

bash
# DANGEROUS: recursive force
rm -rf /  # Never do this!
rm -rf $VAR/  # If $VAR is empty, becomes rm -rf /
# Safer: check first
if [ -n "$VAR" ]; then
    rm -rf "$VAR/"
fi
# Use trash-cli instead
trash-put file.txt

シバン

シバン(#!)はインタプリタを指定します。#!/usr/bin/env bash は #!/bin/bash よりポータブルです。最大のポータビリティには /bin/sh を使用します(bashism を回避)。常に chmod +x でスクリプトを実行可能にしてください。

bash
#!/bin/bash          # bash script
#!/usr/bin/env bash  # Portable bash
#!/usr/bin/env python3  # Python
#!/bin/sh            # POSIX sh (more portable)
# Make executable
chmod +x script.sh
./script.sh

終了ステータス

$? は最後のコマンドの終了ステータスを保持します。0 = 成功、非ゼロ = 失敗。別のコマンドが上書きする前に即座にチェックしてください。直接 if command を使用する方が良いです。スクリプトでは常に意味のあるステータスコードで終了してください。

bash
# Check last command
if [ $? -eq 0 ]; then
    echo "Success"
fi
# Better: direct check
if command; then
    echo "Success"
else
    echo "Failed"
fi
# Exit with status
exit 0  # Success
exit 1  # Failure

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.