1. Shell 基本用法

发布时间 2023-11-28 20:00:44作者: 谱次·

重点:

  1. 条件测试。
  2. read。
  3. Shell 环境配置。
  4. case。
  5. for。
  6. find。
  7. xargs。
  8. gzip,bzip2,xz。
  9. tar。
  10. sed。

1)编程基础

Linus 说:Talk is cheap, show me the code。

1.1)程序组成

截图.png

  1. 程序:算法 + 数据结构
  2. 数据:是程序的核心
  3. 数据结构:数据在计算机中的类型和组织方式
  4. 算法:处理数据的方式

 

1.2)程序编程风格

截图.png

实际上面向对象编程(OOP)和面向过程编程(Procedural Programming)并不是绝对的说辞而是两种不同的编程范式。

几乎所有编程语言都可以在某种程度上支持这两种编程范式。

面向 过程 语言

    1. 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况 A,做什么处理,如果出现了情况 B,做什么处理。
    2. 问题规模小,可以步骤化,按部就班处理。
    3. 以指令为中心,数据服务于指令。
    4. C,shell

 

面向 对象 语言

    1. 将编程看成是一个事物,对外界来说,事物是直接使用的,不用关心事物内部的情况。而编程就是设置事物能够完成功能。
    2. 一种认识世界、分析世界的方法论。将万事万物抽象为各种对象。
    3. 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
    4. 对象是类的具象,是一个实体
    5. 问题规模大,复杂系统
    6. 以数据为中心,指令服务于数据
    7. Java,C#,Python,golang 等

 

1.3)编程语言

编程语言排名链接

https://www.tiobe.com/tiobe-index/

截图.png

 

TIBOE 2021 年 1 月的最新编程语言流行度排名

截图.png

计算机:运行二进制指令

编程语言:人与计算机之间 交互的语言

分为两种:低级语言 和 高级语言。

 

低级编程 语言:

  1. 机器语言:二进制的 0 和 1 的序列,称为 机器指令。与自然语言差异太大,难懂、难写。
  2. 汇编语言:用一些助记符号替代机器指令,称为 汇编语言
    1. 如:ADD A,B 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中。
    2. 汇编语言写好的程序需要汇编程序转换成机器指令。
    3. 汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言。

 

高级编程 语言:

  1. 编译型语言:高级语言 --> 基于编译器 --> 编译成机器二进制代码文件 --> 执行。[ 整体编译执行 ]
    1. 如:CC++
  2. 解释型语言:高级语言 --> 执行 --> 基于解释器 --> 解释成机器二进制代码。 [ 逐行解释执行 ]
    1. 如:ShellPython,php,JavaScript,perl。

 

编译型语言 的代码在执行前需要通过一个称为编译器的特殊程序进行编译。

[ 例如:Windows 客户端工具 EXE ]

    1. 预先编译:源代码在执行前经过编译,生成目标代码,执行时直接运行目标代码,无需再次编译。
    2. 性能高:由于编译器将代码转换为机器码,执行效率通常较高。
    3. 跨平台:需要为每个平台编译不同的目标代码,因此可以实现跨平台运行。

 

解释型语言 的代码在执行时并不是直接转换为机器码或字节码,而是通过解释器逐行解释并执行。

[ 例如:Python Shell 脚本 1.py 1.sh ]

    1. 即时解释:源代码在执行时逐行解释,并实时执行,无需预先编译。
    2. 相对较慢:因为解释器需要逐行解释代码,相比编译型语言,执行效率较低。
    3. 跨平台:由于解释器的存在,通常不需要重新编译,因此可以实现跨平台运行。

 

总结:

编译型语言在执行前将代码编译成机器码,因此执行速度较快,但需要为不同平台编译不同的目标代码。

解释型语言在执行时逐行解释代码,因此执行速度相对较慢,但具有跨平台的优势。不同的语言适用于不同的场景,开发者可以根据需求选择合适的编程语言。

 

1. 低级编程语言 与 高级编程语言 的区别

截图.png

 

2. 编译器 和 解释器 的区别

截图.png

 

1.4)编程逻辑处理方式

三种处理逻辑

  1. 顺序执行:程序 按从上到下顺序执行
  2. 选择执行:程序执行过程中,根据条件的不同,进行选择不同分支继续执行
  3. 循环执行:程序执行过程中需要 重复执行多次某段语句

截图.png

截图.png

 

2)Shell 脚本语言的基本用法

2.1)Shell 脚本的用途

  1. 将简单的命令组合 完成复杂的工作自动化执行命令,提高工作效率。
  2. 减少手工命令的重复输入一定程度上避免人为错误。
  3. 将软件或应用的安装及配置 实现标准化
  4. 用于实现日常性的,重复性的运维工作,如:文件打包压缩备份,监控系统运行状态并实现告警等。

 

2.2)Shell 脚本基本结构

Shell 脚本编程:是基于过程式、解释执行的语言。

编程语言的基本结构:

  1. 各种系统 命令的组合
  2. 数据存储:变量、数组
  3. 表达式:a + b
  4. 控制语句:if

Shell 脚本:包含一些命令或声明,并符合一定格式的文本文件。

格式要求:首行 shebang 机制。

#!/bin/bash

#!/usr/bin/python

#!/usr/bin/perl

 

2.3)Shell 脚本创建过程

第一步:使用文本编辑器来 编写脚本文件。

第一行:必须包括 Shell 声明序列#!

示例:

#!/bin/bash

 

 

第二步:加执行权限

给予执行权限,在命令行上指定脚本的绝对或相对路径。

chmod +x

 

 

 

第三步:运行脚本

直接运行解释器,将脚本作为解释器程序的参数运行 bash

bash

 

[ 演示 ]

1. 编写脚本文件

vim hello.sh

#!/bin/bash

echo 'Hello world'

 

2. 赋予执行权限

chmod +x hello.sh

 

3. 调用 bash 执行脚本

bash hello.sh

截图.png

 

2.4)Shell 脚本 三种执行方式

1)将脚本放置 PATH 路径

echo $PATH

截图.png

 

3. 再次输入 hello.sh

系统基于调用命令的方式执行 hello.sh

hello.sh

截图.png

 

2)基于 软链接 方式

[ 将脚本文件引用到 /usr/local/bin 目录 ]

ln -s "脚本绝对路径" "软链接存放目录"

ln -s /root/hello.sh /usr/local/bin

 

ll /usr/local/bin

截图.png

 

系统成功基于调用 PATH 路径命令的方式执行 hello.sh

hello.sh

截图.png

 

3)基于 脚本绝对路径\相对路径 运行脚本

/root/hello.sh

./hello.sh

截图.png

 

4)无执行权限的脚本,如何执行

bash hello.sh

 

cat hello.sh | bash

 

# 执行远程主机的脚本

curl -s https://www.wangxiaochun.com/testdir/hello.sh | bash

wget -qO - https://www.wangxiaochun.com/testdir/hello.sh | bash

截图.png

截图.png

 

2.5)Shell 脚本注释规范

1、第一行一般为 调用使用的语言

2、程序名,避免更改文件名为无法找到正确的文件

3、版本号

4、更改后的时间

5、作者相关信息

6、该程序的作用,及注意事项

7、最后是各版本的 更新简要说明

 

备份脚本

mkdir /data

#!/bin/bash

#

# ********************************************************************

# Author: wangjun

# QQ: 1281570031

# Date: 2023-08-04

# FileName: backup.sh

# URL: https://linux.wuhanjiayou.cn

# Description: The test script

# Copyright (C): 2023 All rights reserved

# ********************************************************************

echo -e "\033[1;32mStarting backup...\033[0m"

sleep 2

cp -av /etc/ /data/etc`date +%F`/

echo -e "\033[1;32mBackup is finished\033[0m"

截图.png

截图.png

截图.png

 

2.6)Shell 脚本调试

总结:脚本错误常见的有三种

命令错误,默认后续的命令还会继续执行,用 bash -n 无法检查出来 ,可以使用 bash -x 进行观察。

语法错误,会导致后续的命令不继续执行,可以用 bash -n 检查错误,提示的出错行数不一定是准确的。

逻辑错误:只能使用 bash -x 进行观察。

 

2.6.1)命令错误

默认后续的命令 还会继续执行

并且脚本执行报错,会提示哪个位置出错

[00:29:23 root@blog ~]# cat hello.sh

#!/bin/bash

echo 'Hello world'

hosname

echo '123'

截图.png

# 定位到第 3 行

vim +3 hello.sh

 

2.6.2)语法错误

会导致后续的命令不继续执行

[00:36:49 root@blog ~]# cat hello.sh

#!/bin/bash

echo 'Hello world'

hostname

cat > /data/app.conf <<EOF

line1

line2

EoF

 

echo '123'

 

截图.png

 

[ 定位语法错误 ]

检测脚本中的 语法错误

无法检查出 命令错误不真正执行脚本。

bash -n hello.sh

 

[ 注意该行号信息不一定准确 仅能告知大概位置 ]

截图.png

 

修正语法后...

截图.png

bash hello.sh

截图.png

 

2.6.3)逻辑错误

使用 bash -x 进行观察 [ 调试执行 ]

运行 "bash -x hello.sh" 会以调试模式执行 "hello.sh" 脚本,显示每个命令在执行前的扩展状态,方便排查错误和调试。

bash -x hello.sh

 

截图.png

 

2.6.4)案例展示

curl -s https://www.wangxiaochun.com/testdir/system_info.sh | bash

截图.png

 

3)变量

变量 表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据。

 

变量类型:

内置变量如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE。

用户自定义变量

 

不同的变量存放的数据不同,决定了以下:

  1. 数据存储方式
  2. 参与的运算
  3. 表示的数据范围

 

变量数据类型

  1. 字符
  2. 数值:整型、浮点型,bash 不支持浮点数

 

编程语言分类

截图.png

 

静态和动态语言

  1. 静态编译语言:使用变量前,需先声明变量类型,之后类型不能改变,在编译时检查,如:Java,C
  2. 动态编译语言:无需事先声明,可随时改变类型,如:Bash,Python

 

强类型和弱类型语言

  1. 强类型语言:不同类型数据操作必须经过强制转换才同一类型才能运算,如 Java , C#,Python。
  2. 参考以下 python 代码弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用。如:Bash ,php,Javascript。
    1. print('magedu'+ 10) 提示出错,不会自动转换类型
    2. print('magedu'+str(10)) 结果为magedu10,需要显示转换类型

 

3.1)Shell 变量命名

命名要求

  1. 区分大小写
  2. 不要使用系统自带的内置变量或系统命令等
    1. 如:hostname,ls,who,PATH 等
  3. 只能使用数字、字母及下划线,且不能以数字开头,
    1. 注意:不支持短横线 "-",和主机名相反。

 

命令习惯

  1. 见名知义,用英文单词命名,并体现出实际作用,不要用简写
  2. 变量名大写
  3. 局部变量小写
  4. 函数名小写
  5. 部分 企业命名 规范
    1. 大驼峰StudentFirstName
    2. 小驼峰studentFirstName
    3. 下划线student_name

 

变量定义和引用

变量的生效范围等标准划分变量类型

  1. 普通变量:生效范围为当前 shell 进程;对当前 shell 之外的其它shell进程,包括当前 shell 的子 shell 进程均无效。
  2. 环境变量:生效范围为当前 shell 进程及其子进程。
  3. 本地变量:生效范围为当前 shell 进程中某代码片断,通常指函数。

 

3.2)变量赋值

# 变量赋值的正确写法

name='wangj'

 

# 变量赋值的错误写法

# 这种写法用于比较值是否相同: 如 if [ "$name" = "wangj" ]

name = 'wangj'

 

value 还可以是以下多种形式

[ 注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束,也会自动删除 ]

直接字串: name='root'

变量引用: name="$USER"

命令引用: name=`COMMAND` 或者 name=$(COMMAND)

 

3.3)变量引用

弱引用和强引用

  1. "$name" 强引用,其中的变量引用 会被替换 为变量值
  2. '$name' 弱引用,其中的变量引用 不会被替换 为变量值,而保持原字符串。

$name

${name}

 

3.4)示范案例

[ 示范 1 ]

变量的各种赋值方式和引用

# 变量赋值

TITLE='Linux 运维工程师'

 

# 变量引用

echo $TITLE

 

# 强引用

echo "Im a $TITLE"

 

# 弱引用

echo 'Im a $TITLE'

截图.png

 

[ 示范 2 ]

花括号的用途

# 变量赋值

NAME='wangj'

TITLE='Linux'

 

# 变量引用

# 以 : 冒号作为变量值分隔符

echo $NAME:$TITLE

 

# 变量引用

# 以 _ 下划线作为变量值分隔符

echo $NAME_$TITLE

 

截图.png

 

# 使用花括号

echo ${NAME}_$TITLE

截图.png

 

# 取消赋值

unset NAME

截图.png

 

[ 示范 3 ]

调用 命令执行结果 为变量值

NAME=`whoami`

echo $NAME

 

SDA1_UUID=`lsblk -f | grep root | awk '{print $3}'`

echo $SDA1_UUID

截图.png

LOG=`ls /var/log/messages*`

echo $LOG

截图.png

 

[ 示范 4 ]

变量追加值 TITLE+=:wang

TITLE='CTO'

TITLE+=:wang

echo $TITLE

截图.png

 

[ 示范 5 ]

利用变量 实现动态命令

CMD=hostname

 

$CMD

截图.png

 

[ 示范 6 ]

#!/bin/bash

#

#********************************************************************

#Author: wangxiaochun

#QQ: 29308620

#Date: 2019-12-23

#FileName: systeminfo.sh

#URL: http://www.magedu.com

#Description: Show system information

#Copyright (C): 2019 All rights reserved

#********************************************************************

RED="\E[1;31m"

GREEN="echo -e \E[1;32m"

END="\E[0m"

 

 

$GREEN----------------------Host systeminfo--------------------$END

echo -e "HOSTNAME: $RED`hostname`$END"

#echo -e "IPADDR: $RED` ifconfig eth0|grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' |head -n1`$END"

echo -e "IPADDR: $RED` hostname -I`$END"

echo -e "OSVERSION: $RED$PRETTY_NAME$END"

echo -e "KERNEL: $RED`uname -r`$END"

echo -e "CPU: $RED`lscpu|grep '^Model name'|tr -s ' '|cut -d : -f2`$END"

echo -e "MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"

echo -e "DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"

$GREEN---------------------------------------------------------$END

截图.png

 

显示已定义的所有变量

set

set | grep -n TITLE

截图.png

 

3.5)优化脚本

#!/bin/bash

#

# ********************************************************************

# Author: wangjun

# QQ: 1281570031

# Date: 2023-08-04

# FileName: backup.sh

# URL: https://blog.wuhanjiayou.cn

# Description: The test script

# Copyright (C): 2023 All rights reserved

# ********************************************************************

COLOR='echo -e \E[1;35m'

END='\E[0m'

BACKUP_DIR='/backup'

SRC_DIR='/etc'

DATE=`date +%F`

 

 

${COLOR} Starting backup... ${END}

sleep 2

 

cp -av ${SRC_DIR} ${BACKUP_DIR}${SRC_DIR}_$DATE

${COLOR} Backup is finished ${END}

截图.png

 

3.6)练习题

1、编写脚本 systeminfo.sh,显示当前主机系统信息,包括:主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小。

#!/bin/bash

 

# ANSI颜色代码

RED='\033[0;31m'

GREEN='\033[0;32m'

YELLOW='\033[0;33m'

NC='\033[0m' # 恢复默认颜色

 

# 获取主机名

hostname=$(hostname)

 

# 获取IPv4地址

ipv4_address=$(hostname -I | cut -d' ' -f1)

 

# 获取操作系统版本

os_version=$(cat /etc/os-release | grep "PRETTY_NAME" | cut -d'=' -f2 | tr -d '"')

 

# 获取内核版本

kernel_version=$(uname -r)

 

# 获取CPU型号

cpu_model=$(lscpu | grep "Model name" | cut -d':' -f2 | xargs)

 

# 获取内存大小

memory_size=$(free -h | awk '/Mem/ {print $2}')

 

# 获取硬盘大小

disk_size=$(df -h / | awk 'NR==2 {print $2}')

 

# 显示系统信息并高亮显示

echo -e "主机名: ${RED}$hostname${NC}"

echo -e "IPv4地址: ${GREEN}$ipv4_address${NC}"

echo -e "操作系统版本: ${YELLOW}$os_version${NC}"

echo -e "内核版本: ${GREEN}$kernel_version${NC}"

echo -e "CPU型号: ${YELLOW}$cpu_model${NC}"

echo -e "内存大小: ${GREEN}$memory_size${NC}"

echo -e "硬盘大小: ${YELLOW}$disk_size${NC}"

 

2、编写脚本 backup.sh,可实现每日将 /etc/ 目录备份到 /backup/etcYYYY-mm-dd中

#!/bin/bash

 

# 源目录

source_dir="/etc"

 

# 备份目录

backup_dir="/backup"

 

# 获取当前日期, 格式为YYYY-mm-dd

current_date=$(date +%Y-%m-%d)

 

# 创建备份目录, 如果不存在

if [ ! -d "$backup_dir" ]; then

mkdir -p "$backup_dir"

fi

 

# 备份文件的完整路径

backup_file="$backup_dir/etc$current_date.tar.gz"

 

# 执行备份

tar -czvf "$backup_file" "$source_dir"

 

# 检查备份是否成功

if [ $? -eq 0 ]; then

echo "备份成功:$backup_file"

else

echo "备份失败"

fi

 

3、编写脚本 disk.sh,显示当前硬盘分区中空间利用率最大的值

#!/bin/bash

 

# ANSI颜色代码

RED='\033[0;31m'

GREEN='\033[0;32m'

YELLOW='\033[0;33m'

NC='\033[0m' # 恢复默认颜色

 

# 使用df命令获取硬盘分区信息,将其按利用率降序排序,并提取最大利用率的行

max_usage_partition=$(df -h | awk '{print $5 " " $6}' | sort -k1 -n -r | head -n 1)

 

# 提取最大利用率和分区路径

max_usage=$(echo $max_usage_partition | awk '{print $1}')

partition_path=$(echo $max_usage_partition | awk '{print $2}')

 

# 显示最大利用率的分区信息并高亮显示

echo -e "最大利用率分区: ${YELLOW}$partition_path${NC},利用率: ${RED}$max_usage${NC}"

截图.png

 

4、编写脚本 links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序

#!/bin/bash

 

# 使用netstat命令获取连接信息,筛选出IPv4地址,按连接数降序排序,并提取唯一的远程主机IP和连接数

connection_info=$(netstat -n -t | grep ESTABLISHED | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | sort | uniq -c | sort -nr)

 

# 显示每个远程主机的IPv4地址和连接数

echo "远程主机 IPv4 地址 和 连接数:"

echo "$connection_info"

 

4)环境变量

环境变量

    1. 可以 使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量。
    2. 一旦子进程 修改 从父进程继承的变量将会新的值传递给孙子进程。
    3. 一般只在 系统配置文件 中使用,在脚本中较少使用。

# 输出当前终端的进程 PID

echo $BASHPID

 

# 在当前终端执行进程

sleep 10000

截图.png

左边的进程 即是 右边进程的 父进程

截图.png

 

常规 变量

# 父进程定义变量

NAME='wangj'

echo $NAME

 

# 子进程并不会继承父进程的变量信息

bash

echo $NAME

截图.png

 

环境 变量

声明与赋值

"声明并赋值" 环境变量

export NAME=wangj

 

# 引用变量

echo $NAME

 

# 子进程会继承父进程的环境变量

bash

echo $NAME

截图.png

 

显示所有环境变量

env

export

printenv

截图.png

 

面试题:

查看 指定进程 引用的环境变量

# 用法

cat /proc/$PID/environ

 

# 示例

cat /proc/14798/environ

cat /proc/14798/environ | tr '\0' '\n'

截图.png

 

5)只读变量

只读变量:只能声明定义,后续不能修改和删除即常量。

# 声明只读变量

readonly AGE=18

 

# 引用变量

echo $AGE

 

# 修改变量

AGE=20

 

# 删除变量

unset AGE

截图.png

只读变量确实无法在当前会话中被修改或删除。

但由于常规变量是在终端的父进程上赋值的,所以在 退出终端后重新打开 时,这些变量会被重置为它们的初始值。

如果您希望在终端会话之间保留变量的值,可以考虑将它们保存在配置文件中,如 .bashrc 。这样每次打开终端时,这些变量会被自动加载,而不会被重置。

 

查看只读变量

readonly -p

截图.png