1. 视频笔记
1.1. 基本命令
- 创建脚本:
vi my.sh
- 运行脚本:
bash my.sh
1.2. 变量
- PATH、PS1等
1.3. 参数
- $0 脚本名
- $1/2/3... 跟着的第1/2/3...个参数
1.4. 通配符
- *表示任意
- [12]表示方括号里随意选
1.5. 标准头文件
- 专业人士一般会加
- 说明用法,所以如果给别人看的话需要写
1.6. 输入输出与捕获
-
ls blabla
尖括号的作用为捕获 - 重定向
1.7. 小技巧
- grep -vf (反选、文件中选)
- 软链接文件夹无法绕过权限,因此需要软链接文件夹里的所有文件。
ls 目标路径(往往要用*)|while read id; do ln -s $id $(basename $id);done
2. literal和meta
-
常用meta
-
IFS,CR
-
-
屏蔽meta:quoting
- hard quote:''(单引号),凡在 hard quote 中的所有 meta 均被关闭;
- soft quote:""(双引号),凡在 soft quote 中大部分 meta 都会被关闭,但某些会保留 (如 $);
- escape: \ (反斜杠),只有在紧接在 escape(跳脱字符) 之后的单一 meta 才被关闭;
-
enter键产生的字符可能是
- CR
- IFS
- NL(New Line)
- FF(Form Feed)
- NULL
- ...
双引号与单引号的区别中awk相关的部分暂略。
3. 改变meta
IFS=;
- 几个例子
$ A=" abc"
$ echo $A
abc
$ echo "$A" #note1
abc
$ old_IFS=$IFS
$ IFS=; #将IFS设置为null
$ echo $A
abc
$ IFS=$old_IFS
$ echo $A
abc
$ a=";;;test"
$ IFS=";"
$ echo $a
test
$ a=" test"
$ echo $a
test
$ IFS=" "
$ echo $a
test
$ old_IFS=$IFS
$ read A
;a;b;c
$ echo $A
;a;b;c
$ IFS=";" #Note2
$ echo $A
a b c
4. read获取文件的问题
- 如果行的起始部分有 IFS 之类的字符,将被忽略;
- echo $i的解析过程中,首先将 $i 替换为字符串, 然后对 echo 字符串中字符串分词,然后命令重组,输出结果; 在分词,与命令重组时,可能导致多个相邻的 IFS 转化为一个;
- 解决方案:
old_IFS=$IFS
IFS=; #将IFS设置为null
cat file | while read i
do
echo "$i"
done
IFS=old_IFS #恢复IFS的原始值
或使用od,hexdump
5. 变量
5.1. 设定变量的规则
- 变量名首字符不能是数字
- 区分大小写
5.2. 变量替换
- 成为命令
$ A=ls
$ B=la
$ C=/tmp
$ $A -$B $C
- 变量扩充
A=B:C:D
A=$A:E
注意要有分隔符:,要不然就`A=${A}E`
5.3. export
$ A=B
$ B=C
$ export $A #注意被export的是B这个变量
5.4. 取消变量
unset A
- 同样要注意变量替换
- 注意unset和null的区别(用var=${name=value}能否成功赋值的区别)
6. 进程
- 我们所执行的任何程序,都是父进程 (parent process) 产生的一个子进程 (child process), 子进程在结束后,将返回到父进程去。此现象在 Linux 中被称为fork。
- 环境变量只能从父进程到子进程单项传递,因此在子进程中改变环境变量(如工作环境$pwd)不会影响父进程中的环境变量。
- 当我们执行一个 shell script 时,其实是先产生一个 sub-shell 的子进程, 然后 sub-shell 再去产生命令行的子进程。
- 所以要用source来执行脚本,如
$ source ./my_script.sh
,source就是让脚本在当前shell内执行而不是产生一个sub-shell来执行。 - exec也是让脚本在同一个进程上执行,但是原有进程被结束了。暂略
7. ()与{}
- ()将command group置于sub-shell(子shell) 中去执行,也称 nested sub-shell。
- {}则是在同一个shell内完成,也称non-named command group。
- 可以定义function
function function_name {
command1
command2
command3
.....
}
function_name () {
command1
command2
command3
......
}
- 通过source命令, 我们可以自行定义许许多多好用的 function,再集中写在特定文件中, 然后,在其他的 script 中用source将它们载入,并反复执行。
8. $()
- “捕获”,和` `很大程度上等价
- 命令替换
command1 $(commmand2 $(command3))
- 界定变量名称
- 剪切字符串
- 定义一个变量
file=/dir1/dir2/dir3/my.file.txt
- 非贪婪左删除
${file#*/} #其值为:dir1/dir2/dir3/my.file.txt
;${file#*.} #其值为:file.txt
- 贪婪左删除
${file##*/} #其值为:my.file.txt
- 非贪婪右删除
${file%/*} #其值为:/dir1/dir2/dir3
- 贪婪右删除
${file%%/*} #其值为:其值为空。
- 定义一个变量
- 取字符串:
${s:pos:length}
, 取字符串 s 从 pos 位置开始(含)的长度为 length 的子串 - 字符串变量值的替换
- 首次替换:
${s/src_pattern/dst_pattern}
将字符串s中的第一个src_pattern替换为dst_pattern - 全部替换:
${s//src_pattern/dst_pattern}
将字符串s中的所有出现的src_pattern替换为dst_pattern
- 首次替换:
- 针对变量的不同状态(未设定、空值、非空值)进行赋值
- 一般地,不带:,null值不受影响;带:,null值也受影响(:+除外)
-
${file-my.file.txt}
#如果file没有设定,则使用my.file.txt作为返回值,空值及非空值时,不作处理,返回${file}
; -
${file:-my.file.txt}
#如果file没有设定或者file为空值,使用my.file.txt作为其返回值,为非空值时,不作处理,返回${file}
; -
${file+my.file.txt}
#如果file已设定(为空值或非空值), 则使用my.file.txt作为其返回值,未设定时,不作处理; -
${file:+my.file.txt}
#如果${file}为非空值, 则使用my.file.txt作为其返回值,未设定或者为空值时,不作处理。 -
${file=my.file.txt}
#如果file未设定,则将file赋值为my.file.txt,同时将file作为其返回值,file已设定(为空值或非空值),则返回${file}
。 -
${file:=my.file.txt}
#如果file未设定或者为空值,则my.file.txt作为其返回值,同时,将file赋值为my.file.txt,为非空值时不作处理。 -
${file?my.file.txt}
#如果file未设定,则将my.file.txt输出至STDERR, 若已设定(空值与非空值时),不作处理。 -
${file:?my.file.txt}
#若果file未设定或者为空值,则将my.file.txt输出至STDERR,否则, 非空值时,不作任何处理。
- 计算shell字符串变量的长度:
${#var}
- bash数组的处理方法(暂略)
9. $(())
- 用来做整数运算
$ a=5; b=7; c=2;
$ echo $(( a + b * c ))
19
$ echo $(( (a + b)/c ))
6
$ echo $(( (a * b) % c ))
1
10. positional parameter
- $0,$1,$2...
- ${10}
- $#看参数数量,因此用[$#=0]可以判断脚本有没有读入参数
11. @与*
my_fun() {
echo "$#"
}
echo 'the number of parameter in "$@" is ' $(my_fun "$@")
echo 'the number of parameter in "$*" is ' $(my_fun "$*")
- "$@" 则可得到 "p1" "p2 p3" "p4" 这三个不同字段 (word);
- "$*" 则可得到 "p1 p2 p3 p4" 这一整个单一的字段。
12. RV
- shell 下跑的每一个 command 或 function, 在结束的时候都会传回父进程一个值,称为 return value。
- 变量$?中储存了最新的返回值
- 进程的退出状态有两种
- 0值为真
- 非0值为假
- &&与||
- command1 && command2 # command2 只有在 command1 的 RV 为 0(true) 的条件下执行。
- command1 || command2 # command2 只有在 command1 的 RV 为非 0(false) 的条件下执行。
13. test
- test expression 或 [ expression ]
- 支持的测试对象
- string:字符串,也就是纯文字
- integer:整数
- file:文件
- 以 A=123 这个变量为例:
-
[ "$A" = 123 ]
#是字符串测试,测试 $A 是不是 1、2、3 这三个字符。 -
[ "$A" -eq 123 ]
#是整数测试,以测试 $A 是否等于 123。 -
[-e "$A" ]
#文件测试,测试 123 这份文件是否存在。
-
- test也允许多重复合测试:
-
expression1 -a expression2
当两个 expression 都为 true,返回 0,否则,返回非 0; -
expression1 -o expression2
当两个 expression 均为 false 时,返回非 0,否则,返回 0; -
[ -d "$file" -a -x "$file" ]
表示当 $file 是一个目录,且同时具有 x 权限时,test 才会为 true。
-
- 在 command line 中使用test时,请别忘记命令行的 “重组” 特性, 也就是在碰到 meta 时,会先处理 meta,在重新组建命令行。若在test中碰到变量替换,用soft quote是最保险的。
14. >与<
-
File Descriptor(fd)
- 0:standard Input (STDIN)
- 1: standard output (STDOUT)
- 2: standard Error output (STDERR)
-
I/O redirection
- < 输入重定向
- > 输出重定向
- 1>
- 2>
- 需要将两个信息输进同一个文件时:1>&2或2>&或&>
- /dev/null
- >>不覆盖,而是追加
- set -o noclobber限制覆盖,改为+复原
- cat < file > file清空原有文件(输出优先于输入),若无该文件,创建新的空文件,其实>file就可以了
又要管道又要重定向时,用tee代替>:
cmd1 | cmd2 | tee file | cmd3
file I/O 是最常见的效能杀手,要尽量降低频度。
15. if与case
if cmd1
then
cmd2
cmd3
else
cmd4
cmd5
fi
在if的判断式中,else部分可以不用,但then是必需的,若then后不想跑任何command,可用:这个null command代替。当然,then或else后面,也可以再使用更进一层的条件判断式, 这在shell script的设计上很常见。 若有多项条件需要"依序"进行判断的话, 那我们则可使用elif这样的keyword:
if cmd1; then
cmd2;
elif cmd3; then
cmd4
else
cmd5
fi
意思是说:若cmd1为true,然则执行cmd2; 否则在测试cmd3,若为true则执行cmd4; 倘若cmd1与cmd3均不成立,那就执行cmd5。
/etc/init.d/*中的那堆script中的case用法中的一例
case "$1" in
start)
start
;;
stop)
stop
;;
status)
rhstatus
;;
restart|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/syslog ] && restart || :
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart}"
exit 1
esac
16. 循环
16.1. for loop
for var in one two three four five
do
echo -----------------
echo '$var is '$var
echo
done
- 利用$(),for的范围可以有很多种
- `ls data/*.txt`
- `cat file`
16.2. while
num=1
while [ "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
while loop的原理与for loop稍有不同: 它不是逐次处理清单中的变量值, 而是取决于while 后面的命令行的return value
16.3. until
num=1
until [ ! "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
16.4. break & continue
- 常用于复合式循环
- break是结束loop(return是结束function,exit是结束script/shell)
- continue:在continue在done之间的句子略过而返回到循环的顶端
- break和continue后面可以跟一个数值,表示中断/进入从里向外的第n个循环
17. wildcard与RE暂略
最后,向大家隆重推荐生信技能树的一系列干货!
- 生信技能树全球公益巡讲:https://mp.weixin.qq.com/s/E9ykuIbc-2Ja9HOY0bn_6g
- B站公益74小时生信工程师教学视频合辑https://mp.weixin.qq.com/s/IyFK7l_WBAiUgqQi8O7Hxw
- 招学徒https://mp.weixin.qq.com/s/KgbilzXnFjbKKunuw7NVfw