catbook Keyao Zhu Phd student, Peking University

【Linux】bio_funclib函数库

» plogs

参考:

  1. gibbslab/biobash: Common BASH routines for computational biology (github.com)
  2. Linux练习册07.3-Shell脚本在生信中的应用-知识点.pdf

函数库总架构

个人认为一个sh文件做一件事情比较好…所以这里的每个“函数”都是一个独立的可执行文件,最后将这些文件整合在一起,做了一个bio_funclib函数库。

函数 注释
statBioFile 给一个文件(可能是fa, fq, BED, GFF或其它),对文件进行简单统计,输出统计结果
fq2fa 将fq转fa文件,用户提供fq格式的文件,将其转换为fa格式
revCmpDNAseq 对DNA序列进行反转互补,用户可以选择仅反转或仅互补,或反转且互补
DNA2RNA 将DNA序列转化为RNA序列,但也可以选择RNA转DNA

0. 示例文件介绍

文件名 文件格式
Homo_sapiens.GRCh38.110.abinitio.gff3 gff3
homo_sapiens.GRCh38.A549.Regulatory_Build.regulatory_activity.20221007.gff gff
Homo_sapiens.GRCh38.cdna.abinitio.fa fasta
sarsCov2Genomes.fasta fasta
SRR3091420_1_chr6.fastq fastq
chr.bed bed

1. statBioFile

给一个文件(可能是fa, fq, BED, GFF或其它),对文件进行简单统计,输出统计结果。

  1. 对于fa文件,输出
    • seq_count - fasta序列长度
    • base_count - 四种碱基数
    • gc_count - GC含量%
    • length_range - 序列的最长和最短长度
  2. 对于fq文件,输出
    • base_total - 碱基总数
    • n_reads - 含有 N 碱基的reads个数
    • n_count - 所有的reads中N碱基的总数
    • ATCGNdis - 所有序列的第一位碱基的ATCGNatcg分布情况
  3. 对于bed文件,输出
    • lines - bed文件的行数
  4. 对于gff文件,输出
    • columns - gff文件的行数
  5. 对于不在此函数中的文件,如txt文件,提示"Unsupported file type: $fileType"
#!/bin/bash

# 帮助信息
usage() {
cat <<HELP
Usage: $(basename $0) <file>
The script will perform statistics on various biological data files.

Supported File Types:
  .fa    fasta file - outputs sequence count, base count, GC content, and length range.
  .fq    fastq file - outputs base total, number of reads with N base, total N base count, and distribution of the first base.
  .bed   BED file - outputs number of lines, and start and end positions.
  .gff   GFF file - outputs number of columns.

For unsupported file types, the script will provide an appropriate message.

HELP
exit 1
}

handle_file() {
    local file_path=$1
    local file_extension="${file_path##*.}"
    local file_type=$(echo "$file_extension" | awk '{print tolower($0)}')

    case $file_type in
        fa | fasta)
            echo "Processing fasta file: $file_path"
            local seq_count=$(grep -c '^>' "$file_path")
            local base_count=$(grep -v '^>' "$file_path" | grep -o . | wc -l)
            local gc_count=$(grep -v '^>' "$file_path" | grep -o '[GC]' | wc -l)
            local length_ranges=$(grep -v '^>' "$file_path" | awk '{print length}' | sort -n | awk 'NR==1; END{print}')
            echo "* Sequence count: $seq_count"
            echo "* Base count: $base_count"
            echo "* GC count: $gc_count"
            echo "* GC content: $(echo "scale=2; ($gc_count/$base_count)*100" | bc)%"
            echo "* Length range: $length_ranges"
            ;;
        fq | fastq )
            echo "Processing fastq file: $file_path"
            local base_total=$(awk 'NR%4==2' "$file_path" | wc -c)
            local n_reads=$(awk 'NR%4==2' "$file_path" | grep -c 'N')
            local n_count=$(awk 'NR%4==2' "$file_path" | grep -o 'N' | wc -l)
            local atcgn_distribution=$(awk 'NR%4==2' "$file_path" | cut -c1 | sort | uniq -c)
            echo "* Base total: $base_total"
            echo "* Reads with N: $n_reads"
            echo "* N count: $n_count"
            echo "* ATCGN distribution: $atcgn_distribution"
            ;;
        bed)
            echo "Processing BED file: $file_path"
            local lines=$(wc -l < "$file_path")
            echo "* Number of lines: $lines"
            ;;
        gff)
            echo "Processing GFF file: $file_path"
            local columns=$(awk '{print NF; exit}' "$file_path")
            echo "* Number of columns: $columns"
            ;;
        *)
            echo "Unsupported file type: .$file_type"
            return 1
            ;;
    esac
    return 0
}

# 检查是否存在至少一个参数
if [ "$#" -eq 0 ]; then
    usage
fi

file_path="$1"

# 检查文件路径是否为空以及文件是否存在
if [ -z "$file_path" ]; then
    echo "Error: No file path provided."
    usage
elif [ ! -f "$file_path" ]; then
    echo "Error: File does not exist."
    usage
else
    # Call the generic function to handle the file
    handle_file "$file_path"
fi

使用说明:

# 赋予可执行权限
chmod +x statBioFile
# 使用脚本
./statBioFile /path/to/your/datafile.fa

运行示例:

➜ ./statBioFile Homo_sapiens.GRCh38.cdna.abinitio.fa
Processing fasta file: Homo_sapiens.GRCh38.cdna.abinitio.fa
* Sequence count: 50174
* Base count:  62676097
* GC count:  33561747
* GC content: 53.00%
* Length range: 1
60

➜ ./statBioFile sarsCov2Genomes.fasta
Processing fasta file: sarsCov2Genomes.fasta
* Sequence count: 17
* Base count:   508282
* GC count:   183308
* GC content: 36.00%
* Length range: 14
60

➜ ./statBioFile SRR3091420_1_chr6.fastq
Processing fastq file: SRR3091420_1_chr6.fastq
* Base total:  41758450
* Reads with N: 9975
* N count:    11654
* ATCGN distribution: 131514 A
377795 C
262324 G
 519 N
63017 T

➜ ./statBioFile homo_sapiens.GRCh38.A549.Regulatory_Build.regulatory_activity.20221007.gff
Processing GFF file: homo_sapiens.GRCh38.A549.Regulatory_Build.regulatory_activity.20221007.gff
* Number of columns: 12

➜ ./statBioFile chr.bed
Processing BED file: chr.bed
* Number of lines:    16959

2. fq2fa

将fq转fa文件,用户提供fq格式的文件,将其转换为fa格式。

在FASTQ文件中,每个序列由四行表示:

  1. 以’@’开头的行,包含序列标识符和可选的描述(类似于FASTA文件的标题行)。
  2. 序列本身。
  3. 以’+’开头的行,可选地跟随相同的序列标识符和描述。
  4. 包含序列质量分数的行。

而在FASTA文件中,每个序列由两行表示:

  1. 以’>’开头的行,包含序列标识符和可选的描述。
  2. 序列本身。

当从FASTQ格式转换为FASTA格式时,实际上是在移除以’+’开头的行和质量分数行,这样总行数减少了一半。因此,转换后的FASTA文件的行数应该是原始FASTQ文件行数的一半。可以利用这一点来检查确认转换过程如预期进行。

#!/bin/bash

# 检查是否存在至少一个参数
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <input_fastq_file>"
    exit 1
fi

input_fastq_file="$1"
output_fasta_file="${input_fastq_file%.fq}.fa"

# 这里是用的Linux练习册中的命令行
awk '{if (NR%4==1||NR%4==2) print}' "$input_fastq_file" | sed 's/@/>/g' > "$output_fasta_file"

# 检查行数,以免出错
echo "Check of line counts:"
fq_line_count=$(cat "$input_fastq_file" | wc -l)
fa_line_count=$(sed -n '1~4p;2~4p' "$input_fastq_file" | wc -l)
echo "The original fastq file has $fq_line_count lines."
echo "The converted fasta file should have approximately half this number: $fa_line_count lines."

使用说明:

# 赋予可执行权限
chmod +x fq2fa
# 使用脚本
./fq2fa /path/to/your/datafile.fq

但是当我使用这个脚本时,有一个小的报错:

sed: 1: "1~4p;2~4p": invalid command code ~

原因是:~ 运算符在sed中用于指定在每n行上操作的步长值,但在非Linux系统 (如macOS) 上可能不兼容(我就是macOS系统)。更改后的代码如下。

#!/bin/bash

# 检查是否存在至少一个参数
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <input_fastq_file>"
    exit 1
fi

input_fastq_file="$1"
output_fasta_file="${input_fastq_file%.fq}.fa"

awk '{if (NR%4==1) {sub(/^@/, ">", $0); print} else if (NR%4==2) print}' "$input_fastq_file" > "$output_fasta_file"

# 打印新 fasta 文件的开头
echo "The beginning of the new fasta file:"
head "$output_fasta_file"

# 检查行数,以免出错
echo "Check of line counts:"
fq_line_count=$(wc -l < "$input_fastq_file")
fa_line_count=$(awk 'NR%4==1 || NR%4==2' "$input_fastq_file" | wc -l)
half_fq_line_count=$((fq_line_count / 2))

echo "The original fastq file has $fq_line_count lines."
echo "The actual line count of the converted fasta file: $fa_line_count lines."

# 检查转换的行数是否约为原来的一半
if [ "$fa_line_count" -eq "$half_fq_line_count" ]; then
    echo "Line count check passed."
else
    echo "Line count check failed."
fi

运行示例:

➜ ./fq2fa SRR3091420_1_chr6.fastq
The beginning of the new fasta file:
>25
CTCAAGCAATCCTCCCACCTCAGCCTCCTGAGTAGTTGGGACTACAGGT
>1648
GGGATTGCAGGCATGCGCCACCATGCCCAGCTAATTTTTGTGTTTTTAG
>3092
CTGGAGTGCAATGGCGCGATCTCAGCTCACTGCAAGCTCTGCCTCCTGG
>3094
CACGCCACCACACCCAACTAATTTTTGTATTTTTAGTAGAGATGGGGTT
>9980
CCCCCGGTGTGTGATGTTCCCCTCCCTGTGTCCATGTGTTCTCATTGTT
Check of line counts:
The original fastq file has  3340676 lines.
The actual line count of the converted fasta file:  1670338 lines.
Line count check passed.

3. revCmpDNAseq

对DNA序列进行反转互补,用户可以选择仅反转或仅互补,或反转且互补;用户提供的可以是一条序列,或是一个文件(fa或fq),输出可以是fa/fq文件,请自行灵活地设计函数。

参数说明:

  • -r: 反转。对输入的DNA序列进行反转(例如:”GTCA” 变成 “ACTG”)。
  • -c: 互补。对输入的DNA序列进行碱基互补(例如:”GTCA” 变成 “CAGT”)。
  • -rc: 反转互补。先对序列进行互补,然后再反转(例如:”GTCA” 变成 “TGAC”)。
  • -s <sequence>: 指定单条DNA序列。即可以直接在命令行中输入一个DNA序列进行处理。
  • -f <file>: 指定输入文件(fasta 或 fastq 格式)。
  • -o <output_file>: 指定输出文件。如果没有写-o或者-o后面没有指定输出文件名,还是会输出文件,输出的文件名默认为file_processed.filetype
#!/bin/bash

# 打印帮助信息
usage() {
    echo "Usage: $0 [-r] [-c] [-rc] [-s <sequence> | -f <file>] [-o <output_file>]"
    echo "-r: Reverse the sequence."
    echo "-c: Complement the sequence."
    echo "-rc: Reverse complement the sequence."
    echo "-s: Specify a single DNA sequence."
    echo "-f: Specify an input file (fasta or fastq format)."
    echo "-o: Specify an output file (optional)."
    exit 1
}

# 反转序列
reverse_seq() {
    echo "$1" | rev
}

# 互补序列
complement_seq() {
    echo "$1" | tr 'ATCGatcg' 'TAGCtagc'
}

# 反转互补序列
reverse_complement_seq() {
    echo "$1" | rev | tr 'ATCGatcg' 'TAGCtagc'
}

# 处理 fasta 或 fastq 文件
process_file() {
    local file_path=$1
    local operation=$2
    local out_file=$3

    while read -r line; do
        if [[ $line == ">"* ]] || [[ $line == "@"* ]] || [[ $line == "+"* ]]; then
            echo "$line"
        else
            $operation "$line"
        fi
    done < "$file_path" > "$out_file"
}

# 主程序
operation=""
input_seq=""
input_file=""
output_file=""

# 解析命令行参数
while getopts "rcs:f:o:" opt; do
    case $opt in
        r) operation="reverse_seq" ;;
        c) operation="complement_seq" ;;
        rc) operation="reverse_complement_seq" ;;
        s) input_seq="$OPTARG" ;;
        f) input_file="$OPTARG" ;;
        o) output_file="$OPTARG" ;;
        *) usage ;;
    esac
done

# 校验参数
if [ -z "$operation" ] || { [ -z "$input_seq" ] && [ -z "$input_file" ]; }; then
    usage
fi

# 如果提供了序列,直接处理序列
if [ -n "$input_seq" ]; then
    if [ -n "$output_file" ]; then
        $operation "$input_seq" > "$output_file"
    else
        $operation "$input_seq"
    fi
# 否则处理文件
elif [ -n "$input_file" ]; then
    if [ -z "$output_file" ]; then
        output_file="${input_file%.*}_processed.${input_file##*.}"
    fi
    process_file "$input_file" "$operation" "$output_file"
fi

使用说明:

# 赋予可执行权限
chmod +x revCmpDNAseq
# 对单条序列进行操作
./revCmpDNAseq -r -s "GTCA"
# 对文件中的序列进行操作:
./revCmpDNAseq -rc -f input.fa -o output.fa

运行示例:

# 反转一条序列
➜  ./revCmpDNAseq -r -s "GTCAAAAAAATTTTTCGAA"
AAGCTTTTTAAAAAAACTG
# 反转互补一条序列
➜  ./revCmpDNAseq -rc -s "GTCAAAAAAATTTTTCGAA"
CAGTTTTTTTAAAAAGCTT

4. DNA2RNA

将fa或fq文件中的DNA序列转化成RNA序列。

#!/bin/bash

# 打印帮助信息
usage() {
    echo "Usage: $0 [-s <sequence> | -f <file>] [output_file]"
    echo "-s: Specify a single DNA sequence."
    echo "-f: Specify an input file (fasta or fastq format)."
    echo "Converts DNA sequences to RNA sequences."
    exit 1
}

# 将DNA序列转换为RNA序列
convert_dna_to_rna() {
    echo "$1" | tr 'Tt' 'Uu'
}

# 处理 fasta 或 fastq 文件
process_file() {
    local file_path=$1
    local out_file=$2

    awk '{
        if ($0 ~ /^>/ || $0 ~ /^@/ || $0 ~ /^\+/) {
            print
        } else {
            gsub(/T/, "U");
            gsub(/t/, "u");
            print
        }
    }' "$file_path" > "$out_file"
}

# 主程序
input_seq=""
input_file=""
output_file="/dev/stdout"

# 解析命令行参数
while getopts "s:f:" opt; do
    case $opt in
        s) input_seq="$OPTARG" ;;
        f) input_file="$OPTARG" ;;
        *) usage ;;
    esac
done
shift $((OPTIND -1))

# 检查额外的参数是否为输出文件
if [ "$#" -eq 1 ]; then
    output_file="$1"
fi

# 校验参数
if { [ -n "$input_seq" ] && [ -n "$input_file" ]; }; then
    usage
elif [ -z "$input_seq" ] && [ -z "$input_file" ]; then
    usage
fi

# 处理输入
if [ -n "$input_seq" ]; then
    convert_dna_to_rna "$input_seq" > "$output_file"
elif [ -n "$input_file" ]; then
    process_file "$input_file" "$output_file"
fi

echo "Conversion complete. Output in $output_file"

使用说明:

# 赋予可执行权限
chmod +x DNA2RNA
# 处理单条序列:
./DNA2RNA -s "GTCA" output.fa
# 处理文件:
./DNA2RNA -f input.fa output.rna

脚本整合安装程序

参考gibbslab/biobash: Common BASH routines for computational biology (github.com)中的安装程序

#!/bin/bash

default="/usr/local/bin"

echo "
#
           Bio_funclib INSTALLATION
#

"

failure=" == WARNING! Unable to install the tools =="

echo  "Hit ENTER to use the default installation directory ($default) or provide a valid installation path below:"
read -p " > " dir

if [ -z "$dir" ]; then
    installDir=$default
else
    installDir=$dir
fi

# 检查目录是否存在
if [ -d "$installDir" ]; then

    # 检查目录是否可写
    if [ -w "$installDir" ]; then

        echo "Installing to: $installDir"

        # 复制文件到指定目录
        cp ./DNA2RNA $installDir
        cp ./fq2fa $installDir
        cp ./revCmpDNAseq $installDir
        cp ./statBioFile $installDir

        # 确保所有脚本的执行权限
        chmod +x ${installDir}/DNA2RNA
        chmod +x ${installDir}/fq2fa
        chmod +x ${installDir}/revCmpDNAseq
        chmod +x ${installDir}/statBioFile

        echo "Installation complete. Thank you for installing the tools."

    else
        echo $failure
        echo "$installDir is not writable. Please make sure you have write access."
    fi

else
    echo $failure
    echo "The directory you just typed ($installDir) does not exist."
    echo "Please make sure route is correct"
fi

使用说明:

# 给予安装脚本执行权限
chmod +x  Bio_funclib_install.sh
# 运行安装脚本
./install_script.sh
# 使用示例
DNA2RNA --help