omics.co.in logo omics.co.in

11: Production Bash Scripting & Defensive Programming

Tested Stacks: GATK 4.6.1 Samtools 1.21 Nextflow 24.10 Seurat v5.1 Python 3.12
Prerequisites: Basic Linux CLI & Sequencing Formats
📚 Chapter 11 of 13 0% Course Completed

Chapter 11: Production Bash Scripting & Defensive Programming

Writing individual commands on the terminal is sufficient for testing, but biological studies require analyzing dozens or hundreds of patient samples consistently. Bash scripting automates multi-step workflows. However, poorly written scripts fail silently: if an alignment tool fails due to missing memory, a naive script will continue executing downstream steps on empty files, corrupting your final results. Defensive Bash programming ensures your pipelines fail immediately and cleanly when an error occurs.

The Holy Grail: set -euo pipefail

Every production bioinformatics bash script should include this line at the top:

set -euo pipefail
  • -e (errexit): Exit immediately if any command returns a non-zero (error) status.
  • -u (nounset): Treat unset variables as errors and exit immediately (prevents catastrophic bugs like rm -rf $UNSET_VAR/*).
  • -o pipefail: If any tool in a pipeline stream (cmd1 | cmd2 | cmd3) fails, the entire pipeline returns an error status rather than masking it.

1. Complete Production Script Template

Save this as align_cohort.sh:

#!/usr/bin/env bash
# ==============================================================================
# Script Name: align_cohort.sh
# Description: Automated paired-end alignment with defensive error handling
# Author: Omics Research Portal
# ==============================================================================

set -euo pipefail

# 1. Setup Logging Function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

# 2. Cleanup Function on Script Exit or Failure
cleanup() {
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        log "[ERROR] Pipeline failed with exit status code: $exit_code"
    else
        log "[SUCCESS] Pipeline finished successfully!"
    fi
}
trap cleanup EXIT

# 3. Validate Command Line Arguments
if [[ $# -lt 2 ]]; then
    echo "Usage: $0 <reference.fa> <raw_fastq_dir>"
    exit 1
fi

REFERENCE_GENOME="$1"
FASTQ_DIR="$2"
OUTPUT_DIR="aligned_bams"
THREADS=8

# 4. Validate Input File Existence
if [[ ! -f "$REFERENCE_GENOME" ]]; then
    log "[ERROR] Reference genome file '$REFERENCE_GENOME' does not exist!"
    exit 1
fi

mkdir -p "$OUTPUT_DIR"
log "Starting alignment cohort using reference: $REFERENCE_GENOME"

# 5. Iterate Over Paired-End Samples
for R1 in "${FASTQ_DIR}"/*_R1.fastq.gz; do
    # Check if files matching the pattern exist
    [[ -e "$R1" ]] || { log "No FASTQ files found matching pattern!"; exit 1; }

    # Derive Sample Name and Paired Reverse Read
    SAMPLE_NAME=$(basename "$R1" _R1.fastq.gz)
    R2="${FASTQ_DIR}/${SAMPLE_NAME}_R2.fastq.gz"
    OUT_BAM="${OUTPUT_DIR}/${SAMPLE_NAME}.sorted.bam"

    if [[ ! -f "$R2" ]]; then
        log "[WARNING] Reverse read '$R2' missing for sample '$SAMPLE_NAME'. Skipping."
        continue
    fi

    log "Processing sample: $SAMPLE_NAME"

    # Execute Alignment Stream
    bwa-mem2 mem -t "$THREADS" \
        -R "@RG\tID:${SAMPLE_NAME}\tSM:${SAMPLE_NAME}\tPL:ILLUMINA" \
        "$REFERENCE_GENOME" "$R1" "$R2" | \
    samtools sort -@ 4 -o "$OUT_BAM" -

    samtools index "$OUT_BAM"
    log "Finished sample: $SAMPLE_NAME -> $OUT_BAM"
done

log "All cohort samples processed successfully."

2. Executing Your Script

# 1. Grant execute permissions
chmod +x align_cohort.sh

# 2. Run the script with positional arguments
./align_cohort.sh /references/GRCh38.fa ./raw_reads/
⚠️

Common Pitfalls & Troubleshooting Checklist

  • Java Heap Exhaustion (GATK / Picard): If processes terminate with java.lang.OutOfMemoryError, set your environment variable explicitly: export _JAVA_OPTIONS="-Xmx16g -Djava.io.tmpdir=/tmp".
  • Index Timestamp Mismatch: If tools complain that a BAM index is missing or out of date, regenerate with samtools index -@ 4 sample.bam.
  • Conda Channel Conflicts: Avoid ABI incompatibilities between bioconda and conda-forge by setting strict priority: conda config --set channel_priority strict.
  • Chromosome Naming Contamination: Always verify whether reference FASTA headers use chr1 or 1; divergent naming causes silent alignment drops.
Was this guide helpful to your research?
Help us maintain 100% accurate, peer-reviewed bioinformatics standards.
📝 Propose Edit on GitHub ↗