Shell十三问 学习笔记
文本处理
less -SN # -N显示行号,-S整齐,变为一行
grep -w #精准匹配
grep '^#' #匹配以#开头的行。正则表达式
grep -v #排除匹配到的
alias del=rm #给rm起个别名del,del同样发挥rm的作用
tr ';' '\t' # tr命令,把分号换成\t
sed 's/;/\t/g' #与上等效
cut -f1 gtf | sort | uniq -c | sort -k1,1 -r # uniq去重复的行,并计数。去重后再sort排序,按第一列的数值进行由大到小的排序,去除r参数,由小到大
history | awk '{print $2}' | sort | uniq -c | sort -n -k1 # sort -n 按照数值的大小排序,按照第一列排序 -k1。
awk '{print $1}' tmp | paste -s -d + |bc #bc算术表达式,tmp的第一列为几行数值,awk把第一列抽取出来,paste -s把第一列的数值变为一行,-d设置以+分割,最后变为1+2+3的形式,再通过bc求和。需要以*分割的话,设置-d '*'
awk '{if($3=="gene")print}' gtf | wc
Shell脚本编程
cat /proc/cpuinfo | grep processor #查看服务器有多少个线程 / 核
free -g #查看服务器的内存
df -h #查看服务器的硬盘 / 存储
for i in {20..23};do echo SRR10395${i}_1_val_1.fq.gz ;done #注意i的大括号,因为后面接了字符
for bam in *bam;do echo ${bam%%.*} ; done # bam文件格式为SRR1039510.bowtie.bam,一个%.*删除了后一个.后的内容,两个%即%%.*输出的为SRR1039510
samtools idxstats SRR1039508.hisat.bam | awk -F '\t' '{s+=$3}END{print s}' # s+=$3,把以\t分隔开后的第3列的数字加起来求和并输出
export total_reads = $(samtools idxstats $bam | awk -F '\t' '{s+=$3}END{print s}' ) #赋值给total_reads。export后,可以对子
shell同样起作用
Shell 十三问 学习笔记
shell and Carriage 关系
Shell会依据IFS(Internal Field Seperator)
将 command line
所输入的文字给拆解为字段(word)
. 然后在针对特殊的字符(meta)先做处理,最后在重组整行 command line
。
分隔符号IFS
是用来拆解command line
中每一个词 (word)
用的, 因为shell command line是按词来处理的。 而CR(Carriage Return, 由Enter键产生)
则是用来结束command line
用的,这也是为何我们敲Enter
键, 命令就会跑的原因。
""(双引号) 与''(单引号) 差在哪?
hard quote:''(单引号),凡在 hard quote 中的所有 meta 均被关闭;
soft quote:""(双引号),凡在 soft quote 中大部分 meta 都会被关闭,但某些会保留 (如 $);
escape: \ (反斜杠),只有在紧接在 escape(跳脱字符) 之后的单一 meta 才被关闭;
$ A=`B > C > ' $ echo "$A" B C
$ A="B > C > " $ echo $A B C
$ A=B\ > C\ > $ echo $A BC
soft quote 跟 hard quote 的不同,主要是对于某些 meta 的关闭与否,以 $ 来做说明:
$ A=B\ C
$ echo "$A"
B C
$ echo '$A'
$A
awk '{print $0}'
上面的 hard quote 应好理解,就是将原来的 {$}
这几个 shell meta
关闭, 避免掉在 shell 中遭到处理,而完整的成为 awk 的参数中 command meta。
awk "{print \$0}" 1.txt
awk \{print \$0\} 1.txt #与上是等效的,注意`\`关闭了$ 字符
var=value 在 export 前后的差在哪?
所谓的变量,就是利用一个固定的"名称"(name), 来存取一段可以变化的"值"(value)。
设定变量时:
- 等号左右两边不能使用分隔符号(IFS)
name=value
,也应避免使用shell的保留元字符(meta charactor); - 变量的名称(name)的首字符不能是数字(number);
- 变量的名称(name)不能使用$符号。
shell 之所以强大,其中的一个因素是它可以在命令行中对变量作 替换(substitution)处理。 在命令行中使用者可以使用$符号加上变量名称(除了用=定义变量名称之外), 将变量值给替换出来,然后再重新组建命令行。
A=BCD
A=${A}E
上例中,我们使用{}将变量名称范围给明确定义出来, 如此一来, 我们就可以将A的变量值从BCD给扩充为BCDE。
严格来说,我们在当前shell中所定义的变量,均属于 "本地变量"(local variable), 只有经过export命令的 "输出"处理,才能成为"环境变量"(environment variable):
$ A=B
$ export A
或者$ export A=B
经过export输出处理之后,变量A就能成为一个环境变量 供其后的命令使用。
exec 跟 source 差在哪?
了解一下进程 (process) 的概念
首先,我们所执行的任何程序,都是父进程 (parent process) 产生的一个 子进程 (child process), 子进程在结束后,将返回到父进程去。 此现象在 Linux 中被称为fork。当子进程被产生的时候,将会从父进程那里获得一定的资源分配、及 (更重要的是) 继承父进程的环境。
所谓环境变量其实就是那些会传给子进程的变量。 简单而言, "遗传性" 就是区分本地变量与环境变量的决定性指标。 然而,从遗传的角度来看,我们不难发现环境变量的另一个重要特征: 环境变量只能从父进程到子进程单向传递 换句话说:在子进程中环境如何变更,均不会影响父进程的环境。
所谓 shell script 讲起来很简单,就是将你平时在 shell prompt 输入的多行 command line, 依序输入到一个文件文件而已。
再结合以上两个概念 (process + script),那应该不难理解如下的这句话的意思了: 正常来说,当我们执行一个 shell script 时,其实是先产生一个 sub-shell 的子进程, 然后 sub-shell 再去产生命令行的子进程。
cd/etc/aa/bb/cc
可以执行 但是把这条命令放入 shell 脚本后,shell 脚本不执行!脚本运行完后并没有换目录 这是什么原因?
答案:因为,我们一般跑的 shell script 是用 sub-shell 去执行的。 从 process 的概念来看,是 parent process 产生一个 child process 去执行, 当 child 结束后,返回 parent, 但 parent 的环境是不会因 child 的改变而改变的。 所谓的环境变量元数很多,如 effective id(euid),variable, working dir 等等... 其中的 working dir(PWD)正是楼主的疑问所在:当用sub−shell来跑script的话,sub−shell的
pwd 会因为 cd 而变更, 但返回 primary shell 时,$PWD 是不会变更的。
所谓source
命令,就是让 script 在当前 shell 内执行、 而不是产生一个 sub-shell 来执行。 由于所有执行结果均在当前 shell 内执行、而不是产生一个 sub-shell 来执行。
$ source ./my_script.sh
() 与 {} 差在哪?
"命令群组"(command group) 的概念:将许多命令集中处理。
在 shell command line中,()与 {}这两个符号都可以将多个命令当作群组处理, 技术细节上的差异:
()
将command group
置于sub-shell(子shell) 中去执行,也称 nested sub-shell。
{}
则是在同一个shell内完成,也称non-named command group。
要是在 command group
中扯上变量及其他环境的修改, 我们可以根据不同的需求来使用()
或{}
。 通常而言, 若所作的修改是临时的,且不想影响原有或以后的设定, 那我们就使用()
; 反之,则用{}
。
(())与()还有${}差在哪?
$()
与(反引号)都可以用来做命令替换(command substitution)的,完成
或者$()
里面的 命令,将其结果替换出来,再重组命令行。
我更喜欢用$()
先将command3替换出来给command2处理, 然后再将command2的处理结果,给command1来处理。
command1 $(commmand2 $(command3))
command1 `command2 \`command3\` ` #中间用 \ 转义 `
不过,()
并不是没有弊端的...首先,``基本上可用在所有的unixshell中使用,若写成shellscript,其移植性比较高。而()
并不是每一种shell都能使用,我只能说, 若你用bash2的话,肯定没问题... _
#是去掉左边(在键盘上#在$的左边)
%是去掉右边(在键盘上%在$的右边)
单个符号是最小匹配
两个符号是最大匹配
定义一个变量file为:
file=/dir1/dir2/dir3/my.file.txt
我们可以用${}分别替换获得不同的值:
${file#*/} #拿掉第一个/及其左边的字符串,其值为:dir1/dir2/dir3/my.file.txt
${file#*.} #拿掉第一个.及其左边的字符串,其值为:file.txt
${file##*/} #其值为:my.file.txt # 拿掉最后一个/及其左边的字符串,其结果为: my.file.txt
${file##*.} #其值为:txt # 拿掉最后一个.及其左边的字符串,其结果为: txt
${file%/*} #其值为:/dir1/dir2/dir3 # 拿掉最后一个/及其右边的字符串,其结果为: /dir1/dir2/dir3。
${file%.*} #其值为:/dir1/dir2/dir3/my.file # 拿掉最后一个.及其右边的字符串,其结果为: /dir1/dir2/dir3/my.file。
${file%%/*} #其值为:其值为空。 # 拿掉第一个/及其右边的字符串,其结果为: 空串。
${file%%.*} #其值为:/dir1/dir2/dir3/my。# 拿掉第一个.及其右边的字符串,其结果为: /dir1/dir2/dir3/my。
shell 字符串取子串
${file:0:5} #提取最左边的5个字符:/dir1
${file:5:5} #提取第5个字符及其右边的5个字符:/dir2
shell 字符串取子串的格式:${s:pos:length}, 取字符串 s 的子串:从 pos 位置开始的字符(包括该字符)的长度为 length 的的子串; 其中pos为子串的首字符,在 s 中位置; length为子串的长度。
shell 字符串变量值的替换
${file/dir/path} #将第一个dir替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path} #将全部的dir替换为path:/path1/path2/path3/my.file.txt
shell 字符串变量值的替换格式:
- 首次替换: ${s/src_pattern/dst_pattern} 将字符串s中的第一个src_pattern替换为dst_pattern。
- 全部替换: ${s//src_pattern/dst_pattern} 将字符串s中的所有出现的src_pattern替换为dst_pattern.
计算 shell 字符串变量的长度:${#var}
${#file} #其值为27, 因为/dir1/dir2/dir3/my.file.txt刚好为27个字符
数组(array)的处理方法
一般而言, A="a b c def" 这样的变量只是将
$A
替换为一个字符串, 但是改为 A=(a b c def), 则是将$A定义为数组
${A[@]} #方法一
${A[*]} #方法二
以上两种方法均可以得到:a b c def, 即数组的全部元素。
访问数组的成员
${A[0]}
得到数组A的第一个元素
数组的 length
${#A[@]} #方法一,前面加个#
${#A[*]} #方法二
数组元素的重新赋值
A[3]=xyz #将数组A的第四个元素重新定义为 xyz
$(())是用来作整数运算的
在bash中, $(())的整数运算符号大致有这些:
- +- * / #分别为"加、减、乘、除"。
- % #余数运算,(模数运算)
- & | ^ ! #分别为"AND、OR、XOR、NOT"运算。
$ a=5; b=7; c=2;
$ echo $(( a + b * c ))
19
$ echo $(( (a + b)/c ))
6
$ echo $(( (a * b) % c ))
1
&& 与 || 差在哪?
&&
与 ||
都是用来 "组建" 多个 command line 用的;
command1 && command2
# command2 只有在 command1 的 RV 为 0(true) 的条件下执行。
command1 || command2
# command2 只有在 command1 的 RV 为非 0(false) 的条件下执行。
[ -n string ]是测试 string 长度大于 0, 则为 true。
当 $A 被赋值时,在看看其是否小于 100,否则输出 too big!
$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
若取消 A,照理说,应该不会输出文字啊,(因为第一个条件不成立)。
$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
但还是可以得到上面的结果, 解决方法如下
方法1 sub-shell
$ unset A
$ [ -n "$A" ] && ( [ "$A" -lt 100 ] || echo 'too big!' ) #利用 ()
方法2 command group
$ unset A
$ [ -n "$A" ] && { [ "$A" -lt 100 ] || echo 'too big!'} #利用 {}
你要 if 还是 case 呢?
cmd1 && {
cmd2
cmd3
;
} || {
cmd4
cmd5
}
若 cmd1的return value为true的话, 然后执行cmd2与cmd3, 否则执行cmd4与cmd5.
与上等效
if cmd1
then
cmd2
cmd3
else
cmd4
cmd5
fi
只要if后面的command line返回true的return value (我们常用test命令返回的return value), 然则就执行then后面的命令,否则,执行else之后的命令, fi则是用来结束判断式的keyword。
if cmd1; then
cmd2;
elif cmd3; then
cmd4
else
cmd5
fi
若cmd1为true,然则执行cmd2; 否则在测试cmd3,若为true则执行cmd4; 倘若cmd1与cmd3均不成立,那就执行cmd5
for what? while 与 until 差在哪?
for loop
for loop
是从一个清单列表中读进变量的值, 并依次的循环执行do到done之间的命令行。 例:
for var in one two three four five
do
echo -----------------
echo '$var is '$var
echo
done
上例的执行结果将会是:
- for会定义一个叫var的变量,其值依次是one two three four five。
- 因为有5个变量值,因此,do与done之间的命令行会被循环执行5次。
- 每次循环均用echo产生3个句子。而第二行中不在hard quote之内的$var会被替换。
- 当最后一个变量值处理完毕,循环结束。
在for loop中,变量值的多寡,决定循环的次数
对于一些“累计变化”的项目(整数的加减),for 也能处理:
for ((i = 1; i <= 10; i++))
do
echo "num is $i"
done
while loop
除了for loop, 上面的例子, 我们也可改用while loop来做到:
num=1
while [ "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
分析上例:
- 在while之前,定义变量num=1.
- 然后测试(test)$num是否小于或等于10.
- 结果为true,于是执行echo并将num的值加1.
- 再作第二轮测试,此时num的值为1+1=2,依然小于或等于10,因此,为true,循环继续。
- 直到num为10+1=11时,测试才会失败...于是结束循环。
while loop
的原理与for loop
稍有不同: 它不是逐次处理清单中的变量值, 而是取决于while 后面的命令行的return value:
- 若为true, 则执行do与done之间的命令, 然后重新判断while后的return value。
- 若为false,则不再执行do与done之间的命令而结束循环。
until loop
until loop
与while相反, until是在return value 为false时进入循环,否则,结束。 因此,前面的例子也可以轻松的用until来写:
num=1
until [ ! "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
或者
num=1
until [ "$num" -gt 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
if [ 1 -ne 1 ];then
...
fi
-eq:等于
-ne:不等于
-le:小于等于
-ge:大于等于
-lt:小于
-gt:大于
shell loop 中的 break 与 continue
break
用来中断循环,也就是强迫结束循环。
而continue
则与break
相反:强迫进入下一次循环动作
可简单的看成: 在continue
与done
之间的句子略过而返回到循环的顶端