bash

簡単な繰り返し処理はもちろんのこと、大量なデータを処理する際もシェルスクリプトを書いて計算機に投入する必要があるので、シェルスクリプトは Linux ユーザーにとってなくてはならない存在である。bash はシェルスクリプトの一種で、ほとんどの Linux においてデフォルトのシェルとして使われている。

bash の変数

bash の変数には文字列あるいは配列を代入することができる。数字を代入しても、基本的に文字列として扱われる。

seq=ACAGTTAGCTAGCTGATGCACTAGTC
echo ${seq}
## ACAGTTAGCTAGCTGATGCACTAGTC


n=10
m=20
x=${n}+${m}
echo ${x}
## 10+20


arr=("AAA" "CCC" "GGG" "TTT")
echo ${arr}
## AAA
echo ${arr[@]}
## AAA CCC GGG TTT

bash では関数を定義することも可能で、その際に function を利用して定義する。

function sum {
  x=$1$2
  echo ${x}
}
sum ACAG TTGT
## ACAGTTGT

bash 文字列

# を利用することで、文字列の長さを求めることができる。

dna="TCCCA"
echo ${#dna}
## 5

文字列の切り出しは、開始位置と終了位置を指定して切り出す。

dna="TCCCA"
echo ${dna:0:3}
## TCC

文字列の置換はスラッシュを利用する。マッチしたパターンすべてに対して置換を行うときは、// を利用し、マッチしたパターンの最初の 1 つだけに対して置換を行う場合は / を利用する。

dna="TCTTA"
echo ${dna//T/U}
## UCUUA

echo ${dna/T/U}
## UCTTA

文字列の連結は 2 つの変数連続して書く。他のプログラミング言語のような +. などの連結演算子は存在しない。

s1="ACCCC"
s2="GTTTT"

s="${s1}${s2}"
echo ${s}
## ACCCCGTTTT

配列の要素を連結して 1 つの文字列にする場合は、まず連結する際に区切り文字として利用される文字を IFS で指定してから、[*] で配列を展開して、新しい変数に代入すればよい。

arr=( "AAA" "TTT" "GGG" "CCC")
IFS=""
str="${arr[*]}"
echo ${str}
## AAATTTGGGCCC

1 つの文字列を、区切り文字で分割して配列にすることもできる。

str="SRR001,SRR002,SRR003"
IFS=","
ac=(${str})
echo ${ac[@]}
## SRR001 SRR002 SRR003

bash 配列

配列の作成

bash の配列を作るには、複数の要素を括弧で囲んで、変数に代入する。要素と要素の間はスペースで区切る。

arr=()
arr=("magnoliids" "monocots" "eudicots")

配列を作ってから、添え字を指定して要素の変更や追加を行うこともできる。

a=()
b=("mag" "mon" "eud")

a[0]="NN_CC320"
echo ${a[@]}
## NN_CC320

a=("${b[@]}" "NM_AC001" "NX_CB201")
echo ${a[@]}
## mag mon eud NM_AC001 NX_CB201

a[2]="NX_CB202"
echo ${a[@]}
## mag mon NX_CB202 NM_AC001 NX_CB201

配列要素の取り出し

要素を取り出すときは、添え字を指定して取り出す方法の他に、開始位置と終了位置を指定して複数の要素を取り出すこともできる。

arr=("FUN" "PLN" "PRT" "MAM" "HUM" "VRT")

echo ${arr[0]}
## FUN

echo ${arr[@]:1:3}
## PLN PRT MAM

echo ${arr[@]:2}
## PRT MAM HUM VRT

配列から最後の要素を取り出したい場合は、最後のインデックスを計算して取り出す。ただし、bash のバージョンが 4.2 以降の場合、-1 のようにマイナスでインデックスを指定することもできる。

${arr[${#arr[@]}-1]}
## VRT

echo ${arr[-1]}
## VRT

配列中すべての要素を順番に取り出す場合は for を利用する。次のように、添え字を介して要素を取り出す方法と、要素自体を取り出す方法がある

arr=("FUN" "PLN" "PRT" "MAM" "HUM" "VRT")

for (( i = 0; i < ${#arr[@]}; ++i ))
do
    echo ${arr[$i]}
done


for a in ${arr[@]}
do
    echo ${a}
done

配列要素の置換

bash では、配列の要素に対して一括に置換などを行うことができる。ファイル名から拡張子を削除したり、あるいは拡張子を変更したりする場合に便利。

fnames=("NC_01.1.fasta" "NC_02.1.fasta" "NC_03.2.fasta")

次は、配列の各要素に対して、文字列 fasta を文字列 fa に置換する例である。

echo ${fnames[@]/fasta/fa} 
## NC_01.1.fa NC_02.1.fa NC_03.2.fa

置換後の文字列を空文字にすると、文字列の削除が行える。次は、文字列 fasta を削除する例である。

echo ${fnames[@]/\.fasta/}   
## NC_01.1 NC_02.1 NC_03.2

配列の各要素に対して、先頭から最初にマッチした位置まで取り除くとき # を利用する。次は、文字列の先頭から最初のピリオドまでの部分文字列を取り除く例である。

echo ${fnames[@]#*\.} 
##1.fasta 1.fasta 2.fasta

配列の各要素に対して、先頭から最後にマッチした位置まで取り除くとき ## を利用する。次は、文字列の先頭から最後のピリオドまでの部分文字列を取り除く例である。

echo ${fnames[@]##*\.} 
## fasta fasta fasta

次は、配列の各要素に対して、「文字列の後ろから最初にマッチした位置」から文字列の最後までの部分文字列を取り除く例である。

echo ${fnames[@]%\.*} 
## NC_01.1 NC_02.1 NC_03.2

配列の各要素に対して、「文字列の後ろから最後にマッチした位置」から文字列の最後までの部分文字列を取り除く例である

echo ${fnames[@]%%\.*} 
## NC_01 NC_02 NC_03

bash 関数

bash で関数を定義するときは function を利用する。関数の内部で引数を参照するときに $1$2、…のように参照する。

function sum {
  let x=$1+$2
  echo ${x}
}

sum 10 20
## 30

bash の関数に戻り値がないので、戻り値利用する場合はあらかじめグローバル変数を用意して、関数の中でそのグローバル変数に値を代入しておく必要がある。

y=""

function sum {
  let x=$1+$2
  y=${x}
}

sum 10 20
echo ${y}
## 30

bash 制御構文

if 構文

2 つの値を比較して真偽を判定してから、次の処理を行う if 構文は次のようにかける。bash の if 構文の書き方は、他のプログラミング言語のように自由度が高くないので、条件判断のところでスペースが多かったりまたは少なかったりするとエラーになる。

n=10
m=20
if [ ${n} -eq ${m} ]
    then
        echo "n = m"
elif [ ${n} -lt ${m} ]
    then
        echo "n < m"
else
        echo "n > m"
fi

ファイル・ディレクトリの確認

ファイルあるいはディレクトリが存在するかどうかを確認して、作業を切り分ける例を示す。ファイルの存否を書くにインする場合は -e または -f を利用する。

fpath="/path/to/image.jpg"

if [ -e "${fpath}" ]; then
    python predict.py "${fpath}"
else
    echo "File not found."
fi

ディレクトリの存否を確認する場合は -d を利用する。

dpath="/path/to/images"

if [ ! -d "${dpath}" ]; then
    mkdir "${dpath}"
fi

for 構文

for 構文は配列の要素を 1 つずつ繰り返しながらとる出すループ構文である。

for i in 1 2 3 4 5
do
    echo $i    #1 2 3 4 5
done
 

for i in {1..5}
do
    echo $i    #1 2 3 4 5
done


for i in {1..10..2}
do
    echo $i    #1 3 5 7 9
done


for (( i=0; i<=5; i++ ))
do
    echo $i    #0 1 2 3 4 5
done


for i in {1..10}
do
    if [ $i -gt 3 ]
        then
            echo $i  #4
            break
    fi
done

while 構文

while 構文は、与えられた条件が真である限り繰り返すループ構文である。

declare -i i=0
while [ $i -lt 5 ]
do
    echo $i     #0 1 2 3 4
    let i+=1
done

ほぼ同じような機能を有する until 構文もある。until の場合は、ある条件に満たすまで繰り返すループ構文である。

declare -i i=9
until [ $i -lt 5 ]
do
    echo $i     #9 8 7 6 5
    let i-=1
done

特殊変数と演算子

bash で利用される特殊変数と演算子。

特殊変数

$0      #シェルスクリプト名
$1      #第1引数
$2      #第2引数
$#      #引数の個数
$@      #すべての引数を展開 "$1" "$2" ...
$*      #すべての引数を展開 "$1 $2 ..."
$?      #直前に実行したコマンドの終了ステータス
$!      #直前に実行したコマンドのPID
$$      #シェルスクリプトのPID

算術演算子

N + M
N - M
N * M
N / M
N % M   #剰余

論理演算子

N & M   #AND
N | M   #OR
N = M   #==
N > M   #>
N >= M  #>=
N < M   #<
N <= M  #<=
N != M  #!=

比較演算子

N -eq M
N -lt M
N -gt M
N -le M
N -ge M

サンプル

拡張子を変換

現在のディレクトリにある拡張子「fasta」で終わるファイルを「fa」に変更する例。

for i in `ls *.fasta`
do
  mv ${i} ${i%.fasta}.fa
done

ファイル名の一括変換

ファイル名に「AAA」が含んでいれば、それを「BBB」に変更する例。

for i in `ls *AAA*`
do
  mv ${i} `echo ${i} | sed 's/AAA/BBB/g'`
done

ファイルを一つにまとめる

複数の fastq ファイルを一つのファイルにまとめる例。

for i in `*.fastq`
do
  cat ${i} >> all.fastq
done

パイプで変数を利用

シェルスクリプトでパイプを利用するとき、パイプ左側の内容を変数に代入し、パイプ右側で利用することができる。while read を利用する。

echo "SRR000001 SRR000002 SRR000003" | while read line
do
    IFS=" "
    fqs=(${line})

    for f in ${fqs[@]}
    do
        echo ${f}.fq
    done
done
## SRR000001.fq
## SRR000002.fq
## SRR000003.fq