【Bashメモ】01_スクリプト作成基本

## 1. 必須文法と基本構造

### シバン (Shebang)
スクリプトの最初の行に含め、どのシェルで実行するかを指定します。

```bash
#!/bin/bash
```

### コメント
`#` 以降の内容はコメントとして扱われます。

```bash
# これはコメントです。
```

### 変数の宣言と使用
変数名は大文字と小文字を区別し、`=` の両側にスペースを入れてはいけません。使用する際は変数名の前に `$` を付けます。

```bash
LOG_FILE="/var/log/syslog"
echo "ログファイル: $LOG_FILE"
```

**予想される実行結果:**

```
ログファイル: /var/log/syslog
```

### コマンド実行結果の変数への保存
`$(コマンド)` または `` `コマンド` `` (バッククォート) を使用します。

```bash
CURRENT_DATE=$(date +%Y-%m-%d)
echo "今日のA日付: $CURRENT_DATE"
```

**予想される実行結果 (例):**

```
今日のA日付: 2025-06-04
```

### 関数の定義

```bash
function process_log() {
    echo "ログ処理開始: $1"
    # $1, $2 などは関数に渡された引数
}
process_log "access.log"
```

**予想される実行結果:**

```
ログ処理開始: access.log
```

### スクリプト引数
`$0` (スクリプト名), `$1`, `$2`, ... (渡された引数), `$#` (引数の数), `$@` (すべての引数)。

```bash
# スクリプト名: my_script.sh
# 実行コマンド: bash my_script.sh arg1 arg2
echo "スクリプト名: $0"
echo "最初の引数: $1"
echo "全引数の数: $#"
```

**予想される実行結果 (例: `bash my_script.sh arg1 arg2` で実行した場合):**

```
スクリプト名: my_script.sh
最初の引数: arg1
全引数の数: 2
```

---

## 2. 基本データ型の操作

### 2.1. 数値の操作

#### 算術演算
`$((式))` または `expr` コマンドを使用します。

```bash
NUM1=10
NUM2=5
SUM=$((NUM1 + NUM2))
DIFF=$((NUM1 - NUM2))
PROD=$((NUM1 * NUM2))
DIV=$((NUM1 / NUM2)) # 整数除算
echo "足し算: $SUM, 引き算: $DIFF, 掛け算: $PROD, 割り算: $DIV"

# インクリメント/デクリメント
COUNT=1
((COUNT++))
echo "カウント: $COUNT"
```

**予想される実行結果:**

```
足し算: 15, 引き算: 5, 掛け算: 50, 割り算: 2
カウント: 2
```

#### 比較演算
`[[ ... ]]` 内で使用し、整数比較演算子 (`-eq`, `-ne`, `-gt`, `-ge`, `-lt`, `-le`) を使用します。

```bash
NUM1=10
NUM2=5
if [[ $NUM1 -gt $NUM2 ]]; then
    echo "$NUM1 は $NUM2 より大きいです。"
fi
```

**予想される実行結果:**

```
10 は 5 より大きいです。
```

### 2.2. 文字列の操作

#### 文字列の長さ

```bash
STRING="hello world"
LENGTH=${#STRING}
echo "文字列の長さ: $LENGTH"
```

**予想される実行結果:**

```
文字列の長さ: 11
```

#### 部分文字列の抽出
`${変数:開始インデックス:長さ}`。(インデックスは0から始まります)

```bash
STRING="hello world"
SUBSTR=${STRING:0:5} # "hello"
echo "部分文字列: $SUBSTR"
```

**予想される実行結果:**

```
部分文字列: hello
```

#### 文字列の置換
`${変数/検索文字列/置換文字列}` (最初の一致), `${変数//検索文字列/置換文字列}` (すべての一致)。

```bash
STRING="hello world"
REPLACED=${STRING/world/bash} # "hello bash"
echo "置換された文字列: $REPLACED"
```

**予想される実行結果:**

```
置換された文字列: hello bash
```

#### 文字列の比較
`[[ ... ]]` 内で `==` (等しい), `!=` (異なる), `<` (辞書順で小さい), `>` (辞書順で大きい) などを使用します。

```bash
STR1="apple"
STR2="banana"
if [[ "$STR1" < "$STR2" ]]; then
    echo "$STR1 は $STR2 より辞書順で先に来ます。"
fi
```

**予想される実行結果:**

```
apple は banana より辞書順で先に来ます。
```

#### 空文字列の確認
`-z` (空なら真), `-n` (空でなければ真)。

```bash
EMPTY_STR=""
if [[ -z "$EMPTY_STR" ]]; then
    echo "文字列は空です。"
fi
```

**予想される実行結果:**

```
文字列は空です。
```

### 2.3. 真偽値 (Boolean) の操作
Bashに明示的なBoolean型はなく、**コマンドの終了コード (exit code)** を通じて真/偽を判断します。
`0` は成功 (真)、`0` 以外の値は失敗 (偽) を意味します。

#### ファイル/ディレクトリの存在確認

```bash
# 例として存在するファイルと存在しないファイルを設定
touch /tmp/test.log
mkdir /tmp/my_dir

FILE="/tmp/test.log"
NON_EXIST_FILE="/tmp/non_existent.log"
DIR="/tmp/my_dir"

if [[ -f "$FILE" ]]; then # ファイルが存在すれば
    echo "$FILE が存在します。"
fi
if [[ -f "$NON_EXIST_FILE" ]]; then
    echo "$NON_EXIST_FILE が存在します。(表示されない)"
fi
if [[ -d "$DIR" ]]; then # ディレクトリが存在すれば
    echo "$DIR がディレクトリです。"
fi

# 後処理
rm /tmp/test.log
rmdir /tmp/my_dir
```

**予想される実行結果:**

```
/tmp/test.log が存在します。
/tmp/my_dir がディレクトリです。
```

#### 論理演算
`&&` (AND), `||` (OR), `!` (NOT)。

```bash
# 例としてファイルを作成
touch file1.txt
# file2.txt は作成しない

if [[ -f "file1.txt" && -f "file2.txt" ]]; then
    echo "両方のファイルが存在します。(表示されない)"
fi
if [[ -f "file1.txt" || -f "file2.txt" ]]; then
    echo "どちらかのファイルが存在します。"
fi

# 後処理
rm file1.txt
```

**予想される実行結果:**

```
どちらかのファイルが存在します。
```

---

## 3. 日付データの操作
`date` コマンドはログ分析時にタイムスタンプを処理するために不可欠です。

### 現在の日付/時刻

```bash
date
```

**予想される実行結果 (例):**

```
Wed Jun  4 02:18:08 JST 2025
```

### 特定形式での出力
`+%Y` (年), `+%m` (月), `+%d` (日), `+%H` (時), `+%M` (分), `+%S` (秒), `+%s` (Unixタイムスタンプ)。

```bash
echo "現在: $(date +%Y-%m-%d %H:%M:%S)"
echo "Unixタイムスタンプ: $(date +%s)"
```

**予想される実行結果 (例):**

```
現在: 2025-06-04 02:18:08
Unixタイムスタンプ: 1749076688
```

### 文字列を日付として解析
`-d` オプションを使用して、さまざまな形式の日付文字列を解析できます。

```bash
LOG_TIME="2025-06-04 01:23:45"
LOG_TIMESTAMP=$(date -d "$LOG_TIME" +%s)
echo "$LOG_TIME のUnixタイムスタンプ: $LOG_TIMESTAMP"
```

**予想される実行結果 (例):**

```
2025-06-04 01:23:45 のUnixタイムスタンプ: 1749073425
```

### Unixタイムスタンプを日付に変換

```bash
UNIX_TS=1749073425 # 2025-06-04 01:23:45 JST
echo "Unixタイムスタンプ $UNIX_TS は $(date -d "@$UNIX_TS" +%Y-%m-%d %H:%M:%S) です。"
```

**予想される実行結果 (例):**

```
Unixタイムスタンプ 1749073425 は 2025-06-04 01:23:45 です。
```

### 日付の計算

```bash
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
echo "昨日の日付: $YESTERDAY"
THREE_HOURS_AGO=$(date -d "3 hours ago" +%H:%M:%S)
echo "3時間前の時間: $THREE_HOURS_AGO"
```

**予想される実行結果 (例、現在時刻が 02:18:08 の場合):**

```
昨日の日付: 2025-06-03
3時間前の時間: 23:18:08
```

#### ログファイルタイムスタンプの解析 (高度)
`awk` と `mktime` 関数を組み合わせて、ログファイル内のタイムスタンプを比較できます。(詳細については、以前の回答もご参照ください。)

---

## 4. 条件文

### `if-else` 文

```bash
VAR=10
if [[ $VAR -eq 10 ]]; then
    echo "VARは10です。"
elif [[ $VAR -gt 10 ]]; then
    echo "VARは10より大きいです。"
else
    echo "VARは10より小さいです。"
fi
```

**予想される実行結果:**

```
VARは10です。
```

### ファイル条件

```bash
# 例としてファイルを作成
touch /tmp/data.txt
if [[ -f "$FILE" ]]; then
    echo "ファイルが存在します。"
fi
# 後処理
rm /tmp/data.txt
```

**予想される実行結果:**

```
ファイルが存在します。
```

### 文字列パターンマッチング

```bash
LINE="Error in login service"
if [[ "$LINE" =~ "Error" ]]; then
    echo "エラーが発見されました。"
fi
```

**予想される実行結果:**

```
エラーが発見されました。
```

---

## 5. ループ文

### `for` ループ (リストの走査)

```bash
for LOG in access.log error.log auth.log; do
    echo "処理中のログ: $LOG"
done
```

**予想される実行結果:**

```
処理中のログ: access.log
処理中のログ: error.log
処理中のログ: auth.log
```

### `for` ループ (数値範囲)

```bash
for i in {1..3}; do
    echo "数値: $i"
done
```

**予想される実行結果:**

```
数値: 1
数値: 2
数値: 3
```

### `while` ループ (条件が満たされる間)
主にファイルを1行ずつ読み込む際に便利です。

```bash
# 例としてファイルを作成
echo -e "Line 1\nLine 2\nLine 3" > /tmp/input.txt

while IFS= read -r line; do
    echo "読み込んだ行: $line"
done < "/tmp/input.txt"

# 後処理
rm /tmp/input.txt
```

**予想される実行結果:**

```
読み込んだ行: Line 1
読み込んだ行: Line 2
読み込んだ行: Line 3
```

---

## 6. データ構造

### 6.1. 配列 (Array)

#### 宣言

```bash
MY_ARRAY=("item1" "item2" "item3")
```

#### アクセス
インデックスは0から始まります。

```bash
MY_ARRAY=("item1" "item2" "item3")
echo "最初のアイテム: ${MY_ARRAY[0]}"
echo "すべてのアイテム: ${MY_ARRAY[@]}"
```

**予想される実行結果:**

```
最初のアイテム: item1
すべてのアイテム: item1 item2 item3
```

#### 長さ

```bash
MY_ARRAY=("item1" "item2" "item3")
echo "配列の長さ: ${#MY_ARRAY[@]}"
```

**予想される実行結果:**

```
配列の長さ: 3
```

#### 追加/削除

```bash
MY_ARRAY=("item1" "item2" "item3")
MY_ARRAY+=("item4") # 追加
echo "追加後: ${MY_ARRAY[@]}"
unset MY_ARRAY[1]   # 削除 (インデックス1の"item2"を削除)
echo "削除後: ${MY_ARRAY[@]}" # インデックス1が空になり、詰まらないことに注意
```

**予想される実行結果:**

```
追加後: item1 item2 item3 item4
削除後: item1 item3 item4
```

### 6.2. 連想配列 (Associative Array / Map)
Bash 4.0以上でのみサポートされています。宣言時に `declare -A` を使用する必要があります。

#### 宣言

```bash
declare -A STATUS_CODES
STATUS_CODES["200"]="OK"
STATUS_CODES["404"]="Not Found"
STATUS_CODES["500"]="Internal Server Error"
```

#### アクセス

```bash
declare -A STATUS_CODES
STATUS_CODES["200"]="OK"
echo "200コード: ${STATUS_CODES["200"]}"
```

**予想される実行結果:**

```
200コード: OK
```

#### すべてのキー/値

```bash
declare -A STATUS_CODES
STATUS_CODES["200"]="OK"
STATUS_CODES["404"]="Not Found"
echo "すべてのキー: ${!STATUS_CODES[@]}"
echo "すべての値: ${STATUS_CODES[@]}"
```

**予想される実行結果 (順序は不定):**

```
すべてのキー: 404 200
すべての値: Not Found OK
```

---

## 7. 入出力 (I/O)

### 標準出力 (`stdout`)

```bash
echo "メッセージ"
printf "名前: %s, 年齢: %d\n" "山田太郎" 30
```

**予想される実行結果:**

```
メッセージ
名前: 山田太郎, 年齢: 30
```

### 標準エラー (`stderr`)

```bash
echo "エラー発生！" >&2
```

**予想される実行結果 (stderrに出力されるため、通常はコンソールに表示される):**

```
エラー発生！
```

### ファイルへのリダイレクト
`>` (上書き), `>>` (追記)。

```bash
echo "ログ開始" > output.log
echo "新しい行" >> output.log
cat output.log
# 後処理
rm output.log
```

**予想される実行結果:**

```
ログ開始
新しい行
```

### 標準入力 (`stdin`) から読み込み

```bash
echo "名前を入力してください:"
# 以下は手動入力が必要な例
# read USER_NAME
# echo "こんにちは、$USER_NAME さん！"

# スクリプト実行時の例 (実際のテストでは対話的に実行)
# echo -e "TestUser" | bash -c 'echo "名前を入力してください:"; read USER_NAME; echo "こんにちは、$USER_NAME さん!"'
```

**予想される対話的実行結果:**

```
名前を入力してください:
（ここにユーザーが名前を入力、例: "Alice"）
こんにちは、Alice さん！
```

### パイプ (`|`)
あるコマンドの出力を別のコマンドの入力として渡します。

```bash
# 例としてログファイルを作成
echo -e "Line 1 Error\nLine 2\nLine 3 Error" > /tmp/access.log
cat /tmp/access.log | grep "Error" | wc -l
# 後処理
rm /tmp/access.log
```

**予想される実行結果:**

```
2
```

### ヒアドキュメント (`<<EOF`)
スクリプト内で複数行のテキストをコマンドに渡す際に使用します。

```bash
TOTAL_COUNT=123
cat <<EOF > my_report.txt
----- レポート -----
日付: $(date +%Y-%m-%d)
総処理件数: $TOTAL_COUNT
------------------
EOF
cat my_report.txt
# 後処理
rm my_report.txt
```

**予想される実行結果 (例):**

```
----- レポート -----
日付: 2025-06-04
総処理件数: 123
------------------
```

---

## 8. スクリプト間の変数共有と関数の再利用
Bashスクリプトでは、**`source`** コマンド (または `.` ) を使用して、他のスクリプトファイルで定義された変数や関数を現在のスクリプトで読み込むことができます。これは、ライブラリファイルや設定ファイルを分割して管理する際に非常に便利です。

### ソースファイル (例: `config.sh`) の作成

```bash
# config.sh
LOG_DIR="/var/log/my_app"
REPORT_FILE="daily_report.txt"
MAX_ERRORS=100

function log_message() {
    echo "[$(date +%Y-%m-%d %H:%M:%S)] $1"
}
```

### メインスクリプト (例: `main_script.sh`) からの読み込み

```bash
#!/bin/bash

# config.sh ファイルを読み込む
# 現在のスクリプトが config.sh と同じディレクトリにある場合
source "$(dirname "$0")/config.sh"
# または絶対パスを使用: source /path/to/your/config.sh

log_message "スクリプト開始..."
echo "ログディレクトリ: $LOG_DIR"
echo "レポートファイル: $REPORT_FILE"

ERROR_COUNT=150 # 例としてエラー数を設定

if [[ $ERROR_COUNT -gt $MAX_ERRORS ]]; then
    log_message "エラーがしきい値を超過しました！"
fi
```

**予想される実行結果の例 (`main_script.sh` を実行した場合):**

```
[2025-06-04 02:18:08] スクリプト開始...
ログディレクトリ: /var/log/my_app
レポートファイル: daily_report.txt
[2025-06-04 02:18:08] エラーがしきい値を超過しました！
```

* **`source` または `.`**: 他のスクリプトファイルを現在のシェル環境で実行し、そのファイルで定義された変数や関数を現在のスクリプトで直接使用できるようにします。
* **`$(dirname "$0")`**: 現在のスクリプトのディレクトリパスを取得する際によく使われるパターンです。この方法を使うと、スクリプトがどの場所から実行されても `config.sh` ファイルを正しく見つけることができます。

---

## 9. 例外処理とエラーハンドリング

### コマンドの成功/失敗確認
`$?` 変数には最後に実行されたコマンドの終了コードが保存されます。(0: 成功、0以外: 失敗)

```bash
# 存在するファイルをコピーする例
touch source.txt
cp source.txt dest.txt
if [[ $? -ne 0 ]]; then
    echo "ファイルのコピーに失敗しました！" >&2
else
    echo "ファイルが正常にコピーされました。"
fi
# 後処理
rm source.txt dest.txt

echo "---"

# 存在しないファイルをコピーする例
cp non_existent_file.txt /tmp/
if [[ $? -ne 0 ]]; then
    echo "ファイルのコピーに失敗しました！" >&2
    # exit 1 # スクリプトを終了し、失敗コードを返す場合はコメントを解除
else
    echo "ファイルが正常にコピーされました。(表示されない)"
fi
```

**予想される実行結果:**

```
ファイルが正常にコピーされました。
---
cp: 'non_existent_file.txt' を stat できません: そのようなファイルやディレクトリはありません
ファイルのコピーに失敗しました！
```

### `set -e`
どのコマンドでも0以外の終了コードが返された場合、スクリプトを即座に終了します。

```bash
#!/bin/bash
set -e # エラー発生時、即座に終了

echo "スクリプト開始 (set -e 有効)"
# このコマンドが失敗すると、以下のechoは実行されません。
cp non_existent_file_for_set_e.txt /tmp/
echo "このメッセージは表示されないかもしれません。"
```

**予想される実行結果 (上記スクリプトを実行し、`non_existent_file_for_set_e.txt` が存在しない場合):**

```
スクリプト開始 (set -e 有効)
cp: 'non_existent_file_for_set_e.at' を stat できません: そのようなファイルやディレクトリはありません
```

(そして、スクリプトはそこで終了し、"このメッセージは表示されないかもしれません。"は表示されません)

### `trap` コマンド
スクリプトの終了 (EXIT)、シグナル (ERR, INTなど) 発生時に特定のコマンドを実行します。

```bash
cleanup() {
    echo "スクリプト終了。一時ファイルを削除中..."
    rm -f /tmp/temp_*.log
}

trap cleanup EXIT  # スクリプトが終了する際に cleanup 関数を実行
# ERRトラップは、set -e と組み合わせるか、各コマンドの終了コードを個別にチェックする場合に有効
trap 'echo "エラー発生！行: $LINENO" >&2; exit 1' ERR

echo "作業開始..."
# 一部の作業...
# 意図的なエラー発生 (テスト用、コメントを外して実行)
# ls non_existent_dir_for_trap

echo "作業完了。"
```

**予想される実行結果 (エラーが発生しない場合):**

```
作業開始...
作業完了。
スクリプト終了。一時ファイルを削除中...
```

**予想される実行結果 (エラーが発生する場合、例: `ls non_existent_dir_for_trap` のコメントを外して実行):**

```
作業開始...
ls: cannot access 'non_existent_dir_for_trap': そのようなファイルやディレクトリはありません
エラー発生！行: 15
スクリプト終了。一時ファイルを削除中...
```

---

## 10. マルチスレッド (バックグラウンド実行)
Bashは真のマルチスレッドをサポートしていません。代わりに、**バックグラウンドプロセス**を通じて複数のタスクを同時に実行する「並列処理」を模倣できます。

### バックグラウンド実行
コマンドの末尾に `&` を付けます。

```bash
function process_log_bg() {
    echo "ログ処理開始: $1"
    sleep 1 # 処理に時間がかかると仮定
    echo "ログ処理終了: $1"
}

process_log_bg "access.log.1" &
process_log_bg "access.log.2" &
echo "バックグラウンドで処理中..."
wait # すべてのバックグラウンドプロセスが終了するまで待機
echo "メインスクリプト終了。"
```

**予想される実行結果 (出力順序はシステムや処理時間により不定):**

```
ログ処理開始: access.log.1
ログ処理開始: access.log.2
バックグラウンドで処理中...
ログ処理終了: access.log.1
ログ処理終了: access.log.2
メインスクリプト終了。
```

### `wait`
バックグラウンドで実行されたすべてのプロセスが完了するまで待ちます。特定のPIDを指定することもできます。

```bash
# process_log_bg 関数は上記と同じと仮定
process_log_bg "access.log.1" &
PID1=$! # バックグラウンドプロセスのPIDを保存
process_log_bg "access.log.2" &
PID2=$!

echo "PID1: $PID1, PID2: $PID2"
echo "個別のログ処理の完了を待機中..."
wait $PID1
echo "PID1 ($PID1) の処理完了。"
wait $PID2
echo "PID2 ($PID2) の処理完了。"
echo "すべてのログファイル処理完了。"
```

**予想される実行結果 (例):**

```
ログ処理開始: access.log.1
ログ処理開始: access.log.2
PID1: 12345, PID2: 12346
個別のログ処理の完了を待機中...
ログ処理終了: access.log.1
PID1 (12345) の処理完了。
ログ処理終了: access.log.2
PID2 (12346) の処理完了。
すべてのログファイル処理完了。
```

### 限定された同時実行数
多くのファイルを同時に処理する際、システム負荷を軽減するために、同時に実行されるバックグラウンド作業の数を制限できます。

```bash
function process_log_limited() {
    echo "処理開始: $1"
    sleep 0.5 # 処理に時間がかかると仮定
    echo "処理終了: $1"
}

MAX_JOBS=2 # 同時に2つまで実行
JOB_COUNT=0

# 例としてダミーのログファイルリストを作成
for i in {1..5}; do
    echo "log_$i.txt"
done > /tmp/log_list.txt

while IFS= read -r LOG_FILE; do
    process_log_limited "$LOG_FILE" &
    JOB_COUNT=$((JOB_COUNT + 1))
    if [[ $JOB_COUNT -ge $MAX_JOBS ]]; then
        wait -n # いずれかのバックグラウンド作業が1つ終了するまで待機
        JOB_COUNT=$((JOB_COUNT - 1))
        echo "--- ジョブ枠が空きました ---"
    fi
done < /tmp/log_list.txt

wait # 残っているすべてのバックグラウンド作業の完了を待機
echo "すべてのログ処理完了。"
# 後処理
rm /tmp/log_list.txt
```

**予想される実行結果 (例):**

```
処理開始: log_1.txt
処理開始: log_2.txt
処理終了: log_1.txt
--- ジョブ枠が空きました ---
処理開始: log_3.txt
処理終了: log_2.txt
--- ジョブ枠が空きました ---
処理開始: log_4.txt
処理終了: log_3.txt
--- ジョブ枠が空きました ---
処理開始: log_5.txt
処理終了: log_4.txt
処理終了: log_5.txt
すべてのログ処理完了。
```

---

## 11. リモート実行
SSHを使用して、リモートサーバーでコマンドを実行したり、ファイルを転送したりします。

### リモートコマンド実行

```bash
# 実際の実行にはSSH接続設定が必要です。
# ssh user@remote_host "ls -l /var/log/nginx/"
# 予想される実行結果 (リモートサーバーのls -l /var/log/nginx/の出力例):
# -rw-r----- 1 nginx nginx 12345 Jun  3 00:00 access.log
# -rw-r----- 1 nginx nginx   678 Jun  3 00:00 error.log
```

### リモートスクリプト実行
ローカルスクリプトをリモートで実行する最も良い方法は、`ssh` でファイルを転送するか、`ssh` パイプを利用することです。

```bash
# 例としてローカルにmy_script.shを作成
echo 'echo "Hello from remote: Args are $1 $2"' > my_script.sh
chmod +x my_script.sh

# 方法1: scpで転送後実行
# scp my_script.sh user@remote_host:/tmp/
# ssh user@remote_host "bash /tmp/my_script.sh arg1 arg2"
# rm my_script.sh
# 予想される実行結果 (リモートサーバーからの出力):
# Hello from remote: Args are arg1 arg2

# 方法2: stdinを通じてスクリプトを渡す (推奨)
# ssh user@remote_host 'bash -s' < my_script.sh -- arg1 arg2
# rm my_script.sh
# 予想される実行結果 (リモートサーバーからの出力):
# Hello from remote: Args are arg1 arg2
```

### リモートファイルコピー
`scp` コマンドを使用します。

```bash
# 実際の実行にはSSH接続設定とリモートファイルの存在が必要です。
# scp user@remote_host:/var/log/remote.log /tmp/local_remote.log
# scp /tmp/local.log user@remote_host:/var/log/remote_upload.log
# 予想される実行結果 (ファイル転送が成功した場合、特に標準出力はなし):
# (コンソールに何も表示されないか、SSH鍵のパスフレーズ入力など)
```

### `rsync`
`scp` よりも強力で効率的なファイル同期ツールです。大容量ログファイルの同期に便利です。

```bash
# 実際の実行にはSSH接続設定とリモートファイルの存在が必要です。
# rsync -avz user@remote_host:/var/log/nginx/access.log /tmp/
# 予想される実行結果 (rsyncの進捗状況が表示される):
# receiving incremental file list
# access.log
#          123,456 100%    1.23MB/s    0:00:00 (xfr#1, to-chk=0/1)
```

---

## 12. 役立つ追加のヒントとツール

### `grep`
ログフィルタリングの核となるツール。正規表現、特定のコンテキスト (行の上/下) 表示など強力な機能。

```bash
# 例としてログファイルを作成
echo -e "INFO: Application started.\nERROR: Failed to connect DB.\nWARN: Low disk space.\nINFO: User logged in." > my_app.log
grep -E "ERROR|WARN" my_app.log # ERRORまたはWARNを含む行を検索
# 後処理
rm my_app.log
```

**予想される実行結果:**

```
ERROR: Failed to connect DB.
WARN: Low disk space.
```

### `sed`
テキストストリームエディタ。文字列の置換、行の削除/追加などに使用。

```bash
# 例としてログファイルを作成
echo -e "old_string1\nold_string2\nAnother old_string" > input.log
sed 's/old_string/new_string/g' input.log # すべてのold_stringをnew_stringに置換
# 後処理
rm input.log
```

**予想される実行結果:**

```
new_string1
new_string2
Another new_string
```

### `awk`
テキストファイルの解析と処理の究極のツール。フィールドベースの処理、計算、レポート作成など強力な機能。

```bash
# 例としてログファイルを作成
echo -e "127.0.0.1 - - [04/Jun/2025:01:00:00 +0900] \"GET /index.html HTTP/1.1\" 200 1234" > access_log_example.log
awk '{print $1, $9, $10}' access_log_example.log # 最初のフィールド、ステータスコード、バイト数を出力
# 後処理
rm access_log_example.log
```

**予想される実行結果:**

```
127.0.0.1 200 1234
```

### `sort`
行の並べ替え。

```bash
# 例としてデータファイルを作成
echo -e "banana\napple\norange" > fruits.txt
sort fruits.txt
# 後処理
rm fruits.txt
```

**予想される実行結果:**

```
apple
banana
orange
```

### `uniq`
重複行の削除。`sort` と一緒に使用されることが多いです。

```bash
# 例としてデータファイルを作成
echo -e "apple\nbanana\napple\norange" > fruits.txt
sort fruits.txt | uniq -c # 重複する行を数え、削除
# 後処理
rm fruits.txt
```

**予想される実行結果:**

```
      2 apple
      1 banana
      1 orange
```

### `head` / `tail`
ファイルの最初/最後からN行を見る際に使用。`tail -f` はリアルタイムログ監視に不可欠。

```bash
# 例としてログファイルを作成
for i in {1..10}; do echo "Line $i"; done > /tmp/long_log.log
head -n 3 /tmp/long_log.log
echo "---"
tail -n 3 /tmp/long_log.log
# 後処理
rm /tmp/long_log.log
```

**予想される実行結果:**

```
Line 1
Line 2
Line 3
---
Line 8
Line 9
Line 10
```

### `wc`
単語、行、文字数を数える。

```bash
# 例としてログファイルを作成
echo -e "Error line 1\nInfo line 2\nError line 3" > errors.log
wc -l errors.log # 行数
# 後処理
rm errors.log
```

**予想される実行結果:**

```
3 errors.log
```

### プログレスバー (オプション)
大容量ファイル処理時に `pv` のようなツールを使用すると、進捗状況を視覚的に表示できます。

```bash
# `apt install pv` などでpvをインストールする必要があります。
# pv my_large_log.log | grep "keyword" > filtered.log
# 予想される実行結果 (例):
# [=======================================>] 100% | 1.23MB/s | 0:00:05 | 1.23MB
```

---

## Tomcatログの特定の時間範囲を抽出するコマンド

Tomcatのログファイルから特定の時間範囲のログを抽出する方法は、ログファイルのタイムスタンプフォーマットに大きく依存します。ここでは、一般的なフォーマットを想定したコマンドを紹介します。

**前提:**
* ログファイル名が `catalina.out` であると仮定します。
* ログ行に `HH:MM:SS` の形式のタイムスタンプが含まれていると仮定します。

---

### 17:47から17:52までのログを抽出

**目標:** 17時47分00秒 から 17時52分59秒 までのログを抽出します。

#### 1. `grep` コマンドを使用する方法 (最も一般的で簡潔)

ログ行に `HH:MM:SS` の形式のタイムスタンプが直接含まれている場合に有効です。

```bash
grep " 17:4[7-9]:\| 17:5[0-2]:" catalina.out
```

**解説:**
* `\|`: OR (または) 演算子です。複数のパターンを検索できます。
    * `17:4[7-9]:`: 17時47分から17時49分までのログ行に一致します。
    * `17:5[0-2]:`: 17時50分から17時52分までのログ行に一致します。

**予想される実行結果の例:**

```
[2025-06-04 17:47:05,123] INFO Initializing background task.
[2025-06-04 17:48:30,456] DEBUG User 'admin' accessed /dashboard.
[2025-06-04 17:49:59,789] WARN High CPU usage detected.
[2025-06-04 17:50:01,111] INFO Data sync started.
[2025-06-04 17:51:20,222] ERROR Failed to write to database.
[2025-06-04 17:52:45,333] INFO Report generated successfully.
```

#### 2. `awk` コマンドを使用する方法 (より厳密な時間範囲指定)

ログ行のタイムスタンプを正確にパースし、秒単位まで含めた厳密な時間範囲でフィルタリングしたい場合に適しています。

**ログフォーマットの仮定:**
`[YYYY-MM-DD HH:MM:SS,ms] ...` のように、タイムスタンプが角括弧 `[]` 内にあり、その中の時刻部分が `HH:MM:SS` として直接取得できると仮定します。

```bash
awk -F'[\[, ]' '$3 >= "17:47:00" && $3 <= "17:52:59" {print}' catalina.out
```

**解説:**
* `-F'[\[, ]'`: フィールド区切り文字を `[`, `,`, ` ` (スペース) のいずれかに指定します。これにより、`[2025-06-04 17:47:05,123]` という形式のログ行から、`17:47:05` の部分が `$3` として抽出されることを想定しています。**実際のログフォーマットに合わせてこの `-F` や `$3` の部分を調整してください。**
* `$3 >= "17:47:00" && $3 <= "17:52:59"`: 抽出した時刻文字列 `$3` が、`"17:47:00"` 以上 かつ `"17:52:59"` 以下 である場合に真となります。

**予想される実行結果の例:**

```
[2025-06-04 17:47:00,000] DEBUG Connection pool status: OK
[2025-06-04 17:47:30,123] INFO Service started.
... (この間のログ行)
[2025-06-04 17:52:00,500] WARN Memory usage is high.
[2025-06-04 17:52:59,999] INFO End of daily summary.
```

---

このチートシートが、Qiitaでの情報共有や、あなたのBashスクリプト作成に役立つことを願っています。もしQiitaへの投稿で何か特定の要件があれば、お気軽にお申し付けくださいね！