很高兴和你相遇
这里正在记录我的所思所学
订阅免费邮件通讯接收最新内容
首页 归档 想法 通讯 播客 工具 简历 关于

shell 脚本应知应会

基本介绍

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<<EOF>>

#!/bin/bash
cat<<EOF>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

本文作者:思考问题的熊

版权声明:本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。

如果你对这篇文章感兴趣,欢迎通过邮箱或者微信订阅我的 「熊言熊语」会员通讯,我将第一时间与你分享肿瘤生物医药领域最新行业研究进展和我的所思所学所想点此链接即可进行免费订阅。


· 分享链接 https://kaopubear.top/blog/2017-10-22-shellscript1/