0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

膨大なPDFデータから.yaml形式で抽出し、Markdownファイルを作成し、Obsidianで運用する

Last updated at Posted at 2025-07-22

PDFのままそのまま放置されているドキュメントを印刷フォーマットに頼るのではなく.yaml形式で抽出し、構造化されたドキュメントとしてマークダウンファイルにしてObsidianで管理運用する方法を考えてます。

PDFからテキスト抽出

PDFからテキストを抽出するシェルスクリプトについて説明いたします。
基本的な方法

1. pdftotext を使用する方法

最も一般的で信頼性の高い方法です。

#!/bin/bash
# PDFテキスト抽出スクリプト

# 使用方法チェック
if [ $# -eq 0 ]; then
    echo "使用方法: $0 <PDFファイル> [出力ファイル]"
    exit 1
fi

PDF_FILE="$1"
OUTPUT_FILE="${2:-${PDF_FILE%.pdf}.txt}"

# PDFファイルの存在確認
if [ ! -f "$PDF_FILE" ]; then
    echo "エラー: ファイル '$PDF_FILE' が見つかりません"
    exit 1
fi

# pdftotextの存在確認
if ! command -v pdftotext &> /dev/null; then
    echo "エラー: pdftotextがインストールされていません"
    echo "Ubuntu/Debian: sudo apt install poppler-utils"
    echo "CentOS/RHEL: sudo yum install poppler-utils"
    echo "macOS: brew install poppler"
    exit 1
fi

# テキスト抽出実行
echo "PDFからテキストを抽出中: $PDF_FILE"
pdftotext "$PDF_FILE" "$OUTPUT_FILE"

if [ $? -eq 0 ]; then
    echo "抽出完了: $OUTPUT_FILE"
    echo "ファイルサイズ: $(wc -c < "$OUTPUT_FILE") バイト"
    echo "行数: $(wc -l < "$OUTPUT_FILE") 行"
else
    echo "エラー: テキスト抽出に失敗しました"
    exit 1
fi

2. 複数のPDFを一括処理する版

#!/bin/bash
# 複数PDF一括テキスト抽出スクリプト

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="$SCRIPT_DIR/pdf_extract.log"

# ログ関数
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 使用方法
if [ $# -eq 0 ]; then
    echo "使用方法:"
    echo "  $0 <PDFディレクトリ>     # ディレクトリ内の全PDFを処理"
    echo "  $0 file1.pdf file2.pdf  # 指定したPDFファイルを処理"
    exit 1
fi

# pdftotextの存在確認
if ! command -v pdftotext &> /dev/null; then
    log_message "エラー: pdftotextがインストールされていません"
    exit 1
fi

# 処理開始
log_message "PDF テキスト抽出処理を開始"

# 引数がディレクトリの場合
if [ -d "$1" ]; then
    PDF_DIR="$1"
    log_message "ディレクトリを処理: $PDF_DIR"
    
    # PDFファイルを検索
    find "$PDF_DIR" -name "*.pdf" -type f | while read -r pdf_file; do
        output_file="${pdf_file%.pdf}.txt"
        
        log_message "処理中: $(basename "$pdf_file")"
        
        if pdftotext "$pdf_file" "$output_file" 2>/dev/null; then
            log_message "成功: $(basename "$output_file") ($(wc -l < "$output_file") 行)"
        else
            log_message "失敗: $(basename "$pdf_file")"
        fi
    done
else
    # 個別ファイルを処理
    for pdf_file in "$@"; do
        if [ ! -f "$pdf_file" ]; then
            log_message "スキップ: $pdf_file (ファイルが存在しません)"
            continue
        fi
        
        output_file="${pdf_file%.pdf}.txt"
        log_message "処理中: $(basename "$pdf_file")"
        
        if pdftotext "$pdf_file" "$output_file" 2>/dev/null; then
            log_message "成功: $(basename "$output_file")"
        else
            log_message "失敗: $(basename "$pdf_file")"
        fi
    done
fi

log_message "処理完了"

用語解説
シェルスクリプト: Unix/Linux系OSで使用されるコマンドラインインタープリター(shell)で実行される『スクリプトファイル』のことです。複数のコマンドを順次実行できます。
pdftotext: Poppler(PDFレンダリングライブラリ)に含まれるコマンドラインツールで、PDFファイルからプレーンテキストを抽出する『専用プログラム』です。

$#: シェルスクリプトで使用される『特殊変数』で、スクリプトに渡された引数の個数を表します。
$1, $2: 『位置パラメータ』と呼ばれ、スクリプトの第1引数、第2引数を表します。
${PDF_FILE%.pdf}: 『パラメータ展開』という機能で、変数PDF_FILEから末尾の「.pdf」を除去した文字列を取得します。

インストール方法

macOS

brew install poppler

使用例

# スクリプトを実行可能にする
chmod +x pdf_extract.sh

# 単一ファイルを処理
./pdf_extract.sh document.pdf

# 出力ファイル名を指定
./pdf_extract.sh document.pdf output.txt

# ディレクトリ内の全PDFを処理
./pdf_extract.sh /path/to/pdf_directory

このスクリプトは、PDFファイルからテキストを効率的に抽出し、エラーハンドリングとログ機能も備えた実用的なツールとして使用できます。

PDFから抽出したテキストを構造化YAMLデータとしてMarkdownで保存し、ObsidianのVaultに移動する統合スクリプトを作成いたします。

統合スクリプト

#!/bin/bash
# PDF→構造化YAML→Markdown→Obsidian統合スクリプト

set -euo pipefail  # エラー時に停止

# 設定
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMP_DIR="$SCRIPT_DIR/temp"
LOG_FILE="$SCRIPT_DIR/pdf_to_obsidian.log"

# デフォルト設定(環境に合わせて変更)
DEFAULT_VAULT_PATH="$HOME/Documents/ObsidianVault"
DEFAULT_OUTPUT_DIR="PDFImports"

# 設定ファイルの読み込み
CONFIG_FILE="$SCRIPT_DIR/config.conf"
if [ -f "$CONFIG_FILE" ]; then
    source "$CONFIG_FILE"
fi

OBSIDIAN_VAULT="${OBSIDIAN_VAULT:-$DEFAULT_VAULT_PATH}"
OUTPUT_DIR="${OUTPUT_DIR:-$DEFAULT_OUTPUT_DIR}"

# ログ関数
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 使用方法表示
show_usage() {
    cat << EOF
使用方法: $0 [オプション] <PDFファイル>

オプション:
  -v, --vault PATH     Obsidian Vault のパス (デフォルト: $OBSIDIAN_VAULT)
  -o, --output DIR     出力ディレクトリ名 (デフォルト: $OUTPUT_DIR)
  -t, --tags TAGS      追加するタグ (カンマ区切り)
  -c, --category CAT   カテゴリ名
  -h, --help          このヘルプを表示

例:
  $0 document.pdf
  $0 -t "research,ai" -c "技術資料" document.pdf
EOF
}

# 必要なコマンドの確認
check_dependencies() {
    local deps=("pdftotext" "pdfinfo")
    
    for cmd in "${deps[@]}"; do
        if ! command -v "$cmd" &> /dev/null; then
            log_message "エラー: $cmd がインストールされていません"
            echo "インストール方法:"
            echo "  Ubuntu/Debian: sudo apt install poppler-utils"
            echo "  CentOS/RHEL: sudo yum install poppler-utils"
            echo "  macOS: brew install poppler"
            exit 1
        fi
    done
}

# PDFメタデータ抽出
extract_pdf_metadata() {
    local pdf_file="$1"
    local temp_info="$TEMP_DIR/pdf_info.txt"
    
    pdfinfo "$pdf_file" > "$temp_info" 2>/dev/null || {
        log_message "警告: PDFメタデータの取得に失敗"
        return 1
    }
    
    # メタデータをパース
    TITLE=$(grep "^Title:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")
    AUTHOR=$(grep "^Author:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")
    SUBJECT=$(grep "^Subject:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")
    CREATOR=$(grep "^Creator:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")
    PAGES=$(grep "^Pages:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "0")
    CREATION_DATE=$(grep "^CreationDate:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")
    
    rm -f "$temp_info"
}

# テキストを章立てに分割
structure_content() {
    local content_file="$1"
    local structured_file="$TEMP_DIR/structured_content.txt"
    
    # 簡単な章立て検出(改良可能)
    awk '
    BEGIN { 
        chapter_num = 0
        current_chapter = "序文"
        content = ""
    }
    
    # 章タイトルの検出パターン
    /^[0-9]+\./ || /^第[0-9]+章/ || /^Chapter [0-9]+/ || /^[A-Z][^a-z]*$/ {
        if (content != "") {
            print "---CHAPTER:" current_chapter "---"
            print content
            print "---END_CHAPTER---"
        }
        current_chapter = $0
        content = ""
        next
    }
    
    # 空行をスキップ
    /^[[:space:]]*$/ { next }
    
    # 内容を蓄積
    {
        if (content != "") content = content "\n"
        content = content $0
    }
    
    END {
        if (content != "") {
            print "---CHAPTER:" current_chapter "---"
            print content
            print "---END_CHAPTER---"
        }
    }
    ' "$content_file" > "$structured_file"
    
    echo "$structured_file"
}

# YAMLフロントマターとMarkdown生成
generate_markdown() {
    local pdf_file="$1"
    local content_file="$2"
    local output_file="$3"
    local tags="$4"
    local category="$5"
    
    local filename=$(basename "$pdf_file" .pdf)
    local current_date=$(date '+%Y-%m-%d')
    local current_datetime=$(date '+%Y-%m-%d %H:%M:%S')
    
    # タグの処理
    local formatted_tags=""
    if [ -n "$tags" ]; then
        IFS=',' read -ra TAG_ARRAY <<< "$tags"
        for tag in "${TAG_ARRAY[@]}"; do
            tag=$(echo "$tag" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')  # トリム
            if [ -n "$formatted_tags" ]; then
                formatted_tags="$formatted_tags, \"$tag\""
            else
                formatted_tags="\"$tag\""
            fi
        done
    fi
    
    # Markdownファイル生成
    cat > "$output_file" << EOF
---
title: "${TITLE:-$filename}"
author: "${AUTHOR}"
subject: "${SUBJECT}"
category: "${category}"
tags: [${formatted_tags}]
source_file: "${pdf_file}"
creator: "${CREATOR}"
pages: ${PAGES}
creation_date: "${CREATION_DATE}"
imported_date: "${current_datetime}"
type: "pdf_import"
---

# ${TITLE:-$filename}

## メタデータ

| 項目 | 内容 |
|------|------|
| **タイトル** | ${TITLE:-$filename} |
| **著者** | ${AUTHOR:-"不明"} |
| **作成者** | ${CREATOR:-"不明"} |
| **ページ数** | ${PAGES} |
| **作成日** | ${CREATION_DATE:-"不明"} |
| **カテゴリ** | ${category:-"未分類"} |
| **インポート日** | ${current_datetime} |

## 概要

${SUBJECT:-"PDFから抽出されたコンテンツです。"}

## 目次

EOF

    # 構造化されたコンテンツを処理
    local structured_file=$(structure_content "$content_file")
    local toc_generated=false
    
    # 目次生成
    grep "^---CHAPTER:" "$structured_file" | while read -r line; do
        chapter_name=$(echo "$line" | sed 's/^---CHAPTER://' | sed 's/---$//')
        chapter_anchor=$(echo "$chapter_name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
        echo "- [${chapter_name}](#${chapter_anchor})" >> "$output_file"
    done
    
    echo "" >> "$output_file"
    echo "## 内容" >> "$output_file"
    echo "" >> "$output_file"
    
    # 章ごとのコンテンツ追加
    awk '
    BEGIN { in_chapter = 0; chapter_name = "" }
    
    /^---CHAPTER:/ {
        chapter_name = $0
        gsub(/^---CHAPTER:/, "", chapter_name)
        gsub(/---$/, "", chapter_name)
        in_chapter = 1
        print "\n### " chapter_name "\n"
        next
    }
    
    /^---END_CHAPTER---/ {
        in_chapter = 0
        print "\n---\n"
        next
    }
    
    in_chapter {
        print $0
    }
    ' "$structured_file" >> "$output_file"
    
    # フッター追加
    cat >> "$output_file" << EOF

## 関連ノート

- 

## 参考リンク

- 

---
*このノートは PDFファイル「${pdf_file}」から自動生成されました*  
*生成日時: ${current_datetime}*
EOF

    rm -f "$structured_file"
}

# メイン処理
main() {
    local pdf_file=""
    local tags=""
    local category=""
    
    # オプション解析
    while [[ $# -gt 0 ]]; do
        case $1 in
            -v|--vault)
                OBSIDIAN_VAULT="$2"
                shift 2
                ;;
            -o|--output)
                OUTPUT_DIR="$2"
                shift 2
                ;;
            -t|--tags)
                tags="$2"
                shift 2
                ;;
            -c|--category)
                category="$2"
                shift 2
                ;;
            -h|--help)
                show_usage
                exit 0
                ;;
            -*)
                echo "不明なオプション: $1"
                show_usage
                exit 1
                ;;
            *)
                pdf_file="$1"
                shift
                ;;
        esac
    done
    
    # PDFファイルの確認
    if [ -z "$pdf_file" ]; then
        echo "エラー: PDFファイルが指定されていません"
        show_usage
        exit 1
    fi
    
    if [ ! -f "$pdf_file" ]; then
        echo "エラー: ファイル '$pdf_file' が見つかりません"
        exit 1
    fi
    
    # 依存関係チェック
    check_dependencies
    
    # 作業ディレクトリ準備
    mkdir -p "$TEMP_DIR"
    
    # Obsidian Vault ディレクトリの確認・作成
    local vault_output_dir="$OBSIDIAN_VAULT/$OUTPUT_DIR"
    mkdir -p "$vault_output_dir"
    
    log_message "処理開始: $(basename "$pdf_file")"
    
    # PDFからテキスト抽出
    local temp_txt="$TEMP_DIR/extracted_text.txt"
    if ! pdftotext "$pdf_file" "$temp_txt" 2>/dev/null; then
        log_message "エラー: PDFからのテキスト抽出に失敗"
        exit 1
    fi
    
    # メタデータ抽出
    extract_pdf_metadata "$pdf_file"
    
    # 出力ファイル名生成
    local filename=$(basename "$pdf_file" .pdf)
    local safe_filename=$(echo "$filename" | sed 's/[^a-zA-Z0-9._-]/_/g')
    local output_file="$vault_output_dir/${safe_filename}.md"
    
    # Markdown生成
    generate_markdown "$pdf_file" "$temp_txt" "$output_file" "$tags" "$category"
    
    # 一時ファイル削除
    rm -rf "$TEMP_DIR"
    
    log_message "完了: $output_file"
    echo ""
    echo "✅ 処理完了!"
    echo "📁 出力先: $output_file"
    echo "📖 Obsidianで確認してください"
}

# スクリプト実行
main "$@"

用語解説
YAML(ヤムル): 『Yet Another Markup Language』の略で、人間が読みやすいデータ『シリアライゼーション』形式です。設定ファイルやメタデータによく使用されます。
フロントマター: Markdownファイルの先頭に配置される『YAMLブロック』で、ファイルのメタデータを格納します。Obsidianではこの情報でノートを分類・検索できます。
set -euo pipefail: シェルスクリプトの『エラーハンドリング』オプションで、エラー発生時にスクリプトを停止させます。
AWK: テキスト処理用の『プログラミング言語』で、パターンマッチングとデータ抽出に優れています。
IFS: 『Internal Field Separator』の略で、シェルが単語を分割する際の区切り文字を指定する『環境変数』です。

使用方法

# スクリプトを実行可能にする
chmod +x pdf_to_obsidian.sh

# 基本的な使用
./pdf_to_obsidian.sh document.pdf

# タグとカテゴリを指定
./pdf_to_obsidian.sh -t "research,ai,技術" -c "AI研究" document.pdf

# Vaultパスを指定
./pdf_to_obsidian.sh -v "/path/to/your/vault" document.pdf

# 出力ディレクトリを指定
./pdf_to_obsidian.sh -o "研究資料" document.pdf

生成されるMarkdownの構造

---
title: "文書タイトル"
author: "著者名"
tags: ["research", "ai"]
category: "技術資料"
type: "pdf_import"
---

# 文書タイトル

## メタデータ
(表形式でPDF情報を表示)

## 概要
(PDFの概要)

## 目次
(自動生成された章立て)

## 内容
(構造化されたテキスト内容)

## 関連ノート
(手動で追加するリンク領域)

このスクリプトにより、PDFファイルが構造化されたObsidianノートとして自動変換され、効率的な『ナレッジベース』構築が可能になります。

これらの処理を指定のフォルダごとにくりかえし行うスクリプト

PDF一括処理 Obsidian スクリプト

#!/bin/bash
# PDF一括処理→Obsidian統合スクリプト
# フォルダ内のPDFファイルを再帰的に処理してObsidian Vaultに保存

set -euo pipefail

# 設定
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMP_DIR="$SCRIPT_DIR/temp"
LOG_FILE="$SCRIPT_DIR/pdf_batch_obsidian.log"
REPORT_FILE="$SCRIPT_DIR/processing_report.txt"

# デフォルト設定
DEFAULT_VAULT_PATH="$HOME/Documents/ObsidianVault"
DEFAULT_OUTPUT_DIR="PDFImports"
MAX_PARALLEL_JOBS=4

# 設定ファイルの読み込み
CONFIG_FILE="$SCRIPT_DIR/batch_config.conf"
if [ -f "$CONFIG_FILE" ]; then
    source "$CONFIG_FILE"
fi

OBSIDIAN_VAULT="${OBSIDIAN_VAULT:-$DEFAULT_VAULT_PATH}"
OUTPUT_DIR="${OUTPUT_DIR:-$DEFAULT_OUTPUT_DIR}"
PARALLEL_JOBS="${PARALLEL_JOBS:-$MAX_PARALLEL_JOBS}"

# 統計変数
TOTAL_PDFS=0
PROCESSED_PDFS=0
FAILED_PDFS=0
SKIPPED_PDFS=0

# ログ関数
log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

# 進捗表示関数
show_progress() {
    local current="$1"
    local total="$2"
    local percentage=$((current * 100 / total))
    local bar_length=50
    local filled_length=$((percentage * bar_length / 100))
    
    printf "\r進捗: ["
    printf "%*s" $filled_length | tr ' ' '='
    printf "%*s" $((bar_length - filled_length)) | tr ' ' '-'
    printf "] %d%% (%d/%d)" $percentage $current $total
}

# 使用方法表示
show_usage() {
    cat << 'EOF'
使用方法: ./pdf_batch_obsidian.sh [オプション] <対象フォルダ>

オプション:
  -v, --vault PATH       Obsidian Vault のパス
  -o, --output DIR       出力ディレクトリ名(Vault内)
  -t, --tags TAGS        全PDFに追加するタグ(カンマ区切り)
  -c, --category CAT     デフォルトカテゴリ
  -j, --jobs NUMBER      並列処理数(デフォルト: 4)
  -r, --recursive        サブフォルダも再帰的に処理
  -f, --force           既存ファイルを上書き
  -d, --dry-run         実際の処理は行わず、対象ファイルのみ表示
  -q, --quiet           詳細出力を抑制
  --skip-existing       既存のMarkdownファイルをスキップ
  --organize-by-folder  フォルダ構造を保持して整理
  --max-size SIZE       処理するPDFの最大サイズ(MB)
  -h, --help            このヘルプを表示

例:
  # 基本的な使用
  ./pdf_batch_obsidian.sh /path/to/pdf_folder

  # サブフォルダも含めて処理
  ./pdf_batch_obsidian.sh -r /path/to/pdf_folder

  # フォルダ構造を保持して整理
  ./pdf_batch_obsidian.sh -r --organize-by-folder /path/to/pdf_folder

  # タグとカテゴリを指定
  ./pdf_batch_obsidian.sh -t "research,batch" -c "研究資料" /path/to/pdf_folder

  # 並列処理数を指定
  ./pdf_batch_obsidian.sh -j 8 -r /path/to/pdf_folder

  # 事前確認(実行しない)
  ./pdf_batch_obsidian.sh -d -r /path/to/pdf_folder
EOF
}

# 依存関係チェック
check_dependencies() {
    local deps=("pdftotext" "pdfinfo" "find" "parallel")
    local missing_deps=()
    
    for cmd in "${deps[@]}"; do
        if ! command -v "$cmd" &> /dev/null; then
            missing_deps+=("$cmd")
        fi
    done
    
    if [ ${#missing_deps[@]} -gt 0 ]; then
        log_message "ERROR" "以下のコマンドがインストールされていません: ${missing_deps[*]}"
        echo ""
        echo "インストール方法:"
        echo "  Ubuntu/Debian:"
        echo "    sudo apt update"
        echo "    sudo apt install poppler-utils parallel"
        echo ""
        echo "  CentOS/RHEL:"
        echo "    sudo yum install poppler-utils parallel"
        echo ""
        echo "  macOS:"
        echo "    brew install poppler parallel"
        exit 1
    fi
}

# PDFファイル検索
find_pdf_files() {
    local search_dir="$1"
    local recursive="$2"
    local max_size="$3"
    
    local find_cmd="find '$search_dir'"
    
    if [ "$recursive" != "true" ]; then
        find_cmd="$find_cmd -maxdepth 1"
    fi
    
    find_cmd="$find_cmd -type f -iname '*.pdf'"
    
    # ファイルサイズ制限
    if [ -n "$max_size" ]; then
        find_cmd="$find_cmd -size -${max_size}M"
    fi
    
    eval "$find_cmd" | sort
}

# PDFメタデータ抽出
extract_pdf_metadata() {
    local pdf_file="$1"
    local temp_info="$TEMP_DIR/pdf_info_$$.txt"
    
    if ! pdfinfo "$pdf_file" > "$temp_info" 2>/dev/null; then
        log_message "WARN" "PDFメタデータ取得失敗: $(basename "$pdf_file")"
        echo "TITLE="
        echo "AUTHOR="
        echo "SUBJECT="
        echo "CREATOR="
        echo "PAGES=0"
        echo "CREATION_DATE="
        return 1
    fi
    
    # メタデータ抽出
    echo "TITLE=$(grep "^Title:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")"
    echo "AUTHOR=$(grep "^Author:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")"
    echo "SUBJECT=$(grep "^Subject:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")"
    echo "CREATOR=$(grep "^Creator:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")"
    echo "PAGES=$(grep "^Pages:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "0")"
    echo "CREATION_DATE=$(grep "^CreationDate:" "$temp_info" 2>/dev/null | cut -d: -f2- | sed 's/^ *//' || echo "")"
    
    rm -f "$temp_info"
}

# テキスト構造化
structure_content() {
    local content_file="$1"
    local structured_file="$TEMP_DIR/structured_content_$$.txt"
    
    awk '
    BEGIN { 
        chapter_num = 0
        current_chapter = "序文"
        content = ""
        line_count = 0
    }
    
    # 章タイトルの検出パターン(改良版)
    /^[0-9]+\.[0-9]*[[:space:]]/ || /^第[0-9]+章/ || /^Chapter [0-9]+/ || 
    /^[0-9]+[[:space:]]*[A-Za-z]/ || /^[A-Z][A-Z\s]*$/ && length($0) < 50 ||
    /^[0-9]+\.[[:space:]]*[A-Z]/ {
        if (content != "" && line_count > 3) {  # 最低3行以上の内容がある場合のみ
            print "---CHAPTER:" current_chapter "---"
            print content
            print "---END_CHAPTER---"
        }
        current_chapter = $0
        content = ""
        line_count = 0
        next
    }
    
    # 空行や短い行をスキップ
    /^[[:space:]]*$/ || length($0) < 3 { next }
    
    # ページ番号らしい行をスキップ
    /^[0-9]+$/ && length($0) < 4 { next }
    
    # 内容を蓄積
    {
        if (content != "") content = content "\n"
        content = content $0
        line_count++
    }
    
    END {
        if (content != "" && line_count > 3) {
            print "---CHAPTER:" current_chapter "---"
            print content
            print "---END_CHAPTER---"
        }
    }
    ' "$content_file" > "$structured_file"
    
    echo "$structured_file"
}

# 単一PDFファイル処理
process_single_pdf() {
    local pdf_file="$1"
    local base_tags="$2"
    local base_category="$3"
    local output_base_dir="$4"
    local organize_by_folder="$5"
    local source_dir="$6"
    local force_overwrite="$7"
    local skip_existing="$8"
    
    local pdf_basename=$(basename "$pdf_file")
    local pdf_dir=$(dirname "$pdf_file")
    local safe_filename=$(echo "${pdf_basename%.pdf}" | sed 's/[^a-zA-Z0-9._-]/_/g')
    
    # 出力ディレクトリの決定
    local final_output_dir="$output_base_dir"
    if [ "$organize_by_folder" = "true" ]; then
        local relative_dir=$(realpath --relative-to="$source_dir" "$pdf_dir")
        if [ "$relative_dir" != "." ]; then
            final_output_dir="$output_base_dir/$relative_dir"
        fi
    fi
    
    mkdir -p "$final_output_dir"
    local output_file="$final_output_dir/${safe_filename}.md"
    
    # 既存ファイルのチェック
    if [ -f "$output_file" ]; then
        if [ "$skip_existing" = "true" ]; then
            log_message "INFO" "スキップ(既存): $pdf_basename"
            return 2  # スキップを示す特別なリターンコード
        elif [ "$force_overwrite" != "true" ]; then
            log_message "WARN" "スキップ(上書き禁止): $pdf_basename"
            return 2
        fi
    fi
    
    # テンポラリディレクトリ作成
    local temp_work_dir="$TEMP_DIR/work_$$"
    mkdir -p "$temp_work_dir"
    
    # PDFからテキスト抽出
    local temp_txt="$temp_work_dir/extracted_text.txt"
    if ! pdftotext "$pdf_file" "$temp_txt" 2>/dev/null; then
        log_message "ERROR" "テキスト抽出失敗: $pdf_basename"
        rm -rf "$temp_work_dir"
        return 1
    fi
    
    # 抽出されたテキストが空でないかチェック
    if [ ! -s "$temp_txt" ]; then
        log_message "WARN" "空のテキスト: $pdf_basename"
        rm -rf "$temp_work_dir"
        return 1
    fi
    
    # メタデータ抽出
    local metadata
    metadata=$(extract_pdf_metadata "$pdf_file")
    eval "$metadata"
    
    # フォルダベースのタグ追加
    local folder_tag=""
    if [ "$organize_by_folder" = "true" ]; then
        folder_tag=$(basename "$pdf_dir" | sed 's/[^a-zA-Z0-9]/_/g' | tr '[:upper:]' '[:lower:]')
        if [ -n "$folder_tag" ] && [ "$folder_tag" != "." ]; then
            base_tags="${base_tags:+$base_tags,}folder_$folder_tag"
        fi
    fi
    
    # カテゴリの決定(フォルダ名を使用する場合)
    local final_category="$base_category"
    if [ -z "$final_category" ] && [ "$organize_by_folder" = "true" ]; then
        final_category=$(basename "$pdf_dir")
    fi
    
    # Markdown生成
    generate_markdown "$pdf_file" "$temp_txt" "$output_file" "$base_tags" "$final_category" "$temp_work_dir"
    
    # 一時ファイル削除
    rm -rf "$temp_work_dir"
    
    log_message "SUCCESS" "完了: $pdf_basename → $(basename "$output_file")"
    return 0
}

# Markdown生成(改良版)
generate_markdown() {
    local pdf_file="$1"
    local content_file="$2"
    local output_file="$3"
    local tags="$4"
    local category="$5"
    local temp_dir="$6"
    
    local filename=$(basename "$pdf_file" .pdf)
    local current_date=$(date '+%Y-%m-%d')
    local current_datetime=$(date '+%Y-%m-%d %H:%M:%S')
    local file_size=$(stat -f%z "$pdf_file" 2>/dev/null || stat -c%s "$pdf_file" 2>/dev/null || echo "0")
    local file_size_mb=$((file_size / 1024 / 1024))
    
    # タグの処理
    local formatted_tags=""
    if [ -n "$tags" ]; then
        IFS=',' read -ra TAG_ARRAY <<< "$tags"
        for tag in "${TAG_ARRAY[@]}"; do
            tag=$(echo "$tag" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            if [ -n "$formatted_tags" ]; then
                formatted_tags="$formatted_tags, \"$tag\""
            else
                formatted_tags="\"$tag\""
            fi
        done
    fi
    
    # 相対パス計算
    local relative_pdf_path=$(realpath --relative-to="$(dirname "$output_file")" "$pdf_file" 2>/dev/null || echo "$pdf_file")
    
    # Markdownファイル生成
    cat > "$output_file" << EOF
---
title: "${TITLE:-$filename}"
author: "${AUTHOR}"
subject: "${SUBJECT}"
category: "${category}"
tags: [${formatted_tags}]
source_file: "${relative_pdf_path}"
source_file_absolute: "${pdf_file}"
creator: "${CREATOR}"
pages: ${PAGES}
file_size_mb: ${file_size_mb}
creation_date: "${CREATION_DATE}"
imported_date: "${current_datetime}"
type: "pdf_import"
status: "imported"
---

# ${TITLE:-$filename}

> [!info] 文書情報
> - **著者**: ${AUTHOR:-"不明"}
> - **ページ数**: ${PAGES}ページ
> - **ファイルサイズ**: ${file_size_mb}MB
> - **作成日**: ${CREATION_DATE:-"不明"}
> - **インポート日**: ${current_datetime}

## 📋 メタデータ

| 項目 | 内容 |
|------|------|
| **タイトル** | ${TITLE:-$filename} |
| **著者** | ${AUTHOR:-"不明"} |
| **作成者** | ${CREATOR:-"不明"} |
| **ページ数** | ${PAGES} |
| **ファイルサイズ** | ${file_size_mb}MB |
| **作成日** | ${CREATION_DATE:-"不明"} |
| **カテゴリ** | ${category:-"未分類"} |
| **ソースファイル** | \`${relative_pdf_path}\` |

## 📝 概要

${SUBJECT:-"PDFから抽出されたコンテンツです。"}

## 📖 目次

EOF

    # 構造化されたコンテンツを処理
    local structured_file=$(structure_content "$content_file")
    
    # 目次生成
    if [ -s "$structured_file" ]; then
        grep "^---CHAPTER:" "$structured_file" | while read -r line; do
            chapter_name=$(echo "$line" | sed 's/^---CHAPTER://' | sed 's/---$//')
            chapter_anchor=$(echo "$chapter_name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
            echo "- [${chapter_name}](#${chapter_anchor})" >> "$output_file"
        done
    fi
    
    echo "" >> "$output_file"
    echo "## 📄 内容" >> "$output_file"
    echo "" >> "$output_file"
    
    # 章ごとのコンテンツ追加
    if [ -s "$structured_file" ]; then
        awk '
        BEGIN { in_chapter = 0; chapter_name = "" }
        
        /^---CHAPTER:/ {
            chapter_name = $0
            gsub(/^---CHAPTER:/, "", chapter_name)
            gsub(/---$/, "", chapter_name)
            in_chapter = 1
            print "\n### " chapter_name "\n"
            next
        }
        
        /^---END_CHAPTER---/ {
            in_chapter = 0
            print "\n"
            next
        }
        
        in_chapter {
            print $0
        }
        ' "$structured_file" >> "$output_file"
    else
        # 構造化できない場合は元のテキストを使用
        echo "### 抽出されたテキスト" >> "$output_file"
        echo "" >> "$output_file"
        cat "$content_file" >> "$output_file"
    fi
    
    # フッター追加
    cat >> "$output_file" << EOF

---

## 🔗 関連ノート

- 

## 📚 参考リンク

- 

## 🏷️ タグ

${tags}

---

> [!note] 生成情報
> このノートはPDFファイル「${pdf_file}」から自動生成されました  
> 生成日時: ${current_datetime}  
> スクリプト: PDF Batch to Obsidian Processor
EOF

    rm -f "$structured_file"
}

# 処理統計レポート生成
generate_report() {
    local start_time="$1"
    local end_time="$2"
    local source_dir="$3"
    
    local duration=$((end_time - start_time))
    local hours=$((duration / 3600))
    local minutes=$(((duration % 3600) / 60))
    local seconds=$((duration % 60))
    
    cat > "$REPORT_FILE" << EOF
PDF一括処理レポート
==================

処理日時: $(date '+%Y-%m-%d %H:%M:%S')
対象フォルダ: $source_dir
出力先: $OBSIDIAN_VAULT/$OUTPUT_DIR

処理統計:
- 総PDF数: $TOTAL_PDFS
- 処理成功: $PROCESSED_PDFS
- 処理失敗: $FAILED_PDFS
- スキップ: $SKIPPED_PDFS

処理時間:
- 開始: $(date -d @$start_time '+%H:%M:%S' 2>/dev/null || date -r $start_time '+%H:%M:%S' 2>/dev/null || echo "N/A")
- 終了: $(date -d @$end_time '+%H:%M:%S' 2>/dev/null || date -r $end_time '+%H:%M:%S' 2>/dev/null || echo "N/A")
- 所要時間: ${hours}時間${minutes}分${seconds}秒

出力先フォルダ:
$OBSIDIAN_VAULT/$OUTPUT_DIR

設定:
- 並列処理数: $PARALLEL_JOBS
- フォルダ構造保持: $ORGANIZE_BY_FOLDER
- 既存ファイル上書き: $FORCE_OVERWRITE
- 再帰処理: $RECURSIVE

EOF

    if [ $FAILED_PDFS -gt 0 ]; then
        echo "失敗したファイル:" >> "$REPORT_FILE"
        grep "ERROR" "$LOG_FILE" | tail -n $FAILED_PDFS >> "$REPORT_FILE"
    fi
    
    log_message "INFO" "レポート生成完了: $REPORT_FILE"
}

# メイン処理
main() {
    local source_dir=""
    local tags=""
    local category=""
    local recursive=false
    local dry_run=false
    local quiet=false
    local force_overwrite=false
    local skip_existing=false
    local organize_by_folder=false
    local max_size=""
    
    # オプション解析
    while [[ $# -gt 0 ]]; do
        case $1 in
            -v|--vault)
                OBSIDIAN_VAULT="$2"
                shift 2
                ;;
            -o|--output)
                OUTPUT_DIR="$2"
                shift 2
                ;;
            -t|--tags)
                tags="$2"
                shift 2
                ;;
            -c|--category)
                category="$2"
                shift 2
                ;;
            -j|--jobs)
                PARALLEL_JOBS="$2"
                shift 2
                ;;
            -r|--recursive)
                recursive=true
                shift
                ;;
            -f|--force)
                force_overwrite=true
                shift
                ;;
            -d|--dry-run)
                dry_run=true
                shift
                ;;
            -q|--quiet)
                quiet=true
                shift
                ;;
            --skip-existing)
                skip_existing=true
                shift
                ;;
            --organize-by-folder)
                organize_by_folder=true
                shift
                ;;
            --max-size)
                max_size="$2"
                shift 2
                ;;
            -h|--help)
                show_usage
                exit 0
                ;;
            -*)
                echo "不明なオプション: $1"
                show_usage
                exit 1
                ;;
            *)
                source_dir="$1"
                shift
                ;;
        esac
    done
    
    # 引数チェック
    if [ -z "$source_dir" ]; then
        echo "エラー: 対象フォルダが指定されていません"
        show_usage
        exit 1
    fi
    
    if [ ! -d "$source_dir" ]; then
        echo "エラー: フォルダ '$source_dir' が見つかりません"
        exit 1
    fi
    
    # 依存関係チェック
    check_dependencies
    
    # ログ初期化
    > "$LOG_FILE"
    log_message "INFO" "PDF一括処理開始"
    log_message "INFO" "対象フォルダ: $source_dir"
    log_message "INFO" "出力先: $OBSIDIAN_VAULT/$OUTPUT_DIR"
    
    # 作業ディレクトリ準備
    mkdir -p "$TEMP_DIR"
    
    # Vault準備
    local vault_output_dir="$OBSIDIAN_VAULT/$OUTPUT_DIR"
    mkdir -p "$vault_output_dir"
    
    # PDFファイル検索
    log_message "INFO" "PDFファイル検索中..."
    local pdf_files
    mapfile -t pdf_files < <(find_pdf_files "$source_dir" "$recursive" "$max_size")
    
    TOTAL_PDFS=${#pdf_files[@]}
    
    if [ $TOTAL_PDFS -eq 0 ]; then
        log_message "WARN" "処理対象のPDFファイルが見つかりません"
        exit 0
    fi
    
    log_message "INFO" "発見されたPDF数: $TOTAL_PDFS"
    
    # Dry run モード
    if [ "$dry_run" = true ]; then
        echo "=== 処理対象ファイル一覧 ==="
        printf "%s\n" "${pdf_files[@]}"
        echo ""
        echo "総数: $TOTAL_PDFS ファイル"
        echo "出力先: $vault_output_dir"
        exit 0
    fi
    
    # 処理開始
    local start_time=$(date +%s)
    log_message "INFO" "一括処理開始(並列数: $PARALLEL_JOBS)"
    
    # 並列処理用の関数をエクスポート
    export -f process_single_pdf extract_pdf_metadata structure_content generate_markdown log_message
    export TEMP_DIR OBSIDIAN_VAULT OUTPUT_DIR
    export TITLE AUTHOR SUBJECT CREATOR PAGES CREATION_DATE
    
    # 並列処理実行
    printf "%s\n" "${pdf_files[@]}" | parallel -j "$PARALLEL_JOBS" --bar \
        process_single_pdf {} "$tags" "$category" "$vault_output_dir" "$organize_by_folder" "$source_dir" "$force_overwrite" "$skip_existing" \
        '&&' 'echo "SUCCESS"' '||' 'echo "FAILED"' | \
    while read result; do
        case $result in
            "SUCCESS")
                ((PROCESSED_PDFS++))
                ;;
            "FAILED")
                ((FAILED_PDFS++))
                ;;
            *)
                ((SKIPPED_PDFS++))
                ;;
        esac
        
        if [ "$quiet" != true ]; then
            show_progress $((PROCESSED_PDFS + FAILED_PDFS + SKIPPED_PDFS)) $TOTAL_PDFS
        fi
    done
    
    echo ""  # 進捗バーの後の改行
    
    # 処理完了
    local end_time=$(date +%s)
    log_message "INFO" "一括処理完了"
    log_message "INFO" "成功: $PROCESSED_PDFS, 失敗: $FAILED_PDFS, スキップ: $SKIPPED_PDFS"
    
    # 統計レポート生成
    generate_report "$start_time" "$end_time" "$source_dir"
    
    # 一時ディレクトリ削除
    rm -rf "$TEMP_DIR"
    
    # 結果表示
    echo ""
    echo "✅ 処理完了!"
    echo "📊 統計:"
    echo "   - 処理成功: $PROCESSED_PDFS/$TOTAL_PDFS"
    echo "   - 処理失敗: $FAILED_PDFS/$TOTAL_PDFS" 
    echo "   - スキップ: $SKIPPED_PDFS/$TOTAL_PDFS"
    echo "📁 出力先: $vault_output_dir"
    echo "📄 詳細レポート: $REPORT_FILE"
    echo "📋 ログファイル: $LOG_FILE"
}

# トラップ設定(Ctrl+C対応)
trap 'echo ""; log_message "WARN" "処理が中断されました"; rm -rf "$TEMP_DIR"; exit 130' INT TERM

# スクリプト実行
main "$@"

バッチ処理設定ファイル

# PDF一括処理→Obsidian 設定ファイル
# このファイルをスクリプトと同じディレクトリに配置

# Obsidian Vault のパス
OBSIDIAN_VAULT="$HOME/Documents/MyObsidianVault"

# 出力ディレクトリ名(Vault内)
OUTPUT_DIR="ImportedPDFs"

# デフォルトタグ(カンマ区切り)
DEFAULT_TAGS="pdf,imported,batch"

# デフォルトカテゴリ
DEFAULT_CATEGORY="資料"

# 並列処理数(CPUコア数に応じて調整)
PARALLEL_JOBS=4

# ファイルサイズ制限(MB)- 大きすぎるファイルをスキップ
MAX_FILE_SIZE_MB=100

# ログ保持日数
LOG_RETENTION_DAYS=30

# フォルダ別の処理設定
# フォルダ名をキーとして、そのフォルダ内のPDFに適用する設定
declare -A FOLDER_CONFIGS
FOLDER_CONFIGS["研究論文"]="tags:paper,research,academic category:論文"
FOLDER_CONFIGS["技術資料"]="tags:tech,manual,reference category:技術文書"
FOLDER_CONFIGS["会議資料"]="tags:meeting,presentation category:会議"
FOLDER_CONFIGS["契約書"]="tags:contract,legal,document category:法務"
FOLDER_CONFIGS["マニュアル"]="tags:manual,howto,guide category:マニュアル"

# 除外パターン(正規表現)
EXCLUDE_PATTERNS=(
    ".*temp.*"
    ".*backup.*"
    ".*~$"
    ".*\.tmp$"
)

# 処理対象外のファイル名パターン
SKIP_FILE_PATTERNS=(
    "draft_*"
    "old_*"
    "*_backup*"
)

フォルダ監視・自動処理スクリプト

#!/bin/bash
# PDF監視・自動処理スクリプト
# 指定フォルダを監視し、新しいPDFファイルが追加されたら自動的にObsidianに取り込む

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WATCH_CONFIG="$SCRIPT_DIR/watch_config.conf"
WATCH_LOG="$SCRIPT_DIR/pdf_watcher.log"
PID_FILE="$SCRIPT_DIR/pdf_watcher.pid"

# デフォルト設定
DEFAULT_WATCH_DIRS=()
DEFAULT_POLL_INTERVAL=30

# 設定読み込み
if [ -f "$WATCH_CONFIG" ]; then
    source "$WATCH_CONFIG"
fi

WATCH_DIRS=("${WATCH_DIRS[@]:-${DEFAULT_WATCH_DIRS[@]}}")
POLL_INTERVAL="${POLL_INTERVAL:-$DEFAULT_POLL_INTERVAL}"

# ログ関数
log_watch() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$WATCH_LOG"
}

# 使用方法表示
show_watch_usage() {
    cat << 'EOF'
PDF監視・自動処理スクリプト

使用方法:
  ./pdf_watcher.sh start              # 監視開始
  ./pdf_watcher.sh stop               # 監視停止
  ./pdf_watcher.sh status             # 実行状態確認
  ./pdf_watcher.sh restart            # 再起動
  ./pdf_watcher.sh test <dir>         # テスト実行
  ./pdf_watcher.sh add-watch <dir>    # 監視対象追加
  ./pdf_watcher.sh list-watches       # 監視対象一覧

設定ファイル: watch_config.conf
ログファイル: pdf_watcher.log
EOF
}

# 監視対象ディレクトリの追加
add_watch_dir() {
    local new_dir="$1"
    
    if [ ! -d "$new_dir" ]; then
        echo "エラー: ディレクトリ '$new_dir' が存在しません"
        return 1
    fi
    
    # 設定ファイルに追加
    if ! grep -q "\"$new_dir\"" "$WATCH_CONFIG" 2>/dev/null; then
        echo "WATCH_DIRS+=(\"$new_dir\")" >> "$WATCH_CONFIG"
        echo "監視対象に追加しました: $new_dir"
    else
        echo "既に監視対象です: $new_dir"
    fi
}

# 監視対象ディレクトリ一覧表示
list_watch_dirs() {
    echo "監視対象ディレクトリ:"
    if [ ${#WATCH_DIRS[@]} -eq 0 ]; then
        echo "  設定されていません"
    else
        for dir in "${WATCH_DIRS[@]}"; do
            echo "  - $dir"
        done
    fi
}

# プロセス状態確認
check_status() {
    if [ -f "$PID_FILE" ]; then
        local pid=$(cat "$PID_FILE")
        if ps -p "$pid" > /dev/null 2>&1; then
            echo "実行中 (PID: $pid)"
            return 0
        else
            echo "停止中 (PIDファイルが残存)"
            rm -f "$PID_FILE"
            return 1
        fi
    else
        echo "停止中"
        return 1
    fi
}

# ファイル変更検出
detect_new_pdfs() {
    local watch_dir="$1"
    local state_file="$SCRIPT_DIR/.watch_state_$(echo "$watch_dir" | sed 's|/|_|g')"
    local current_state_file="$SCRIPT_DIR/.current_state_$$"
    
    # 現在のPDFファイル一覧を取得
    find "$watch_dir" -type f -iname "*.pdf" -exec stat -f "%m %N" {} \; 2>/dev/null | sort > "$current_state_file" || \
    find "$watch_dir" -type f -iname "*.pdf" -exec stat -c "%Y %n" {} \; 2>/dev/null | sort > "$current_state_file"
    
    # 前回の状態ファイルが存在しない場合は作成
    if [ ! -f "$state_file" ]; then
        cp "$current_state_file" "$state_file"
        rm -f "$current_state_file"
        return 0
    fi
    
    # 差分を検出
    local new_files
    new_files=$(comm -13 "$state_file" "$current_state_file" | cut -d' ' -f2-)
    
    if [ -n "$new_files" ]; then
        echo "$new_files"
        # 状態更新
        cp "$current_state_file" "$state_file"
    fi
    
    rm -f "$current_state_file"
}

# 単一PDFファイルの処理
process_detected_pdf() {
    local pdf_file="$1"
    local watch_dir="$2"
    
    log_watch "INFO" "新しいPDFを検出: $(basename "$pdf_file")"
    
    # フォルダベースの設定を適用
    local folder_name=$(basename "$watch_dir")
    local tags=""
    local category=""
    
    # 設定ファイルからフォルダ別設定を取得
    if declare -p FOLDER_CONFIGS > /dev/null 2>&1; then
        if [[ -v FOLDER_CONFIGS["$folder_name"] ]]; then
            local config="${FOLDER_CONFIGS[$folder_name]}"
            tags=$(echo "$config" | grep -o 'tags:[^[:space:]]*' | cut -d: -f2)
            category=$(echo "$config" | grep -o 'category:[^[:space:]]*' | cut -d: -f2)
        fi
    fi
    
    # メインスクリプトを呼び出し
    if "$SCRIPT_DIR/pdf_batch_obsidian.sh" -t "$tags" -c "$category" --skip-existing "$pdf_file"; then
        log_watch "SUCCESS" "処理完了: $(basename "$pdf_file")"
        
        # 通知(オプション)
        if command -v notify-send > /dev/null 2>&1; then
            notify-send "PDF取り込み完了" "$(basename "$pdf_file") をObsidianに取り込みました"
        fi
    else
        log_watch "ERROR" "処理失敗: $(basename "$pdf_file")"
    fi
}

# メイン監視ループ
watch_loop() {
    log_watch "INFO" "PDF監視開始"
    log_watch "INFO" "監視間隔: ${POLL_INTERVAL}秒"
    
    # 監視対象確認
    if [ ${#WATCH_DIRS[@]} -eq 0 ]; then
        log_watch "ERROR" "監視対象ディレクトリが設定されていません"
        exit 1
    fi
    
    for dir in "${WATCH_DIRS[@]}"; do
        log_watch "INFO" "監視対象: $dir"
    done
    
    # 無限ループで監視
    while true; do
        for watch_dir in "${WATCH_DIRS[@]}"; do
            if [ ! -d "$watch_dir" ]; then
                log_watch "WARN" "監視対象が存在しません: $watch_dir"
                continue
            fi
            
            # 新しいPDFファイルを検出
            local new_pdfs
            new_pdfs=$(detect_new_pdfs "$watch_dir")
            
            if [ -n "$new_pdfs" ]; then
                while IFS= read -r pdf_file; do
                    if [ -f "$pdf_file" ]; then
                        # 少し待機(ファイルの書き込み完了を待つ)
                        sleep 2
                        process_detected_pdf "$pdf_file" "$watch_dir" &
                    fi
                done <<< "$new_pdfs"
            fi
        done
        
        # 指定間隔で待機
        sleep "$POLL_INTERVAL"
    done
}

# 監視開始
start_watch() {
    if check_status > /dev/null 2>&1; then
        echo "既に実行中です"
        return 1
    fi
    
    echo "PDF監視を開始します..."
    nohup "$0" --daemon > /dev/null 2>&1 &
    echo $! > "$PID_FILE"
    echo "監視開始 (PID: $(cat "$PID_FILE"))"
}

# 監視停止
stop_watch() {
    if [ -f "$PID_FILE" ]; then
        local pid=$(cat "$PID_FILE")
        if ps -p "$pid" > /dev/null 2>&1; then
            kill "$pid"
            rm -f "$PID_FILE"
            echo "監視を停止しました"
        else
            rm -f "$PID_FILE"
            echo "プロセスが見つかりません"
        fi
    else
        echo "監視は実行されていません"
    fi
}

# テスト実行
test_watch() {
    local test_dir="$1"
    
    if [ ! -d "$test_dir" ]; then
        echo "エラー: テスト対象ディレクトリが存在しません: $test_dir"
        return 1
    fi
    
    echo "テスト実行: $test_dir"
    WATCH_DIRS=("$test_dir")
    POLL_INTERVAL=5
    
    echo "5秒間隔で監視します(Ctrl+Cで停止)"
    watch_loop
}

# メイン処理
case "${1:-}" in
    start)
        start_watch
        ;;
    stop)
        stop_watch
        ;;
    restart)
        stop_watch
        sleep 1
        start_watch
        ;;
    status)
        check_status
        ;;
    test)
        if [ -z "${2:-}" ]; then
            echo "エラー: テスト対象ディレクトリを指定してください"
            exit 1
        fi
        test_watch "$2"
        ;;
    add-watch)
        if [ -z "${2:-}" ]; then
            echo "エラー: 監視対象ディレクトリを指定してください"
            exit 1
        fi
        add_watch_dir "$2"
        ;;
    list-watches)
        list_watch_dirs
        ;;
    --daemon)
        # デーモンモード(内部使用)
        watch_loop
        ;;
    *)
        show_watch_usage
        exit 1
        ;;
esac
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?