shell脚本和shell十三问

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


      shell脚本和shell十三问_第1张图片
      meta.png
  • 屏蔽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暂略

最后,向大家隆重推荐生信技能树的一系列干货!

  1. 生信技能树全球公益巡讲:https://mp.weixin.qq.com/s/E9ykuIbc-2Ja9HOY0bn_6g
  2. B站公益74小时生信工程师教学视频合辑https://mp.weixin.qq.com/s/IyFK7l_WBAiUgqQi8O7Hxw
  3. 招学徒https://mp.weixin.qq.com/s/KgbilzXnFjbKKunuw7NVfw

你可能感兴趣的:(shell脚本和shell十三问)