从0开始-Linux学习笔记(五)

刘小泽写于18.6.24-7.1 一个星期入门linux
我相信这是比之前写的 “测序的世界” 更浩大的工程,但值得写出来!

离用户最近的强大程序——bash

bash及其特性:

  • 发展历史:

    shell翻译为外壳,是用户连入计算机后进行交互式操作的程序
    广义上shell包含两类:
    GUI:Gnome、KDE、Xface
    CLI:最早期bsh(近似B语言) --> csh(编程方式类似C语言), 大大促进Unix流行-->ksh(比csh更强大,但需要付费) --> linux流行后也需要一个shell, 出现了bash(born again shell),兼具了csh、ksh的各种特性,且更强大 --> 最新的zsh更丰富,但不是很流行

  • shell特点:

    1. shell本身就是进程。可以新建多个shell,且互不冲突

      进程:在每个进程看来,当前主机上只存在内核和当前进程。名字可以相同,但进程号各自唯一,Linux只识别进程号【就像全国重名的人很多,但身份证没有重复】

    2. shell作为一个程序、一个外部命令(相对于内核来讲)。作为程序就能运行内部命令,因此还能继续创建子shell。因此很多时候,对子shell的设定,对父shell无效;反之亦如此。

    3. shell可以交互打开。例如当前打开的是bash,可以在bash中敲ksh,在ksh中又可以敲zsh...

  • bash特性

    1. 命令行编辑:
      光标跳转:
      Ctrl+A:跳到命令行首
      Ctrl+E:跳到命令行尾
      Ctrl+U:删除光标至命令行首的内容
      Ctrl+K:删除光标至命令行尾的内容
      Ctrl+L:清屏

    2. 命令历史:history

      • 删除:

        -c : 清空全部历史
        -d:删除指定位置【要删除指定位置向下n个,在-d后加n】

      • 调用:

        !n: 执行第n条命令
        !-n:执行倒数第n条
        !!:执行上一条
        !string: 执行命令历史中最近一个以指定字符串开头的命令
        Esc松开后按 .: 引用上一个命令的最后一个参数【也可以!$】

    3. 命令补全:
      输入的前几个字符是在PATH中能唯一识别的,敲一次tab就能打出来;
      如果不能,敲两次tab会列出所有和输入字符相关的命令

    4. 命令别名alias:

      • bash只是一个程序,当前所有的设置将在退出这一个程序后失效

        若要长期使用alias,可将相应的alias命令存放到bash的初始化文件/etc/bashrc

      • 方法:alias 别名='原命令 -选项/参数'

      • 撤销:unalias 别名

      • \别名:适用于别名和原命令一样,只是添加了一些参数,现在想使用原命令

    5. 命令替换:
      把$()中的子命令替换为前面命令的执行结果,举两个例子就能懂

      例如要打印当前路径:
      echo "The current dir is (pwd)" 例如要在当前目录下新建一个包含年月日时分秒的文件 touch ./file-(date +%F-%H-%M-%S).txt

    6. 命令行展开

      用命令行展开特性一步完成需要分开成多步完成的操作
      使用{}将相应的参数括起来,括号中的参数以逗号分隔, 例如:

      /tmp/{x,y} #生成/tmp/x和/tmp/y
      mkdir {1..5} #生成1-5为名的文件夹
      mkdir -p {1..5}/{1..5} #在1-5的文件夹里再生成1-5的文件夹

    7. 文件名通配 globbing
      快速匹配到你想要的文件

      例如:touch a123 abc ab123 xyz x12 xyz123 新建这6个文件
      目的:

      1. 找出a开头的文件:ls a* => a123 ab123 abc

        :匹配任意长度的任意字符】*

      2. 找出第二个字母是y的文件 ls ?y* => xyz xyz123

        【?:任意单个字符】
        【如果找第三个字母是y的,只需要 ??y* 】

      3. 以字母开头,数字结尾,中间不限

        ls [a-zA-Z] * [0-9]

      [ ]:匹配指定范围内的任意单个字符
      [a-z], [A-Z], [0-9], [a-zA-Z]
      [^]:匹配指定范围之外的任意单个字符
      [^0-9] 非数字 [^[:alpha]] 非大小写字母
      [[:alpha:]]:大小写字母 = [a-zA-Z]
      [[:space:]]:空白字符
      [[:punct:]]:标点符号
      [[:lower:]]:小写字母
      [[:upper:]]:大写字母
      [[:digit:]]:数字
      [[:alnum:]]:数字和大小写字母 = [a-zA-Z0-9]
      练习:
      【1. 创建如下文件:xi、jie6、u56m、my、m.r、t 94、8%u、567
      注意:t 94文件中间有空格!

      1. 显示以a或m开头的文件;
        3. 显示文件名中包含数字的文件;
        4. 显示以数字结尾且文件名中没有空白的文件;
        5. 显示文件名中包含非字母或数字的特殊符号文件

Shell编程:

1. 基础知识:

人和机器交流需要语言,这种语言叫做编程语言,使用编译器或解释器让人机解读
编程语言:机器语言、汇编语言、高级语言

  1. 高级语言:最接近人类的思维方式,相对容易学习;并且通过编译器也可以让机器理解
  • 静态语言:编译型语言

    有一个程序开发环境,不需要借助额外的二进制程序,就能直接写代码。写完代码后需要一个编译器,将其直接转换为二进制后可以独立运行

    强类型语言(类型即变量类型):必须事先声明变量,可能还需要初始化
    (数值为0,字符串为空NULL

    需要事先完全转换成可执行的二进制格式
    C、C++、JAVA、C#

  • 动态语言:解释型语言
    弱类型语言:变量随用随声明(默认为字符串)

    执行的时候再转换【有一个解释器,边解释边执行
    PHP、SHELL、python、perl

    面向过程: Shell, C【linux就是基于C语言】
    面向对象:JAVA,Python,perl【perl也面向过程】,C++

  1. 变量:明确的说变量是内存空间

首先数据存储在内存中,如果将所有待计算的数值都存在内存,内存肯定不够大。因此随着运算器运算,内存空间是不断变化的。比如要计算1加到1000,先存进来1,然后运算器拿走,接着把2放入内存空间,然后2被拿走...
所以这段内存空间是不断变化的,这就是变量

【了解即可】变量有两种类型:字符和数值(整型和浮点型)
为何要区分类型呢?=> 用于事先确定数据存储格式和长度,防止数据溢出

【逻辑运算】:与(&)、或、非(!) 1:真;0:假
短路逻辑运算:
(1): 与:只要有一个为假,结果一定为假
(2): 或:只要有一个为真,结果一定为真

【 变量赋值】VAR_NAME=VALUE

【变量名称】只能包含字母、数字、下划线,且不能以数字开头;
不应该与系统中已有的变量重名;
最好做到见名知义

2. bash变量

  • 变量类型:本地变量(局部变量)、环境变量、位置变量、特殊变量

    • 本地变量(局部变量)

      本地:
      VARNAME=VALUE:【作用域为当前bash进程, 对子shell失效】;
      引用变量:${VARNAME},不影响变量名时可以略去括号。
      e.g. 定义animal=pig
      分别输入三条命令,看看结果:
      1. echo "there are some $animals"
      2. echo "there are some ${animal}s"
      3. echo 'there are some ${animal}s'
      【单引号是强引用;双引号弱引用】
      
      局部:
      local VARNAME=VALUE:作用域为当前代码段;
      
    • 环境变量:

      作用域为:【当前shell进程及其子进程】。
      脚本在执行时会启动一个子shell进程:
        命令行中启动的脚本会继承当前shell环境变量;
        系统自动执行的脚本(非命令行启动)就要定义需要的各环境变量
        export VARNAME=VALUE “导出环境变量”
        VARNAME=VALUE
        export VARNAME
      
      
    • 位置变量:

      $1, $2, ... #表示命令的第 1 个参数,第 2 个参数...
      
      shift:对于同一个$1,可以进行轮换【好比执行过的$1为老员工,他要退休,把位置让给年轻人,也就是下一个$1】;同时shift # 也可以同时shift多个
      
    • 特殊变量:

      $? 上一个命令的执行状态返回值
      【当数据被丢掉/dev/null中,没有返回结果时,可以用这个判断】
      
      一般程序执行可能有两类返回值:
      1. 程序执行结果
      2. 程序状态返回代码(0-255)
        0:正确执行--幸福
        1-255:错误执行--不幸[1,2,127系统预留]
      【应了一句名言:幸福的家庭都是相似的,不幸的家庭各有各的不幸】
      
      $# 参数的个数
      $* 参数列表
      $@ 参数列表
      
  • 撤销变量:
    定义变量可以用set,set $VARNAME, 但是不用也行
    撤销变量用unset VARNAME

  • 查看shell中的变量
    set -- 包括了环境变量和本地变量
    printenv / env / export -- 查看环境变量

  • 添加变量:(如添加环境变量)

    #在环境变量后面添加变量
    export PATH=$PATH:/usr/local/apache/bin
    #在环境变量前面添加变量
    export PATH=/usr/local/apache/bin:$PATH
    

3.脚本:

什么是脚本?
命令的堆砌,按实际需要,结合命令流程控制机制实现的程序

  • 魔数:shebang

    #!/bin/bash
    #注释行不执行
    
  • 条件判断【可以说是追求真值的过程】

    • 命令间的逻辑关系:

      • 逻辑与:&&【只为验证结果是否为真】
        第一个条件为假,第二个条件不用再判断;
        第一个条件为真,结果取决于第二个条件

      • 逻辑或:||【只为验证结果是否为真】

        ​ 第一个条件为假,第二个继续判断,有一个真就行;
        第一个为真,则第二个不用再判断

        例如:如果用户存在,就显示“用户存在”,否则添加:
        id user1 && echo "user exists" || useradd user1

        进阶: 如果用户不存在,新建用户并且给他密码,否则显示存在
        ! id user1 && useadd user1 && echo "user1" | passwd -stdin user1 || echo "user1 exists"

  • 条件测试: 【整数、字符、文件测试】
    测试表达式:【一定要有空格】

    1. [ expression ] -- 命令测试法

    2. [[ expression ]] — 关键字测试法

    3. test expression

      例如:测试两个整数变量是否相等
      INT1=66
      INT2=77

      1. [ $INT1 -eq $INT2 ]
      2. [[ $INT1 -eq INT2 ]]
      3. set $INT1 -eq $INT2
    • 整数测试:
    [ expression ]:表达式两段必须要有空格
    -eq:测试两个整数是否相等;比如 `$A -eq $`B;
    -ne:测试两个整数是否不等;不等,为真;相等,为假;
    -gt:[great than]测试一个数是否大于另一个数;大于,为真;
    -lt:[less than]测试一个数是否小于另一个数;小于,为真;
    -ge:大于或等于
    -le:小于或等于
    

    例如:检查用户UID是否为0,为0就显示管理员,否则显示为普通
    【注意:先定义变量,这样在整个脚本中都能重复使用】

    #!/bin/bash
    NAME=user1
    USERID=`id -u $NAME` 
    [ $USERID -eq 0 ] && echo 'Admin' || echo 'Common'
    

    ⚠️小Tip:
    【反引号的意思是调取命令执行的结果;**
    不加反引号只是表明命令的运行状态,是成功,还是失败,如果只需要状态的话,那么命令结果对我们来讲就是无用信息,可以丢到/dev/null中】**

    • 文件测试:

        -e FILE:测试文件是否存在;e.g. [ -e /etc/innitab ]
        -f FILE:测试文件是否为普通文件;
        -d FILE:测试指定路径是否为目录;
        -r FILE:测试当前用户对指定文件是否有读取权限;
        -w FILE:测试当前用户对指定文件是否有写入权限;
        -x FILE:测试当前用户对指定文件是否有执行权限;
      

      例如:给定一个文件,比如/etc/passwd,判断这个文件中是否有空白行;如果有,则显示其空白行数;否则,显示没有空白行。

      #!/bin/bash
      #先判断这个文件是否存在,不存在就退出
      FILE=/etc/passwd
      if [ ! -e $FILE ];then
        echo "No such file"
        exit 1
      fi
      #存在再继续判断空白行
      if grep "^$" $FILE &> /dev/null;then
        echo "Blank rows number is: `grep "^$" $FILE | wc -l`"
      else
        echo "No blank line"
      fi
      
    • 字符测试:
      等值比较:[ $A = $B ]
      不等值比较:[ $A != $B ]
      测试是否为空:-n
      测试指定字符串是否不空:-z

    • 如何组合两个以上的条件?
      -a 与关系; -o:或关系; !非关系
      e.g. 表示1 <= # <= 3: `if [# -ge 1 -a # -le 3]` 或者 `if [# -ge 1] && [ $# -le 3]`

  • 控制结构:

    • 单分支if语句

      #几个必须条件:必须有分号,必须有then,但是then可以另起一行,结尾必须是fi且单独一行
       
      if 判断条件; then
          statement1
          statement2
          ...
        fi
      
    • 双分支if语句

       if 判断条件; then
          statement1
          statement2
          ...
        else
          statement3
          statement4
        fi
      

      例如:判断当前系统上是否有用户的默认shell为bash; 如果有,就显示其中一个的用户名;否则,就显示没有这类用户
      【借用/etc/passwd可以查看用户名、UID、GID、SHELL等】

      #!/bin/bash
      
      #先看抓取‘结尾的bash’的状态结果是否为0,如果有bash,继续
      #另外判断完后,这个信息不用输出,所以丢到/dev/null中
      grep '/ /dev/null
      RETVAL=$?  #retval意思是‘返回值’
      if [ $RETVAL -eq 0 ]; then    #返回值为0,即正确,有结果
        AUSER=`grep '\>bash$' /etc/passwd | head -1 | cut -d: -f1`  #使用反引号``是赋给变量运行结果
        echo "$AUSER does exist"    #双引号弱引用
      else
        echo "No such user"
      fi
        
      
    • 多分支if语句:

        if 判断条件1; then
          statement1
          ...
        elif 判断条件2; then
          statement2
          ...
        elif 判断条件3; then
          statement3
          ...
        else
          statement4
          ...
        fi
      
    • case 语句:号称比if多分枝更易懂的结构

      case SWITCH in 
      value1)
        statement 
        ;;
      value2)
        statement 
        ;;
      *)
        statement 
        ;;
      esac
      
  • for循环:

    for 变量 in 列表; do
      循环体
    done
    
    遍历完成之后,退出;
    
    # 如何生成列表:
    1. {1..100}
    2.  `seq [起始数 [步进长度]] 结束数` 
      # 起始、步长可省略
    e.g. seq 1 2 10 => 1 3 5 7 9
    
    # 声明 SUM 是整型
    declare -i SUM=0
         [-x # 声明是环境变量]
         
    #计算从 1 加到 100
    #!/bin/bash
    #
    declare -i SUM=0
    for I in {1..100}; do
      #SUM=$[$SUM+$I]
      #或 SUM+=$I
    done
    echo "The sum is $SUM."
    

    e.g.1 for循环脚本,依次向/etc/passwd中的每个用户问好

    #!/bin/bash
    #
    LINES=`wc -l /etc/passwd | cut -d' ' -f1`
    for i in `seq 1 $LINES`;do
        echo "Hello, `head -n $i /etc/passwd | tail -1 | cut -d: -f1`"
    done
    

    e.g.2 计算1000以内奇数之和、偶数之和:

    #第一种写法
    #!/bin/bash
    #
    declare -i ODDSUM=0
    for i in `seq 1 2 1000`;do
        ODDSUM=$[$ODDSUM+$i]
    done
    echo "the sum of odd is:$ODDSUM"
    
    declare -i EVENSUM=0
    for i in `seq 1 2 1000`;do
        EVENSUM=$[$EVENSUM+$i]
    done
    echo "the sum of even is:$EVENSUM"
    
    #第二种写法
    #!/bin/bash
    #
    declare -i EVENSUM=0
    declare -i ODDSUM=0
    
    for I in {1..1000};do
        if [ $[$I%2] -eq 0];then
            let EVENSUM+=$I
        else
            let ODDSUM+=$I
        fi
    done
    
    echo"Odd sum is $ODDSUM"
    echo "Even sum is $EVENSUM"
    

    结果就是:the sum of odd is:250000
    the sum of even is: 250500

  • while循环

    适用于循环次数未知的情况

    while CONDITION; do
      statement
      ...
    done
    
    例如:计算100以内所有正整数的和
    #!/bin/bash
    declare -i I=1
    declare -i SUM=0
    
    while [ $I -le 100 ]; do
      SUM+=$I
      let I++
    done
    
    echo $SUM
    
  • Shell中进行算术运算【前三种最为常见】

    1. let 算术运算表达式
      let C=$A+$B

    2. $ [算术运算表达式]

      C=$[$A+$B]

    3. $((算术运算表达式))

      C=$(($A+$B))

    4. expr 算术运算表达式,
      表达式中各操作数及运算符之间要有空格,而且要使用命令引用

      C=expr $A + $B

  • 随机数
    echo $RANDOM
    RANDOM: 0-32768

  • exit 提前结束脚本

    exit #
    状态值0代表执行成功,其他值代表执行失败。
    如果脚本没有明确定义退出状态码,那么,最后执行的一条命令的退出码即为脚本的退出状态码(所以需要自己指定一个状态码,例如exit 1)

  • 测试脚本是否有语法错误:
    bash -n 脚本 但是测试结果是模糊的,不能作为最终判断依据
    bash -x 脚本 可以清楚看到脚本中哪个地方不符合我们预期,
    对于大段脚本尤其适用!

  • 之前学过了变量的四种类型-->
    本地变量(局部变量)、环境变量、位置变量、特殊变量

    上面使用的主要有本地变量、特殊变量($?、$#).
    位置变量如何在脚本中应用呢?

    位置变量最大的作用就是将命令后的各个参数引入进脚本,
    化身成$1, $2...这种
    e.g. 给脚本传递两个参数(整数);显示此两者之和,之积

    #!/bin/bash
    #如果获取的参数不是两个,那么就退出不再进行
    if [ $# -ne 2 ]; then
      echo "Usage:test.sh ARG1 ARG2 [...]"
      exit 1
    fi
    #####以上就是使用命令时出现的使用帮助,就是这么来的#####
    #接下来如果是两个,就求和、求乘积【注意echo的弱引用】
    echo "The sum is:$[$1+$2]."
    echo "The prod is:$[$1*$2]."
    
    

欢迎关注我们的公众号~_~  
我们是两个农转生信的小硕,打造生信星球,想让它成为一个不拽术语、通俗易懂的生信知识平台。需要帮助或提出意见请后台留言或发送邮件到[email protected]

从0开始-Linux学习笔记(五)_第1张图片
bioinfoplanet

你可能感兴趣的:(从0开始-Linux学习笔记(五))