基本介绍
shell脚本通常是一个以shebang起始的文本文件
#!/bin/bash
其中#!
位于解释器路径之前。/bin/bash
是Bash的解释器命令路径。
还有一种常见的写法是#!/bin/bash -ex
这里的-e
类似于在第二行写set -e
其意义是Exit immediately if a command exits with a non-zero status.
;而-x
的意思是Print commands and their arguments as they are executed.
终端打印
echo
echo 加或者不加单双引号都可以打印echo 后面的内容,默认情况下echo在每次调用后会添加一个换行符。
不加双引号的问题是不能显示echo后的;
,单引号中变量替换无效。
echo同样接受双引号字符串内的转义序列作为参数。如果需要使用转义序列,则采用echo –e "包含转义序列的字符串"这种形式。
$ name=dou; echo -e "my name is\t$name"
my name is dou
有的时候编写脚本需要在不同的命令之间输出一些信息给用户进行提示,这个时候如果能输出不一样的颜色或者背景会比较醒目。打印彩色输出可以使用如下方式。
下面是一些常用颜色的对应码
- 字体 重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37
- 背景 重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47
打印彩色字体 echo -e "\e[1;32m This is green text \e[0m"
打印彩色背景 echo -e "\e[1;42m This is green text \e[0m"
printf
我们可以在printf中使用格式化字符串,还可以指定字符串的宽度、左右对齐方式等。在默认情况下,printf并不像echo命令一样会自动添加换行符,我们必须在需要的时候手动添加。
举例如下
$ printf "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456
1 Sarath 80.35
%s、%c、%d和%f都是格式替换符,%-5s
的含义是左对齐且宽度为5,%-4.2f\n
的含义是浮点数且宽度为5保留两位小数。
变量
var=value
var是变量名,value是赋给变量的值。如果value不包含任何空白字符(例如空格),那么它就不需要使用引号进行引用,否则必须使用单引号或双引号。在变量名之前加上$前缀就可以打印出变量的内容。以在printf或echo命令的双引号中引用变量值。
export
命令用来设置环境变量。至此之后,从当前shell脚本执行的任何应用程序都会继承这个变量。我们可以按照自己的需要,在执行的应用程序或者shell脚本中导出特定的变量。
添加环境变量
日常,安装各种软件是最常见的事情。当你必须使用源代码编译生成程序并将其安装到某个特定路径中时,有项极其常见的任务就是将该程序的bin目录加入PATH环境变量。
我们可以在自己home目录下的.bashrc
文件添加一些路径,以指定二进制文件或者库文件,让每次shell启动时执行,例如:
PATH="$PATH:/home/user/bin"
export PATH
#### 或者
export PATH=/opt/myapp/bin:$PATH
export LD_LIBRARY_PATH=/opt/myapp/lib;$LD_LIBRARY_PATH
如果只是在当前shell 执行,可以写一个函数放在bashrc
中
addpath() { [ -d "$2" ] && eval $1=\"$2\$\{$1:+':'\$$1\}\" && export $1 ; }
使用addpath PATH /add/path
可以快速把一个路径添加到当前shell中
数学运算
shell 不像R可以天然支持各种数学计算,但是可以利用let、(( ))和[]执行基本的算术操作。而在进行高级操作时,
expr和bc这两个工具也会非常有用。
let命令可以直接执行基本的算术操作。当使用let时,变量名之前不需要再添加$。
#!/bin/bash
no1=4;
no2=5;
let result=no1+no2
echo $result
#自加
let no1++
let no+=6
# []
result=$[ no1 + no2 ]
bc
可以进行浮点数运算的高级函数,通过scale
设定小数精度
no=54;
result=`echo "$no * 1.5" | bc`
echo $result
$ echo "scale=4;3/8" | bc
.3750
echo "sqrt(100)" | bc #Square root
echo "10^10" | bc #Square
文件描述符及重定向
在编写脚本的时候会频繁使用标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。通过内容过滤将输出重定向到文件是我们平日里的基本任务之一。当命令输出文本时,这些输出文本有可能是错误信息,也可能是正常的(非错误的)输出信息。单靠查看输出的文本本身,我们没法区分哪些是正常,哪些是错误。
这个时候可以通过文件描述符来解决问题。
- 0 标准输入
- 1 标准输出
- 2 标准错误
当一个命令发生错误并退回时,它会返回一个非0的退出状态;而当命令成功完成后,它会返回数字0。退出状态可以从特殊变量$?
中获得(在命令执行之后立刻运行echo $?
,就可以打印出退出状态)
分别重定向输出和错误可以使用cmd 2>stderr.txt 1>stdout.txt
;重定向到一个文件可以使用cmd 2>&1 output.txt
不查看输出内容或者错误,可以重定向到“垃圾桶”/dev/null
中。
tee 可以在显示输出的时候,将输出内容重定向到一个文件,但是需要注意的是,错误信息是不会保存到重定向的文件中的,tee 只能接受stdout。如果像追加操作而非覆盖的话,需要使用 tee -a
参数。
将脚本内部的文本块进行重定向
有时候需要在脚本中输出大量内容,可以使用cat<
#!/bin/bash
cat<log.txt
LOG FILE HEADER
This is a test log file
Function: System statistics
EOF
# 两个EOF之间的内容会写进log.txt
# 如果不指定文件,会直接打印到屏幕
数组
数组是shell脚本非常重要的组成部分,它借助索引将多个独立的数据存储为一个集合。普通数组只能使用整数作为数组索引,关联数组可以使用字符串作为索引。
定义数组array_var=(1 2 3 4 5 6)
调用某个数组元素echo ${array_var[0]}
调用所有元素 echo ${array_var[*]}
调用数组长度 echo ${ #array_var[*]}
调用数组索引 echo ${!sample_names[*]}
获取、设置日期和延时
用格式串结合 + 作为date命令的参数,可以按照你的选择打印出对应格式的日期。
$ date "+%d %B %Y"
21 October 2017
检查命令花费的时间,可以把开始和结束时间嵌入写好的脚本头和尾
#!/bin/bash
start=$(date +%s)
commands;
statements;
end=$(date +%s)
difference=$(echo "scale=2;($end-$start)/60" | bc)
echo Time taken to execute scripts is $difference mins
脚本中生成延时
使用sleep 函数,sleep 1m
延迟1分钟
shell 脚本调试
shell 的调试相对简单和单调。可以使用set -x和set +x对脚本进行部分调试。
对于想要输出的命令的区域,可以限定在set -x 和set +x 之前
函数与参数
定义函数:
# 定义函数
function fname()
{
statements;
}
# 使用函数
fname arg1 arg2
例如
fname()
{
echo $1, $2; #访问参数1和参数2
echo "$@";#以列表的方式一次性打印所有参数
echo "$*"; #类似于$@,但是参数被作为单个实体
return 0; #返回值
}
退出状态
查看命令是否成功执行,如果成功退出则推出状态为0。
#!/bin/bash
fastqc input.bam
if [ $? -eq 0 ];
then
echo "fastqc executed successfully"
else
echo "fastqc terminated unsuccessfully"
fi
输出变输入
使用管道(pipe)连接每个过滤器 cmd1|cmd2|cmd3
字shell 和反引用
# 子shell
ouptup=$(commands)
# 反引用
output=`commands`
读取键盘输入
如果用户不知道你写的脚本怎么用,那么我们可以提示用户在直接运行脚本后进行参数的输入。
在shell 中,我们可以使用read 直接读取键盘输入。
# 让用户输入内容
read -p "input fastq file name: " inputfile
fastqc $inputfile
read -p "are you sure to continue(y/n): " judge
if [ "$judge" == "y" ]
then
echo ok coutine
else
exit 0
fi
重复命令直到成功
有些命令比如(下载),可能需要重复执行指导成功,可以使用whil来构造函数进行判断
repeat() { while :; do $@ && return; sleep 30; done }
# : 是shll内建命令每次会返回退出码0
# $@ 表示输入的所有命令和参数
### 增加延时尝试
waitrepeat() { while :; do $@ && return; sleep 30s; done }
字段分隔符和迭代器
IFS shell 中内置的字段分隔符,IFS的默认值为:空白(包括:空格,tab, 和新行),当文件中的分隔符是逗号或者其他是就需要使用到IFS
oldIFS=$IFS
IFS=,
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
使用循环进行迭代
# for
for var in list
do
commands #使用变量$var
done
for((i=0;i<10;i++))
{
commands; #使用变量$i
}
# while
while condition
do
commands;
done
# until 直到条件为真时执行
x=0;
until [ $x -eq 9 ];
do
let x++; echo $x;
done
比较测试
我们可以用if、if else以及逻辑运算符进行测试,用比较运算符来比较数据项。除此之外,还有一个test命令也可以用于测试。
if判断
# if条件
if condition;
then
commands;
fi
# else if和else
if condition;
then
commands;
else if condition; then
commands;
else
commands;
fi
算数比较
条件通常被放置在封闭的中括号内,且一定要注意在[或]与操作数之间有一个空格。
[ $var -eq 0 ] #当 $var 等于 0 时,返回真
[ $var -ne 0 ] #当 $var 为非 0 时,返回真
# 其他
-gt:大于
-lt:小于
-ge:大于等于
-le:小于等于
文件系统
[ -f $file_var ]:如果给定的变量包含正常的文件路径或文件名,则返回真。
[ -x $var ]:如果给定的变量包含的文件可执行,则返回真。
[ -d $var ]:如果给定的变量包含的是目录,则返回真。
[ -e $var ]:如果给定的变量包含的文件存在,则返回真。
[ -c $var ]:如果给定的变量包含的是一个字符设备文件的路径,则返回真。
[ -b $var ]:如果给定的变量包含的是一个块设备文件的路径,则返回真。
[ -w $var ]:如果给定的变量包含的文件可写,则返回真。
[ -r $var ]:如果给定的变量包含的文件可读,则返回真。
[ -L $var ]:如果给定的变量包含的是一个符号链接,则返回真。
字符串
使用字符串比较时,最好使用双中括号。
[[ $str1 == $str2 ]]:当str1等于str2时,返回真。
[[ $str1 != $str2 ]]:如果str1和str2不相同,则返回真
[[ -z $str1 ]]:如果str1包含的是空字符串,则返回真。
[[ -n $str1 ]]:如果str1包含的是非空字符串,则返回真。
逻辑运算符
&& 逻辑与 || 逻辑或
if [[ -n $str1 ]] && [[ -z $str2 ]] ;
then
commands;
fi
test
如果不想写括号,可以使用test
if test $var -eq 0 ;
then
commands;
fi