shell脚本基础和脚本循环

shell基础

概述

Shell(外壳)是在 Linux/Unix 系统中用于与操作系统进行交互的一种命令行界面。
Shell 的主要功能包括:

  1. 命令执行:用户可以在 Shell 中输入命令,系统会解释并执行这些命令。
  2. 文件操作:Shell 提供了一系列用于创建、删除、移动、复制文件的命令。
  3. 进程管理:Shell 可以用来启动、停止、监控后台进程。
  4. 权限管理:Shell 允许用户查看和修改文件和目录的权限。
  5. 变量操作:Shell 支持变量的创建、赋值和引用。
  6. 输入输出重定向:Shell 允许用户将命令的输出重定向到文件,或者从文件读取输入。
  7. 管道和重定向:Shell 支持将一个命令的输出作为另一个命令的输入,这种操作称为管道;同时支持将命令的输出重定向到文件或者从文件读取输入。
  8. 脚本编程:Shell 提供了一种脚本语言,用户可以通过编写 Shell 脚本来自动化执行一系列命令。

常见的 Shell 类型有:

  • Bash:GNU Bourne-Again SHell,是 Linux 发行版中最常用的 Shell。
  • sh:Bourne Shell,是 Unix 系统中最早的 Shell。
  • zsh:Z Shell,是一个功能强大的交互式Shell,用于Unix和类Unix系统(如Linux和macOS),提供了许多有用的功能,如命令补全、命令纠错等。
  • fish:Friendly Interactive SHell,是一个用户友好的 Shell,提供了丰富的功能和易于使用的界面。
  • csh、tcsh:C Shell,是 C 语言风格的 Shell,提供了命令补全和命令纠错等功能。
  • nologin:奇怪的shell,作用是禁止用户登录

Shell脚本的一些主要应用场景:

  • 自动化任务

  • 系统管理

  • 日志分析

  • 数据处理

  • 软件部署和发布

  • 小型工具开发

  • 网络管理

  • 信息安全管理

  • 持续集成和自动化测试

shell脚本的构成

基本的Shell脚本模板:

#!/bin/bash
# 这是一个Shell脚本示例
# 文件名: /root/first.sh
# 该脚本的功能是显示一个简单的欢迎信息

# 使用echo命令输出信息
echo "欢迎来到Shell脚本的世界!"

# 下面是一个带有变量的示例
NAME="用户"
echo "你好, $NAME!"

# 使用if语句进行条件判断
if [ -f /root/testfile.txt ]; then
    echo "/root/testfile.txt 文件存在。"
else
    echo "/root/testfile.txt 文件不存在。"
fi

# 使用循环遍历一个序列
for i in {1..5}
do
    echo "这是第 $i 次循环。"
done

# 使用函数封装一段代码
greet() {
    echo "你好,从函数greet()传来的问候!"
}

# 调用函数
greet

Shell脚本的执行

方法一:指定路径的命令
  • 要求:文件必须有执行(x)权限。

  • 使用chmod +x添加执行权限

    chmod +x /root/first.sh
    
  • 执行脚本

    • 指定绝对路径

      /root/first.sh
      
    • 指定相对路径(假设你已经在脚本所在的目录):

      ./first.sh
      

      注意:如果当前目录不在PATH环境变量中,使用./来指定当前目录是必需的。

方法二:指定Shell来解释脚本
  • 不要求文件必须有执行权限

  • 使用shbash直接执行

    sh /path/to/first.sh
    bash /path/to/first.sh
    

    这里/path/to/first.sh是脚本的路径,可以使用绝对路径或相对路径。

  • 使用source.命令

    source /path/to/first.sh
    . /path/to/first.sh
    

    这两个命令效果相同,都是在当前shell环境中执行脚本中的命令,而不是创建一个新的子shell。

  • source命令用于在当前shell环境中读取并执行指定脚本中的命令。这意味着脚本中定义的变量或函数会在执行后被保留在当前shell环境中。(source读取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。)

子shell
  • 目的:多进程并行处理任务。
  • 产生方式:使用bash或其他shell命令执行脚本时,通常会创建一个新的子shell来执行脚本中的命令。
  • 查看子shell
    • 使用ps -ef --forest查看进程树。
    • 使用$BASH_SUBSHELL环境变量(在bash中)查看当前处于第几层子shell。
示例
  • 查看子shell

    echo $BASH_SUBSHELL    # 如果没有在子shell中,通常输出为空
    (echo $BASH_SUBSHELL)  # 输出1,表示在子shell中
    
  • 嵌套子shell

    (echo $BASH_SUBSHELL; (echo $BASH_SUBSHELL))  # 输出1,然后输出2
    
结论
  • 使用shbash或指定路径执行脚本时,会创建一个新的子shell。
  • 使用source.执行脚本时,在当前shell环境中执行脚本命令,不创建新的子shell。
  • 子shell用于并行处理任务,但需要注意变量和函数的作用域。

管道操作(|)与重定向

管道操作是Linux和Unix系统中一个非常强大的特性,它允许你将一个命令的输出直接作为另一个命令的输入。这种操作方式可以极大地提高命令行的灵活性和效率。

  • 基本用法command1 | command2
    • 这里,command1的输出会被直接传递给command2作为输入。
  • 示例
    • ps aux | grep nginx:列出所有进程,并通过grep命令过滤出包含nginx的行。
    • cat file.txt | wc -l:计算file.txt文件中的行数。
重定向

重定向允许你改变命令的标准输入(STDIN)、标准输出(STDOUT)和标准错误(STDERR)的默认设备(通常是键盘和显示器)。

  • 标准输入(STDIN):文件编号为0,默认是键盘。

  • 标准输出(STDOUT):文件编号为1,默认是显示器。

  • 标准错误(STDERR):文件编号为2,默认也是显示器,但用于输出错误信息。

  • 重定向操作符

    • <:重定向输入,从文件读取数据。
    • >:重定向输出,覆盖文件内容。
    • >>:重定向输出,追加到文件尾部。
    • 2>2>>:分别用于重定向错误输出到文件和追加到文件。
    • &>:将标准输出和标准错误都重定向到同一个文件。
    • 2>&1:将标准错误重定向到标准输出的当前位置(常用于将错误输出和标准输出合并)。
  • 示例

    • echo "Hello, World!" > output.txt:将输出保存到output.txt,覆盖原有内容。
    • echo "Another line" >> output.txt:将输出追加到output.txt的末尾。
    • ls -lh 2> errors.txt:将ls -lh命令的错误输出保存到errors.txt
    • ls -lh > output.txt 2>&1ls -lh &> output.txt:将ls -lh的标准输出和错误输出都保存到output.txt
注意
  • 当使用重定向时,如果目标文件不存在,系统会自动创建它。
  • 如果目标文件已经存在,并且你使用的是>2>操作符,那么文件内容将被覆盖。使用>>2>>可以保留原有内容并追加新内容。
  • setenforce 0命令用于关闭SELinux的安全策略,但请注意,这样做可能会降低系统的安全性。在生产环境中,建议谨慎使用,并确保了解关闭SELinux可能带来的后果。

脚本调试debug

使用 -x 选项检查逻辑错误

当你运行 Bash 脚本时,可以在命令行中使用 -x 选项来启用调试模式。这将使 Bash 在执行脚本中的每个命令之前,先将其打印到标准错误(stderr)。这有助于你理解脚本的执行流程,并查看哪些命令被实际执行了。

bash -x script.sh

或者,如果你已经使脚本具有执行权限(通过 chmod +x script.sh),你也可以这样调用它:

bash -x ./script.sh
在脚本内部使用 set -xset +x

如果你只想在脚本的某个部分启用调试模式,可以在脚本内部使用 set -x 来开启调试,并在需要时使用 set +x 来关闭它。

#!/bin/bash

# 脚本的其他部分...

set -x  # 开启调试模式
# 这里是要调试的命令
echo "This command will be printed before execution"
# ... 其他命令
set +x  # 关闭调试模式

# 脚本的其他部分...

使用 -n 选项检查语法错误

虽然 -n 选项不直接用于调试执行流程,但它可以帮助你检查脚本中的语法错误。使用 -n 选项运行脚本时,Bash 会读取脚本中的命令但不会执行它们,如果发现有语法错误,它会打印出错误消息。

bash -n script.sh

使用 trap 命令捕获错误

trap 命令允许你指定在接收到特定信号时要执行的命令。你可以使用它来捕获脚本中的错误(如退出信号),并执行一些清理操作或打印出调试信息。

#!/bin/bash

trap 'echo "Error occurred at line $LINENO"; exit' ERR

# 脚本的其他部分,可能会产生错误...

# 如果发生错误,上面的 trap 命令将执行

shell中的变量

概述

Shell 变量是 Shell 编程中用于存储和引用数据的实体。它们可以是简单的文本字符串、数字,或者在某些情况下,甚至是更复杂的数据结构(尽管这通常依赖于特定的 Shell 和其扩展功能)。变量使得 Shell 脚本能够处理动态数据,增强脚本的灵活性和可重用性。

变量的作用
  1. 存储数据和字符串:Shell变量可以存储数字、文本字符串、文件名等各种类型的数据。这些数据可以是用户输入的信息、命令的输出结果,或者是脚本内部需要的临时数据。
  2. 传递参数:在Shell脚本中,可以通过特殊的变量来获取传递给脚本的参数。比如,$1、 2 、 2、 2@等变量分别表示第一个参数、第二个参数和所有参数的列表。
  3. 控制程序流程:变量在控制程序流程中也起着重要作用。通过设置变量的值,可以控制脚本的分支和循环,实现条件判断和循环操作。
  4. 提高可读性和可维护性:使用变量可以使脚本更具可读性和可维护性。通过给数据和字符串起一个有意义的名字,可以使脚本更易于理解和修改。
  5. 避免重复输入:使用变量可以避免在脚本中重复输入相同的值或字符串,提高了代码的复用性和效率。
Shell 变量名与变量值

变量名:

使用固定的名称,由系统预设或用户定义

  • 规则:
    1. 不要使用系统的命令作为变量名
    2. 不要使用中文
    3. 不能特殊符号开头 可以用_来开头
    4. 在指定变量名的时候有一个默认的规则:计算机:computer 学生:student a= b=
    5. 要有注释,还要注意前后一致
    6. 只能包含字母、数字、下划线
    7. 严格区分大小写
变量值:

能够根据用户设置、系统环境的变化而变化

  1. 字符串(String)
    Shell脚本中的变量默认就是字符串。当你给变量赋值时,无论值是什么,它都被视为字符串。
  2. 整数(Int)
    虽然变量本身是字符串,但你可以使用Shell的算术扩展($((expression)))来进行整数运算。算术扩展允许你执行加、减、乘、除等整数运算。
  3. 布尔值(Boolean)
    Shell没有原生的布尔类型,但你可以使用整数(0表示假,非0表示真)或字符串(空字符串表示假,非空字符串表示真)来模拟布尔值。在条件判断语句(如if语句)中,你可以根据这些模拟的布尔值来决定是否执行某个代码块。
  4. 浮点数(Float)和双精度浮点数(Double)
    Shell本身不直接支持浮点数运算。但是,你可以通过调用外部程序(如bcawkperlpython等)来处理浮点数和双精度浮点数。这些外部程序提供了更丰富的数学运算功能,包括浮点数运算。
    • bc是一个任意精度的计算器语言,它允许你设置小数点后的位数(即精度),并执行浮点数运算。
    • awk是一个强大的文本处理工具,它内置了对浮点数的支持,并提供了printf函数来格式化输出浮点数。

变量的作用范围

在编程中,变量的作用范围(也称为变量的作用域)指的是变量在程序中可以被访问和修改的区域。根据变量的作用范围,变量可以被分为局部变量和全局变量两大类。

局部变量(Local Variables)

局部变量是在函数或代码块内部定义的变量。它们的作用范围仅限于定义它们的函数或代码块内部。一旦离开这个函数或代码块,局部变量就不再可用(即它们会被销毁)。这意味着局部变量只在定义它们的函数或代码块执行时存在,并且每个函数或代码块的局部变量都是独立的,互不影响。
特点

  • 只在定义它们的函数或代码块内部可见和可用。
  • 函数或代码块执行完毕后,局部变量会被销毁。
  • 不同的函数或代码块可以定义同名的局部变量,这些变量互不影响。
全局变量(Global Variables)

全局变量是在函数或代码块外部定义的变量。它们的作用范围是整个程序,即在整个程序中都可以访问和修改全局变量。全局变量在程序启动时被创建,在程序结束时被销毁。
特点

  • 在整个程序中都是可见的。
  • 可以在程序的任何地方被访问和修改(但在某些编程语言中,可能需要特定的声明来在函数内部访问全局变量)。
  • 如果多个函数或代码块需要共享数据,全局变量是一个选择,但过度使用全局变量可能会导致代码难以理解和维护。
注意
  • 局部变量和全局变量的命名可以相同,但在同一个作用域内,局部变量的优先级高于全局变量。这意味着在函数内部,如果定义了与全局变量同名的局部变量,那么在该函数内部访问该变量时,会先访问局部变量。
  • 为了避免命名冲突和提高代码的可读性,建议给局部变量和全局变量使用不同的命名规则或前缀。
  • 在多线程编程中,全局变量的访问需要特别注意线程安全问题,因为多个线程可能会同时修改全局变量的值。

变量的类型

1. 环境变量

定义:环境变量是在操作系统中用来指定操作系统运行环境的一些参数。它们是在操作系统中一个具有特定名字的对象,包含了一个或多个应用程序所将使用到的信息。环境变量对系统内的所有用户进程都是可见的,可以被子进程继承。
常见环境变量

  • PATH:存储了所有命令所在的路径,当要求系统运行一个程序而没有给出其完整路径时,系统会在PATH指定的路径中查找该程序。
  • HOME:用户登录时的主目录。
  • USERUID:当前用户的用户名和用户ID。
  • HOSTNAME:当前主机名。
  • PWD:当前工作目录的路径。
  • PS1:定义系统提示符的变量,用于显示命令行的提示符。
  • LANG:当前语言设置。
2. 位置变量

定义:位置变量主要是用来向脚本或程序中传递参数或数据的。它们在脚本或程序中被自动赋值,其名称和作用是固定的,不能自定义。
常见的位置变量:

  • $0:表示当前执行的脚本或命令的名称(包括路径)。
  • $1$9:分别代表传递给脚本或命令的第一个到第九个参数。
  • ${10} 及以上:对于超过9个的参数,需要使用大括号{}来明确变量的界限,如${10}${11}等。
  • $*:表示传递给脚本的所有参数,但是将所有参数视为一个整体(即一个字符串)。当$*被双引号(")包围时,它会将所有的参数视为一个单一的字符串。
  • $@:也表示传递给脚本的所有参数,但是与$*不同的是,$@将每个参数视为独立的字符串。当$@被双引号(")包围时,它会保留每个参数作为独立的字符串。
  • $#:表示传递给脚本的参数的个数。

注意:

  • 在编写Shell脚本时,应该注意参数的数量和顺序,因为位置变量是根据它们在命令行中出现的顺序来命名的。
  • 当处理不确定数量的参数时,应该使用$#来检查参数的个数,以避免潜在的错误。
  • 在使用$*$@时,要注意它们在被双引号包围时的行为差异,并根据需要选择使用。
3. 预定义变量

常见预定义变量

  1. $0:当前执行的脚本或命令的名称(包括路径)。在脚本中,它通常用于输出脚本自身的名称。
  2. $1$9:位置参数,分别代表传递给脚本或命令的第一个到第九个参数。超过9个的参数可以通过${10}${11}等方式访问。
  3. $*$@:都代表传递给脚本的所有参数,但在双引号中引用时行为不同。$*将所有参数视为一个整体(一个字符串),而$@将每个参数视为独立的字符串。
  4. $#:位置参数的数量,即传递给脚本的参数个数。
  5. $$:当前Shell进程的PID(进程ID)。这是一个非常重要的变量,用于唯一标识当前运行的Shell进程。
  6. $!:后台运行的最后一个进程的PID。这在你需要知道某个后台进程的ID以便稍后对其进行管理时非常有用。
  7. $?:上一条命令的退出状态。如果命令成功执行,则$?的值通常为0;如果命令执行失败,则$?的值为非0值。这个变量在条件判断中非常有用。
  8. $-:显示Shell使用的当前选项,这与set命令的功能相同。它可以用来查看或修改Shell的行为选项。

使用:预定义变量为Shell编程提供了许多便利,它们允许脚本或命令获取关于当前环境或之前操作的信息。

4.自定义变量

定义:

  • 变量名:自定义变量的名称可以由字母、数字和下划线组成,但不能以数字开头。为了避免与系统命令或环境变量冲突,建议变量名具有一定的描述性和可读性。
  • 赋值:定义变量时,使用等号(=)将变量名与变量值连接起来,注意等号两边不能有空格。如果变量值中包含空格、特殊字符(如*、?、$等),则需要用引号(单引号或双引号)将变量值括起来。

示例:
定义一个名为my_var的自定义变量,并赋予它值"Hello, world!",可以这样做:

my_var="Hello, world!"

引用:

  • 调用变量:在Shell中,要引用(或称为“展开”)自定义变量的值,需要在变量名前加上美元符号($)。例如,要打印上面定义的my_var变量的值,可以使用:
echo $my_var

特性:

  • 作用域:自定义变量默认只在定义它们的shell进程及其子进程中有效。一旦shell进程结束,这些变量也将不再存在。
  • 修改和覆盖:可以通过重新赋值来修改自定义变量的值,新的值会覆盖旧的值。
  • 只读变量:可以使用readonly命令将自定义变量设置为只读,之后就不能再修改这个变量的值了。

查看和取消:

  • 查看变量:可以使用set命令(不带任何选项)来查看当前shell环境中定义的所有变量,包括自定义变量和环境变量。但更常用的是通过echoprintenv(针对环境变量)命令来查看特定变量的值。
  • 取消变量:可以使用unset命令来取消(或称为“删除”)自定义变量,之后这个变量将不再存在,也不能再引用它的值了。

变量中的符号

基础符号
  1. 双引号 " ":弱引用, 双引号允许在字符串中引用变量,同时允许在字符串中包含转义字符。
  2. 单引号 ’ ': 强引用,单引号中的字符会原样输出,不会进行变量替换或命令替换。
  3. 小括号 ( ): 在shell中,用于创建子shell,定义数组。在正则表达式中,用于捕获组,可以匹配并记住匹配的字串。
  4. 中括号 [ ]: 用于条件测试和数组索引。在条件测试中,它用于比较数字或字符串是否满足某个条件(如[ $a -eq $b ])。在数组索引中,用于指定数组中的元素(如${array[index]})。在正则表达式中,用于定义字符集,匹配中括号中的任意一个字符。
  5. 大括号 { }: shell中用于扩展字符串或者序列,也用于定义代码块。在正则表达式中不常用,用于扩展的正则表达语法中可能表示的数量范围。
  6. 双中括号 [[ ]]: 在bash等shell中,用于更强大的条件测试,是更强大的引用。
  7. 双小括号 (( )): 专门用于算术运算,支持算术比较,并且算术表达式中的变量可以自动转换为整数。

变量的运算

1. 使用expr命令

expr命令是一个用于执行整数算术运算的命令行工具。它也可以用于字符串操作和其他功能,但在这里我们只关注算术运算。

a=5
b=3
c=$(expr $a + $b)
echo $c  # 输出 8
2. 使用双小括号(( ))
a=5
b=3
((c = a + b))
echo $c  # 输出 8

注意:在(( ))中,变量名前的$符号是可选的,但在使用$(( ))进行算术扩展时,变量名前的$是必须的。

3. 使用let命令

let命令也是Bash Shell中用于执行算术运算的命令。它的语法比较接近C语言中的赋值语句。

a=5
b=3
let c=a+b
echo $c  # 输出 8
4. 使用bc命令

对于更复杂的算术运算(如浮点运算),可以使用bc命令。bc是一个任意精度的计算器语言。

a=5
b=3
c=$(echo "$a / $b" | bc)
echo $c  # 输出 1.6666...(根据bc的配置,可能会有不同的精度)

# 对于浮点运算,可以设置bc的小数点后的位数:
c=$(echo "scale=2; $a / $b" | bc)
echo $c  # 输出 1.67

#scale是一种特殊的变量可以用于指定小数位的精度:
scale=2
result=$(echo "scale=$scale;10/3" | bc )
echo=result
3.33

后缀自增和自减(i++i--

在Bash中,你可以通过先使用变量的值,然后再将其增加或减少来实现后缀自增自减的效果。但是,由于Bash的语法限制,你通常需要在两条语句中完成这个操作:

i=5
# 后缀自增
echo $i    # 输出 5
((i++))    # 或者 let i=i+1
echo $i    # 输出 6

# 后缀自减
((i--))    # 或者 let i=i-1
echo $i    # 输出 5(如果之前i是6的话)

前缀自增(++i

前缀自增在Bash中可以通过在算术扩展中先增加变量的值,然后再使用它的值来实现:

i=5
# 前缀自增
((++i))    # 或者 let i=i+1,但这里主要是展示++i的效果
echo $i    # 输出 6

条件运算

test命令(也称为[ ])是Shell脚本中用于进行条件测试的命令。它用于检查某个条件是否成立,并根据结果返回一个退出状态码(exit status)。如果条件成立,test命令返回0;如果条件不成立,返回非0值。

test命令的语法:

test condition

或者使用方括号形式:

[ condition ]

常用的test命令条件:

  1. 文件测试
    [ 操作符 文件/目录 ]
    • -e:文件存在
    • -f:普通文件存在
    • -d:目录存在
    • -r:文件可读
    • -w:文件可写
    • -x:文件可执行
  2. 字符串测试
[ 字符串1 = 字符串2 ]或者[ 字符串1 == 字符串2 ]
[ 字符串1 != 字符串2]
  • ===:字符串相等
  • !=:字符串不等
  • -z:字符串长度为0
  • -n:字符串长度不为0
[ -z "字符串" ]#检查是否为空(未定义或者赋空值的变量视为空串)
[ -n "字符串" ]#检查是否字符串存在
  1. 整数测试
[ 整数变量1 操作符 整数变量2]
  • -eq:整数相等
  • -ne:整数不等
  • -gt:整数大于
  • -lt:整数小于
  • -ge:整数大于等于
  • -le:整数小于等于
  1. 逻辑测试
[ 表达式1 ] 操作符 [ 表达式2 ]
   命令1 操作符 命令2
  • -a&&:逻辑与
  • -o||:逻辑或
  • -!!:逻辑非
    &&、|| 能正常存在于[[]]条件判断中,但[]中会报错:
a=5
[$a -ne 1] &&[ $a != 2] 等同于 [ $a -ne | -a $a != 2 ]

示例:

# 文件测试
if test -f "myfile.txt"; then
  echo "File exists"
fi

# 字符串测试
name="World"
if [ "$name" = "World" ]; then
  echo "Hello, World!"
fi

# 整数测试
a=5
b=3
if [ $a -eq $b ]; then
  echo "a equals b"
else
入代码片
  echo "a does not equal b"
fi

需要注意的是,在使用方括号[ ]时,左右两边需要有空格,否则会被解释为文件名。例如,[ $a -eq $b ]是正确的,而[$a -eq $b]是不正确的。

if的条件分支

在Shell脚本中,if语句用于根据条件表达式的结果来执行不同的代码块。if语句可以包含多个条件分支,包括else if(或写作elif)和else

基本if语句

if [ 条件表达式 ]; then
    # 如果条件为真,则执行这里的命令
    echo "条件为真"
fi

包含elseif语句

if [ 条件表达式 ]; then
    # 如果条件为真,则执行这里的命令
    echo "条件为真"
else
    # 如果条件为假,则执行这里的命令
    echo "条件为假"
fi

包含else if(或elif)的if语句

if [ 条件表达式1 ]; then
    # 如果条件1为真,则执行这里的命令
    echo "条件1为真"
elif [ 条件表达式2 ]; then
    # 如果条件1为假但条件2为真,则执行这里的命令
    echo "条件2为真"
else
    # 如果所有条件都为假,则执行这里的命令
    echo "所有条件都为假"
fi

注意

  • 条件表达式需要用方括号[]包围,并且方括号和表达式之间必须有空格。
  • 在Shell中,常见的条件测试包括文件测试(如-f表示文件存在且为普通文件)、字符串测试(如=!=用于字符串比较)和数值测试(如-eq-ne-gt-ge-lt-le分别用于等于、不等于、大于、大于等于、小于、小于等于的比较)。
  • 在Shell脚本中,thenelifelsefi是关键字,用于控制if语句的流程。

示例

#!/bin/bash

# 检查文件是否存在
if [ -f /path/to/your/file.txt ]; then
    echo "文件存在"
elif [ -d /path/to/your/directory ]; then
    echo "目录存在但文件不存在"
else
    echo "文件或目录都不存在"
fi

在这个示例中,脚本首先检查一个文件是否存在。如果文件存在,它会输出“文件存在”。如果文件不存在,它会检查一个目录是否存在。如果目录存在,它会输出“目录存在但文件不存在”。如果两者都不存在,它会输出“文件或目录都不存在”。

if的嵌套使用

#!/bin/bash

# 假设这是用户输入的性别编号和成绩
read -p "请输入性别编号(1为男生,2为女生):" gender
read -p "请输入成绩(0~10):" score

# 第一个if语句检查性别
if [ $gender -eq 1 ]; then
    # 男生
    if [ $score -ge 0 -a $score -le 5 ]; then
        echo "该男学员不合格"
    elif [ $score -ge 6 -a $score -le 7 ]; then
        echo "该男学员合格"
    elif [ $score -ge 8 -a $score -le 10 ]; then
        echo "该男学员很优秀"
    else
        echo "成绩输入错误,男生的成绩应该在0到10之间。"
    fi
elif [ $gender -eq 2 ]; then
    # 女生
    if [ $score -ge 0 -a $score -le 6 ]; then
        echo "该女学员不合格"
    elif [ $score -ge 7 -a $score -le 8 ]; then
        echo "该女学员合格"
    elif [ $score -ge 9 -a $score -le 10 ]; then
        echo "该女学员很优秀"
    else
        echo "成绩输入错误,女生的成绩应该在0到10之间。"
    fi
else
    # 性别输入错误
    echo "性别编号输入错误,请输入1(男生)或2(女生)。"
fi

在这个例子中,有两个嵌套的if语句:

  1. 外层的if语句检查用户输入的性别编号是否为1(男生)或2(女生)。
  2. 对于每个性别,内层的if语句检查成绩并根据成绩给出评价。
    如果性别编号不是1也不是2,外层的if语句将执行else部分,并输出性别编号输入错误的消息。
    对于每个性别,如果成绩不在预期的范围内,内层的if语句将执行else部分,并输出成绩输入错误的消息。

case

在Shell脚本中,case语句是一种多路分支语句,它允许脚本根据变量的值来执行不同的代码块。这种方式比多个嵌套的if-elif-else语句更简洁、更易于理解。

case语句的基本语法:

case 变量 in
    模式1)
        # 如果变量匹配模式1,则执行这里的命令
        ;;
    模式2)
        # 如果变量匹配模式2,则执行这里的命令
        ;;
    ...
    *)
        # 默认模式,如果没有任何模式匹配,则执行这里的命令
        ;;
esac

这里的esaccase语句的结束标记,类似于if语句的fi

示例

#!/bin/bash

# 读取用户输入的星期几
read -p "请输入星期几(1-7):" day

case $day in
    1)
        echo "今天是星期一"
        ;;
    2)
        echo "今天是星期二"
        ;;
    3)
        echo "今天是星期三"
        ;;
    4)
        echo "今天是星期四"
        ;;
    5)
        echo "今天是星期五"
        ;;
    6)
        echo "今天是星期六"
        ;;
    7)
        echo "今天是星期日"
        ;;
    *)
        echo "无效的输入,请输入1到7之间的数字"
        ;;
esac

case语句中,每个模式后面可以跟一个或多个模式匹配符,用于扩展模式匹配的能力。但是,在简单的使用场景中,通常不需要这些高级功能。

注意,在case语句中,每个;;表示一个模式块的结束。如果某个模式块中的命令不需要立即结束(即,你想在同一个模式块中执行多个命令),你可以简单地将这些命令依次写在该模式块中,而不需要在每个命令后面都加上;(尽管加上也无妨)。但是,模式块之间的分隔是必须的,以确保Shell能够正确地区分不同的模式块。

此外,case语句中的模式匹配是区分大小写的,这意味着11(虽然在这里它们看起来一样)会被视为相同的模式,但1One则会被视为不同的模式。如果你需要进行不区分大小写的匹配,你可能需要使用外部命令(如trawk等)来转换变量和模式的大小写,或者在case语句中使用通配符(尽管这通常不是进行不区分大小写匹配的最佳方法)。

使用if语句结合case

在某些情况下,你可能不需要完整的 case 嵌套,而是可以简单地使用 if 语句来结合多个条件。这虽然不是 case 的嵌套,但可以达到类似的效果。

#!/bin/bash

read -p "Enter a number (1-3): " number

if [ $number -eq 1 ]; then
    read -p "Enter a letter (a-c): " letter
    case $letter in
        a)
            echo "Number 1 and letter a"
            ;;
        b)
            echo "Number 1 and letter b"
            ;;
        c)
            # 这里可以模拟另一个“嵌套”的case,但实际上只是另一个case
            echo "Number 1 and letter c, entering another 'case' like scenario"
            read -p "Sub-option (x-z): " sub_option
            case $sub_option in
                x)
                    echo "Sub-option x"
                    ;;
                y)
                    echo "Sub-option y"
                    ;;
                z)
                    echo "Sub-option z"
                    ;;
                *)
                    echo "Invalid sub-option"
                    ;;
            esac
            ;;
        *)
            echo "Invalid letter for number 1"
            ;;
    esac
elif [ $number -eq 2 ]; then
    echo "Number 2 selected"
elif [ $number -eq 3 ]; then
    echo "Number 3 selected"
else
    echo "Invalid number"
fi

在这个例子中,并没有真正嵌套 case 语句,而是在第一个 case 语句的一个分支内使用了另一个 case 语句来模拟更复杂的决策流程。在Shell脚本中,通常通过函数、循环和条件语句(如 ifcase)的组合来实现复杂的逻辑。

for循环

在Bash(以及许多其他Shell)中,for循环有几种不同的格式,但主要可以归纳为两大类:基于列表的for循环和C语言风格的for循环。

for循环的基本格式

1. 基于列表的for循环

这种for循环遍历一个由空格分隔的列表(或数组)中的每个元素。
基本格式

for variable(←变量名) in list(←范围)
do(←开始)
 #(执行内容:若满足循环则作什么动作)
  command1
  command2
  ...
done(←结束)
  • variable:在每次迭代中,列表中的一个元素会被赋值给这个变量。
  • list:一个由空格分隔的元素列表,或者是一个命令的替换结果(即命令的输出),或者是一个数组。
  • do ... done:循环体,即每次迭代时要执行的命令序列。

示例

for i in 1 2 3 4 5
do
  echo "Number $i"
done

或者使用序列扩展(Bash 4.0及以上):

for i in {1..5}
do
  echo "Number $i"
done

遍历当前目录下的所有.txt文件:

for file in *.txt
do
  echo "Processing $file"
done

2. C语言风格的for循环

这种for循环提供了更复杂的迭代控制,类似于C语言中的for循环。
基本格式

for (( initialization; condition; step ))
#即:((表达式1(定义、赋值);表达式2(是否循环、退出条件);表达式3(决定循环变量如何改变、何时结束)))
do
  command1
  command2
  ...
done
  • initialization:在循环开始前执行的初始化表达式。
  • condition:在每次迭代开始前评估的条件表达式。如果条件为真(即非零),则执行循环体;如果为假(即零),则退出循环。
  • step:在每次迭代结束时执行的更新表达式。
  • do ... done:循环体,即条件为真时执行的命令序列。

示例

for (( i=1; i<=5; i++ ))
do
  echo "Number $i"
done

这个循环会打印从1到5的数字。

注意

  • Bash中的for循环是区分大小写的。
  • 在使用基于列表的for循环时,如果列表中的元素包含空格或特殊字符(如换行符、引号等),可能需要采取额外的措施来正确处理这些元素(例如,使用find ... -print0read -d ''的组合)。
  • C语言风格的for循环是Bash的一个扩展特性,可能在非Bash的Shell中不可用或行为不同。然而,它在Bash脚本中非常有用,特别是当需要执行复杂的数学计算或需要更精细地控制迭代过程时。

其他循环

基于文件的for循环

这种循环结构用于遍历一个目录中的所有文件。

for file in /path/to/directory/*; do
 command
done

其中,file是循环变量,/path/to/directory/*是目录路径。
示例:

for file in /home/user/documents/*; do
 echo "file is $file"
done

输出:

file is /home/user/documents/1.txt
file is /home/user/documents/2.txt
file is /home/user/documents/3.txt

死循环

在Shell脚本中,可以通过设置一个永远为真的条件来创建一个死循环。以下是一个简单示例:

for (( ; ; )); do
    echo "这是一个死循环"
done

在这个示例中,for循环的条件部分为空,表示永远为真。因此,循环将无限次执行,直到手动停止脚本。
为了防止死循环导致系统资源耗尽,可以在循环体内添加一些条件判断,以便在满足特定条件时退出循环。例如:

count=0
for (( ; ; )); do
    echo "这是一个死循环"
    count=$((count + 1))
    if [ $count -ge 10 ]; then
        break
    }
done

在这个示例中,我们使用一个计数器变量count来记录循环执行的次数。当count达到10时,使用break命令退出循环。
死循环可能会导致系统资源耗尽,因此在实际应用中应尽量避免使用死循环。

常用转义符

在Shell脚本中,除了常见的转义字符如反斜杠(\)、单引号(')、双引号(")等,还有许多其他特殊字符的转义用法。这些特殊字符在Shell中具有特定的含义,但通过使用转义符(主要是反斜杠\),可以改变它们的解释方式,使它们被当作普通字符处理。

  1. 变量替换和命令替换相关字符
    • $:用于变量替换。如果需要在字符串中直接使用$字符,需要对其进行转义,如\$
    • `(反引号)或$()用于命令替换。但在现代Shell脚本中,更推荐使用$()来进行命令替换,因为它更易读且支持嵌套。如果需要在字符串中直接使用反引号,也需要进行转义,但通常建议使用单引号或双引号加转义的方式来实现。
  2. 通配符和文件名扩展相关字符
    • *:通配符,匹配任意数量的字符(包括零个字符)。如果需要在字符串中直接使用*字符,需要对其进行转义,如\*
    • ?:通配符,匹配任意单个字符。同样,如果需要在字符串中直接使用?字符,也需要进行转义,如\?
    • [ ]:字符类匹配。用于匹配方括号内的任意单个字符。如果需要在字符串中直接使用方括号,特别是当它们用作字符类的边界时,可能需要进行转义,如\[\]
  3. 控制字符
    • \b:转义后相当于按退格键(backspace),但前提是"\b"后面存在字符;“\b"表示删除前一个字符,”\b\b"表示删除前两个字符。
    • \c:不换行输出,在"\c"后面不存在字符的情况下,作用相当于 echo -n; 但是当"\c"后面仍然存在字符时,"\c"后面的字符将不会被输出。
    • \n:换行,被输出的字符从"\n"处开始另起一行。
    • \f:换行,但是换行后的新行的开头位置连接着上一行的行尾。
    • \v:与\f相同。
    • \t:转以后表示插入tab,即横向制表符(tab)。
    • \\ 表示插入"\"本身;
    • \r 光标移至行首,但不换行,相当于使用"\r"以后的字符覆盖"\r"之前同等长度的字符;但是当"\r"后面不存在任何字符时,"\r"前面的字符不会被覆盖
  4. 其他特殊字符
    • &:在后台运行命令。如果需要在字符串中直接使用&字符,并且不希望它被解释为后台运行的指示符,需要对其进行转义,如\&
    • |:管道符。用于将一个命令的输出作为另一个命令的输入。如果需要在字符串中直接使用|字符,并且不希望它被解释为管道符,也需要进行转义,如\|
    • ;:命令分隔符。用于在同一行中分隔多个命令。如果需要在字符串中直接使用;字符,并且不希望它被解释为命令分隔符,需要进行转义,如\;
    • \(反斜杠本身):在需要输出反斜杠字符时,需要使用两个反斜杠来表示一个反斜杠字符,即\\

需要注意的是,转义字符\只对紧跟其后的单个字符起作用。如果需要转义多个字符,需要为每个字符分别添加\。此外,在某些上下文中(如正则表达式中),转义字符的用法可能有所不同,需要根据具体情况进行处理。

关于 continuebreakexit

continue

continue 用于跳过当前循环的剩余部分,并立即开始下一次循环。它不会终止整个循环,只会跳过当前迭代。

示例:
for i in {1..5}
do
    if [ $i -eq 3 ]
    then
        continue
    fi
    echo "$i"
done

输出:

1
2
4
5

在嵌套循环中,continue [数字] 可以跳过指定层数的循环。例如:

for i in {1..10}
do
    if [ $i -eq 5 ]
    then
        continue 2
    fi
    echo "i=$i"
done

break

break 用于立即终止当前循环,并继续执行循环后面的代码。它可以带一个数字参数,表示要跳出的循环层数。

示例:
for i in {1..10}
do
    if [ $i -eq 5 ]
    then
        break
    fi
    echo "$i"
done

输出:

1
2
3
4

在嵌套循环中,break [数字] 可以跳出指定层数的循环。例如:

for j in {1..3}
do
    for i in {1..5}
    do
        if [ $i -eq 2 ]
        then
            break 2
        fi
        echo "$i"
    done
done

输出:

1
3

exit

exit 命令用于立即终止整个脚本的执行。当 exit 被执行时,脚本会立即停止运行,不再执行后续的任何代码。

示例:
#!/bin/bash
for j in {1..3}
do
    for i in {1..5}
    do
        if [ $i -eq 3 ]
        then
            exit
        fi
        echo "$i"
    done
done

输出:

1
2

总结:

  • continue 用于跳过当前循环的剩余部分,并立即开始下一次循环。
  • break 用于立即终止当前循环,并继续执行循环后面的代码。
  • exit 用于立即终止整个脚本的执行。
  • continue [数字]break [数字] 可以用于嵌套循环中,分别跳过或终止指定层数的循环。

set -x 和 set +x

  • set -x:开启调试模式,会在命令执行前打印出要执行的命令。
  • set +x:关闭调试模式,不再打印要执行的命令。

while循环与until循环

while循环的基本格式

while [ 判断条件 ]
do
    执行动作
done
  • 判断条件:这是一个返回真(0)或假(非0)的表达式。如果条件为真,则执行dodone之间的命令;如果条件为假,则跳过这些命令,继续执行done之后的命令。

  • 执行动作:这是当判断条件为真时要执行的命令或命令序列。

  • done:这标志着while循环的结束。
死循环的几种实现方式

死循环是指循环条件永远为真,导致循环体内的命令无限次执行。

  1. 使用固定为真的条件

    while [ 1 -eq 1 ]
    do
        # 执行命令
    done
    
  2. 使用true命令
    true是一个总是返回真(0)的命令,因此可以直接用作while循环的条件。

    while true
    do
        # 执行命令
    done
    
  3. 使用冒号(:
    冒号(:)是一个空命令,它等价于true,也常用于创建死循环。

    while :
    do
        # 执行命令
    done
    
退出循环
  • break:用于立即退出循环。
  • continue:用于跳过本次循环的剩余部分,直接开始下一次循环。
  • 修改循环条件:通过修改循环条件使其变为假,从而自然退出循环。

until循环、与while循环的区别

until 循环确实是一种在条件不满足时持续执行循环体,直到条件满足时退出循环的控制结构。它与 while 循环相反,while 循环是在条件满足时执行循环体。

until 循环示例

首先,我们来看 until 循环的示例:

#!/bin/bash
i=0
sum=0
until [ $i -gt 10 ]  # 当 $i 大于 10 时退出循环
do
    echo $i          # 打印当前的 $i 值
    let i++          # $i 自增
    sum=$(($sum+$i)) # 将 $i 的值加到 $sum 上
done
echo "Sum: $sum"     # 循环结束后打印总和

在这个脚本中,until 循环会一直执行,直到 $i 的值大于 10。每次循环都会打印当前的 $i 值,将其加到 $sum 上,并将 $i 自增。循环结束后,会打印出 $sum 的值。

while 循环方式

接下来,我们看一个 while 循环的示例,该示例计算从 0 到 100(包括 100)的整数和:

#!/bin/bash
i=0
sum=0
while [ $i -le 100 ]  # 当 $i 小于或等于 100 时执行循环
do
    sum=$((sum+i))    # 将 $i 的值加到 $sum 上,注意这里使用了更现代的算术扩展语法
    let i++           # $i 自增
done
echo "Sum: $sum"      # 循环结束后打印总和

在这个脚本中,while 循环会一直执行,直到 $i 的值大于 100。每次循环都会将 $i 的值加到 $sum 上,并将 $i 自增。循环结束后,会打印出 $sum 的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值