Другое

Полное руководство: Bash-обработка файлов для автоматизации

Освойте Bash-скрипты для обработки файлов: цикл по каталогам, изменение имён с расширением параметров и запуск программ с пользовательскими аргументами. Туториал.

Как написать скрипт, который будет проходить по файлам в каталоге, менять пути и добавлять суффиксы к именам файлов для запуска программы?

Мне нужно создать скрипт, который будет запускать мою программу с разными аргументами. Программа запускается так:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt]

Псевдокод того, что я хочу получить:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

Как можно сгенерировать второй аргумент из первого, преобразовав его в что‑то вроде dataABCD_Log1.txt, а затем выполнить программу с этими аргументами?

Вы можете создать bash‑скрипт, который будет проходить по файлам в каталоге, генерировать изменённые имена файлов с суффиксами и выполнять вашу программу с этими аргументами. Ниже приведено полное решение, использующее bash‑скриптинг с расширением параметров и вложенными циклами для достижения требуемой функциональности.

Contents

Basic Script Structure

Чтобы создать скрипт, который будет проходить по файлам и обрабатывать их вашей программой, вам понадобится использовать встроенные возможности bash для итерации по файлам и строковой манипуляции. Базовая структура включает:

  1. Настройку входного и выходного каталогов
  2. Создание вложенных циклов (внешний цикл для файлов, внутренний цикл для итераций суффиксов)
  3. Использование расширения параметров для изменения имён файлов
  4. Выполнение вашей программы с сгенерированными аргументами

Вот фундаментальный подход:

bash
#!/bin/bash

# Define directories
INPUT_DIR="Data"
OUTPUT_DIR="Logs"

# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"

# Loop through files in input directory
for file in "$INPUT_DIR"/*.txt; do
    # Get filename without extension
    filename=$(basename "$file" .txt)
    
    # Inner loop for suffix iterations
    for i in {1..4}; do
        # Generate log filename with suffix
        log_file="${OUTPUT_DIR}/${filename}_Log${i}.txt"
        
        # Execute your program
        ./MyProgram.exe "$file" "$log_file"
    done
done

Parameter Expansion for Filename Modification

Ключ к преобразованию имён файлов — возможности расширения параметров bash. Ниже приведены основные техники:

Basic Parameter Expansion

bash
filename="dataABCD"
echo "${filename}_Log1.txt"  # Outputs: dataABCD_Log1.txt

More Advanced Transformations

Вы можете извлекать различные части имён файлов с помощью различных шаблонов расширения параметров:

bash
# Full path handling
full_path="/Data/dataABCD.txt"
basename=$(basename "$full_path")           # dataABCD.txt
filename_no_ext="${basename%.*}"            # dataABCD
dir_name=$(dirname "$full_path")            # /Data

# Extension removal
"${filename%.txt}"                          # Removes .txt extension
"${filename%%.*}"                           # Removes all extensions

# Adding suffixes before extension
"${filename%.*}_Log${i}.txt"                # dataABCD_Log1.txt

Practical Examples for Your Use Case

bash
# From "dataABCD.txt" to "dataABCD_Log1.txt"
input_file="dataABCD.txt"
output_file="${input_file%.*}_Log1.txt"     # dataABCD_Log1.txt

# From "Data/data1.txt" to "Logs/data1_Log1.txt"
input_path="Data/data1.txt"
output_dir="Logs"
filename=$(basename "$input_path" .txt)
output_path="${output_dir}/${filename}_Log1.txt"

Complete Working Script

Ниже приведён полностью готовый скрипт, реализующий ваши требования:

bash
#!/bin/bash

# Script to process files with MyProgram.exe using multiple suffix iterations
# Usage: ./process_files.sh

# Configuration
INPUT_DIR="Data"
OUTPUT_DIR="Logs"
PROGRAM="./MyProgram.exe"
MAX_ITERATIONS=4

# Validate input directory exists
if [ ! -d "$INPUT_DIR" ]; then
    echo "Error: Input directory '$INPUT_DIR' does not exist."
    exit 1
fi

# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"

# Check if program exists
if [ ! -x "$PROGRAM" ]; then
    echo "Error: Program '$PROGRAM' does not exist or is not executable."
    exit 1
fi

echo "Processing files in '$INPUT_DIR' directory..."

# Loop through all .txt files in input directory
for file in "$INPUT_DIR"/*.txt; do
    # Skip if no files match (prevents error when glob doesn't match)
    [ -e "$file" ] || continue
    
    # Extract filename without extension
    filename=$(basename "$file" .txt)
    echo "Processing file: $filename"
    
    # Inner loop for suffix iterations (0 to 3, which becomes 1 to 4)
    for ((i=1; i<=MAX_ITERATIONS; i++)); do
        # Generate log filename with suffix
        log_file="${OUTPUT_DIR}/${filename}_Log${i}.txt"
        
        echo "  Iteration $i: $PROGRAM $file $log_file"
        
        # Execute your program with both arguments
        "$PROGRAM" "$file" "$log_file"
        
        # Optional: Check exit status
        if [ $? -ne 0 ]; then
            echo "Warning: Program execution failed for $file (iteration $i)"
        fi
    done
    echo "Completed processing: $filename"
    echo "------------------------"
done

echo "All files processed successfully!"

Script Execution and Testing

Making the Script Executable

bash
chmod +x process_files.sh

Running the Script

bash
./process_files.sh

Testing with Sample Files

Сначала создайте тестовые файлы и каталоги:

bash
# Create directories
mkdir -p Data Logs

# Create sample data files
echo "Sample data 1" > Data/data1.txt
echo "Sample data 2" > Data/data2.txt
echo "Sample data ABCD" > Data/dataABCD.txt

# Make your program executable (or create a mock for testing)
cat > MyProgram.exe << 'EOF'
#!/bin/bash
echo "Processing: $1"
echo "Log file: $2"
echo "Arguments received: $@"
echo "Program executed successfully" > "$2"
EOF

chmod +x MyProgram.exe

Expected Output

Скрипт выдаст вывод, похожий на:

Processing files in 'Data' directory...
Processing file: data1
  Iteration 1: ./MyProgram.exe Data/data1.txt Logs/data1_Log1.txt
  Iteration 2: ./MyProgram.exe Data/data1.txt Logs/data1_Log2.txt
  Iteration 3: ./MyProgram.exe Data/data1.txt Logs/data1_Log3.txt
  Iteration 4: ./MyProgram.exe Data/data1.txt Logs/data1_Log4.txt
Completed processing: data1
------------------------
Processing file: data2
  Iteration 1: ./MyProgram.exe Data/data2.txt Logs/data2_Log1.txt
  Iteration 2: ./MyProgram.exe Data/data2.txt Logs/data2_Log2.txt
  Iteration 3: ./MyProgram.exe Data/data2.txt Logs/data2_Log3.txt
  Iteration 4: ./MyProgram.exe Data/data2.txt Logs/data2_Log4.txt
Completed processing: data2
------------------------
Processing file: dataABCD
  Iteration 1: ./MyProgram.exe Data/dataABCD.txt Logs/dataABCD_Log1.txt
  Iteration 2: ./MyProgram.exe Data/dataABCD.txt Logs/dataABCD_Log2.txt
  Iteration 3: ./MyProgram.exe Data/dataABCD.txt Logs/dataABCD_Log3.txt
  Iteration 4: ./MyProgram.exe Data/dataABCD.txt Logs/dataABCD_Log4.txt
Completed processing: dataABCD
------------------------
All files processed successfully!

Advanced Variations

Different Suffix Patterns

Если вам нужны другие шаблоны суффиксов, вы можете изменить генерацию имени файла:

bash
# Using different numbering schemes
for ((i=0; i<4; i++)); do
    log_file="${OUTPUT_DIR}/${filename}_Log$(($i+1)).txt"  # 1-4
    # OR
    log_file="${OUTPUT_DIR}/${filename}_Log${i}.txt"       # 0-3
    # OR
    log_file="${OUTPUT_DIR}/${filename}_Log$(printf "%02d" $i).txt"  # 00-03
done

Recursive Directory Processing

Если нужно обрабатывать файлы во вложенных каталогах рекурсивно:

bash
#!/bin/bash
find "$INPUT_DIR" -name "*.txt" -type f | while read -r file; do
    filename=$(basename "$file" .txt)
    relative_path="${file#$INPUT_DIR/}"
    output_subdir="${OUTPUT_DIR}/$(dirname "$relative_path")"
    mkdir -p "$output_subdir"
    
    for ((i=1; i<=4; i++)); do
        log_file="${output_subdir}/${filename}_Log${i}.txt"
        ./MyProgram.exe "$file" "$log_file"
    done
done

Parallel Processing

Для ускорения при большом количестве файлов можно использовать параллельную обработку:

bash
#!/bin/bash
export INPUT_DIR OUTPUT_DIR PROGRAM MAX_ITERATIONS

export -f process_file

process_file() {
    local file="$1"
    filename=$(basename "$file" .txt)
    
    for ((i=1; i<=MAX_ITERATIONS; i++)); do
        log_file="${OUTPUT_DIR}/${filename}_Log${i}.txt"
        "$PROGRAM" "$file" "$log_file"
    done
}

export -f process_file
find "$INPUT_DIR" -name "*.txt" -type f | xargs -n 1 -P 4 -I {} bash -c 'process_file "$@"' _ {}

Error Handling and Validation

Enhanced Error Checking

Ниже приведён более надёжный вариант с обширной обработкой ошибок:

bash
#!/bin/bash

# Enhanced script with error handling and validation

set -euo pipefail  # Exit on error, undefined variables, and pipe failures

# Configuration
INPUT_DIR="Data"
OUTPUT_DIR="Logs"
PROGRAM="./MyProgram.exe"
MAX_ITERATIONS=4

# Function to display error and exit
error_exit() {
    echo "Error: $1" >&2
    exit 1
}

# Validate dependencies
command -v "$PROGRAM" >/dev/null 2>&1 || error_exit "Program '$PROGRAM' not found or not in PATH"

# Validate directories
[ -d "$INPUT_DIR" ] || error_exit "Input directory '$INPUT_DIR' does not exist"
mkdir -p "$OUTPUT_DIR" || error_exit "Cannot create output directory '$OUTPUT_DIR'"

# Check for .txt files
shopt -s nullglob
txt_files=("$INPUT_DIR"/*.txt)
shopt -u nullglob

if [ ${#txt_files[@]} -eq 0 ]; then
    error_exit "No .txt files found in '$INPUT_DIR'"
fi

echo "Found ${#txt_files[@]} files to process..."

# Process each file
for file in "${txt_files[@]}"; do
    filename=$(basename "$file" .txt)
    echo "Processing: $filename"
    
    # Process each iteration
    for ((i=1; i<=MAX_ITERATIONS; i++)); do
        log_file="${OUTPUT_DIR}/${filename}_Log${i}.txt"
        
        echo "  Running iteration $i..."
        
        # Execute with error handling
        if ! "$PROGRAM" "$file" "$log_file"; then
            echo "  Warning: Iteration $i failed for $filename"
            # Continue with next iteration or exit
            # continue  # Remove this line to stop on first error
        fi
        
        # Verify log file was created
        if [ ! -f "$log_file" ]; then
            echo "  Warning: Log file '$log_file' was not created"
        fi
    done
    
    echo "  Completed: $filename"
done

echo "All files processed successfully!"

Command Line Arguments

Сделайте ваш скрипт более гибким, принимая аргументы командной строки:

bash
#!/bin/bash

# Script with command line argument support

# Default values
INPUT_DIR="${1:-Data}"
OUTPUT_DIR="${2:-Logs}"
PROGRAM="${3:-./MyProgram.exe}"
MAX_ITERATIONS="${4:-4}"

# Rest of the script using these variables...

Использование:

bash
./process_files.sh Data Logs ./MyProgram.exe 4

Эта всесторонняя реализация предоставляет надёжный shell‑скрипт, который проходит по файлам в каталоге, генерирует изменённые имена файлов с суффиксами и запускает вашу программу с этими аргументами, как указано в ваших требованиях. Скрипт включает надёжную обработку ошибок, проверку и может быть легко настроен под различные сценарии использования.

Sources

  1. GNU Bash Manual - Parameter Expansion
  2. Stack Overflow - Bash loop through files in directory
  3. DigitalOcean - How to Use Wildcards and Globbing in Linux Commands
  4. GeeksforGeeks - Bash Scripting - For Loop
  5. IBM Documentation - Advanced Bash-Scripting Guide

Conclusion

  • Скрипт успешно проходит по файлам .txt в каталоге Data и обрабатывает каждый файл несколько раз с разными суффиксами лог‑файлов
  • Расширение параметров с помощью ${filename%.*}_Log${i}.txt эффективно преобразует имена входных файлов в требуемый формат
  • Вложенная структура циклов (внешний цикл для файлов, внутренний цикл для итераций) обеспечивает точную функциональность, которую вы запросили в псевдокоде
  • Обработка ошибок и проверка гарантируют надёжную работу скрипта даже при отсутствии файлов или каталогов
  • Вы можете настроить скрипт, изменив переменную MAX_ITERATIONS, названия каталогов или логику преобразования имён файлов в соответствии с вашими конкретными требованиями

Для продакшн‑использования рассмотрите добавление логирования, отслеживания прогресса и возможностей параллельной обработки, если вы работаете с большим объёмом файлов. Скрипт предоставляет надёжную основу, которую можно расширять в зависимости от конкретных требований вашей программы и задач обработки файлов.

Авторы
Проверено модерацией
Модерация