鑒于shell的高效、通用,使用shell編寫腳本實(shí)現(xiàn)日常使用的一些小功能。
處理生成文件的問題
強(qiáng)烈建議,在生成文件之前,先檢測(cè)文件是否存在,如果存在就刪除這個(gè)文件。對(duì)于使用管道符>指定輸出文件時(shí),請(qǐng)一定先檢查文件是否存在。現(xiàn)在許多程序運(yùn)行可能出錯(cuò),需要重新運(yùn)行,但是用戶基本不會(huì)手動(dòng)刪除先前生成的錯(cuò)誤的文件,所以我們?cè)谳敵龅轿募?,要先檢測(cè)之前是否存在
輸出文件前,可以請(qǐng)使用下面的命令檢測(cè)是否存在該文件。
#檢測(cè)是否存在該文件,如果有,則自動(dòng)刪除舊版本
if [ -e $output ]; then
echo "之前存在文件$output,自動(dòng)刪除舊版本。"
rm -rf $output
fi
1.編寫前必知的編寫規(guī)范
#!/bin/bash
##debug
set -x #直接輸出每次執(zhí)行的命令
set -e #程序異常結(jié)束時(shí)候,輸出錯(cuò)誤。
1.1 shell腳本開頭如上三行。
1.2 檢查shell腳本可以使用shellcheck,
sudo apt-get install shellcheck
shellcheck test.sh #檢測(cè)test.sh的語法是否正確。
1.3 在bash,如果不加 local 限定詞,變量默認(rèn)都是全局的。
對(duì)于在函數(shù)內(nèi)聲明的變量,請(qǐng)務(wù)必記得加上 local 限定詞。
1.4 trap函數(shù)的使用
對(duì)于程序結(jié)束的方式進(jìn)行判斷,如果正常結(jié)束,則執(zhí)行命令1,錯(cuò)誤則執(zhí)行命令2,如果是Ctrl+C終止,可以執(zhí)行命令3.
trap "echo end analysis" EXIT #程序退出時(shí)執(zhí)行,無論是正常退出,還是錯(cuò)誤退出。
trap "echo there have a error" ERR #出錯(cuò)時(shí),執(zhí)行
2. 基礎(chǔ)shell語法
shell腳本的執(zhí)行方式
方法1:bash test.bash
方法2:
chmod 757 test.bash
./test.bash
變量
shell變量:要求等號(hào)后面緊跟變量,不能有空格
例如:Name=Zhangsan
shell中雙引號(hào)和單引號(hào),如果引號(hào)內(nèi)有變量,使用雙引號(hào)。單引號(hào)內(nèi)部的字符不會(huì)被轉(zhuǎn)變?yōu)樽兞俊?br>
引用變量方式:$Name或者${Name},一般推薦變量引用的時(shí)候,都加上大括號(hào),不然后面某些時(shí)候,拼接字符串的時(shí)候會(huì)出現(xiàn)無法識(shí)別變量名的問題。
只讀變量 readonly Name 上面的Name變量就變成只讀變量,不能被修改。
刪除變量 unset variable_name
局部變量 默認(rèn)的變量都是全局變量
local age=18
字符串操作
拼接字符串
your_name="runoob"
# 使用雙引號(hào)拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
#輸出內(nèi)容
#hello, runoob ! hello, runoob !
查找字符串(使用正則expr index )
string="runoob bilideise site"
echo `expr index "$string" o` # 輸出 4
獲取字符串長(zhǎng)度 ${#string}
string="abcd"
echo ${#string} #輸出 4
提取字符串,第2:4個(gè)。
string="runoob is a great site"
echo ${string:1:4} # 輸出 unoo
bash中的index下標(biāo)從0開始。0是第一個(gè)。
數(shù)組
array_name=(value0 value1 value2 value3)或者單獨(dú)定義array_name[4]=value4
獲取數(shù)組第1個(gè)元素 ${array_name[0]}
獲取數(shù)組所有元素 ${array_name[@]}
獲取數(shù)組元素個(gè)數(shù) length=${#array_name[@]}
獲取某個(gè)元素的長(zhǎng)度arr1_length=${#array_name[1]}
注釋符號(hào)
單行注釋#
多行注釋
<<!
此處是區(qū)塊注釋
多行注釋文本
多行注釋文本
!
參數(shù)傳遞
傳參分為$1,$2,……$n代表傳入的第1,2,n個(gè)參數(shù)。
$0代表執(zhí)行的文件(包括路徑)
$#傳入?yún)?shù)的個(gè)數(shù)
$*傳入的所有參數(shù)(以一個(gè)單字符顯示)
$@傳入的所有參數(shù)(依次顯示每個(gè)參數(shù))
$$腳本運(yùn)行的當(dāng)前進(jìn)程id
$!后臺(tái)運(yùn)行的最后一個(gè)進(jìn)程id
$?顯示命令最后退出的狀態(tài),如果不是0,則代表程序沒有正常結(jié)束。
``
if [ -n "$1" ]; then
echo "包含第一個(gè)參數(shù)"
else
echo "沒有包含第一參數(shù)"
fi
算術(shù)比較, 比如一個(gè)變量是否為0,[ $var -eq 0 ]。
文件屬性測(cè)試,比如一個(gè)文件是否存在,[ -e $var ], 是否是目錄,[ -d $var ]。
字符串比較, 比如兩個(gè)字符串是否相同, [[ $var1 = $var2 ]]。
運(yùn)算符
算術(shù)運(yùn)算符+ - * / % 加減乘除整除取余
=是賦值,== 和!=僅用于數(shù)字比較。
運(yùn)算符使用時(shí),必須在中括號(hào)內(nèi),且兩邊均有空格。
例如:[ 5==5.0 ]
關(guān)系運(yùn)算符(僅限于數(shù)字之間比較)
-eq -ne -gt -lt -ge -le依次是相等,不等,大于,小于,大于等于,小于等于
邏輯運(yùn)算符
! -o -a 非;或;與
&& 與 ||或
字符串運(yùn)算符
= != 等于 ;不等于
-z 字符串長(zhǎng)度為0,則返回true
-n字符串長(zhǎng)度不為0,則返回true
$ 檢查字符串是否為空,不為空返回true.
文件運(yùn)算符
-d檢查文件是否是目錄,是,則返回true
-r 檢測(cè)文件是否可讀
-w檢測(cè)文件是否可寫
-x檢測(cè)文件是否可執(zhí)行
-s檢測(cè)文件是否為空,不為空則返回true
-e檢測(cè)文件(或目錄)是否存在,是,則返回true
-L 檢測(cè)文件是否存在并且是一個(gè)符號(hào)鏈接
echo命令
輸出換行echo -e "this is a new line! \n"
shell流程控制
shell里面else里面如果沒有東西,就不要寫else.
流程控制里不能為空。
if [ $age -ge 18 ] then
echo "成年人!"
else
echo “未成年!”
fi
多組if-elif-else-fi
if [ $age -lt 18 ] then
echo "未成年"
elif [ $age -ge 60 ] then
echo "老年人"
else
echo "青年和中年"
fi
每組的if后一定要有then。
循環(huán)
for循環(huán)
for var in `ls namefile`
do
echo $var
done
#for循環(huán)實(shí)例
for((i=1;i<=5;i++));do
echo "這是第 $i 次調(diào)用";
done;
#生成0到100的自然數(shù)。即0,1,2,3,……,100
for i in {0..100};do
echo "${i}.tar.gz"
done
while循環(huán)
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
無限循環(huán)
while true
do
ping www.baidu.com
done
case
echo "輸入1到4之間的數(shù)字"
read aNum
case $aNum in
1) echo "輸入的是1"
;;
2) echo "輸入的是2"
;;
*) echo "您輸入的不在范圍"
;;
esac
中斷或者跳出循環(huán)的方法
break終止后續(xù)循環(huán)
continue中止當(dāng)前循環(huán),后續(xù)循環(huán)繼續(xù)運(yùn)行。
內(nèi)部函數(shù)basename和dirname
basename用于獲取文件名
basename /softwares/test/test1.sh 返回值是test1.sh
basename -s .sh /softwares/test/test1.sh 返回值是test1
dirname用于獲取文件路徑
dirname /softwares/test/test1.sh 返回的值是路徑
上述2個(gè)函數(shù)可以用于獲取用戶輸入的文件名的前綴和路徑。
函數(shù)的定義
##提取指定染色體的bam文件
getchr(){
samtools view -b -h $1 $2 | samtools sort - > $3
}
#調(diào)用函數(shù)
getchr all.bam chr1 chr1.bam
函數(shù)里可以返回值
sum2(){
echo "success!"
return 0
}
一般情況下,return返回的只能是0-255的整數(shù),默認(rèn)和習(xí)慣,返回0是正常,1是錯(cuò)誤,用來判斷程序是否正常運(yùn)行。
使用$?可以獲取上一條命令運(yùn)行的結(jié)果,如果是0就正常。
if ! [ $? -eq 0 ] ;
then
echo "上一條命令有錯(cuò)誤。"
exit
fi
return用于返回狀態(tài)碼,只能是數(shù)字0-255
##定義檢測(cè)網(wǎng)絡(luò)連接是否正常
check_net(){
#檢測(cè)網(wǎng)絡(luò)
ping -c 3 -w 3 www.baidu.com >network_cache
#-c ping 3次 ,-w 3 ping 3s 后結(jié)束
#檢測(cè)ping的結(jié)果狀態(tài)碼
if [ $? -eq 0 ];then
echo "network connect success!"
echo "檢測(cè)網(wǎng)絡(luò),正常!"
return 0
#sleep 3m #休眠3min后再檢測(cè)
else
echo "network connect Wrong,Please check network!"
return 1
fi
}
#檢測(cè)網(wǎng)絡(luò)
check_net
#獲取檢測(cè)網(wǎng)絡(luò)的返回值(即check_net函數(shù)里的return的值)
if [ $? -eq 0 ];then
echo "網(wǎng)絡(luò)正常!" #此處可以放置,網(wǎng)絡(luò)正常需要運(yùn)行的命令腳本。
else
echo "網(wǎng)絡(luò)未連接!" #此處可以放置,網(wǎng)絡(luò)登錄的命令腳本。
fi
設(shè)置參數(shù)的默認(rèn)值
# 設(shè)置null或空值時(shí),默認(rèn)值是10
${number:=10}
#設(shè)置null或空值時(shí),報(bào)錯(cuò)信息。
${name:?Please input name}
#設(shè)置參數(shù)不為空時(shí)的默認(rèn)值
${userpasswd:+******}
read函數(shù)的使用
示例:用戶協(xié)議同意的交互設(shè)計(jì)
###用戶協(xié)議同意模塊
echo "用戶協(xié)議
1.僅限非商業(yè)使用(學(xué)術(shù)研究免費(fèi)使用)
2.轉(zhuǎn)載或二次開發(fā),請(qǐng)保留原作者的信息
3.商業(yè)用途請(qǐng)聯(lián)系原作者!"
##-n 1是限制只能輸入1個(gè)字符,-p "……"用于給用戶提示需要輸入的內(nèi)容
read -p "Please make sure to agree with the agreement (Y/N):" -n 1 answer
case $answer in
Y|y)
echo -e "\n Thank you!Install will continue……"
reset
;;
N|n)
echo -e "\n No agree!exit!"
exit
;;
*)
echo -e "\n Please make sure input Y or N"
exit
;;
esac
引入其他腳本
例如:本目錄下有commerge和getchr兩個(gè)腳本。
在getchr里可以使用source commerge或者source ./commerge,來引用腳本commerge.
獲取輸入?yún)?shù)的處理
#運(yùn)行腳本命令
getchr -i file1 -o file2 -s std
getchr腳本里獲取輸入?yún)?shù)的方法,
while [ -n "$1" ];do
case $1 in
-i|--input)
inputfile=$2
shift
;;
-o|--output)
output=$2
shift
;;
-s|--select)
select=$2
;;
esac
shift
done
使用shift作為左移工具,來獲取后面的參數(shù)和參數(shù)的值。
3.實(shí)戰(zhàn)1:編寫兩個(gè)文件合并的小教本
因時(shí)間倉促,寫了簡(jiǎn)單的腳本,以其能夠理解其原理和shell語法。腳本在github.下載后,直接運(yùn)行命令commerge -h即可。如果不能執(zhí)行,請(qǐng)?zhí)砑訄?zhí)行權(quán)限即可chmod 757 commer。
單行命令速查
shell算術(shù)運(yùn)算
num=`expr 12 - 6`
pi=`awk 'BEGIN{print 2*2-0.86 }'`
area=`awk -v pi=$pi -v num=$num 'BEGIN{print pi*num }'`
注意:整數(shù)型運(yùn)算使用expr而且后面的運(yùn)算和數(shù)字之間要有一個(gè)空格。浮點(diǎn)運(yùn)算需要借用awk來完成。awk使用shell的變量時(shí),需要使用-v來引入。
4.高級(jí)功能
使用臨時(shí)目錄和臨時(shí)文件,可以作為中轉(zhuǎn)目錄或文件。有些內(nèi)容需要輸出到文件,但是只是作為中間文件,最后需要?jiǎng)h除,就可以用臨時(shí)文件。
創(chuàng)建臨時(shí)文件
##生成臨時(shí)文件(示例:臨時(shí)文件的變量名稱是${tempfpkm})
trap 'rm -f "$tempfpkm"' EXIT #退出時(shí),刪除臨時(shí)文件(trap是根據(jù)后面的系統(tǒng)信號(hào),執(zhí)行前面的命令)
tempfpkm=$(mktemp -p ${PWD}) || exit 1 #在工作路徑,生成臨時(shí)文件,如果生成失敗,則退出。
創(chuàng)建臨時(shí)目錄
##生成臨時(shí)目錄(示例:臨時(shí)文件夾變量名稱是${fpkm})
trap 'rm -rf "$fpkm"' EXIT
fpkm=$(mktemp -d -p ${PWD}) || exit 1
mktemp參數(shù):
-p指定生成文件或文件夾的路徑
-d 指定生成的是文件夾,不使用-d生成的是文件。
tmp.XXXX 制定模板(注意:X必須是大寫,而且最少需要三個(gè)X,X有幾個(gè),輸出的隨機(jī)字符就有幾位)
mktemp -d -p ${PWD} zhansan.XXXX 在當(dāng)前目錄創(chuàng)建文件前綴為zhansan.的文件夾。此處輸出為 zhansan.pmJt 每個(gè)人運(yùn)行,后面的四位不一樣
mktemp -p ${PWD} temp.XXXXXX創(chuàng)建臨時(shí)文件,前綴是temp,后面是6位字符。
創(chuàng)建臨時(shí)文件夾,并在臨時(shí)文件夾里創(chuàng)建臨時(shí)文件
trap 'rm -rf "$zhangsan" ' EXIT
zhangsan=$(mktemp -d -p ${PWD}) || exit 1
temp=$(mktemp -p ${zhangsan} temp.XXXXXX)
trap是捕捉系統(tǒng)退出時(shí)執(zhí)行的命令
trap 'rm -rf "${zhangsan}";rm -rf "${temp}"' EXIT
當(dāng)有很多臨時(shí)文件需要清理時(shí):可以自定義清理函數(shù),然后trap執(zhí)行即可
#定義清理函數(shù)
function cleanup(){
rm -rf "$zhangsan"
rm -rf "$bam"
}
trap cleanup EXIT #trap信號(hào)調(diào)用清理函數(shù)
zhangsan=$(mktemp -d -p ${PWD}) || exit 1
temp=$(mktemp -p ${zhangsan} temp.XXXXXX) || exit 1
bam=$(mktemp -p ${PWD} bam.XXXX) || exit 1
上面生成的temp.XXXXXX 文件是在zhangsan.XXXX文件夾里的,通過清理zhangsan文件夾即可清理里面的文件。
bam.XXXX文件是在工作目錄,需要指定清理。
trap是系統(tǒng)信號(hào),所以只能執(zhí)行一次就會(huì)退出。如果有多個(gè)文件需要清理,就只能是使用上面的函數(shù)模式或者是使用;來連接多個(gè)清理命令。
如果在一個(gè)進(jìn)程中寫有多個(gè)trap函數(shù),只會(huì)執(zhí)行最后一個(gè)。
下面的示例:實(shí)際就執(zhí)行第二句,第一個(gè)文件$zhangsan不會(huì)被清理。
trap 'rm -rf "$zhangsan" ' EXIT
trap 'rm -rf "$bam" ' EXIT
獲取輸入文件的路徑和文件名
在無法確定用戶的輸入是絕對(duì)或相對(duì)路徑時(shí)使用
- 獲取文件的路徑(輸入的是絕對(duì)路徑,輸出也是絕對(duì)路徑,輸入是相對(duì)路徑,輸出也是相對(duì)路徑)
dirname ../genome.fa - 獲取文件的名稱(無論是絕對(duì)或相對(duì)路徑,輸出的都只是文件名)
basename ../genome.fa#輸出是genome.fa
獲取文件的前綴
basename ../genome.fa .fa#輸出是genome, - 獲取絕對(duì)路徑(如果是文件,返回是文件的絕對(duì)路徑,如果是路徑,返回是絕對(duì)路徑)
readlink -f ../genome.fa#輸出是/share/home/……/genome.fa
在腳本中使用,賦值給變量的時(shí)候,兩端一定要使用雙引號(hào)括住。
#獲取輸入文件的絕對(duì)路徑的文件
absol_genome="`readlink -f ../genome.fa`"
#獲取輸入文件的路徑
genome_path="`dirname ../genome.fa`"
#獲取輸入文件的絕對(duì)路徑
absol_path="`readlink -f ${genome_path}`"
# 修改文件的后綴
genome.gff="`basename ../genome.fa .fa`.gff"
使用${}來獲取文件路徑和文件,文件后綴
此部分轉(zhuǎn)載自https://www.cxyzjd.com/article/lifuxiangcaohui/50153207
file=/dir1/dir2/dir3/my.file.txt
我們可以用 ${ } 分別替換獲得不同的值:
${file#*/}:拿掉第一條/ 及其左邊的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:拿掉最后一條 / 及其左邊的字符串:my.file.txt
${file#*.}:拿掉第一個(gè) . 及其左邊的字符串:file.txt
${file##*.}:拿掉最后一個(gè) . 及其左邊的字符串:txt
${file%/*}:拿掉最后條 / 及其右邊的字符串:/dir1/dir2/dir3
${file%%/*}:拿掉第一條 / 及其右邊的字符串:(空值)
${file%.*}:拿掉最后一個(gè) . 及其右邊的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:拿掉第一個(gè) . 及其右邊的字符串:/dir1/dir2/dir3/my
記憶的方法為:
#是去掉左邊(在鍵盤上 # 在 之右邊)
單一符號(hào)是最小匹配﹔兩個(gè)符號(hào)是最大匹配。
${file:0:5}:提取最左邊的 5 個(gè)字節(jié):/dir1
${file:5:5}:提取第 5 個(gè)字節(jié)右邊的連續(xù) 5 個(gè)字節(jié):/dir2
我們也可以對(duì)變量值里的字符串作替換:
${file/dir/path}:將第一個(gè) dir 提換為 path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:將全部 dir 提換為 path:/path1/path2/path3/my
其他shell命令和多線程相關(guān) shell單行命令速查
殺死進(jìn)程
pkill --ns 332314 殺死pid與332314的進(jìn)程的程序名相同的所有程序 慎重使用,會(huì)殺掉一大批程序,適合批量提交后,需要?dú)⒌襞康娜蝿?wù)
kill -9 332314 殺掉pid進(jìn)程為332314的程序
bkill 332314 用于lsf系統(tǒng)的殺掉JOBID 是332314的任務(wù)。
ps uux|grep ascp|awk '{print $2}'|xargs -i kill -9 {} #批量殺掉包含ascp的任務(wù)
定時(shí)執(zhí)行程序
常規(guī)做法,寫個(gè)死循環(huán),定時(shí)執(zhí)行即可
#定時(shí)檢測(cè)執(zhí)行上傳數(shù)據(jù)到NCBI
runinfo="ascp -i $HOME/cmm.aspera.openssh -QT -l 30m -k1 -d $HOME/upload_to_NCBI subasp@upload.ncbi.nlm.nih.gov:uploads/wangjing_gmail.com_XXXXXXX"
while true;
do
#檢測(cè)網(wǎng)絡(luò)
ping -c 3 -w 3 www.baidu.com >network_cache
#-c ping 3次 ,-w 3 ping 3s 后結(jié)束
if [ $? -eq 0 ];then
echo "network connect success!"
echo "檢測(cè)網(wǎng)絡(luò),正常!"
aspera=`ps aux|grep ascp|grep -v grep|wc -l`
if [ "$aspera" -eq 0 ];then
`${runinfo}`
fi
sleep 3m #休眠3min后再檢測(cè)
else
echo "network connect Wrong,Please check network!"
fi
done
優(yōu)雅的定時(shí)使用crontab來控制
使用crontab -e建立一個(gè)新的定時(shí)控制
注意:腳本里調(diào)用其他程序,程序需要使用完整的路徑,同時(shí)也用使用完整路徑指定運(yùn)行腳本的位置。如果不指定位置,則可能找不到腳本調(diào)用的程序或者找不到腳本的位置。
*/10 * * * * bash /$HOME/keeprun.sh >/dev/null 2>&1 &
保存上面的即可每10分鐘,執(zhí)行一次命令bash keeprun.sh $HOME/.aspera/connect/bin/ascp -i $HOME/cmm.aspera.openssh -QT -l 30m -k1 -d $HOME/upload_to_NCBI subasp@upload.ncbi.nlm.nih.gov:uploads/wangjing_gmail.com_XXXXXXX
keeprun.sh內(nèi)容如下:
#!/usr/bin/bash
#檢測(cè)進(jìn)程是否在運(yùn)行,并將進(jìn)程的行數(shù)信息傳給count變量。
command="$HOME/.aspera/connect/bin/ascp -i $HOME/cmm.aspera.openssh -QT -l 30m -k1 -d $HOME/upload_to_NCBI subasp@upload.ncbi.nlm.nih.gov:uploads/wangjing_gmail.com_XXXXXXX"
file=`echo $command|rev|cut -d "/" -f1|cut -d " " -f2|rev`
count=`ps uux|grep $file|grep -v grep|grep -v keeprun|wc -l`
#如果count值大于0,則表示進(jìn)程不存在
if [ $count -eq 0 ]
then
#進(jìn)程不存在則運(yùn)行下面的命令,尾部加上&使其在后臺(tái)運(yùn)行
`${command} &`
fi
# Example of job definition:
# .---------------- minute (00 - 59)
# | .------------- hour (00 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed


查看指定用戶的指定任務(wù)
crontab -u zhangsan -l
查看當(dāng)前用戶的定時(shí)任務(wù)
crontab -l