非常多的朋友在看我們公眾號過往轉錄組,WES,等流程分享的時候發現很難理解我們的代碼,其實就是缺乏shell腳本知識,那麼這篇教程你就不容錯過。
內容
使用多個命令
創建腳本文件
顯示消息
使用變量
輸入輸出重定向
管道
數學運算
退出腳本
一個腳本例子bed=exon_probe.hg38.gene.bed
for bam in /home/project/*.bam
do
file=$(basename $bam )
sample=${file%%.*}
echo $sample
export total_reads=$(samtools idxstats $bam|awk -F '\t' '{s+=$3}END{print s}')
echo The number of reads is $total_reads
bedtools multicov -bams $bam -bed $bed |perl -alne '{$len=$F[2]-$F[1];if($len <1 ){print "$.\t$F[4]\t0" }else{$rpkm=(1000000000*$F[4]/($len* $ENV{total_reads}));print "$.\t$F[4]\t$rpkm"}}' >$sample.rpkm.txt
done
這個腳本看起來不複雜,但是裡面應用到了接下來要講解的所有知識點。
使用多個命令如果多個命令一起使用,可以放在一行並用分號分隔。
wsx@wsx-ubuntu:~$ date; who
2017年 07月 26日 星期三 09:53:43 CST
wsx tty7 2017-07-26 09:48 (:0)
創建腳本文件在創建腳本文件時,必須在文件的第一行指定要使用的shell,格式為:
#!/bin/bash
腳本文件的第一行中 #後的驚嘆號會告訴shell使用哪個shell來運行腳本(如果是其他編碼語言腳本,像python,第一行類似)。
其他地方的 #用作注釋行。
添加名為 test1的腳本文件,內容為:
#!/bin/bash
# This script displays the date and who's logged on
date
who
運行一個腳本怎麼會那麼難?現在運行腳本,結果會是:
wsx@wsx-ubuntu:~/script_learn$ test1
未找到 'test1' 命令,您要輸入的是否是:
命令 'testr' 來自於包 'python3-testrepository' (main)
命令 'testr' 來自於包 'python-testrepository' (universe)
命令 'test' 來自於包 'coreutils' (main)
test1:未找到命令
我們現在需要做的是讓bash shell能夠找到我們的腳本文件。shell會通過 PATH環境變量來查找命令,我們可以看看:
wsx@wsx-ubuntu:~/script_learn$ echo $PATH
/home/wsx/Anaconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
很顯然,我們的文件沒有在這些目錄範圍內。要讓shell找到test1腳本,我們可以採取以下兩種做法之一:
第二種方法比較簡單,我們在這裡試試:
wsx@wsx-ubuntu:~/script_learn$ ./test1
bash: ./test1: 權限不夠
wsx@wsx-ubuntu:~/script_learn$ ll test1 # 發現權限不夠,查看文件的權限
-rw-rw-r-- 1 wsx wsx 73 7月 26 10:03 test1
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test1 # 修改權限,添加可執行屬性
wsx@wsx-ubuntu:~/script_learn$ ./test1 # 成功運行腳本
2017年 07月 26日 星期三 10:09:23 CST
wsx tty7 2017-07-26 09:48 (:0)
顯示消息在 echo命令後面加上一個字符串,就能顯示出這個文本字符串。這種方式可以添加自己的文本消息來告訴腳本用戶腳本正在做什麼。
wsx@wsx-ubuntu:~/script_learn$ echo This is a test
This is a test
如果文本本身帶有字符串,我們需要用單引號或雙引號來劃定文本字符串。
wsx@wsx-ubuntu:~/script_learn$ echo "Let's see if this'll work"
Let's see if this'll work
我們修改下之前的test1文件,增加消息顯示:
#!/bin/bash
# This script displays the date and who's logged on
echo The time and date are:
date
echo "Let's see who's logged into the system"
who
運行:
wsx@wsx-ubuntu:~/script_learn$ ./test1
The time and date are:
2017年 07月 26日 星期三 10:17:59 CST
Let's see who's logged into the system
wsx tty7 2017-07-26 09:48 (:0)
如果想把文本字符串和命令輸出顯示在同一行中,可以用 echo語句的 -n參數。需要在字符串的兩側加上引號,並且保證字符串尾部有一個空格(不然字符串和命令輸出就粘連到一起了)。
#!/bin/bash
# This script displays the date and who's logged on
echo -n "The time and date are: "
date
echo "Let's see who's logged into the system: "
who
# 運行結果輸出
wsx@wsx-ubuntu:~/script_learn$ ./test1
The time and date are: 2017年 07月 26日 星期三 10:24:04 CST
Let's see who's logged into the system:
wsx tty7 2017-07-26 09:48 (:0)
使用變量變量允許我們臨時性地將信息存儲在shell腳本中,以便和腳本中的其他命令一起使用。
環境變量
shell維護著一組環境變量,用來記錄特定的系統信息。比如系統的名稱、登錄到系統上的用戶名、用戶的系統ID(也稱為UID)、用戶默認主目錄以及shell查找程序的搜索路徑。
使用 set命令顯示一份完整的當前環境變量列表。 env與 printenv命令都可以顯示全局變量。(這些命令輸出結果比較多,不展示了。之前關於環境變量的筆記有比較詳細的描述。)
在環境變量名稱之前加上美元符可以使用這些環境變量。
wsx@wsx-ubuntu:~/script_learn$ cat test2
#! /bin/bash
# display user information from the system
echo "User info for userid: $USER"
echo UID: $UID
echo HOME: $HOME
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test2
wsx@wsx-ubuntu:~/script_learn$ ./test2
User info for userid: wsx
UID: 1000
HOME: /home/wsx
可以想像的到,如果我們想要使用實際的美元符而不是引用變量,肯定會出問題。這時候我們需要在美元符前面加上 \進行轉義,以顯示美元符本身。
用戶變量
使用等號將值賦給用戶變量。注意,在變量、等號和值之間不能出現空格!這個是初學者常見的一個問題,本人也非常不太適應這個。因為在其他語言中不區分等號兩邊的空格,相信接觸過其他腳本的朋友們肯定有習慣打空格使代碼美觀的,這在bash shell中是萬萬行不通滴。
一個使用的例子:
wsx@wsx-ubuntu:~/script_learn$ cat test3
#!/bin/bash
# testing variables
days=10
guest="Katie"
echo "$guest checked in $days days ago"
days=5
guest="Jessica"
echo "$guest checked in $days days ago"
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test3
wsx@wsx-ubuntu:~/script_learn$ ./test3
Katie checked in 10 days ago
Jessica checked in 5 days ago
變量每次被引用時,都會輸出當前賦給它的值。重要的是要記住,引用一個變量值時需要使用美元符,而引用變量來對其進行賦值時則不需要使用美元符。
命令替換shell腳本最有用的特性之一就是可以從命令輸出中提取信息,並將其賦給變量。
有兩種方法可以將命令輸出賦給變量:
要麼用一對反引號把整個命令行命令圍起來:
testing=`date`
要麼使用$()格式
testing=$(date)
下面是一個例子,在腳本中通過命令替換獲得當前日期並用它來生成唯一文件名:
wsx@wsx-ubuntu:~/script_learn$ cat test4
#!/bin/bash
# copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test4
wsx@wsx-ubuntu:~/script_learn$ ./test4
重定向輸入和輸出通過幾個操作符進行重定向,我們可以將命令的結果輸出到另外的位置(文件)。當然,重定向可以用於輸入。
輸出重定向
最基本的操作符是 >。比如我們想要輸出命令結果到一個指定文件:
wsx@wsx-ubuntu:~/script_learn$ date > test6
wsx@wsx-ubuntu:~/script_learn$ ls -l test6
-rw-rw-r-- 1 wsx wsx 43 7月 26 16:42 test6
wsx@wsx-ubuntu:~/script_learn$ cat test6
2017年 07月 26日 星期三 16:42:34 CST
如果想要將命令的輸出追加到已有文件中,需要用雙大於號(>>)來追加數據。
輸入重定向
輸入重定向和輸出重定向正好相反。輸入重定向將文件的內容重定向到命令,而非將命令的輸出重定向到文件。
使用的符號是小於號(<)。
一種簡單的記憶方法是:在命令行上,命令總是在左側,而重定向符號「指向」數據流動的方向。小於號說明數據正在從輸入文件流向命令。
比如用wc命令檢查文本的行數、詞數和字節數。
wsx@wsx-ubuntu:~/script_learn$ wc < test6
1 6 43
另一種輸入重定向的方法是內聯輸入重定向。它無需使用文件進行重定向,只需要在命令行中指定用於輸入重定向的數據即可。它使用的符號是遠小於號(<<),除了這個符號,我們還需要指定一個文本標記用來劃分輸入數據的開始和結尾。任何字符串都可以作為文本標記,但在數據的開始和結尾文本標記必須一致。
wsx@wsx-ubuntu:~/script_learn$ wc << EOF
> test string1
> test string2
> test string3
> EOF
3 6 39
它的形式為:
command << marker
data
marker
管道很多生信命令行工具需要提供多個輸入和輸出參數,這用在管道命令裡可能會導致非常低效的情形(管道只接受一個標準輸入和輸出)。幸好,我們可以使用命令管道來解決此類問題。
命名管道,也稱為FIFO。它是一個特殊的排序文件,命名管道有點像文件,它可以永久保留在你的文件系統上(估計本質就是文件吧~)。
比如我想查看某個文件(test1)的前兩行並進行排序,操作如下:
wsx@wsx-ubuntu:~/script_learn$ cat test1
#!/bin/bash
# This script displays the date and who's logged on
echo -n "The time and date are: "
date
echo "Let's see who's logged into the system: "
who
wsx@wsx-ubuntu:~/script_learn$ cat test1 | head -2 | sort
#!/bin/bash
# This script displays the date and who's logged on
管道的強大之處在於可以根據自己的需求靈活地組合和使用各種linux命令工具。這裡只是一個簡單的例子,要熟練掌握少不了平時多多研究和練習。
進程替換有些命令需要接受多個管道的輸入作為自己的輸出,這個時候普通的管道已經無法完成任務了。需要用到進程替換,來避免多次創建中間文件,代碼如下:
start=$(date +%s.%N)
echo VarScan `date`
normal_pileup="samtools mpileup -q 1 -f $reference $normal_bam";
tumor_pileup="samtools mpileup -q 1 -f $reference $tumor_bam";
# Next, issue a system call that pipes input from these commands into VarScan :
java -Djava.io.tmpdir=$TMPDIR -Xmx40g -jar ~/biosoft/VarScan/VarScan.v2.3.9.jar \
somatic <($normal_pileup) <($tumor_pileup) ${sample}_varscan
java -jar ~/biosoft/VarScan/VarScan.v2.3.9.jar processSomatic ${sample}_varscan.snp
echo VarScan `date`
dur=$(echo "$(date +%s.%N) - $start" | bc)
printf "Execution time for VarScan : %.6f seconds" $dur
echo
執行數學運算對shell腳本來說,執行數學運算非常麻煩。有兩種實現方式。
expr命令
expr命令允許在命令行上處理數學表達式,但是特別笨拙。(Bourne shell中)
wsx@wsx-ubuntu:~/script_learn$ exrpr 1 + 5
未找到 'exrpr' 命令,您要輸入的是否是:
命令 'expr' 來自於包 'coreutils' (main)
exrpr:未找到命令
看到沒有,那算了。它基本涉及的操作跟我們使用的其他語言是一致的。但是有些問題需要處理,像 *是通配符,在運算是是做乘號處理的,需要進行轉義。
使用方括號
bash shell提供了一種更簡單的方法來執行數學表達式。在bash中,在將一個數學運算結果賦給某個變量時,可以用美元符和方括號($[operator])將數學表達式圍起來。
wsx@wsx-ubuntu:~/script_learn$ var1=$[1+5]
wsx@wsx-ubuntu:~/script_learn$ echo $var1
6
wsx@wsx-ubuntu:~/script_learn$ var2=$[$var1+2]
wsx@wsx-ubuntu:~/script_learn$ echo $var2
8
這種方式不僅方便,而且因為在方括號內,不會讓shell誤解乘號或其他符號。
但bash shell計算有一個主要限制:它只支持整數運算!
浮點解決方案
最常見的方案是用內建的bash計算器。它實際上是一門程式語言,它允許在命令行中輸入浮點表達式,然後解釋並計算該表達式,最後返回結果。bash計算器能夠識別:
數字(整數和浮點數)
變量(簡單變量和數組)
注釋(/* */開始的行)
表達式
編程語句
函數
wsx@wsx-ubuntu:~/script_learn$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
12 * 5.4
64.8
3.156 * (3 + 5)
25.248
quit
在腳本中使用bc
可以用命令替換運行bc命令,並將輸出賦給一個變量。基本格式如下:
variable=$(echo "options; expression" | bc)
options設置變量,expression參數定義了通過bc執行的數學表達式。
看一個簡單實例:
wsx@wsx-ubuntu:~/script_learn$ cat test9
#!/bin/bash
var1=$(echo "scale=4; 3.44/5" | bc)
echo The answer is $var1
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test9
wsx@wsx-ubuntu:~/script_learn$ ./test9
The answer is .6880
這個例子將 scale變量設置為四位小數,並在 expression部分指定了特定的運算。
這個方法適用於較短的運算,但有時我們會涉及更多的數字。如果需要進行大量運算,在一個命令行中列出多個表達式就會有點麻煩。
這裡有一個解決方法:使用內聯輸入重定向,將一個文件重定向到bc命令來處理。格式為:
variable=$(bc << EOF
options
statements
expressions
EOF)
EOF文本字符串標識了內聯重定向數據的起始。注意,仍然需要命令替換符號將bc命令的輸出賦給變量。
下面是一個例子:
wsx@wsx-ubuntu:~/script_learn$ cat test10
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc <<EOF
scale=4
a1 = ( $var1 * $var2)
b1 = ( $var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test10
wsx@wsx-ubuntu:~/script_learn$ ./test10
The final answer for this mess is 2813.9882
在普通的shell腳本中,數字默認當做字符串處理。這也是為什麼我們腳本處理計算麻煩和我們需要特定的工具和方法來進行處理。一定要注意區分。
退出腳本前面運行的腳本都是命令執行完成,腳本自動結束。其實我們可以用更為優雅的方式告訴shell命令運行完成,因為每個命令都使用退出狀態碼(exit status),它是一個0-255的整數值,我們可以捕獲這個值並在腳本中使用。
Linux提供了一個專門的變量$?`來保存上個已執行命令的退出狀態碼。
wsx@wsx-ubuntu:~/script_learn$ date
2017年 07月 27日 星期四 10:44:18 CST
wsx@wsx-ubuntu:~/script_learn$ echo $?
0
按照慣例,一個成功結束的命令的退出狀態碼是0。如果有錯誤,則顯示一個正數值。
Linux錯誤退出狀態碼沒有什麼標準,但有一些參考:
狀態碼描述0命令成功結束1一般性未知錯誤2不適合的shell命令126命令不可執行127沒找到命令128無效的退出參數128+x與Linux信號x相關的嚴重錯誤130通過Ctrl+C終止的命令255正常範圍之外的退出狀態碼wsx@wsx-ubuntu:~/script_learn$ asfg
未找到 'asfg' 命令,您要輸入的是否是:
命令 'asdfg' 來自於包 'aoeui' (universe)
asfg:未找到命令
wsx@wsx-ubuntu:~/script_learn$ echo $?
127
exit命令
默認,shell腳本會以腳本最後的一個命令的退出狀態碼退出。
但是我們可以改變這種默認行為,返回自己的退出狀態碼。exit命令允許在腳本結束時指定一個狀態退出碼。
wsx@wsx-ubuntu:~/script_learn$ cat test13
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[$var1 + $var2]
echo The answer is $var3
exit 5
## 上面就是腳本的內容,用cat可以查看文本文件,下面就添加可執行權限並且執行該腳本
wsx@wsx-ubuntu:~/script_learn$ chmod u+x test13
wsx@wsx-ubuntu:~/script_learn$ ./test13
The answer is 40
wsx@wsx-ubuntu:~/script_learn$ echo $?
5
注意最大255,如果大於它,得到的是求模的結果(餘數)。
腳本高級知識還有一些腳本高級知識,不予講解,感興趣的同學可以自行購買相關書籍和專業視頻自學:
處理信號
以後臺模式運行腳本
禁止掛起
作業控制
修改腳本優先級
腳本執行自動化
這裡強烈推薦馬哥linux視頻,在生信技能樹的公眾號後臺回復 馬哥 可以拿到系列視頻~