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)。

设定变量时:

  1. 等号左右两边不能使用分隔符号(IFS)name=value,也应避免使用shell的保留元字符(meta charactor);
  2. 变量的名称(name)的首字符不能是数字(number);
  3. 变量的名称(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 字符串变量值的替换格式:

  1. 首次替换: ${s/src_pattern/dst_pattern} 将字符串s中的第一个src_pattern替换为dst_pattern。
  2. 全部替换: ${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中, $(())的整数运算符号大致有这些:

  1. +- * / #分别为"加、减、乘、除"。
  2. % #余数运算,(模数运算)
  3. & | ^ ! #分别为"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

上例的执行结果将会是:

  1. for会定义一个叫var的变量,其值依次是one two three four five。
  2. 因为有5个变量值,因此,do与done之间的命令行会被循环执行5次。
  3. 每次循环均用echo产生3个句子。而第二行中不在hard quote之内的$var会被替换。
  4. 当最后一个变量值处理完毕,循环结束。
    在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

分析上例:

  1. 在while之前,定义变量num=1.
  2. 然后测试(test)$num是否小于或等于10.
  3. 结果为true,于是执行echo并将num的值加1.
  4. 再作第二轮测试,此时num的值为1+1=2,依然小于或等于10,因此,为true,循环继续。
  5. 直到num为10+1=11时,测试才会失败...于是结束循环。

while loop的原理与for loop稍有不同: 它不是逐次处理清单中的变量值, 而是取决于while 后面的命令行的return value:

  1. 若为true, 则执行do与done之间的命令, 然后重新判断while后的return value。
  2. 若为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相反:强迫进入下一次循环动作
可简单的看成: 在continuedone之间的句子略过而返回到循环的顶端

你可能感兴趣的:(Shell十三问 学习笔记)