02-shell变量

1.什么是变量

在编程和数学领域中,变量是一个非常重要的概念。

一、编程中的变量

  1. 定义

    • 在编程中,变量是一个存储数据的容器,它可以存储各种类型的数据,如数字、文本、列表、对象等。变量有一个名称,通过这个名称可以在程序中访问和操作它所存储的数据。例如,在Python语言中,可以使用以下方式定义一个变量:
      age = 20
      
      这里定义了一个名为age的变量,并将整数20存储在其中。
  2. 数据类型

    • 变量的数据类型决定了它可以存储的数据的种类。常见的数据类型包括整数(int)、浮点数(float)、字符串(string)、布尔值(bool)等。不同编程语言的数据类型分类和具体规则可能会有所不同。
    • 例如,整数变量用于存储没有小数部分的数字,像123等;浮点数变量可以存储带有小数部分的数字,如3.142.5等;字符串变量用于存储文本内容,像"Hello, World!"等。
    • 以Java语言为例,定义不同数据类型的变量:
      int number = 5; // 定义一个整数变量
      double pi = 3.14; // 定义一个浮点数变量
      String message = "Good morning"; // 定义一个字符串变量
      boolean flag = true; // 定义一个布尔变量
      
  3. 作用域

    • 变量的作用域是指变量在程序中可以被访问和使用的范围。通常分为全局变量和局部变量。
    • 全局变量是在整个程序或模块的顶层定义的变量,它可以在程序的大部分地方被访问。而局部变量是在函数、方法或代码块内部定义的变量,它只能在定义它的函数、方法或代码块内部被访问。
    • 例如,在Python中: ```python global_variable = 100 # 全局变量

    def my_function():

    local_variable = 20 # 局部变量
    print(local_variable)
    print(global_variable)
    

    my_function() print(global_variable)

    尝试在函数外访问局部变量会出错

    print(local_variable)

4. **变量的修改和更新**
   - 变量的值在程序运行过程中可以被修改。例如,在一个循环中,每次迭代都可以更新变量的值。
   - 以JavaScript为例,在一个`for`循环中更新变量的值:
   ```javascript
   for (let i = 0; i < 10; i++) {
       console.log(i);
   }

这里变量i0开始,每次循环迭代都会增加1,直到i的值达到10

二、数学中的变量

  1. 定义
    • 在数学中,变量是一个代表数值的符号,它的值可以根据问题或方程的条件而变化。通常用字母(如xyz等)来表示变量。
    • 例如,在方程y = 2x + 1中,xy是变量。当x取不同的值时,y的值会根据给定的关系相应地改变。
  2. 应用场景
    • 变量在代数、函数、方程求解等多个数学领域广泛应用。在函数中,变量可以用来描述输入和输出之间的关系。
    • 例如,对于函数f(x)=x^{2}x是自变量,当x的值改变时,f(x)的值也会随之改变。我们可以通过给x赋予不同的值,如x = 1时,f(1)=1^{2}=1;当x = 2时,f(2)=2^{2}=4等来研究函数的性质。
变量、顾名思义

量就是记录事务的状态;
变就是事务的状态是会发生变化的;

变量本质是计算机中一种存、取数据的机制;
变量的数据都直接存储在内存中;

为什么要有变量?
程序的本质就是数据的不断变化,存取;

计算机程序的本质

程序=数据+算法(逻辑功能)

程序执行的本质就是一系列状态的变化,变是程序执行的直接体现;
所以我们需要有一种机制能够反映或者说是保存下来程序执行时状态以及状态的变化。
(例如一个变量,原本值是空字符串,接收用户输入的数据后,就存储了用户的数据)

# 比如:
    王者荣耀初始的等级为1,打小兵升级后,等级变为了2,3,4
    于超老师的名字在数据库里 叫做 "于超",也可以改为"于大胆"

shell变量是什么

变量在每一种变量语言中都会用到;
shell也不例外,shell变量用于存、取数据,便于后续的反复使用。

2.变量分类

变量可以从多个角度进行分类,以下是常见的分类方式:

一、按数据类型分类(以编程为例)

  1. 数值型变量
    • 整数变量(int)
      • 用于存储没有小数部分的整数,取值范围因编程语言和计算机硬件等因素而异。例如,在许多编程语言中,一个普通的32位整数可以表示范围大致是 -2147483648到2147483647。在Python中,定义整数变量很简单:
        num1 = 5
        num2 = -10
        
    • 浮点数变量(float)
      • 用来存储带有小数部分的数字,它在计算机内部是以二进制形式存储的近似值。由于浮点数的存储方式,可能会出现精度丢失的情况。例如,在Python中:
        pi = 3.14159
        height = 1.75
        
    • 复数变量(complex)
      • 用于存储复数,复数是由实数部分和虚数部分组成的数。在Python中,复数的表示形式为a + bj,其中a是实数部分,b是虚数部分,j是虚数单位。例如:
        z = 3 + 4j
        
  2. 字符型变量(string)
    • 用于存储文本数据,即一系列字符。在大多数编程语言中,字符串需要用引号(单引号或双引号)括起来。例如,在Java中:
      String greeting = "Hello, World!";
      String name = 'John';
      
    • 字符串可以进行各种操作,如拼接、截取、查找等。例如在Python中:
      str1 = "Hello"
      str2 = "World"
      result = str1 + " " + str2 # 字符串拼接
      print(result)
      
  3. 布尔型变量(bool)
    • 它只有两个可能的值,即True(真)和False(假)。布尔变量通常用于条件判断和逻辑运算。在C++中:
      bool flag1 = true;
      bool flag2 = false;
      if (flag1) {
        // 当flag1为真时执行的代码
      }
      
  4. 枚举型变量(enum)

    • 在一些编程语言中存在枚举类型。枚举是一种用户定义的数据类型,它由一组命名的常量组成。例如在C#中:
      enum WeekDays {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
      };
      WeekDays today = WeekDays.Monday;
      
    • 枚举变量的值只能是枚举定义中列出的常量之一,这样可以提高代码的可读性和可维护性。
  5. 数组和列表型变量(array/list)

    • 数组(array):在许多编程语言中,数组是一种存储相同类型数据的集合。例如在C语言中,定义一个整数数组:
      int numbers[5]; // 定义一个包含5个整数的数组
      numbers[0] = 1;
      numbers[1] = 2;
      
    • 列表(list):和数组类似,但在一些动态语言中,列表可以存储不同类型的数据,并且长度可以动态变化。例如在Python中:
      my_list = [1, "two", 3.0]
      my_list.append(4)
      
  6. 对象型变量(object)

    • 在面向对象编程中,对象是类的实例。对象变量可以存储对对象的引用,通过对象变量可以访问对象的属性和方法。例如在Java中:
      class Car {
        String color;
        int speed;
        public void drive() {
            // 开车的方法实现
        }
      }
      Car myCar = new Car();
      myCar.color = "red";
      myCar.drive();
      

二、按作用域分类(以编程为例)

  1. 全局变量

    • 全局变量是在程序的最外层定义的变量,它的作用域通常是整个程序文件或模块。全局变量可以被程序中的多个函数或代码块访问和修改。例如在Python中: ```python global_variable = 10 # 全局变量

    def function1():

    print(global_variable)
    

    def function2():

    global global_variable
    global_variable += 5
    print(global_variable)
    

    function1() function2() function1() ```

    • 不过,过度使用全局变量可能会导致代码的可读性和可维护性变差,因为很难追踪全局变量在各个地方的修改情况。
  2. 局部变量

    • 局部变量是在函数、方法或代码块内部定义的变量,它的作用域仅限于定义它的函数、方法或代码块。当程序执行离开这个范围时,局部变量就会被销毁。例如在C++中:
      void myFunction() {
        int local_variable = 20; // 局部变量
        // 可以在这个函数内部使用local_variable
      }
      // 在函数外部无法访问local_variable
      
  3. 静态变量(在部分语言中有此概念)

    • 静态变量主要用于在函数调用之间保持变量的值。在C语言中,在函数内部定义静态变量可以使变量在函数的多次调用之间保持它的值。例如:
      void countFunction() {
        static int count = 0;
        count++;
        printf("%d\n", count);
      }
      int main() {
        countFunction();
        countFunction();
        return 0;
      }
      
    • 每次调用countFunction函数时,静态变量count的值都会在上一次调用的基础上增加,而不是每次都重新初始化为0。

三、按变量的变化性质分类(以数学为例)

  1. 自变量
    • 在函数关系中,自变量是可以自由变化的变量。例如,在函数y = f(x)中,x通常是自变量。我们可以给x赋予不同的值,从而得到相应的y值。比如在函数y = 3x + 2中,x是自变量,当x = 1时,y = 3×1+2 = 5;当x = 2时,y = 3×2+2 = 8等。
  2. 因变量
    • 因变量是随着自变量的变化而变化的变量。在上面的函数y = 3x + 2中,y是因变量,它的值取决于x的值。也就是说,当我们改变自变量x的值时,因变量y的值会根据函数关系相应地改变。
  3. 常量(有时也看作特殊的变量)
    • 常量是在某个过程中或问题里保持固定不变的值。例如,在圆的周长公式C = 2πr中,2π在这个公式的应用场景下是常量,r是自变量,C是因变量。常量的作用是确定变量之间的固定关系。在编程中,也有常量的概念,用于表示那些不应该被修改的值,不过在不同编程语言中定义和使用常量的方式有所不同。
环境变量(全局变量) 系统中全局生效的变量;
普通变量(局部变量)只对局部环境,如当前某一个login.sh脚本生效;

变量生命周期
1. 永久变量,写入文件,反复读取,加载,让其永久生效,如/etc/profile的PATH修改;
2. 临时变量,如命令行export定义一个变量存储数据,关闭shell会话后失效;

2.1 是否添加export的变量

  1. 理解Shell中的变量作用域

    • 在Shell中,变量分为局部变量和全局变量。局部变量只在当前的Shell脚本或命令行环境的局部范围内有效。例如:
      #!/bin/bash
      local_var=10
      echo $local_var
      
    • 当这个脚本执行完或者离开定义local_var的代码块后,这个变量就不再可用。
  2. export变量的作用

    • 当使用export命令时,变量会被设置为,环境变量,这意味着该变量可以被子进程(由当前Shell启动的其他进程)继承和使用。
    • 例如,考虑以下脚本:
      #!/bin/bash
      export ENV_VAR="This is an exported variable"
      bash -c 'echo $ENV_VAR'
      
    • 在这个例子中,首先通过export设置了ENV_VAR为环境变量。然后,通过bash -c命令启动了一个子Shell,并在这个子Shell中尝试打印ENV_VAR。由于ENV_VAR已经被export,所以子Shell能够获取并打印这个变量的值。
  3. 决定是否添加export变量的情况

    • 需要在子进程中使用变量时
      • 如果你正在编写一个Shell脚本,并且其中启动了其他程序或者脚本(子进程),而这些子进程需要访问当前脚本中的某些变量,那么就需要使用export。例如,在一个自动化部署脚本中,可能需要将服务器的IP地址、用户名和密码等信息传递给后续执行的scp(用于文件传输)或者ssh(用于远程登录)命令,这些命令在执行时会启动新的子进程,所以需要将相关变量export
    • 配置系统范围的变量时
      • 对于一些系统范围的配置,如设置PATH(可执行文件的搜索路径)、LD_LIBRARY_PATH(共享库的搜索路径)等变量,通常需要使用export。这些变量的改变会影响整个系统环境或者用户的工作环境。例如,如果你安装了一个新的软件,并且希望能够在命令行的任何位置都能直接执行它,就需要将该软件的可执行文件路径添加到PATH中并export这个变量。
    • 不需要export变量的情况
      • 如果变量仅在当前脚本内部使用,并且不会被其他进程访问,那么就不需要使用export。例如,一个只在脚本内部进行简单数学计算或者文本处理的变量,如用于计数的变量或者临时存储文件内容的变量等,就没有必要设置为环境变量。这样可以避免不必要地将变量暴露给其他可能不需要它的进程,也有助于提高系统的安全性和性能。

补充:还得看你脚本如何执行!回忆前面给我讲的,四中脚本执行方式。

临时变量,是否添加export的区别
1. 不加export,只对当前shell生效,子shell看不到;
[root@yuchao-tx-server ~]#pstree -p |grep ssh
           |-sshd(13730)---sshd(9399)---bash(9406)-+-grep(9690)
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#name='我是临时变量,于超老师你进入子shell就看不到我了,试试执行bash开启子shell'

------------------------------------------------------------------------------------------------------------------------
[root@yuchao-tx-server ~]#pstree -p |grep ssh
           |-sshd(13730)---sshd(9399)---bash(9406)---bash(9804)-+-grep(9974)
[root@yuchao-tx-server ~]#echo $name

[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#export name2='我是export设置的全局变量,父子shell都可以看到,nb吗?'
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#bash
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#pstree -p |grep ssh
           |-sshd(13730)---sshd(9399)---bash(9406)---bash(9804)---bash(10085)-+-grep(10139)
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $name2
我是export设置的全局变量,父子shell都可以看到,nb吗?

什么是环境变量文件

  1. 定义
    • Shell环境变量文件是一种包含了可以被Shell(命令行解释器,如Bash、Zsh等)读取的环境变量,定义的文件。
    • 这些文件用于配置Shell的工作环境,当Shell启动时,会读取这些文件来设置各种环境变量的值。
    • 环境变量是在操作系统或特定软件环境中用于存储系统配置信息、用户偏好以及程序运行所需参数等内容的变量。
  2. 常见的Shell环境变量文件类型及作用
    • 系统级环境变量文件(以Linux为例)
      • /etc/profile:这是一个系统级别的配置文件,用于为所有用户设置环境变量。当用户登录系统时,这个文件会被读取。它会设置一些基本的系统环境变量,比如PATH(用于指定命令搜索路径)、USER(当前用户名称)等。例如,在许多Linux发行版中,系统会通过这个文件将一些基本的系统命令目录(如/usr/bin、/usr/sbin等)添加到PATH变量中,这样用户在任何目录下都可以直接运行这些命令。
      • /etc/bashrc(对于Bash Shell):这个文件主要用于为Bash Shell配置系统范围的默认设置。它会在Bash Shell启动时被读取,用于设置一些Bash相关的选项和环境变量,如别名(aliases)等。例如,可以在这个文件中定义一个别名,让用户在输入“ll”时实际上执行“ls -l”命令,方便用户操作。
    • 用户级环境变量文件(以Linux为例)
      • ~/.bash_profile(对于Bash Shell):这个文件是用户特定的配置文件,主要用于用户登录时的环境配置。当用户登录到系统并启动Bash Shell时,这个文件会被读取。用户可以在这个文件中设置自己的环境变量,比如添加自己的自定义命令目录到PATH变量中。例如,如果用户自己编译安装了一些软件到自己的主目录下的一个特定目录(如~/my_bin),可以将这个目录添加到PATH中,这样就可以直接运行这些软件。
      • ~/.bashrc(对于Bash Shell):这个文件也用于用户特定的Bash Shell配置。它在每次打开一个新的Bash终端(非登录终端)时会被读取。与~/.bash_profile不同的是,~/.bashrc主要用于交互式的Bash环境配置,比如设置终端提示符的格式、定义一些在终端中常用的函数等。例如,可以在这个文件中定义一个函数,用于快速切换到某个特定的项目目录。
  3. 语法规则
    • 在这些环境变量文件中,通常使用以下语法来设置环境变量。以Bash为例,设置一个环境变量可以使用“变量名=变量值”的形式,例如“PATH = /usr/bin:/usr/local/bin:$PATH”,这里是将/usr/bin和/usr/local/bin目录添加到已有的PATH变量中($PATH表示原来的PATH变量值)。如果要让这个环境变量在子进程中也有效,需要使用“export变量名”的语法,如“export PATH”。对于定义别名,语法通常是“alias别名='实际命令'”,例如“alias cls='clear'”,这样在终端中输入“cls”就会执行“clear”命令,用于清除屏幕。
  4. 重要性
    • 这些文件对于系统管理和用户自定义工作环境非常重要。系统管理员可以通过修改系统级环境变量文件来统一配置系统环境,例如安装新的软件包后,可能需要更新PATH变量,让所有用户都能够方便地使用新的命令。用户则可以通过用户级环境变量文件来根据自己的工作习惯和需求定制自己的工作环境,提高工作效率。

加载环境变量文件的顺序

  1. 系统启动时的加载顺序(全局环境变量)

    • /etc/environment
      • 这个文件是系统范围的环境变量配置文件。它在系统启动过程的早期阶段就会被读取,用于设置全局的环境变量。这些变量会被所有用户和进程继承。例如,在这里设置的PATH变量修改会影响整个系统对可执行文件路径的搜索。它的格式比较简单,每行是一个“变量=值”的形式,如PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    • /etc/profile
      • 它是系统范围的启动脚本,用于为所有用户设置环境变量和启动程序。当用户登录系统时,这个文件会被执行。它会加载一些系统默认的环境设置,并且可以通过/etc/profile.d/目录下的脚本文件进行扩展。在这个文件中,可以使用export命令来设置环境变量,如export JAVA_HOME=/usr/lib/jvm/java - 8 - openjdk - amd64。这个文件中的设置会覆盖/etc/environment中相同变量的设置。
    • /etc/profile.d/*.sh
      • 这是一个目录下的一系列脚本文件。系统在执行/etc/profile时会执行这个目录下的所有.sh脚本。这是一种方便的方式来添加系统范围的环境变量或者启动命令,而不需要直接修改/etc/profile。例如,一些软件包的安装会在这个目录下添加自己的脚本文件,用于设置软件相关的环境变量,如对于安装了node.js的系统,可能会有一个nodejs.sh文件在这里,用于设置NODE_PATH等变量。
  2. 用户登录时的加载顺序(用户环境变量)

    • ~/.bash_profile(或~/.bash_login~/.profile
      • 对于Bash用户(Ubuntu默认的Shell是Bash),当用户登录时,首先会检查~/.bash_profile文件。如果这个文件存在,就会执行它来设置用户特定的环境变量。如果这个文件不存在,会检查~/.bash_login,然后是~/.profile。这些文件的作用是类似的,主要用于为用户个人设置环境变量,比如用户自定义的PATH扩展,添加自己的可执行文件路径,或者设置一些软件的个性化配置变量。例如,用户可以在这里设置export MY_VARIABLE="My custom value",这样在用户的整个登录会话中,这个变量就可以被访问。
    • ~/.bashrc
      • 这个文件主要用于非登录Shell环境下的配置。当用户打开一个新的终端(不是通过登录系统,而是在已经登录的情况下打开一个新终端),~/.bashrc文件会被加载。它也可以用于设置环境变量,并且会覆盖前面文件中相同变量的设置。不过,它的重点在于配置终端相关的行为和变量,如设置终端的提示符颜色、别名等。通常,在~/.bash_profile中会有一行代码(如果是Bash Shell)来加载~/.bashrc,这样可以保证在登录和非登录情况下都能应用~/.bashrc中的设置。例如,if [ -f ~/.bashrc ]; then. ~/.bashrc; fi
1. 登录ssh会话后,要加载/etc/profile
2. 执行用户家目录中的环境变量文件配置文件 ~/.bash_profile
3. 执行 ~/.bashrc
4. 执行/etc/bashrc

2.3 变量规范写法

  1. 编程中的变量规范(以常见编程语言为例)

    • 命名规则
      • 可读性优先:变量名应该具有描述性,能够清晰地表达变量的用途。例如,在一个计算圆面积的程序中,使用radius(半径)和area(面积)作为变量名,比使用ra更易于理解。在Python中:
        radius = 5
        area = 3.14 * radius * radius
        
      • 遵循语言命名约定:不同编程语言有不同的命名约定。一般来说,变量名可以包含字母、数字和下划线,但通常不能以数字开头。例如,在Java中:
        int studentAge;  // 合法的变量名
        int 1stStudent;  // 不合法的变量名,以数字开头
        
      • 区分大小写:大多数编程语言是区分大小写的。例如在C++中,myVariablemyvariable是两个不同的变量。
      • 避免使用关键字:不能使用编程语言中的关键字作为变量名。例如,在JavaScript中,ifelsefor等是关键字,不能用作变量名。
    • 变量类型声明规范(对于静态类型语言)

      • 明确声明类型:在静态类型语言中,如Java和C#,变量在使用前需要明确声明其类型。例如,在Java中:
        int count;
        String name;
        
      • 初始化变量:最好在声明变量时就对其进行初始化,以避免使用未初始化的变量导致的错误。例如,在C++中:
        int number = 0;
        
    • 变量作用域规范

      • 限制变量作用域:尽量缩小变量的作用域,使变量只在需要的地方可见。例如,在函数内部定义的局部变量,应该避免在函数外部访问。在Python中:
        def my_function():
          local_variable = 10
          print(local_variable)
        my_function()
        # 在这里尝试访问local_variable会导致错误
        
      • 明确全局变量的使用:如果需要使用全局变量,应该在代码中明确标识,并且谨慎使用,因为过多的全局变量可能会使代码难以理解和维护。在Python中:
        global_variable = 20
        def another_function():
          global global_variable
          global_variable += 5
          print(global_variable)
        another_function()
        
  2. 数学中的变量规范
    • 符号选择:通常使用字母(如xyz等)来表示变量。在同一问题或方程中,应该尽量使用不同的字母来表示不同的变量,避免混淆。例如,在一个方程组2x + 3y = 74x - y = 5中,xy分别代表不同的未知数。
    • 书写清晰:在书写变量时,要注意书写规范,使其易于辨认。例如,在手写数学式子时,变量要写得清晰,避免和数字或者其他符号混淆。在印刷或电子文档中,变量的字体和格式应该保持一致。
    • 定义范围明确:在使用变量之前,应该明确变量的定义范围,例如是在一个函数内部、一个特定的数学模型或者一个方程组内有效。例如,在定义一个函数f(x) = x^2 + 3x - 1时,x是函数的自变量,其取值范围(定义域)应该根据具体的数学背景或者问题要求来确定,可能是实数集R,也可能是某个区间,如[0, 10]
变量由三大部分组成
1. 变量名,用于访问变量值的入口
2. 赋值符号,将内存中的数据,绑定到变量名
3. 变量值,具体的内存中数据

2.4 变量命名规范

定义变量的目的在于

1.存储数据,通过变量名提取

2.要不断的基于变量名,修改变量值,读取变量值。

# 变量名的命令应该见名知意,同时遵循如下规则
以字母或下划线开头,剩下的部分可以是:字母、数字、下划线,最好遵循下述规范:
    1.以字母开头
    2.使用下划线做单词的连接
    3.同类型的用数字区分
    4.对于文件名的命名最好在末尾加上拓展名
    5.注意,变量名=变量值,等号两边没有空格;
    6. 系统默认使用的变量,基本是完全大写的变量名;

例如: sql_bak.tar.gz,log_bak.tar.bz2  
    5、不要带有空格、?、*等特殊字符 
    6、不能使用bash中的关键字,例如if,for,while,do等,如定义ls="大傻春醒醒"
    7、不要和系统环境变量冲突,如覆盖PATH,错误。


变量参考写法
纯字母:name
全小写:my_name
驼峰法:My_Name
全大写:MY_NAME

3.变量实际使用

3.1 字符串变量值

1. 先定义、再引用

[root@yuchao-tx-server ~]#my_website="http://www.yuchaoit.cn"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $my_website
http://www.yuchaoit.cn

[root@yuchao-tx-server ~]#name="yuchao"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#age="18"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $name $age
yuchao 18

[root@yuchao-tx-server ~]#echo ${name} ${age}
yuchao 18

[root@yuchao-tx-server ~]#url="www.yuchaoit.cn"
[root@yuchao-tx-server ~]#echo $url
www.yuchaoit.cn

3.2 命令定义变量

用变量存储linux命令的执行结果。



变量名存储机器的ip
[root@yuchao-tx-server ~]#local_ip="$(ifconfig eth0 |awk 'NR==2{print $2}')"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $local_ip
10.141.32.137


变量名存储主机名
[root@yuchao-tx-server ~]#local_hostname=`hostname`
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $local_hostname
yuchao-tx-server


[root@yuchao-tx-server ~]#Now_Time=$(date +%F)
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $Now_Time
2022-06-15


命令定义变量的两个方式
1. 反引号、$(命令执行) 都可以将结果赋值给变量
2.  建议用$(),否则太多引号,容易搞混;

3.3 提取变量值

需要注意如下写法的区别,以及坑。

使用$变量名,还是使用${变量名}的区别,看如下的坑
[root@yuchao-tx-server ~]#echo $name
yuchao
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $name_666

[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo ${name}_666
yuchao_666

因此使用${变量}提取值,更为保险,定义了边界;





特殊情况,如设置百分比,建议这样用
[root@yuchao-tx-server ~]#progress="70"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo "当前进度是${progress}%"
当前进度是70%

单引号、双引号的坑

[root@yuchao-tx-server ~]#echo '我的名字是 ${name}'
我的名字是 ${name}
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo "我的名字是 ${name}"
我的名字是 yuchao

结论
1. 单引号,原样输出
2. 双引号、可以解析变量

什么情况下用这俩引号?

1. 你确认要提取变量的值,就双引号
2. 你确认必须要原样输出,是一个普通的字符串,或者显示特殊字符,就用单引号、或者反斜线  \   ,这个撬棍。

3.4 修改、删除变量

[root@yuchao-tx-server ~]#echo $name
yuchao
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#name="吴彦祖"
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $name
吴彦祖
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#unset name
[root@yuchao-tx-server ~]#
[root@yuchao-tx-server ~]#echo $name

[root@yuchao-tx-server ~]#

4.变量传递,参数传递(重要)

变量除了在命令行直接调用、存、取值。

还经常用在脚本传递的参数中,但是脚本接收的参数,这个特殊变量,是shell内置固定的语法规则。

# 执行脚本,获取传入的位置参数,如 ./vars.sh yuchao1 yuchao2 yuchao3 yuchao4 yuchao5
# 通过$n 获取第n个位置的参数值,从10开始,必须使用${n}不得简写
# shell变量的花式玩法

cat > vars.sh <<'EOF'
#!/usr/bin/env bash
echo "当前shell脚本文件名:$0"
echo "第1个传入位置参数:$1"
echo "第2个传入位置参数:$2"
echo "第3个传入位置参数:$3"
echo "第4个传入位置参数:$4"
echo "第5个传入位置参数:$5"
echo "第6个传入位置参数:$6"
echo "第7个传入位置参数:$7"
echo "第8个传入位置参数:$8"
echo "第9个传入位置参数:$9"
echo "第10个传入位置参数:${10}"
echo "第11个传入位置参数:${11}"
echo "第12个传入位置参数:${12}"
echo "提取所有的位置参数,使用\$*提取:$*"
echo "提取所有的位置参数,使用\$@提取:$@"
echo "位置参数总个数是:$#"
echo "当前shell脚本执行的pid是:$$"
echo "上一个命令的执行返回结果是:$?"
EOF

执行结果

image-20220615155706152

使用场景?

例如启动脚本的 start|stop|restart

打开/etc/init.d/network 看看就知道了

脚本参数$*和$@区别

在 Bash 脚本中,$*$@ 都可以用来获取传递给脚本的所有参数,但它们在处理参数时存在一些细微的差别,以下是具体的解释和示例:

一、$* 的用法和特点

当使用 $* 时,它会将所有的参数作为一个整体,其中参数之间的分隔符是脚本的第一个字符(通常是一个空格),除非 IFS(内部字段分隔符)被修改。

以下是一个使用 $* 的示例脚本:

#!/bin/bash
echo "Using \$*: $*"
for arg in "$*"; do
    echo "Argument: $arg"
done

将上述脚本保存为 test.sh,并运行:

./test.sh arg1 arg2 arg3

输出结果如下:

Using $*: arg1 arg2 arg3
Argument: arg1 arg2 arg3

可以看到,在 for 循环中,$* 把所有的参数当成一个整体来处理,for 循环只迭代了一次。

二、$@ 的用法和特点

$@ 会将每个参数都视为一个独立的元素,即使参数中包含空格,也会将其作为一个单独的参数处理。

以下是一个使用 $@ 的示例脚本:

#!/bin/bash
echo "Using \$@: $@"
for arg in "$@"; do
    echo "Argument: $arg"
done

将上述脚本保存为 test.sh,并运行:

./test.sh arg1 arg2 arg3

输出结果如下:

Using $@: arg1 arg2 arg3
Argument: arg1
Argument: arg2
Argument: arg3

在这个示例中,for 循环会将每个参数分别迭代处理,因此会打印出三个不同的参数。

三、总结

  • $*:将所有参数作为一个整体,除非 IFS 被修改,否则参数之间默认以空格分隔。
  • $@:将每个参数视为一个独立的元素,在迭代参数时,更符合通常的使用习惯,尤其是当参数本身可能包含空格或其他特殊字符时,使用 $@ 会更加安全和方便。

四、使用注意事项

  1. 为了更安全地使用 $@,通常会将其放在双引号中,如 "$@",这样可以防止参数中的空格被错误解析。
  2. 在使用 $* 时,如果想要遍历参数,通常会结合 IFS 进行处理,以确保参数的分隔符符合预期。

以下是一个结合 IFS 修改的 $* 示例:

#!/bin/bash
# IFS 代表 "Internal Field Separator",即内部字段分隔符。在这里,将 IFS 的值设置为逗号 ,,这会改变默认的分隔符(通常是空格)。这意味着在后续的命令中,当需要分割字符串时,将使用逗号作为分隔符。
IFS=","
# set 命令通常用于设置脚本的位置参数。-- 是一个特殊标志,用于确保后面的参数被当作位置参数而不是选项。这里将位置参数设置为一个包含逗号分隔元素的字符串 "arg1,arg2,arg3"。
set -- "arg1,arg2,arg3"
echo "Using modified IFS with $*"
for arg in $*; do
    echo "Argument: $arg"
done

在这个示例中,我们将 IFS 修改为逗号,然后使用 $* 来遍历参数,结果如下:

Using modified IFS with arg1,arg2,arg3
Argument: arg1
Argument: arg2
Argument: arg3

通过这些示例,可以清楚地看到 $*$@ 在处理脚本参数时的区别和使用方法,在实际的脚本编写中,可以根据具体的需求来选择使用哪个变量。如果需要将参数作为一个整体处理,可能会使用 $*;如果需要分别处理每个参数,通常会使用 "$@"

4.1 实战开发

1编写脚本,通过位置参数形式,免交互的创建linux用户与密码。


[root@yuchao-tx-server ~/p3-shell]#cat user.sh
#!/bin/bash
# 位置参数变量
username="${1}"
pwd="${2}"

# 创建用户
useradd ${username}

# 设置密码
echo ${pwd} | passwd --stdin ${username}


# ubuntu下
echo "yuyu:123" | chpasswd

------------------------------------------------------------------------------------
执行
[root@yuchao-tx-server ~/p3-shell]#bash user.sh pyyu01 pyyu666
更改用户 pyyu01 的密码 。
passwd:所有的身份验证令牌已经成功更新。


登录测试
ssh pyyu01@ip

shell if语法

以下是一个简单的 bash 脚本,用于判断用户是否登录:

#!/bin/bash

# 获取当前登录用户
current_user=$(whoami)

if [ "$current_user" == "root" ]; then
  echo "当前用户是 root 用户。"
else
  echo "当前用户是 $current_user,不是 root 用户。"
fi

代码解释:

  • #!/bin/bash:这是一个 shebang 行,告诉系统使用 /bin/bash 解释器来执行这个脚本。
  • current_user=$(whoami):使用 whoami 命令获取当前登录用户,并将结果存储在 current_user 变量中。$(whoami) 是命令替换的一种形式,将命令的执行结果存储到变量中。
  • if [ "$current_user" == "root" ]; then:这是一个 if 语句,[ "$current_user" == "root" ] 是条件判断,使用 == 来比较 current_user 变量的值是否等于 "root"。注意这里的双引号是为了防止变量为空时出现语法错误,同时确保比较的是字符串内容而不是变量本身。
  • echo "当前用户是 root 用户。":如果 if 条件为真(即当前用户是 root),则执行该 echo 语句。
  • else:如果 if 条件为假,将执行 else 部分。
  • echo "当前用户是 $current_user,不是 root 用户。":输出当前用户的信息,表明不是 root 用户。

如果你想根据系统中是否有其他用户登录,可以使用 who 命令来检查:

#!/bin/bash

# 获取当前登录用户列表
logged_users=$(who)

if [ -n "$logged_users" ]; then
  echo "有用户登录:"
  echo "$logged_users"
else
  echo "没有用户登录。"
fi

代码解释:

  • logged_users=$(who):使用 who 命令获取当前登录用户的列表,并将结果存储在 logged_users 变量中。
  • if [ -n "$logged_users" ]; then-n 是一个测试操作符,用于检查变量 logged_users 的长度是否不为零。如果不为零,说明有用户登录。
  • echo "有用户登录:":如果有用户登录,输出相应信息。
  • echo "$logged_users":输出登录用户的详细信息。
  • else:如果没有用户登录,执行 else 部分。
  • echo "没有用户登录。":输出没有用户登录的信息。

以下是一个更复杂的脚本,它可以检查特定用户是否登录:

#!/bin/bash

# 要检查的用户
user_to_check="john"

# 获取当前登录用户列表
logged_users=$(who | awk '{print $1}')

# 使用循环和 grep 检查特定用户是否在登录列表中
user_found=false
for user in $logged_users; do
  if [ "$user" == "$user_to_check" ]; then
    user_found=true
    break
  fi
done

if [ "$user_found" == "true" ]; then
  echo "$user_to_check 已登录。"
else
  echo "$user_to_check 未登录。"
fi

代码解释:

  • user_to_check="john":定义要检查的用户为 "john"
  • logged_users=$(who | awk '{print $1}'):使用 who 命令获取登录用户列表,并使用 awk 命令提取用户名({print $1} 表示打印第一列,即用户名),将结果存储在 logged_users 变量中。
  • user_found=false:初始化一个标志变量 user_foundfalse,表示还未找到用户。
  • for user in $logged_users; do:使用 for 循环遍历 logged_users 中的每个用户。
  • if [ "$user" == "$user_to_check" ]; then:在循环中检查当前用户是否为要检查的用户。
  • user_found=true:如果找到用户,将 user_found 标志变量设置为 true
  • break:找到用户后,使用 break 终止循环,提高性能。
  • if [ "$user_found" == "true" ]; then:根据 user_found 标志变量的值输出相应信息。

程序优化

#!/bin/bash

# 让用户传入参数,第一个是用户名,第二个是密码
if [ $# -ne 2 ]; then
    echo  -e "\e[31m错误:请传入两个参数:用户名和密码\e[0m"
    echo "用法:$0 <username> <password>"
    exit 1
fi

username=$1
pwd=$2

# 验证用户是否已经存在
if id "$username" &>/dev/null; then
    echo "\e[33m警告:用户 '$username' 已存在\e[0m"
    exit 1
fi

# 创建用户
echo "\e[34m用户 '$username' 正在创建..\e[0m"
if useradd -m -s /bin/bash "$username"; then
    echo "\e[32m用户 '$username' 创建成功\e[0m"
else
    echo "\e[31m错误:创建用户 '$username' 失败\e[0m"
    exit 1
fi

# 设置密码
echo "$username:$pwd" | chpasswd
if [ $? -eq 0 ]; then
    echo "\e[32m密码设置成功\e[0m"
else
    echo "\e[31m错误:设置密码失败\e[0m"
    exit 1
fi

# 验证账户
id "$username"
if [ $? -ne 0 ]; then
    echo "\e[31m错误:无法验证用户 '$username'\e[0m"
    exit 1
fi

# 验证SSH登录
echo -e "\e[34m验证用户 '$username' 的SSH登录\e[0m"
sshpass -p "$pwd" ssh -o StrictHostKeyChecking=no "$username"@127.0.0.1 "whoami;hostname;date"
if [ $? -eq 0 ]; then
    echo -e "\e[32mSSH登录成功\e[0m"
else
    echo "\e[31m错误:SSH登录失败\e[0m"
    exit 1
fi

echo -e "\e[32m程序结束\e[0m"

优化点说明

  1. 输入校验:添加了参数个数检查,防止用户输入错误导致脚本执行失败。
  2. 用户存在性检查:提前验证用户是否已存在,避免重复创建引起错误。
  3. 结果判断:每个关键步骤后增加返回值判断,确保脚本执行稳定。
  4. SSH连接验证:使用 StrictHostKeyChecking=no 避免首次连接时卡住。
  5. 输出美化:使用 ANSI 转义序列为输出添加颜色,提升用户体验。
  6. 容错能力:在每个步骤失败后立即退出,并提供明确的错误信息。

sshpass非交互ssh登录

apt update
apt install -y sshpass
sshpass -p "password" ssh -o StrictHostKeyChecking=no username@remote_host
优先选择使用 SSH 密钥对,它更安全、更方便。
sshpass 和 expect 不推荐用于生产环境,因为密码以明文形式存在,存在泄露风险。

编写通过位置参数,自动修改主机名的脚本。

cat > set_hostname.sh <<'EOF'
#!/bin/bash
# 位置参数变量
hostname=${1}

# 设置主机名
hostnamectl set-hostname ${hostname}

# 检查当前主机名
echo "当前主机名:$(hostname)"
EOF

------------------------------------------------------------------------------------
执行

[root@yuchao-tx-server ~/p3-shell]#bash set_hostname.sh yuchaoit666
当前主机名:yuchaoit666

5. 交互式参数传递

利用read命令,接收用户输入,从键盘读取标准输入。


语法
read -p "提示信息" 变量名

5.1 read综合案例

#!/bin/bash

# 1. 基本的 read 命令使用
echo "请输入你的名字:"
read name
echo "你输入的名字是:$name"


# 2. 使用 read 命令读取多个变量
echo "请输入你的名字和年龄,用空格分隔:"
read name age
echo "你的名字是:$name,你的年龄是:$age"


# 3. 使用 read 命令读取数组
echo "请输入三个数字,用空格分隔:"
read -a numbers
echo "变量numbers的值,数组不能直接打印,否则结果是:$numberes"
echo "你输入的数字是:${numbers[0]}, ${numbers[1]}, ${numbers[2]}"


# 4. 使用 read 命令设置超时时间
echo -n "请在 5 秒内输入一些内容:"
if read -t 5 in_text; then
    echo "你输入的是:$in_text"
else
    echo "输入超时!"
fi


# 5. 使用 read 命令隐藏输入(例如密码输入)
echo -n "请输入你的密码:"
read -s password
echo
echo "你输入的密码是:$password"


# 7. 使用 read 命令结合重定向和管道
# for有限制的循环
# while无限制的循环

echo "以下是一个示例列表:"
echo "项羽 刘邦 曹操" | while read item; do
    echo "处理:$item"
done


# 8. 使用 read 命令结合循环
echo "请输入多行内容(输入 'end' 结束):"
while true; do
    read line
    if [[ "$line" == "end" ]]; then
        break
    fi
    echo "你输入的行:$line"
done


# 9. 使用 read 命令从命令输出中读取
echo "当前目录下的文件列表:"
ls | while read file; do
    echo "文件:$file"
done


# 10. 使用 read 命令的 -p 选项(提示信息)
read -p "请输入你的爱好:" hobby
echo "你的爱好是:$hobby"


# 11. 使用 read 命令的 -n 选项(限制输入长度)
read -n 4 -p "请输入最多 4 个验证码:" verify_code
echo
echo "你输入的验证码是:$verify_code"


# 12. 使用 read 命令的 -d 选项(指定分隔符)
echo "请输入一段以 '#' 结尾的文本:"
read -d '#' text
echo "你输入的文本是:$text"

5.2 备份目录小脚本

以下是使用 cprsync 命令编写的 bash 脚本,用于备份目录:

使用 cp 命令的备份脚本

#!/bin/bash

# 源目录
source_dir="/etc/"

# 目标目录,单独一块磁盘,
backup_dir="/backup_etc/"

# 检查源目录是否存在
# 先判断错误场景,最好处理
# 判断,逻辑有问题,程序直接就退出。。

if [ ! -d "$source_dir" ]; then
        # 如果这个目录存在,且是文件夹,则条件成立,进入这里
        # 进行下一步动作
    echo "源目录 $source_dir 不存在,请立即创建!再使用!"
    exit 1
fi

# 检查目标目录是否存在,如果不存在则创建
if [ ! -d "$backup_dir" ]; then
    mkdir -p "$backup_dir"
    echo "$backup_dir 已经创建!可以开始备份!"
fi

# 使用 cp 命令进行备份
cp -a "$source_dir" "$backup_dir"

echo "备份完成"

代码解释

  1. #!/bin/bash:这是一个 bash 脚本的头部,指定使用 bash 作为解释器。
  2. source_dirbackup_dir:定义源目录和目标目录的路径。
  3. if [! -d "$source_dir" ]; then...:检查源目录是否存在,如果不存在则打印错误信息并退出脚本。
  4. if [! -d "$backup_dir" ]; then...:检查目标目录是否存在,如果不存在则创建目标目录。
  5. cp -r "$source_dir" "$backup_dir":使用 cp 命令的 -r 选项进行递归复制,将源目录的内容复制到目标目录。
  6. echo "备份完成":输出备份完成的消息。

使用 rsync 命令的备份脚本

#!/bin/bash

# 源目录
source_dir="/path/to/source"
# 目标目录
backup_dir="/path/to/backup"

# 检查源目录是否存在
if [! -d "$source_dir" ]; then
    echo "源目录 $source_dir 不存在"
    exit 1
fi

# 检查目标目录是否存在,如果不存在则创建
if [! -d "$backup_dir" ]; then
    mkdir -p "$backup_dir"
fi

# 使用 rsync 命令进行备份
rsync -avzP "$source_dir" "$backup_dir"

echo "备份完成"

代码解释

  1. #!/bin/bash:指定 bash 作为解释器。
  2. source_dirbackup_dir:定义源目录和目标目录的路径。
  3. if [! -d "$source_dir" ]; then...:检查源目录是否存在,若不存在则报错并退出。
  4. if [! -d "$backup_dir" ]; then...:检查目标目录是否存在,若不存在则创建。
  5. rsync -avz "$source_dir" "$backup_dir"
    • -a:归档模式,递归并保留文件属性。
    • -v:显示详细信息。
    • -z:压缩文件数据,加快传输速度。
    • 此命令将源目录的内容同步到目标目录,仅传输更改的文件,并且可以处理符号链接等特殊文件类型。
  6. echo "备份完成":输出备份完成的消息。

使用说明

  1. 将上述脚本保存为 .sh 文件,例如 backup_with_cp.shbackup_with_rsync.sh
  2. 为脚本添加执行权限:
     chmod +x backup_with_cp.sh
     chmod +x backup_with_rsync.sh
    
  3. 运行脚本:
    ./backup_with_cp.sh
    ./backup_with_rsync.sh
    
  4. 根据实际情况修改 source_dirbackup_dir 的值,将 /path/to/source/path/to/backup 替换为你要备份的源目录和备份目标目录的真实路径。

cprsync 的比较

  • cp
    • 简单直接,适用于简单的备份任务。
    • 每次运行时会复制整个目录,即使文件未发生变化。
    • 不支持增量备份。
    • 对于大文件和大量文件的备份可能效率较低。
  • rsync
    • 更强大,支持增量备份,只复制修改过的文件。
    • 支持远程备份,可以通过 SSH 传输文件。
    • 可以压缩文件,加快传输速度。
    • 对于大文件和大量文件的备份更具优势,尤其是在网络环境中。

请根据实际需求选择使用 cprsync 进行目录备份。如果你需要更复杂的备份任务,如定时备份、远程备份等,可以结合 cron 任务调度和 rsync 的远程功能。

5.3 echo打印颜色(了解)

在Bash中,可以使用转义序列来设置终端文本的颜色。以下是详细内容:

1. 基本原理

  • 终端的颜色设置是通过发送特定的转义序列(escape sequence)来实现的。这些转义序列以\033[(也可以写成\e[)开头,后面跟着颜色代码和其他控制字符,最后以m结尾。
  • 当终端接收到这样的序列时,它会根据序列中的代码来改变后续文本的颜色或者其他显示属性。

2. 颜色代码

  • 文本颜色
    • 30:黑色
    • 31:红色
    • 32:绿色
    • 33:黄色
    • 34:蓝色
    • 35:洋红色
    • 36:青色
    • 37:白色
    • 例如,要将文本颜色设置为红色,可以使用\033[31m
  • 背景颜色
    • 40:黑色背景
    • 41:红色背景
    • 42:绿色背景
    • 43:黄色背景
    • 44:蓝色背景
    • 45:洋红色背景
    • 46:青色背景
    • 47:白色背景
    • 例如,要设置绿色背景,可以使用\033[42m

3. 其他显示属性

  • 除了颜色,还可以设置其他显示属性,如文本加粗、下划线等。
    • 1:加粗
    • 4:下划线
    • 例如,\033[1m会使后续文本加粗。

4. 示例脚本

# 输出红色文本
echo -e "\033[31m This text is red. \033[0m"
# 输出绿色背景的文本
echo -e "\033[42mThis text has a green background.\033[0m"
# 输出加粗的蓝色文本
echo -e "\033[4;34mThis text is bold and blue.\033[0m"

在上述示例中,\033[0m是非常重要的,它用于恢复终端的默认颜色和属性。如果不加上这部分,后续所有在该终端输出的文本都会保持之前设置的颜色或属性。

另外,有些终端模拟器可能还支持更高级的颜色设置,例如256色模式或者真彩色(RGB)模式。这些模式需要不同的转义序列来控制。在256色模式下,转义序列格式为\033[38;5;${COLOR_CODE}m用于设置文本颜色,\033[48;5;${COLOR_CODE}m用于设置背景颜色,其中${COLOR_CODE}是0 - 255之间的颜色代码。例如,\033[38;5;196m会将文本颜色设置为一种亮红色。

程序员也是有美感的,可以让你的程序显示更美观,echo命令的玩法。

# 其中43的位置代表背景色, 30的位置是代表字体颜色
echo -e "\033[43;30m 跟着于超老师学linux,没毛病!! \033[0m"

echo -e "\033[31m www.yuchaoit.cn红色字 \033[0m"
echo -e "\033[33m www.yuchaoit.cn黄色字 \033[0m"


echo -e "\033[41;33m www.yuchaoit.cn红底黄字 \033[0m"


echo -e "\033[41;37m www.yuchaoit.cn红底白字 \033[0m"

image-20220615164409945

颜色笔记

下面是相应的字和背景颜色,可以自己来尝试找出不同颜色搭配

例
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[41;33m 红底黄字 \033[0m"
echo -e "\033[41;37m 红底白字 \033[0m"

字颜色:30—–37
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[32m 绿色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天蓝字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"

字背景颜色范围:40—–47
echo -e "\033[40;37m 黑底白字 \033[0m"
echo -e "\033[41;37m 红底白字 \033[0m"
echo -e "\033[42;37m 绿底白字 \033[0m"
echo -e "\033[43;37m 黄底白字 \033[0m"
echo -e "\033[44;37m 蓝底白字 \033[0m"
echo -e "\033[45;37m 紫底白字 \033[0m"
echo -e "\033[46;37m 天蓝底白字 \033[0m"
echo -e "\033[47;30m 白底黑字 \033[0m"

最后面控制选项说明
\033[0m 关闭所有属性
\033[1m 设置高亮度
\033[4m 下划线
\033[5m 闪烁
\033[7m 反显
\033[8m 消隐

\033[30m — \33[37m

设置前景色
\033[40m — \33[47m 设置背景色
\033[nA 光标上移n行
\033[nB 光标下移n行
\033[nC 光标右移n行
\033[nD 光标左移n行
\033[y;xH设置光标位置
\033[2J 清屏
\033[K 清除从光标到行尾的内容
\33[s 保存光标位置
\033[u 恢复光标位置
\033[?25l 隐藏光标
\033[?25h 显示光标

5.4 用户输入综合小练习(实践)

脚本练习题,建议大家都先自己写,随便你用什么写法,只要完成结果。一百个人,写出100种风格也没问题。

需求

接收用户输入,创建用户,且将用户输入保存到文件/tmp/user_info.log,保存格式为username:pwd 键值对形式

# 分析如何做
1. 基础命令得熟练
2. 如何接受用户输入
3. 灵活使用变量


cat >user_info.sh<<'EOF'
# 接收输入,保存变量
read -p "please input your name:" username
read -p "please input user uid:"  uid
read -p "please input your pwd:" pwd

# 使用变量,创建用户
useradd -u $uid $username
echo "$pwd" | passwd --stdin $username
echo "${username}:${pwd}" >> /tmp/user_info.glo
EOF

------------------------------------------------------------------------------------
执行



[root@yuchaoit666 ~/p3-shell]#bash user_info.sh
please input your name:yuchao02
please input user uid:11222
please input your pwd:666666
更改用户 yuchao02 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@yuchaoit666 ~/p3-shell]#
[root@yuchaoit666 ~/p3-shell]#
[root@yuchaoit666 ~/p3-shell]#cat /tmp/user_info.glo
yuchao02:666666

# 优化改进就是,密码应该是不显示,来的更安全。
# 提示 -s参数

5.5 修改主机IP、主机名脚本(实践)

cat >modify_net_info.sh<<'EOF'
# 拿到需求之后,分析,思考如何,提取主机名,修改主机名,结合变量,用户输入的玩法
# 别忘记变量定义的规范


# 1.接收用户输入的信息
read -p  "请输入要修改的主机名: "   new_hostname
read -p  "请输入要修改的IP地址: "   new_ip

# 2.替换用户输入
hostnamectl set-hostname ${new_hostname}
sed -i "/IPADDR/c IPADDR=${new_ip}" /etc/sysconfig/network-scripts/ifcfg-eth0

# 3.检查修改
echo -e "当前主机名是:\033[41;37m $(hostname) \033[0m"
echo -e "当前网卡配置文件内容是\n$(cat /etc/sysconfig/network-scripts/ifcfg-eth0 )"
EOF

5.6 定时任务脚本

需求:通过交互式脚本,创建新的定时任务规则,要求

每十分钟和阿里云时间服务器同步。

# 思路
1. 先想好定时任务语句
2. 看要求,是交互式,还是非交互式
3. 如何写入定时任务规则



cat >create_crontab.sh <<'EOF'
#!/bin/bash
read -p "请输入crontab的时间频率:" cron_time
read -p "请输入crontab的具体命令:" cron_job

# 让用户输入生效
echo "#crontab by yuchao at $(date +%F)" >> /var/spool/cron/root
echo "$cron_time $cron_job" >> /var/spool/cron/root

echo -e "当前最新的计划任务是:\n$(crontab -l)"
EOF

------------------------------------------------------------------------------------
执行


[root@yuchaoit666 ~/p3-shell]#bash create_crontab.sh
请输入crontab的时间频率:*/5 * * *
请输入crontab的具体命令:ntpdate -u ntp.aliyun.com
当前最新的计划任务是:
#crontab by yuchao at 2022-06-15
*/5 * * * ntpdate -u ntp.aliyun.com

5.7 综合练习

要求:分别以交互式、非交互式两种开发脚本。


1. 创建3个用户,设置密码(限定11位密码,且隐藏显示),且记录到文件中/tmp/user_info.log,为如下格式
username:pwd
username:wpd
username:pwd


2. 编写修改主机名和IP的脚本,且立即生效

3. 编写设置定时任务的脚本,每五分钟和阿里云时间服务器同步,且不提示任何日志。

4. 写一个探测主机存活的脚本(提示用$?预定义变量)

6.预定义变量

刚才超哥演示了几个特殊变量,用于提取位置参数,这里再详细的看看用法,难点。

几个特殊的位置参数变量

$* 接收所有位置参数

$@ 接收所有位置参数

$# 参数个数

$$ 获取当前进程的PID

$? 上一个命令的返回值,0是正确,其他都错误(指的是你脚本上,查看$?的上一条命令)

6.1 测试预定义变量

cat >special_vars.sh <<'EOF'
echo "提取所有的位置参数,使用\$*提取:$*"
echo "提取所有的位置参数,使用\$@提取:$@"
echo "位置参数总个数是:$#"
echo "当前shell脚本执行的pid是:$$"
echo "上一个命令的执行返回结果是:$?"
EOF

------------------------------------------------------------------------------------
执行


[root@yuchaoit666 ~/p3-shell]#bash special_vars.sh bob01 jerry01 fake01
提取所有的位置参数,使用$*提取:bob01 jerry01 fake01
提取所有的位置参数,使用$@提取:bob01 jerry01 fake01
位置参数总个数是:3
当前shell脚本执行的pid是:22335
上一个命令的执行返回结果是:0

6.2 探测主机是否存活脚本

#!/bin/bash

# 检查输入参数是否正确
if [ $# -eq 0 ]; then
    echo "请输入要探测的主机 IP 或主机名列表,多个用空格分隔"
    exit 1
fi

# 循环遍历输入的主机列表
for host in $@; do
    # 使用 ping 命令探测主机,-c 1 表示发送一个 ICMP 包,-W 1 表示等待 1 秒超时
    if ping -c 1 -W 1 $host > /dev/null; then
        echo "$host 存活"
    else
        echo "$host 不存活"
    fi
done

6.3 获取所有位置参数(难点)

当我们想从脚本运行,传入的参数中获取具体的参数,可以用${n}

但是如果用户传入的参数不固定,你知道到底用 ${数字写多少合适?}

因此这时候,$* 和 $@ 的威力就来了,可以获取所有的位置参数(除了$0)

但是这俩变量虽说都是提取所有参数,但是提取之后的数据构造不一样,具体是什么,看于超老师给你演示一波。。

场景1,不加引号

1.当用户传入多个参数,都是以空格作为分隔符,获取每一个元素,此时$*和$@无区别。

2.注意此时,$*和$@ 这俩特殊变量,都没添加引号

cat >test_all_var.sh <<'EOF'

echo "----------------测试\$*,且无引号--------------"
for v in $*
do
    echo "传入的位置参数,分别是${v}"
done

echo "----------------测试\$@,且无引号--------------"
for v in $@
do
    echo "传入的位置参数,分别是${v}"
done
EOF

执行结果

[root@master-61 ~]#bash test_all_var.sh yu1 yu2 yu3 yu4 yu5
----------------测试$*,且无引号--------------
传入的位置参数,分别是yu1
传入的位置参数,分别是yu2
传入的位置参数,分别是yu3
传入的位置参数,分别是yu4
传入的位置参数,分别是yu5
----------------测试$@,且无引号--------------
传入的位置参数,分别是yu1
传入的位置参数,分别是yu2
传入的位置参数,分别是yu3
传入的位置参数,分别是yu4
传入的位置参数,分别是yu5
[root@master-61 ~]#

场景2,加上引号

1.此时用户传入的参数,比较特殊了,存在空格

2.因此你给特殊变量,加上引号,含义就不一样了!!

[root@master-61 ~]#cat test_all_var.sh 

echo "----------------测试\$*,且无引号--------------"
for v in "$*"
do
echo "传入的位置参数,分别是${v}"
done

echo "----------------测试\$@,且无引号--------------"
for v in "$@"
do
echo "传入的位置参数,分别是${v}"
done

执行结果

[root@master-61 ~]#bash test_all_var.sh yu1 yu2 yu3 yu4
----------------测试$*,且无引号--------------
传入的位置参数,分别是yu1 yu2 yu3 yu4
----------------测试$@,且无引号--------------
传入的位置参数,分别是yu1
传入的位置参数,分别是yu2
传入的位置参数,分别是yu3
传入的位置参数,分别是yu4
[root@master-61 ~]#

结论、你会发现

"$*" 添加了引号的变量是: 将位置参数作为了一个大字符串整体,单个的参数。

"$@" 添加了引号的变量是: 依然是以空格区分位置参数


但是,你确认你的想法正确吗?再来一个场景。。

场景3,位置参数比较特殊

image-20220615185329880

[root@master-61 ~]#cat test_all_var.sh 

echo "----------------测试\$*,且无引号--------------"
for v in "$*"
do
echo "传入的位置参数,分别是${v}"
done

echo "----------------测试\$@,且无引号--------------"
for v in "$@"
do
echo "传入的位置参数,分别是${v}"
done

总结

当你要将所有位置参数,多个参数,识别为一个整体,就用 "$*"

当你要单独的,提取为一个个参数,就用"$@" ,注意添加引号,否则没效果。

先记住语法,以后你会在高级脚本中,看到这个影子的。

image-20220615190458908

7.常量(了解)

常量在shell中没有严格的语法支持,但是只有约定俗称的写法(全大写),以及通过命令强制性转为常量;
在其他数据类型更丰富的语言中,支持设定

普通变量,如name,age(如果age设置为常量,你理解下是什么意思,这种bug,常出现于初级开发的代码中。。)

常量,如月份,是固定的12个月,因此只能设定为无法修改的值,就是常量。

shell设置常量

通过readonly设置为常量。

image-20220615190845585


image-20220615191026541

8. 数据类型

8.1 什么是数据类型

数据就是指如变量的值,如web_site="www.yuchaoit.cn", 后面这个引号里的,就是数据,值。

变量是用来反应、保持数据的状态,以及变化的,并且生活里,除了文字,还有整数,小数,等丰富的数据类型,需要我们通过计算机程序去表示。

8.2 shell语言

shell是一个解释型、弱类型、动态语言;

shell变量的数据类型,由解释器在运行shell时,决定是什么数据类型。

bash中变量默认都是字符串类型,虽说都是字符串类型,但是分为如下几个场景

  • 数值
    • age=18
  • 字符串
    • name=yuchao
  • 数组
    • students=("张三" "李四" "于超")
  • 列表
    • {1..10}
shell比较特殊,不像其他编程语言,有明确的数据类型,是什么就是什么。
也就是,虽说有这些类型的区分,但是shell都是当做字符串去存储。

如
name="yuchao"
age=18

这俩变量的值都被shell以字符串形式在内存中存储了,但是age的值是数字,它是可以计算的。
  1. 编译型、解释型

于超老师之前带着大家讲解,演示过
编译型的golang,需要go build
解释型的python,shell,需要用解释器执行代码

2.强类型、弱类型

强类型的golang,定义变量,以及数据类型,需要如下

var name string = "yuchao666"


----------------------------------------------------------------------------
弱类型的python,无须提前写好变量的类型

name = "yuchao666"  # 注意有空格

----------------------------------------------------------------------------
弱类型的shell,无须提前写好变量的类型
name="yuchao666"  # 注意无空格

----------------------------------------------------------------------------
因此强类型语言,定义变量,存储的数据,必须声明好它的类型,到底是string,还是int,还是floot

而弱类型的语言,数据类型是可以被忽略的,且修改变量的值之后,数据类型是可以随意切换的,没那么严格。

----------------------------------------------------------------------------

8.3 基本数据类型(shell中都以字符串对待)

数字

# int整型,存储如年龄、等级、身份证号、qq号、商品数量
age=10
level=3
qq_num=877348180
apple_count=9999999

----------------------------------------------------------------------------

# floot 浮点型,存储小数,如工资条,身高,体重
salary=8500.8
weight=175.53


----------------------------------------------------------------------------

数值类型变量支持的算术运算以及对应的算术运算符如下

加:+、+=、++
减:-、-=、--
乘:*、*=
除:/
取余 :%、%=

----------------------------------------------------------------------------


数值类型变量用于条件测试时支持的比较运算以及对应的运算符如下

等于:-eq
不等于:-ne
小于等于:-le
大于等于:-ge
大于:-gt
小于:-lt
逻辑与:&&
逻辑非:!
逻辑或:||

----------------------------------------------------------------------------


数值类型变量用于for循环时支持的比较运算以及对应的运算符如下

等于:==
不等于:!=
小于等于:<=
大于等于:>=
大于:>
小于:<
逻辑与:&&
逻辑非:!
逻辑或:||

字符串

# shell中,加了引号的就是字符串,但是shell比较特殊,默认都以字符串存储数据,因此字符串你可以省去引号,除非存在特殊符号,如空格等,必须加引号,否则有歧义。

如

[root@yuchaoit666 ~]#name=yuchao
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#name2="yuchao2"
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#name3="yu chao"
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $name $name2 $name3
yuchao yuchao2 yu chao

----------------------------------------------------------------------------


# 结论
1. 不加引号的普通字符串,且是连续的字符(建议加引号,更规范)
2. 带有空格的,必须加引号
3. 单引号,双引号作用不一样。


----------------------------------------------------------------------------

单引号、双引号的区别
" " 弱引用,引号内加载特殊符号的作用
' ' 强引用,引号内的特殊字符全部取消意义

[root@yuchaoit666 ~]#name="yuchao"
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo "${name} is 666"
yuchao is 666
[root@yuchaoit666 ~]#echo '${name} is 666'
${name} is 666

数组,作为难点,在后面讲。

9. 变量运算

9.1 变量运算语法

在Bash中,有多种变量运算符号,以下是一些常见的:

一、算术运算

  1. 加法(+)和减法(-)
    • 示例:
      • a=5; b=3; result=$(($a + $b)); echo $result,在这个例子中,首先定义了两个变量ab,分别赋值为5和3。然后通过$(())这种语法结构来进行算术运算,$(($a + $b))表示将ab相加,结果赋值给result变量,最后通过echo输出结果,这里会输出8。
      • 减法的操作类似,例如result=$(($a - $b)); echo $result会输出2。
  2. 乘法(*)和除法(/)
    • 示例:
      • 乘法:a=4; b=2; result=$(($a*$b)); echo $result,这里$(($a*$b))计算ab的乘积,输出为8。
      • 除法:a=6; b=3; result=$(($a / $b)); echo $result,输出为2。需要注意的是,在Bash中的除法运算结果是整数部分,如果要进行浮点数运算,需要使用其他工具如bc
  3. 取余(%)
    • 示例:a=7; b=3; result=$(($a % $b)); echo $result$(($a % $b))计算a除以b的余数,这里输出为1。
  4. 自增(++)和自减(--)
    • 自增:a=3; ((a++)); echo $a,先使用a的值,然后再将a的值加1,所以输出为4。也可以使用前置自增((++a)),这种情况下是先将a的值加1,然后再使用a的值。
    • 自减:a=5; ((a--)); echo $a,输出为4。同样,前置自减((--a))会先将a的值减1,然后再使用a的值。

二、赋值运算

  1. 简单赋值(=)
    • 示例:var=10,这将值10赋给变量var
  2. 复合赋值运算符
    • +=:例如a=5; ((a += 3)); echo $a,相当于a = a + 3,输出为8。
    • -=a=7; ((a -= 2)); echo $a,相当于a = a - 2,输出为5。
    • *=a=3; ((a *= 4)); echo $a,相当于a = a * 4,输出为12。
    • /=a=10; ((a /= 2)); echo $a,相当于a = a / 2,输出为5。
    • %=a=9; ((a %= 2)); echo $a,相当于a = a % 2,输出为1。

三、比较运算(常用于条件判断)

  1. 等于(==)和不等于(!=)
    • 示例:
      • a=5; b=5; if [ $a == $b ]; then echo "a equals b"; fi,这里通过[ ](注意[$a之间以及$b]之间都要有空格)来进行条件判断,$a == $b判断ab是否相等,如果相等则输出a equals b
      • 对于不等于的判断:a=3; b=5; if [ $a!= $b ]; then echo "a is not equal to b"; fi,会输出a is not equal to b
  2. 大于(>)、小于(<)、大于等于(>=)和小于等于(<=)
    • 示例:
      • a=7; b=3; if [ $a > $b ]; then echo "a is greater than b"; fi,会输出a is greater than b
      • 对于小于判断:a=2; b=4; if [ $a < $b ]; then echo "a is less than b"; fi,输出a is less than b
      • 大于等于:a=5; b=5; if [ $a >= $b ]; then echo "a is greater than or equal to b"; fi,输出a is greater than or equal to b
      • 小于等于:a=3; b=4; if [ $a <= $b ]; then echo "a is less than or equal to b"; fi,输出a is less than or equal to b

四、逻辑运算(常用于条件判断组合)

  1. 逻辑与(&&)和逻辑或(||)
    • 逻辑与示例:a=5; b=3; if [ $a -gt 3 ] && [ $b -lt 4 ]; then echo "Both conditions are met"; fi,这里-gt是大于的意思,[ $a -gt 3 ] && [ $b -lt 4 ]表示a大于3并且b小于4这两个条件同时成立时,才会输出Both conditions are met
    • 逻辑或示例:a=2; b=5; if [ $a -gt 3 ] || [ $b -gt 4 ]; then echo "At least one condition is met"; fi,因为b大于4,所以会输出At least one condition is met
顾名思义,就是对变量计算数值计算,支持如下运算符。
数值类型变量支持的算术运算以及对应的算术运算符如下

加:+、+=、++ 
减:-、-=、--
乘:*、*=
除:/
取余 :%、%=

9.2 支持shell变量运算的命令

expr                 # 只支持整数运算
$(( ))             # 双括号运算,只支持整数运算,效率更高
$[]                    # 整数运算,语法简洁
bc                    # 支持整数,小数点运算
awk                    # 支持整数,小数点运算
%                        # 取余运算

前言

1.通过如下命令,能够理解变量值是支持数学运算的,以及一些语法上的学习。

2.不同命令,语法不一样,做好笔记,了解,看懂即可。

expr

纯数字计算

image-20220616112403310

变量运算

[root@yuchaoit666 ~]#num1=6
[root@yuchaoit666 ~]#num2=2

# 加减乘除

[root@yuchaoit666 ~]#expr ${num1} + ${num2}
8
[root@yuchaoit666 ~]#expr ${num1} - ${num2}
4
[root@yuchaoit666 ~]#expr ${num1} \* ${num2}
12
[root@yuchaoit666 ~]#expr ${num1} / ${num2}
3
[root@yuchaoit666 ~]#

$(( ))

纯数值计算

[root@yuchaoit666 ~]#echo $(( 6+3))
9
[root@yuchaoit666 ~]#echo $(( 6-3))
3
[root@yuchaoit666 ~]#echo $(( 6*3))
18
[root@yuchaoit666 ~]#echo $(( 6/3))
2
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $(( 6/3+10*2))
22

变量计算

[root@yuchaoit666 ~]#num1=3
[root@yuchaoit666 ~]#num2=2

[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $((num1+num2))
5
[root@yuchaoit666 ~]#echo $((num1-num2))
1
[root@yuchaoit666 ~]#echo $((num1*num2))
6
[root@yuchaoit666 ~]#echo $((num1/num2))
1
[root@yuchaoit666 ~]#echo $((num1*num2+3*2))
12

$[]

[root@yuchaoit666 ~]#num1=8
[root@yuchaoit666 ~]#num2=2
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $[num1+num2]
10
[root@yuchaoit666 ~]#echo $[num1-num2]
6
[root@yuchaoit666 ~]#echo $[num1*num2]
16
[root@yuchaoit666 ~]#echo $[num1/num2]
4
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $[num1/num2+3]
7

let

[root@yuchaoit666 ~]#let result=num1+num2
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $result
10


[root@yuchaoit666 ~]#let result=num1-num2
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $result
6

[root@yuchaoit666 ~]#let result=num1*num2
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo $result
16

bc

[root@yuchaoit666 ~]#num1=10
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#num2=2


[root@yuchaoit666 ~]#echo $num1+$num2 |bc
12

[root@yuchaoit666 ~]#echo $num1-$num2 |bc
8
[root@yuchaoit666 ~]#echo $num1*$num2 |bc
20
[root@yuchaoit666 ~]#echo $num1/$num2 |bc
5

awk运算

awk调用shell变量是双引号+单引号,双引号是保证正确处理变量值的空格。
awk支持小数处理。


[root@yuchaoit666 ~]#num1=10
[root@yuchaoit666 ~]#num2=3
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#awk 'BEGIN{print "'$num1'" + "'$num2'" }'
13
[root@yuchaoit666 ~]#awk 'BEGIN{print "'$num1'" - "'$num2'" }'
7
[root@yuchaoit666 ~]#awk 'BEGIN{print "'$num1'" * "'$num2'" }'
30
[root@yuchaoit666 ~]#awk 'BEGIN{print "'$num1'" / "'$num2'" }'
3.33333

9.3 变量计算练习题(至少3个写法)

  1. 根据系统时间打印出,今天、明天的时间;至少3种写法



[root@yuchaoit666 ~]#echo "Today is $(date +%d) 号"
Today is 16 号

写法1 $(())
[root@yuchaoit666 ~]#echo "Tomorrow  is $(($(date +%d)+1)) 号"
Tomorrow  is 17 号

写法2 $[]
[root@yuchaoit666 ~]#echo "Tomorrow  is $[$(date +%d)+1] 号"
Tomorrow  is 17 号


写法3  expr
[root@yuchaoit666 ~]#echo "Tomorrow is $(expr $(date +%d) + 1 ) 号"
Tomorrow is 17 号

2.根据系统当前时间,今年还剩下多少个星期

[root@yuchaoit666 ~]#echo "今年已经过了 $(date +%j) 天"
今年已经过了 167 天
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#echo "今年还剩下$(($(date +%j)/7)) 个星期 "
今年还剩下23 个星期

[root@yuchaoit666 ~]#echo "今年还剩下$[$(date +%j)/7] 个星期 "
今年还剩下23 个星期

[root@yuchaoit666 ~]#echo "今年还剩下$(expr $(date +%j) / 7) 个星期 "
今年还剩下23 个星期

3.写一个计算机程序

要求如下形式

请输入数值1:
请输入运算符:
请输入数值2:
计算结果是:
cat > calc.sh <<'EOF'
# 数据输入
read -p "请输入数值1:" num1
read -p "请输入运算符:" symbol
read -p "请输入数值2:" num2

# 结果计算
echo "计算结果是 $[ ${num1} ${symbol} ${num2} ]"
EOF

计算

[root@yuchaoit666 ~]#bash calc.sh
请输入数值1:10
请输入运算符:+
请输入数值2:2
计算结果是 12
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#bash calc.sh
请输入数值1:10
请输入运算符:-
请输入数值2:2
计算结果是 8
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#bash calc.sh
请输入数值1:10
请输入运算符:*
请输入数值2:2
计算结果是 20
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#
[root@yuchaoit666 ~]#bash calc.sh
请输入数值1:10
请输入运算符:/
请输入数值2:2
计算结果是 5

10.今日作业

1. 你理解的shell脚本是什么,作用,使用场景有哪些;
2. shell脚本书写规范;
3. shell定义变量的方式;
4. shell如何引用变量;
5. shell脚本的预定义变量的含义(面试经常问,笔试题常见)
$0
$1
$2
$*
$@
$#
$$
$?

6. 变量的运算方式有哪些。

7. 今日课堂练习讲解动手练,做笔记;

练习题

8. 写脚本,提取当前机器的静态属性,包括如下,写入文件/tmp/server_info.log
系统版本
内核版本
主机名
eth0网卡ip
eth1网卡ip
当前主机的公网ip
内存使用率
磁盘使用率
CPU使用率



9.开发计算器脚本,支持加减乘除即可。看看谁的计算器更牛。

11.补充:变量子串

shell子串

子串就是一个完整字符串的一部分,通过shell特有语法截取。

${变量}                                      返回变量值
${#变量}                                  返回变量长度,字符长度
${变量:start}                     返回变量Offset数值之后的字符
${变量:start:length}   提取offset之后的length限制的字符 
${变量#word}                          从变量开头删除最短匹配的word子串
${变量##word}                      从变量开头,删除最长匹配的word
${变量%word}                         从变量结尾删除最短的word
${变量%%word}                     从变量结尾开始删除最长匹配的word
${变量/pattern/string}  用string代替第一个匹配的pattern
${变量//pattern/string} 用string代替所有的pattern

案例

子串基本用法

Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。

从指定位置开始截取

这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串。

既然需要指定起始位置,那么就涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数。答案是 Shell 同时支持两种计数方式。

\1) 从字符串左边开始计数

如果想从字符串的左边开始计数,那么截取字符串的具体格式如下:

${string: start :length}

其中,string 是要截取的字符串,start 是起始位置(从左边开始,从 0 开始计数),length 是要截取的长度(省略的话表示直到字符串的末尾)。

[root@chaogelinux ~]# name="chao"
[root@chaogelinux ~]# echo ${name}
chao

[root@chaogelinux ~]# echo ${#name}
4

# 从start位置开始截取
[root@chaogelinux ~]# echo ${name:3}
o
[root@chaogelinux ~]# echo ${name:2}
ao
[root@chaogelinux ~]# echo ${name:1}
hao

# 指定start,以及元素长度
[root@chaogelinux ~]# echo ${name:1:2}
ha

计算变量值,长度的玩法

# 计算变量值,长度的玩法

[root@chaogelinux ~]# echo $name|wc -L #计算字符串长度
11
# 解释
# 打印行数
[root@chaogelinux shell_program]# cat test.txt |wc -l
2
# 打印最长行数的元素个数
[root@chaogelinux shell_program]# cat test.txt |wc -L
5

[root@chaogelinux ~]# expr length "$name"  #expr的length函数计算长度
11

[root@chaogelinux ~]# echo "$name" | awk '{print length($0)}'    #用awk的length函数
11

#最快的方式
[root@chaogelinux ~]# echo ${#name}
11

字符串长度计算,多种方法,谁最快?

速度比较

# 最快方式
# seq -s 指定分隔符
# seq -s ":" 100
# 执行3次打印的命令,打印出一个指定了分隔符的1~100的序列
for n in {1..3};do char=`seq -s ":" 100`;echo ${char} ;done

# 实践
[root@chaogelinux ~]# time for n in {1..10000};do char=`seq -s "chaoge" 100`;echo ${#char} &>/dev/null;done

real    0m11.041s
user    0m4.585s
sys    0m6.232s

#计算速度很慢,管道符和wc -L
[root@chaogelinux ~]# time for n in {1..10000};do char=`seq -s "chaoge" 100`;echo ${char}|wc -L &>/dev/null;done

real    0m38.577s
user    0m15.394s
sys    0m22.491s

# 性能还不错
[root@chaogelinux ~]# time for n in {1..10000};do char=`seq -s "chaoge" 100`;expr length "${char}" &>/dev/null;done

real    0m21.053s
user    0m8.673s
sys    0m11.944s

# awk再次加工,最慢
[root@chaogelinux ~]# time for n in {1..10000};do char=`seq -s "chaoge" 100`;echo ${char}|awk '{print length($0)}' &>/dev/null ;done

real    0m33.728s
user    0m13.839s
sys    0m19.121s

shell编程,尽量用内置系统操作,与内置函数

截取字符串

基本语法

# 从开头删除匹配最短
## 从开头删除匹配最长
% 从结尾删除匹配最短
%% 从结尾删除匹配最长

# 指定字符内容截取
a*c  匹配开头为a,中间任意个字符,结尾为c的字符串
a*C  匹配开头为a,中间任意个字符,结尾为C的字符串




#语法
name="yuchao"  # 该变量的值,有索引,分别是从 0,1,2,3,4开始

${变量}                                       返回变量值
${#name}                            返回变量长度,字符长度--------
${变量:start}                      返回变量start数值之后的字符,且包含start的数字
${变量:start:length}      提取start之后的length限制的字符 ,例如${name:4:1}
${变量#word}                           从变量开头删除最短匹配的word子串 ${name:yu}
${变量##word}                       从变量开头,删除最长匹配的word
${变量%word}                          从变量结尾删除最短的word
${变量%%word}                      从变量结尾开始删除最长匹配的word

替换
${变量/pattern/string}   用string代替第一个匹配的pattern
${变量//pattern/string}  用string代替所有的pattern

删除匹配的内容

[root@chaogelinux ~]# echo ${name} 
I am chaoge

[root@chaogelinux ~]# echo ${name:2:2} #第二个开始,取2个
am

[root@chaogelinux ~]# name2=abcABC123ABCabc
[root@chaogelinux ~]#
[root@chaogelinux ~]
[root@chaogelinux ~]#

# 从开头删除
[root@chaogelinux ~]# echo ${name2#a*C}  #从开头删除最短的a*C
123ABCabc

[root@chaogelinux ~]# echo ${name2##a*C}  #从开头删除最长的匹配
abc

# 从结尾删除
# 从结尾没有匹配到结果,原样返回
[root@chaogelinux ~]# echo ${name2%a*C}
abcABC123ABCabc

# 匹配到了就删除
[root@chaogelinux ~]# echo ${name2%a*c}
abcABC123ABC

# 匹配长的删除

# 删干净了,因为变量值name2=abcABC123ABCabc,匹配a*c,取最长的也就从前删到结尾
[root@chaogelinux ~]# echo ${name2%%a*c}

# 原样返回,因为从结尾开始匹配,压根就找不到a*C,因此不做处理

[root@chaogelinux ~]# echo ${name2%%a*C}
abcABC123ABCabc

替换字符串

[root@chaogelinux ~]# str1="Hello,man,i am your brother."
[root@chaogelinux ~]#
[root@chaogelinux ~]#
[root@chaogelinux ~]#
[root@chaogelinux ~]# echo $str1
Hello,man,i am your brother.
[root@chaogelinux ~]#
[root@chaogelinux ~]#
# 一个/ 替换匹配第一个合适的字符串
[root@chaogelinux ~]# echo ${str1/brother/sister}
Hello,man,i am your sister.

# 两个//,匹配所有的合适的字符串
# 替换所有的o为大写O
[root@chaogelinux ~]# echo ${str1//o/O}
HellO,man,i am yOur brOther.

删除文件名练习

删除所有图片文件名中的子串
[root@chaogelinux ~]# touch stu_102999_{1..5}_finished.jpg
[root@chaogelinux ~]# touch stu_102999_{1..5}_finished.png
[root@chaogelinux ~]# ll *.jpg *.png
-rw-r--r-- 1 root root 0 5月  26 18:05 stu_102999_1_finished.jpg
-rw-r--r-- 1 root root 0 5月  26 18:07 stu_102999_1_finished.png
-rw-r--r-- 1 root root 0 5月  26 18:05 stu_102999_2_finished.jpg
-rw-r--r-- 1 root root 0 5月  26 18:07 stu_102999_2_finished.png
-rw-r--r-- 1 root root 0 5月  26 18:05 stu_102999_3_finished.jpg
-rw-r--r-- 1 root root 0 5月  26 18:07 stu_102999_3_finished.png
-rw-r--r-- 1 root root 0 5月  26 18:05 stu_102999_4_finished.jpg
-rw-r--r-- 1 root root 0 5月  26 18:07 stu_102999_4_finished.png
-rw-r--r-- 1 root root 0 5月  26 18:05 stu_102999_5_finished.jpg
-rw-r--r-- 1 root root 0 5月  26 18:07 stu_102999_5_finished.png

1.去掉所有_finished字符串

思路:
1.单个文件去掉后缀,很简单
[root@chaogelinux str1]# mv stu_102999_1_finished.jpg stu_102999_1.jpg

2.通过子串的替换方式
[root@chaogelinux str1]# f=stu_102999_1_finished.jpg
[root@chaogelinux str1]#
[root@chaogelinux str1]#
# 变量的子串功能,去掉后缀
[root@chaogelinux str1]# echo ${f//_finished/}
stu_102999_1.jpg

# 利用变量的反引用替换文件名
[root@chaogelinux str1]# mv $f `echo ${f//_finished/}`

# 剩下的文件,利用循环操作
# 找出剩下所有需要替换的jpg文件
[root@chaogelinux str1]# ls *fin*.jpg
stu_102999_2_finished.jpg  stu_102999_3_finished.jpg  stu_102999_4_finished.jpg  stu_102999_5_finished.jpg
[root@chaogelinux str1]#

# 写shell循环代码,循环操作
# 去掉所有jpg文件的_finished后缀
[root@chaogelinux str1]# for file in `ls *fin*.jpg`;do mv $file `echo ${file//_finished/}`;done
[root@chaogelinux str1]# ls *.jpg
stu_102999_1.jpg  stu_102999_2.jpg  stu_102999_3.jpg  stu_102999_4.jpg  stu_102999_5.jpg
[root@chaogelinux str1]#

特殊shell扩展变量处理

语法

parameter,参数,范围

如果parameter变量值为空,返回word字符串
${parameter:-word} 


如果para变量为空,则word替代变量值,且返回其值
${parameter:=word}


如果para变量为空,word当作stderr输出,否则输出变量值
用于设置变量为空导致错误时,返回的错误信息
${parameter:?word}


如果para变量为空,什么都不做,否则word返回
${parameter:+word}

扩展变量实践

演示1

:-

[root@chaogelinux str1]# echo $chaoge

[root@chaogelinux str1]#
[root@chaogelinux str1]#
# 当chaoge没有值,heihei被返回,赋值给result
[root@chaogelinux str1]# result=${chaoge:-heihei}
[root@chaogelinux str1]#
[root@chaogelinux str1]# echo $result
heihei
# 要注意的是,此时chaoge还是空
[root@chaogelinux str1]# echo $chaoge

[root@chaogelinux str1]#


# 情况2,当chaoge变量有值时,该特殊扩展变量的符号,也就不起作用了
[root@chaogelinux str1]# echo $chaoge
pangzi

[root@chaogelinux str1]#
[root@chaogelinux str1]# result=${chaoge:-heihei}
[root@chaogelinux str1]# echo $result
pangzi
[root@chaogelinux str1]# echo $chaoge
pangzi

演示2

:=

该特殊情况用于保证变量始终有值

# 撤销变量
[root@chaogelinux str1]# echo $chaoge
[root@chaogelinux str1]# unset result
# 发现,hehe不但给了result,还给了chaoge变量
[root@chaogelinux str1]# result=${chaoge:=hehe}
[root@chaogelinux str1]# echo $result
hehe
[root@chaogelinux str1]# echo $chaoge
hehe

# 如果变量有值,什么事也不做
[root@chaogelinux str1]# result=${chaoge:apple}
[root@chaogelinux str1]# echo $result
hehe
[root@chaogelinux str1]# echo $chaoge
hehe

演示3

:?,当变量不存在时候,输出指定信息

[root@chaogelinux str1]# echo ${cc}

# 默认错误

[root@chaogelinux str1]# echo ${cc:?}
-bash: cc: 参数为空或未设置

[root@chaogelinux str1]# echo ${cc:?cc不存在}
-bash: cc: cc不存在

# 变量有值,则不做处理
[root@chaogelinux str1]# cc="happy"
[root@chaogelinux str1]# echo ${cc:?cc不存在}
happy

演示4

:+ 如果变量为空,什么都不做,否则替换

[root@chaogelinux str1]# unset cc result chaoge

# 为空
[root@chaogelinux ~]# result=${name:+chaoge}
[root@chaogelinux ~]# echo $result

[root@chaogelinux ~]# echo $name

[root@chaogelinux ~]#

# 不为空
[root@chaogelinux ~]# name="xiaoyu"
[root@chaogelinux ~]#
# 后面的值,返回给result
[root@chaogelinux ~]# result=${name:+chaoge}
[root@chaogelinux ~]# echo $result
chaoge
[root@chaogelinux ~]# echo $name
xiaoyu

扩展变量的应用场景

在脚本开发中,例如数据备份、删除的脚本

删除7天前的过期数据

[root@chaogelinux shell_program]# cat del_data.sh
find ${path:=/tmp} -name '*.tar.gz' -type f -mtime +7|xargs rm -f

# 上述就对path变量做了处理,否则如果path变量为定义,命令就会报错
# 有误的脚本,未指定path的路径,就会在当前目录删除,程序就有了歧义,bug
[root@chaogelinux shell_program]# cat del_data.sh
find ${path} -name '*.tar.gz' -type f -mtime +7|xargs rm -f
[root@chaogelinux shell_program]#
Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2025-01-19 19:36:44

results matching ""

    No results matching ""