Shell编程

Shell 简介

  • Linux Shell 是 Linux 操作系统中用于与内核进行交互的命令行界面。它是用户与操作系统之间的中介,允许用户通过输入指令(命令)来执行操作,如文件管理、程序执行、设备控制等。
  • Shell 本身是一个程序,当用户登录到 Linux 系统时,它提供了一个用户会话,并解释用户输入的命令。Linux Shell 同时也是一种脚本语言,可以编写脚本(一系列的命令)来自动化常见的任务,提高工作效率。

Shell 类型

  • Linux 操作系统支持多种 Shell,每种都有其独特的功能和特性。以下是一些最常见的 Shell 类型:
    1. Bash (Bourne Again Shell): 是 Bourne Shell 的后继版本,由 GNU 项目创建,成为大多数 Linux 发行版的默认 Shell。
    2. Sh (Bourne Shell): 是 Unix Shell 的原始版本,由 Steve Bourne 编写,现在已经被它的各种扩展版本如 Bash 所取代。
    3. Dash (Debian Almquist Shell): 是 Almquist Shell (ash) 的 Debian 版本,以速度快而闻名,用于满足 POSIX 标准要求。
    4. Ksh (Korn Shell): 由 David Korn 开发,结合了 C Shell 和 Bourne Shell 的特点,提供了许多高级的编程功能。
    5. Csh (C Shell): 由 Bill Joy 创建,其语法类似于 C 语言,主要在某些特定系统用户中流行。
    6. Tcsh (TENEX C Shell): 是 C Shell 的增强版本,提供了命令行编辑和历史功能。
    7. Zsh (Z Shell): 结合了 Bash、ksh 和 tcsh 的特点,提供了许多强大的特性,比如更好的自动补全和命令修正功能。
    8. Fish (Friendly Interactive Shell): 是一个相对较新的、用户友好的 Shell,以其易用性和高级的特性为特点,例如自动建议和丰富的颜色化输出。
  • 每种 Shell 都有其特定的用途和功能,用户可以根据自己的喜好和需求选择合适的 Shell 环境。例如,Bash 由于其广泛的支持和灵活性,通常是用户的首选。而 Zsh 因其丰富的特性和定制能力,特别受到高级用户和开发者的喜爱。
  • 查看当前使用的 Shell
    1
    echo $SHELL
  • 列出系统上可以用的 Shell
    1
    chsh -l
    或者你可以直接查看 /etc/shells 文件来了解系统上有哪些可用的 shell:
    1
    cat /etc/shells

如何执行 Shell 脚本 ?

  1. 给予执行权限:
    首先,确保你的脚本文件具有执行权限。你可以使用 chmod 命令来添加执行权限。例如,如果你的脚本文件名为script.sh,你可以使用以下命令:
    1
    chmod +x script.sh
  2. 直接运行脚本:
    如果脚本文件已经有了执行权限,你可以通过在脚本所在目录下使用下面的命令来直接运行它:
    1
    ./script.sh

    这里的 ./ 表示当前目录。

  3. 使用 Shell 运行脚本:
    你还可以通过调用特定的 Shell 来执行脚本,即使脚本没有执行权限。例如,如果你想用 Bash 运行你的脚本,你可以使用以下命令:
    1
    bash script.sh
    或者如果是用 sh(Bourne Shell):
    1
    sh script.sh
  4. 使用路径运行脚本:
    如果脚本位于你的 PATH 环境变量所列的目录之一,你可以从任何地方运行它而不需要指定路径。
  • 注意事项
    • 确保脚本的第一行包含了shebang(#!),它告诉系统使用哪个解释器来执行文件。例如,如果你的脚本是用Bash编写的,那么脚本的第一行应该是:
      1
      #!/bin/bash
    • 如果是用 Python 写的脚本,第一行可能会是:
      1
      #!/usr/bin/python3
    • 确保在尝试执行脚本之前,脚本是针对你的系统和环境正确编写的。如果脚本中有错误或者编写的环境与执行的环境不兼容,它可能无法正确运行。

Shell 变量

  • 简介

    在 Linux Shell 中,变量是存储值的标识符。在 Shell 脚本和命令行中,变量用于保存数据,如文本字符串、数字、文件名等,以便在需要的时候重复使用。

  • 设置变量

    要在 Shell 中设置变量,您可以使用等号(=)将值分配给变量名。在等号的两侧不应有空格。例如:

    1
    my_variable="Hello, World!"
  • 访问变量

    访问变量的值时,您需要在变量名前加上美元符号($)。例如:

    1
    echo $my_variable

    也可以使用 ${variable} 这种格式来引用变量,这与直接使用 $variable 效果是相同的。这种格式在变量周围添加了大括号,这样可以帮助解释器区分变量名和紧随其后的字符。通常在变量和文字紧密结合时这种方法非常有用。

    1
    2
    my_variable="World"
    echo "Hello, ${my_variable}!"
  • 环境变量

    • 查看环境变量

      使用 envprintenv 命令来列出所有的环境变量及其值。

      1
      env

      1
      printenv
    • 打印指定环境变量

      使用 echo 命令结合美元符号($)和大括号来打印指定的环境变量。例如,打印 PATH 环境变量:

      1
      echo $PATH

      1
      echo ${PATH}

      PATH 是在大多数操作系统中使用的一个环境变量,尤其是在 Unix 和类 Unix 系统中,如 Linux、MacOS、BSD 以及 Windows 的命令行环境。它定义了一个由冒号(在 Windows 中是分号)分隔的目录列表,这些目录被用来查找和执行命令行中输入的可执行文件。

    • 修改环境变量

      要修改一个环境变量,你可以导出新的值。例如,添加一个新路径到 PATH 环境变量,可以这样做:

      1
      export PATH=$PATH:/new/path

      这样不会移除 PATH 当前的内容,而是在尾端追加了 :/new/path

    • 设置新的环境变量

      要创建一个新的环境变量,可以直接使用 export 命令:

      1
      export MY_VARIABLE="my_value"
    • 持久化环境变量

      环境变量的更改默认只在当前 shell 会话中有效。要想持久化环境变量的更改,需要将 export 命令添加到你的 shell 配置文件中,例如 ~/.bashrc~/.profile(对于 Bash 用户),或者是全局文件如 /etc/environment

      1
      echo 'export MY_VARIABLE="my_value"' >> ~/.bashrc

      然后,使用 source 命令来应用改变或重新登录。

      1
      source ~/.bashrc
    • 删除环境变量

      要删除环境变量,可以使用 unset 命令:

      1
      unset MY_VARIABLE
  • 位置参数

    在编写脚本时,位置参数变量 $1$2$3 等用于访问脚本的命令行参数。例如,$1 是传递给脚本的第一个参数。

  • 特殊变量

    • $0 - 脚本名
    • $# - 传递给脚本的参数数量
    • $* - 传递给脚本的所有参数的单个字符串
    • $@ - 传递给脚本的所有参数,每个参数都是独立的引用字符串
    • $$ - 当前 Shell 进程的进程ID(PID)
    • $? - 上一个命令的退出状态
  • 变量扩展

    Shell 提供了多种变量扩展的方法,可以用来修改变量的值、测试其状态或进行字符串操作。例如:

    1
    echo ${#my_variable}    # 输出变量值的长度
  • 只读变量

    要使变量成为只读,可以使用 readonly 命令。这样可以创建一个常量,一旦设定,就无法修改其值。例如:

    1
    readonly my_variable

    尝试修改一个只读变量将会导致错误:

    1
    2
    my_variable="Try to change"
    # bash: constant: readonly variable

    请注意,一旦你将变量设为只读,你就不能再对它进行 unset

  • 取消变量

    要取消设置变量,可以使用 unset 命令。例如:

    1
    unset my_variable

    现在,my_variable 不再存在。

单、双、反引号的区别

  • 单引号 '

    • 任何在单引号内的字符都会被视为普通字符,即 shell 不会对其进行任何特殊处理。变量和命令不会被展开或执行。
    • 例如:
      1
      echo '$HOME'
      这个命令将输出 $HOME 字符串本身,而不是 HOME 环境变量的值。
  • 双引号 "

    • 双引号内的字符大多会被当作普通字符对待,但是某些特殊字符,如美元符号 ($)、反引号、反斜杠 (\),会被 shell 特殊处理,允许变量展开、命令替换或者转义字符的使用。
    • 例如:
      1
      echo "$HOME"
      这个命令将输出 HOME 环境变量的值。
    • 另外一个例子:
      1
      echo "The date is `date`"
      这将输出当前日期和时间,因为 date 命令会被执行并替换到字符串中。
    • 特别注意
      • 双引号可以防止通配符扩展,比如 *?。如果你不想让 shell 扩展这些特殊字符,可以将它们放在双引号中。
      • 在双引号内,如果你还想表示字面量的特殊字符(比如 $),可以使用反斜杠 (\) 来转义。
        举个例子:
        1
        echo "Value of \$HOME is $HOME"
        这将输出类似 Value of $HOME is /home/username
  • 反引号 `

    • 反引号,有时也被称作倒引号、重音符或者反引号,用于在 shell 中执行命令替换。当你将一段文本或命令包围在反引号中时,shell 会先执行那个命令,然后将输出的结果替换到原来命令的位置。
    • 例如:
      1
      echo `date`
      这个命令会执行 date 命令,并将它的输出作为 echo 命令的参数。结果就是打印出当前的日期和时间。
    • 现代 shell 编程实践中,反引号已经逐渐被 $() 所取代,因为后者可读性更好,且易于嵌套。$() 与反引号有着同样的效果,但是它使用圆括号而不是反引号。
    • 使用 $() 的同一命令会是这样的:
      1
      echo $(date)
    • 这样也会执行 date 命令,但是 $() 语法更加清晰,并且当需要嵌套命令替换时能更容易地阅读和理解。例如,如果你需要在命令替换中再进行命令替换,使用 $() 就比使用反引号简洁得多:
      1
      echo $(basename $(pwd))
      这会打印出当前工作目录的基本名(即最后一个目录的名字),而不是完整的路径。
    • 总的来说,反引号和 $() 都用于命令替换,但是推荐使用 $() 因为它更加现代和灵活。
  • 反斜线 \

    • 反斜线在 Unix 和类 Unix 操作系统的 shell 编程中扮演着很重要的角色。它主要用于以下几个方面:
      1. 转义字符:
        反斜线用于移除紧随其后的字符的特殊意义。这在处理包含特殊字符或保留字(比如空格、*?$"' 等)的字符串时特别有用。例如,如果你想打印 "$HOME" 字面量,你需要使用转义字符来防止 $ 被当作变量的引用来处理:
        1
        echo "\$HOME"
      2. 续行符:
        在 shell 脚本或命令行中,反斜线也用作续行符,用于将长命令分割成几行以提高可读性。当在行尾使用反斜线时,shell 会忽略换行符,并将下一行作为当前行的继续:
        1
        2
        echo "This is a very long line, \
        split over two lines."
      3. 字符字面量:
        在某些命令中,比如 sedawk,反斜线用于指定某些字符的字面量,如 \n 代表换行符,\t 代表制表符等。
      4. 引用下一个字符:
        在正则表达式中,反斜线用于引用下一个字符,标示它是一个特殊字符或一个普通字符。比如 \. 用于匹配点(.)字符,而不是任意字符。
    • 注意:
      使用反斜线时需要注意,因为它对于 shell 是特殊的,有时你可能需要使用双反斜线(\\)来表示反斜线本身,特别是在传递给需要接受反斜线的程序时。

字符串操作

  1. 字符串拼接

    可以直接将字符串放在一起,shell 会自动拼接它们。

    1
    2
    3
    4
    str1="Hello,"
    str2=" World!"
    greeting="${str1}${str2}"
    echo $greeting # 输出 Hello, World!
  2. 获取字符串长度

    使用 ${#string} 来获取字符串长度。

    1
    2
    str="Hello"
    echo ${#str} # 输出 5
  3. 字符串切片

    使用 ${string:position:length} 来获取子字符串。

    1
    2
    str="Hello, World!"
    echo ${str:7:5} # 输出 World

    在 Bash shell 中, ${str:7:5} 这种参数扩展的语法表示从字符串 str 的第 7 个字符(从 0 开始计数)开始提取长度为 5 的子字符串。因此,如果 str 的值是 "Hello, World!",那么 ${str:7:5} 的结果将会是 "World"

  4. 查找子字符串

    • 在 shell 脚本中,expr 是一个用于执行表达式求值的命令,它可以用来执行数学运算、比较运算,或者对字符串执行一些操作。对于字符串,expr 命令中的 indexmatch 函数分别用于查找子字符串的位置和匹配字符串。
    • 使用 expr index
      • expr index "$string" "$characters" 会返回 $string$characters 中任何一个字符首次出现的位置,位置的计数从 1 开始。如果没有字符匹配,它返回 0。
        1
        2
        3
        str="Hello, World!"
        pos=$(expr index "$str" "W")
        echo $pos # 如果W存在于str中,将输出W首次出现的位置,从1开始计数
      • 在这个例子中,"W" 是在 Hello, World! 的第 8 个位置,因此 expr index 会输出 8
    • 使用 expr matchexpr
      • expr match "$string" "$pattern"expr "$string" : "$pattern" 会根据 $pattern 来匹配 $string 的开头部分。如果匹配成功,它返回匹配的字符串长度;如果失败,它返回 0。
      • $pattern 是一个正则表达式,它应该从字符串的开始进行匹配,而不是在中间的任意位置。这与其他语言或工具中的正则匹配有所不同,那里通常正则表达式可以在字符串中的任何位置匹配。
      • 示例
        1
        2
        3
        str="Hello, World!"
        pos=$(expr match "$str" '.*World')
        echo $pos # 输出匹配到的字符串长度,如果是 .*World,则是 "Hello, World" 的长度
      • 在这个例子中,正则表达式 '.*World' 匹配从开始到 "World" 的子字符串。由于这个子字符串是 "Hello, World",它的长度是 13,所以 expr match 会输出 13
    • 注意事项
      • expr 对正则表达式的支持比较基础,并不支持所有可能习惯的正则表达式特性。
      • expr 命令在使用时,某些字符可能需要转义,如 *() 等,因为它们有特殊的 shell 意义。
      • 使用 expr 执行数学运算时,表达式中的元素间需要有空格,如 expr 2 + 2 而不是 expr 2+2
    • expr 命令在现代 shell 脚本中使用越来越少,因为 Bash 自带的 [[ ]] 测试和 $(( )) 算术扩展提供了更为直观和强大的功能。
  5. 替换字符串

    • 使用 ${string/substring/replacement} 来替换第一次出现的子字符串。
    • 使用 ${string//substring/replacement} 来全局替换子字符串。
    • 示例
      1
      2
      str="Hello, World!"
      echo ${str/World/Shell} # 输出 Hello, Shell!
  6. 删除子字符串

    • 使用 ${string#substring} 来删除字符串开头的部分。
    • 使用 ${string##substring} 来贪婪地删除字符串开头的部分。
    • 使用 ${string%substring} 来删除字符串结尾的部分。
    • 使用 ${string%%substring} 来贪婪地删除字符串结尾的部分。
    • 示例
      1
      2
      file="image.png"
      echo ${file%.*} # 输出 image
  7. 转化大小写(需要 Bash 4.0 及以上版本)

    • 使用 ${string,,} 来将字符串转为小写。
    • 使用 ${string^^} 来将字符串转为大写。
    • 示例
      1
      2
      3
      str="Hello World!"
      echo ${str,,} # 输出 hello world!
      echo ${str^^} # 输出 HELLO WORLD!
  8. 比较字符串

    使用 ==!=[[ ]] 测试构造中比较字符串。

    1
    2
    3
    4
    5
    6
    7
    str1="hello"
    str2="world"
    if [[ $str1 == $str2 ]]; then
    echo "Strings are equal."
    else
    echo "Strings are not equal."
    fi # 输出 Strings are not equal.
  9. 字符串截断

    • cut 是 Linux 和 Unix 系统中常用的命令行工具,用于按列提取文本数据的一部分。你可以指定一个分隔符,然后根据需要选择字段(列)。
    • 以下是 cut 命令的一些常用选项
      • -d:指定字段的分隔符,默认分隔符是制表符。
      • -f:选择要显示的字段,字段编号从 1 开始。
      • -c:选择要显示的字符。
    • 使用 -d-f 选项
      • 当你想要根据特定的分隔符来提取字符串时,你可以使用 -d 选项来指定分隔符,使用 -f 选项来指定你想要提取的字段。
      • 例如,假设你有一个字符串 “name:john,age:30” 并且你想要提取 “john” 这个值,你可以通过指定分隔符为冒号 : 和逗号 , 来提取第一个字段:
        1
        2
        3
        str="name:john,age:30"
        name=$(echo $str | cut -d':' -f2 | cut -d',' -f1)
        echo $name # 输出 john
      • 在这个例子中:
        • 第一个 cut 使用 -d':' 来指定冒号为分隔符,并使用 -f2 来选择第二个字段,这将输出 “john,age”。
        • 第二个 cut 使用 -d',' 来指定逗号为分隔符,并使用 -f1 来选择第一个字段,这将最终输出 “john”。
    • 使用 -c 选项
      • 如果你想要根据字符位置来提取字符串,可以使用 -c 选项来指定要提取的字符范围。
      • 例如,如果你有一个字符串 “12345” 并且只想提取中间的 “234”,你可以这样做:
        1
        2
        3
        str="12345"
        chars=$(echo $str | cut -c2-4)
        echo $chars # 输出 234
      • 在这个例子中,-c2-4 表示从第二个字符开始,截取到第四个字符。
      • cut 命令在文本文件和管道命令中非常有用,让你能够方便地提取你需要的数据。它通常用于处理 CSV 文件、日志文件,或任何有固定分隔符的文本数据。

数组

  • 定义数组

    创建一个数组通常使用圆括号 (),元素之间用空格分开:

    1
    array=(element1 element2 element3)
  • 访问数组元素

    访问数组中的元素需要使用大括号 {},并用索引指定元素的位置(索引从0开始):

    1
    2
    echo ${array[0]}  # 输出第一个元素
    echo ${array[1]} # 输出第二个元素
  • 获取全部数组元素

    使用 @* 可以获取数组中的所有元素,它们将展开为数组中的所有元素,并且元素之间由 IFS 环境变量(默认为空格)分隔:

    1
    2
    echo ${array[@]} # 输出element1 element2 element3
    echo ${array[*]} # 输出element1 element2 element3

    两者在大多数情况下是可互换的,但在双引号中使用时,"@" 会保留元素之间的空白符(如空格、换行符),而 "*" 则会将所有元素合并成一个字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/bash

    array=("first element" "second element" "third element")

    echo "Using [@]:"
    for item in "${array[@]}"; do
    echo "$item"
    done

    echo "Using [*]:"
    for item in "${array[*]}"; do
    echo "$item"
    done

    输出

    1
    2
    3
    4
    5
    6
    Using [@]:
    first element
    second element
    third element
    Using [*]:
    first element second element third element

    总结一下:

    • "${array[@]}" 在双引号中使用时,会安全地处理数组元素内部的空白符,并且保持每个元素作为独立的单位,这在传递参数给命令或是遍历数组元素时特别有用。
    • "${array[*]}" 在双引号中使用时,会将所有元素合并成一个包含空格分隔的单一字符串,这在需要将数组元素合并为一个字符串时很有帮助
  • 获取数组的长度

    要获取数组的长度(即元素的数量),可以使用 #

    1
    echo ${#array[@]}  # 输出数组元素的个数
  • 修改数组元素

    可以直接通过索引来修改数组中的某个元素:

    1
    array[0]=newElement1  # 将第一个元素修改为 newElement1
  • 添加数组元素

    在数组尾部添加元素可以直接指定新的索引或者不指定索引:

    1
    array+=(newElement)  # 在数组尾部添加一个新的元素
  • 删除数组元素

    使用 unset 命令可以删除数组中的元素:

    1
    unset array[1]  # 删除第二个元素

    请注意,使用 unset 删除元素后,数组的索引将不会重新排列。例如,如果你有一个包含4个元素的数组,然后你删除了第二个元素,数组将包含元素0、2和3(1号索引将不存在)。

  • 遍历数组

    使用 for 循环可以遍历数组中的所有元素:

    1
    2
    3
    4
    for elem in "${array[@]}"
    do
    echo $elem
    done

    注意,这里使用的是 "${array[@]}" 来确保元素内部的空白符被正确处理。

  • 切割数组

    你可以使用 : 和数值来切割数组:

    1
    echo ${array[@]:1:2}  # 输出从第二个元素开始的2个元素
  • 关联数组(类似于其他语言中的字典)

    Bash 4.0 引入了关联数组,它使用字符串作为索引,而不是数字。要声明关联数组,需要使用 -A 选项:

    1
    2
    3
    4
    5
    declare -A assoc_array
    assoc_array[key1]=value1
    assoc_array[key2]=value2

    echo ${assoc_array[key1]} # 输出 value1

seq

  • 简介

    • 在 Linux shell 中,seq 是一个常用的命令,用于生成从一个数到另一个数之间的数字序列。
    • seq 命令非常有用,尤其在编写需要序列数据的脚本时。你可以将 seq 生成的序列用于循环控制或者任何需要数列的场景。
  1. 生成一个简单的数列

    1
    seq 1 10

    这将生成从 1 到 10 的数字序列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. 指定一个增量(步长)

    1
    seq 1 2 10

    这将生成一个从 1 开始到 10 结束的序列,步长为 2,即:1, 3, 5, 7, 9。

    1
    2
    3
    4
    5
    1
    3
    5
    7
    9
  3. 生成倒序数列

    1
    seq 10 -1 1

    这将生成从 10 到 1 的倒序数列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
  4. 使用固定的宽度格式化数字

    1
    seq -w 0 5 50

    这将生成一个从 0 到 50 的数字序列,步长为 5,并且每个数字都会被格式化为相同的宽度,通过在前面添加零来实现,例如:00, 05, 10, …, 50。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    00
    05
    10
    15
    20
    25
    30
    35
    40
    45
    50
  5. 将序列输出到一个文件

    1
    seq 1 100 > numbers.txt

    这将生成一个从 1 到 100 的数字序列,并将其保存到 numbers.txt 文件中。

  6. 生成浮点数序列

    1
    seq 0.5 0.25 2

    这将生成一个以 0.25 为步长的浮点数序列,从 0.5 开始到 2 结束。

    1
    2
    3
    4
    5
    6
    7
    0.50
    0.75
    1.00
    1.25
    1.50
    1.75
    2.00

expr

  • 简介

    • 在 Linux shell 中,expr 是一个用于执行整数或字符串表达式求值的命令工具。它可以用来进行基本的数学运算、比较运算、字符串操作等。
    • 请注意,在使用 expr 时,表达式中的元素需要用空格隔开。同时,因为某些字符(比如 *( )在 shell 中有特殊含义,可能需要使用反斜杠进行转义,以避免 shell 直接解释这些字符。
  1. 基本数学运算

    例如,执行加法运算:

    1
    expr 5 + 3

    输出将会是 8

  2. 整数乘法

    注意,由于星号(*)是 shell 的特殊字符,所以在用作乘法符号时,需要用反斜杠转义:

    1
    expr 5 \* 3

    输出将会是 15

  3. 整数除法

    1
    expr 10 / 2

    输出将会是 5

  4. 整数取模(求余数)

    1
    expr 10 % 3

    输出将会是 1

  5. 比较运算

    expr 也可以用于比较两个数值或字符串,并返回比较结果(通常是 1 表示真,0 表示假):

    1
    expr 5 \< 10

    输出将会是 1,表示 5 小于 10 的比较结果为真。

  6. 字符串长度

    1
    expr length "Hello World"

    输出将会是字符串 “Hello World” 的长度,即 11

  7. 字符串索引

    找出字符串 “Hello” 中字母 “l” 首次出现的位置(位置索引从 1 开始):

    1
    expr index "Hello" l

    输出将会是 3

  8. 字符串截取

    从字符串 “Hello World” 中截取第一个单词:

    1
    expr substr "Hello World" 1 5

    输出将会是 Hello

    • 1 表示子字符串的起始位置(注意,在 expr 中位置是从 1 开始计数的,而不是从 0 开始)。
    • 5 表示要提取的子字符串的长度。
  9. 正则表达式匹配

    expr 命令可以使用 : 运算符来执行基于正则表达式的匹配。

    1
    2
    expr "Hello World" : '.*' # 输出 11
    expr "Hello World" : '\(.*\)' # 输出 Hello World

    在大多数 expr 版本中,正则表达式匹配返回的是捕获的字符串的长度。但是,如果模式包含了括号,它通常返回括号内匹配到的字符串,而不是长度。

运算方法

  1. 内置的 Shell 算术扩展 ($((...)))

    1
    2
    result=$((3 + 5))
    echo $result # 输出 8
  2. 使用 let 命令

    1
    2
    let result=3+5
    echo $result # 输出 8
  3. 使用 expr 命令

    1
    2
    result=$(expr 3 + 5)
    echo $result # 输出 8
  4. 使用 bc (基础计算器)

    1
    2
    result=$(echo "3+5" | bc)
    echo $result # 输出 8
  5. 使用 awk 进行计算

    1
    2
    result=$(awk 'BEGIN {print 3+5}')
    echo $result # 输出 8
  • 总结

    • $((...)) 是一种内置的 shell 算术扩展,适合进行整数运算。
    • let 命令也是一个内置的 shell 命令,它同样用于执行整数运算。
    • expr 是一个外部程序,可以处理整数和字符串运算,但在使用时需要注意空格和字符的转义。
    • bc 是一个精度任意的计算器语言,可以进行浮点数运算。
    • awk 是一个功能强大的文本处理工具,它内置了支持浮点运算的算术操作。

运算分类

  1. 整数运算

    • 简介

      • Shell 可以直接执行整数的加、减、乘、除等基本算术运算。例如使用 $(( ... )) 扩展或 let 命令。
      • 请注意,在使用乘法运算符时,如果您直接在命令行或脚本中输入命令,星号 * 可能需要转义或用引号括起来,以避免它被解释为通配符。但在 $(( ... )) 算术扩展中,通常不需要转义。
      • 另外,Shell 的除法是整数除法,如果除不尽,小数部分会被舍弃。如果您需要进行浮点运算,就需要使用 bcawk 等工具。
    • 加法(+)

      1
      echo $(( 5 + 3 ))  # 输出 8
    • 减法(-)

      1
      echo $(( 5 - 3 ))  # 输出 2
    • 乘法(*)

      1
      echo $(( 5 * 3 ))  # 输出 15
    • 除法(/)

      1
      echo $(( 15 / 3 ))  # 输出 5
    • 求余(%)

      1
      echo $((10 % 3)) # 输出 1
  2. 浮点运算

    • 简介

      • Shell 本身不支持浮点运算,但可以通过调用像 bcawk 这样的外部程序来执行浮点数学运算。
      • 在使用 bc 时,scale 设置了小数点后的精度。如果不设置 scalebc 默认的小数精度是 0,即执行整数除法。
      • awk 默认对浮点数具有内置的支持,并且会根据计算的结果自动设置精度。
      • 这些命令可以在 Linux shell 中直接执行,也可以在 shell 脚本中使用。 使用 echo 命令将运算表达式传给 bc 是因为 bc 是一个交互式的计算器程序,它从标准输入读取表达式。相比之下,awk 是一个模式扫描和文本处理语言,它的 BEGIN 块在处理任何实际的文本输入之前执行,并直接输出计算结果。
    • 加法(+)

      • bc
        1
        echo "scale=2; 5.75 + 2.50" | bc  # 输出 8.25
      • awk
        1
        awk 'BEGIN {print 5.75 + 2.50}'  # 输出 8.25
    • 减法(-)

      • bc
        1
        echo "scale=2; 5.75 - 2.50" | bc  # 输出 3.25
      • awk
        1
        awk 'BEGIN {print 5.75 - 2.50}'  # 输出 3.25
    • 乘法(*)

      • bc
        1
        echo "scale=2; 5.75 * 2.50" | bc  # 输出 14.37
      • awk
        1
        awk 'BEGIN {print 5.75 * 2.50}'  # 输出 14.375
    • 除法(/)

      • bc
        1
        echo "scale=2; 5.75 / 2.50" | bc  # 输出 2.30
      • awk
        1
        awk 'BEGIN {print 5.75 / 2.50}'  # 输出 2.3
  3. 比较运算

    • 数值比较

      • 等于:-eq
        1
        2
        if [ 10 -eq 10 ]; then echo "Equal"; fi
        # 输出:Equal
      • 不等于:-ne
        1
        2
        if [ 10 -ne 20 ]; then echo "Not Equal"; fi
        # 输出:Not Equal
      • 小于:-lt
        1
        2
        if [ 5 -lt 10 ]; then echo "Less Than"; fi
        # 输出:Less Than
      • 大于:-gt
        1
        2
        if [ 20 -gt 10 ]; then echo "Greater Than"; fi
        # 输出:Greater Than
      • 小于或等于:-le
        1
        2
        if [ 10 -le 10 ]; then echo "Less Than or Equal"; fi
        # 输出:Less Than or Equal
      • 大于或等于:-ge
        1
        2
        if [ 10 -ge 5 ]; then echo "Greater Than or Equal"; fi
        # 输出:Greater Than or Equal
    • 字符串比较

      • 字符串相等: =
        1
        2
        if [ "hello" = "hello" ]; then echo "Strings are equal"; fi
        # 输出:Strings are equal
      • 字符串不相等: !=
        1
        2
        if [ "hello" != "world" ]; then echo "Strings are not equal"; fi
        # 输出:Strings are not equal
      • 字符串为空: -z
        1
        2
        3
        str=""
        if [ -z "$str" ]; then echo "String is empty"; fi
        # 输出:String is empty

        -z 测试操作符用来检查字符串是否为空(Zero length)。如果给定的字符串长度为零,则返回真(true)

      • 字符串非空: -n
        1
        2
        3
        str="hello"
        if [ -n "$str" ]; then echo "String is not empty"; fi
        # 输出:String is not empty

        -n 测试操作符用来检查字符串是否非空(Non-zero length)。如果给定的字符串长度非零,则返回真(true)。

      • 字符串小于: <
        1
        2
        if [[ "abc" < "def" ]]; then echo "abc comes before def"; fi
        # 输出:abc comes before def
      • 字符串大于: >
        1
        2
        if [[ "def" > "abc" ]]; then echo "def comes after abc"; fi
        # 输出:def comes after abc
      • 注意:

        在进行字符串比较时,<> 需要在 [[ ]] 中使用,或者在 [ ] 中使用时需要转义,如 \<\>,以避免被 shell 当作重定向操作。

  4. 逻辑运算

    • 简介

      • Shell 支持逻辑运算符,包括逻辑与(&&)、逻辑或(||)和逻辑非(!)。
      • 在 Linux shell 中,可以使用 -a-o 进行逻辑运算,也可以使用 &&|| 进行条件组合。
      • 注意,-a-otest[ .. ] 命令中用于逻辑运算,但已经被认为是过时的,而且在 POSIX 标准中不鼓励使用。在现代 shell 脚本中,建议使用 [[ .. ]] 结构与 &&|| 运算符。
      • 同时请注意,&&|| 在 shell 中有双重含义。它们既可以用作逻辑运算符,也可以用作控制运算符,用于根据之前命令的退出状态来执行命令。在这里,我们主要讨论逻辑运算的使用。
    • 逻辑与 (&&)

      • 如果两个条件都为真,整个表达式为真。

        1
        [[ 3 -lt 5 && 7 -gt 2 ]] && echo "Both conditions are true"  # 输出 "Both conditions are true"

        检查 3 是否小于 5 并且同时 7 是否大于 2。因为两个条件都是真的,所以 echo 命令被执行。

      • 在一行中使用 && 来连续执行命令,仅当第一个命令成功时,第二个命令才会执行:

        1
        2
        # 更新软件包列表并且安装一个新的软件包(仅当第一个命令成功时)
        sudo apt update && sudo apt install -y package-name
    • 逻辑或 (||)

      • 如果两个条件中至少有一个为真,整个表达式为真。

        1
        [[ 3 -gt 5 || 7 -gt 2 ]] && echo "At least one condition is true"  # 输出 "At least one condition is true"

        检查 3 是否大于 5 或者 7 是否大于 2。在这个案例中,第一个条件是假的,但第二个条件是真的,所以 echo 命令被执行。

      • 在一行中使用 || 来尝试执行第一个命令,如果它失败了,则执行第二个命令:

        1
        2
        # 尝试先创建一个目录,如果失败了(目录已存在),则打印一个消息
        mkdir /path/to/dir || echo "Directory already exists"
    • 逻辑非 (!)

      • 如果条件为假,则整个表达式为真。

        1
        [[ ! 3 -gt 5 ]] && echo "Condition is not true"  # 输出 "Condition is not true"

        检查 3 是否不大于 5。因为 3 不大于 5,逻辑非将这个假条件转换为真,所以 echo 命令被执行。

      • 使用 ! 来反转一个命令的退出状态:

        1
        2
        # 如果文件不存在,则创建它
        [ ! -e /path/to/file ] && touch /path/to/file
    • 组合使用

      布尔运算符可以组合使用来构造复杂的条件表达式

      1
      2
      3
      4
      # 检查文件是否不存在或者不可写
      if [ ! -e /path/to/file ] || [ ! -w /path/to/file ]; then
      echo "File does not exist or is not writable"
      fi

      这些基本的布尔逻辑可以帮助你构建更复杂的脚本逻辑,以处理各种条件和情况。记得,为了避免执行逻辑可能不符合预期的情况,使用括号 () 来明确表达式的优先级是一个好习惯。在 shell 中,你需要对括号使用转义字符,像这样 \(\),或者将它们放在单引号内,像这样 '( )'

  5. 位运算

    • 简介

      • Shell 支持位运算符,如位与(&)、位或(|)、位非(~)、位异或(^)、位左移(<<)和位右移(>>)。
      • 在 Linux shell 中,您可以使用 $(( ... )) 算术扩展来执行位运算。
      • 在下述位非运算的例子中,结果可能会让人感到意外。在 Bash 中,数字默认被看作是有符号整数。位非运算会将数字的所有位取反。由于大多数 Bash 实现使用的是补码形式表示负数,所以在对 5 进行位非运算后,我们得到了 -6 而不是一个大正数。如果你对一个 8 位的无符号整数进行位非运算,结果会是 250 (二进制:11111010),这是因为全部的位从 00000101 变成了 11111010。但在有符号的环境中,最高位(符号位)为 1 表示这是一个负数,具体的数值需要根据补码规则来解释。
    • 位与(AND)运算符 &

      1
      echo $(( 5 & 3 ))  # 输出 1 (二进制: 101 & 011 = 001)
    • 位或(OR)运算符 |

      1
      echo $(( 5 | 3 ))  # 输出 7 (二进制: 101 | 011 = 111)
    • 位非(NOT)运算符 ~

      1
      echo $(( ~5 ))  # 输出 -6 (二进制: ~101 = ...1111111111111010, 为二进制补码形式)
    • 位异或(XOR)运算符 ^

      1
      echo $(( 5 ^ 3 ))  # 输出 6 (二进制: 101 ^ 011 = 110)
    • 位左移(LEFT SHIFT)运算符 <<

      1
      echo $(( 5 << 1 ))  # 输出 10 (二进制: 101 << 1 = 1010)
    • 位右移(RIGHT SHIFT)运算符 >>

      1
      echo $(( 5 >> 1 ))  # 输出 2 (二进制: 101 >> 1 = 010)
  6. 文件测试运算

    • 检查文件是否存在: -e

      1
      2
      3
      if [ -e /path/to/file ]; then
      echo "File exists."
      fi
    • 检查文件是否是常规文件: -f

      1
      2
      3
      if [ -f /path/to/file ]; then
      echo "This is a regular file."
      fi
    • 检查文件是否是目录: -d

      1
      2
      3
      if [ -d /path/to/dir ]; then
      echo "This is a directory."
      fi
    • 检查文件是否可读: -r

      1
      2
      3
      if [ -r /path/to/file ]; then
      echo "File is readable."
      fi
    • 检查文件是否可写: -w

      1
      2
      3
      if [ -w /path/to/file ]; then
      echo "File is writable."
      fi
    • 检查文件是否可执行: -x

      1
      2
      3
      if [ -x /path/to/file ]; then
      echo "File is executable."
      fi
    • 检查文件是否为空: -s

      1
      2
      3
      4
      5
      if [ -s /path/to/file ]; then
      echo "File is not empty."
      else
      echo "File is empty."
      fi
    • 检查文件是否是符号链接: -L

      1
      2
      3
      if [ -L /path/to/link ]; then
      echo "This is a symbolic link."
      fi
      • 符号链接(symbolic link)是一种特殊类型的文件,它包含对另一个文件或目录的引用。当访问一个符号链接时,操作系统会自动将该访问重定向到链接指向的目标文件或目录。
      • 符号链接在概念上类似于 Windows 系统中的快捷方式,但在 Unix 和类 Unix 系统中,符号链接是文件系统级别的实体。
      • 符号链接有两种类型:
        • 软链接(或称为符号链接): 这是最常见的类型,创建时使用 ln -s 命令。软链接可以跨文件系统,并且可以链接到目录。
        • 硬链接: 硬链接是对文件系统中文件的另一个引用,创建时使用 ln 命令。硬链接不能跨越文件系统,也不能链接到目录。
    • 检查文件是否最近被访问过: -a

      1
      2
      3
      if [ -a /path/to/file ]; then
      echo "File has been accessed recently."
      fi

      注意:-a 在某些环境中可能是检查文件是否存在的老旧方法,这里的“访问”是指使用 touch 命令或者类似的方式更新文件的访问时间。

    • 检查一个文件是否比另一个文件新: -nt

      1
      2
      3
      if [ /path/to/file1 -nt /path/to/file2 ]; then
      echo "file1 is newer than file2."
      fi
    • 检查一个文件是否比另一个文件旧: -ot

      1
      2
      3
      if [ /path/to/file1 -ot /path/to/file2 ]; then
      echo "file1 is older than file2."
      fi
    • 检查一个文件是否为字符设备文件: -c

      字符设备文件是一种支持以字符为单位的顺序访问的设备文件。它们通常用于像键盘、鼠标或其他类型的字符流设备。

      1
      2
      3
      if [ -c /dev/tty ]; then
      echo "/dev/tty is a character device file."
      fi
    • 检查一个文件是否为块设备文件: -b

      块设备文件是一种支持随机访问的设备文件。对于块设备文件的每次读写操作都以块为单位进行,例如硬盘驱动器、光盘驱动器等。

      1
      2
      3
      if [ -b /dev/sda ]; then
      echo "/dev/sda is a block device file."
      fi

test 命令

  • 简介

    在 Linux 中,test 命令是一个检查文件类型和比较值的工具。它用于评估条件表达式。如果表达式为真,则 test 命令退出状态为 0(表示成功)。如果表达式为假,则退出状态为 1(表示失败)。除了直接使用 test 命令,还有一个 [ 的同义命令,它们的行为是一样的,但是使用 [ 的时候需要注意,命令的末尾必须有一个相应的 ]

  • 文件属性测试

    • 检查文件是否存在:test -e filename
    • 检查文件是否是目录:test -d filename
    • 检查文件是否可读:test -r filename
    • 检查文件是否可写:test -w filename
    • 检查文件是否可执行:test -x filename
    • 检查文件是否是普通文件:test -f filename
    • 检查文件是否是符号链接:test -L filename
    • 检查文件是否大小大于零:test -s filename
  • 字符串比较测试

    • 检查字符串是否为空:test -z string
    • 检查字符串是否非空:test -n string
    • 检查两个字符串是否相等:test "string1" = "string2"
    • 检查两个字符串是否不相等:test "string1" != "string2"
  • 整数比较测试

    • 检查两个数是否相等:test num1 -eq num2
    • 检查第一个数是否大于第二个数:test num1 -gt num2
    • 检查第一个数是否小于第二个数:test num1 -lt num2
    • 检查第一个数是否大于等于第二个数:test num1 -ge num2
    • 检查第一个数是否小于等于第二个数:test num1 -le num2
    • 检查两个数是否不相等:test num1 -ne num2
  • 组合条件测试

    • 逻辑与:test condition1 -a condition2
    • 逻辑或:test condition1 -o condition2
    • 逻辑非:test ! condition
  • 示例

    • 在 shell 脚本中,通常会在 if 语句中使用 test 命令,例如:

      1
      2
      3
      4
      5
      if test -f "/path/to/somefile"; then
      echo "File exists."
      else
      echo "File does not exist."
      fi
    • 或者使用 [ 的同义命令形式:

      1
      2
      3
      4
      5
      if [ -f "/path/to/somefile" ]; then
      echo "File exists."
      else
      echo "File does not exist."
      fi

      注意:使用 [ 时,在 [] 之间以及它们与表达式之间都应该有空格。

分支结构

  • if 语句

    • if 语句用来基于一定条件执行命令,常见的形式为:

      1
      2
      3
      4
      5
      6
      7
      if [ condition ]; then
      # 命令
      elif [ another_condition ]; then
      # 其他命令
      else
      # 默认命令
      fi
    • 例如,检查用户输入的数字是否为正数、负数或零:

      1
      2
      3
      4
      5
      6
      7
      8
      read -p "Enter a number: " num
      if [ "$num" -gt 0 ]; then
      echo "The number is positive."
      elif [ "$num" -lt 0 ]; then
      echo "The number is negative."
      else
      echo "The number is zero."
      fi
      • 在 Bash shell 中,read 命令用于从标准输入(通常是键盘)读取一行数据。当你在 read 命令中使用 -p 选项时,它允许你指定一个提示字符串,用于在读取输入之前显示给用户。
      • 这里的 -p 选项后面跟随的字符串 "Enter a number: " 会在命令行上显示出来,并立即等待用户输入。用户输入的值将被存储到变量 num 中,然后可以在脚本中使用这个变量。
  • case 语句

    • case 语句用于基于不同的值执行不同命令,语法如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      case $variable in
      pattern1)
      # 命令
      ;;
      pattern2)
      # 命令
      ;;
      *)
      # 默认命令
      ;;
      esac
    • 例如,根据用户输入的字符进行不同的问候:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      read -p "Enter a letter (a/b/c): " letter
      case $letter in
      a)
      echo "Hello, Apple!"
      ;;
      b)
      echo "Hello, Banana!"
      ;;
      c)
      echo "Hello, Cherry!"
      ;;
      *)
      echo "I don't know this letter."
      ;;
      esac
  • select 语句

    • select 语句用于生成简单的菜单,它基本上是 case 语句的一种特殊形式,常用于交互式脚本。select 语句为用户提供一个简单的菜单选择机制,用户可以从一系列选项中选择一个。当执行 select 语句时,它会创建一个菜单并提示用户进行选择。用户输入一个数字来选择与该数字相关联的选项。用户的选择将赋值给变量,然后可以在脚本的其他部分使用该变量。
    • 一旦 break 命令执行,控制流程将退出 select 循环,继续执行后面的命令。如果没有 break 命令,select 语句将无限循环,不断提示用户选择。语法如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      select item in option1 option2 option3; do
      case $item in
      option1)
      echo "You picked $item."
      ;;
      option2)
      echo "You picked $item."
      ;;
      option3)
      echo "You picked $item."
      ;;
      *)
      echo "Invalid option."
      break
      ;;
      esac
      done
    • 例如,创建一个简单的菜单让用户选择:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      echo "Please pick an option:"
      select fruit in Apple Banana Cherry Quit; do
      case $fruit in
      Apple)
      echo "Apples are delicious!"
      ;;
      Banana)
      echo "Bananas are full of potassium!"
      ;;
      Cherry)
      echo "Cherries are sweet!"
      ;;
      Quit)
      break
      ;;
      *)
      echo "Please choose a valid option."
      ;;
      esac
      done
      输出:
      1
      2
      3
      4
      5
      6
      Please pick an option:
      1) Apple
      2) Banana
      3) Cherry
      4) Quit
      #?

循环结构

  • for 循环

    • for 循环通常用于遍历列表中的每一个元素。
    • 示例 1:数值范围
      1
      2
      3
      for i in {1..5}; do
      echo "Number $i"
      done

      这将打印数字1到5。

    • 示例 2:遍历列表
      1
      2
      3
      for fruit in apple banana cherry; do
      echo "I like $fruit!"
      done

      这将遍历列表中的每种水果,并打印出相应的语句。

  • while 循环

    • while 循环会在指定的条件为真时不断执行一组命令。
    • 示例:
      1
      2
      3
      4
      5
      counter=1
      while [ $counter -le 5 ]; do
      echo "Counter is $counter"
      ((counter++))
      done

      这里只要 counter 的值小于或等于5,循环就会继续执行,打印出 counter 的值,并将 counter 的值加1。

  • until 循环

    • until 循环则是当指定条件为假时执行,一旦条件为真,循环停止。
    • 示例:
      1
      2
      3
      4
      5
      counter=1
      until [ $counter -gt 5 ]; do
      echo "Counter is $counter"
      ((counter++))
      done

      这个循环会一直执行直到 counter 的值大于5。

  • C 风格的 for 循环

    • Bash 也支持 C 程序设计语言风格的 for 循环。
    • 示例:
      1
      2
      3
      for ((i = 0; i <= 5; i++)); do
      echo "Number $i"
      done

      这将按照 C 语言风格递增 i 的值,并打印出来,直到它大于 5。

  • break 和 continue

    • 简介

      • 在 Linux shell 中,breakcontinue 是用于控制循环流程的命令,它们可以用在 forwhileuntil 循环中。
      • 这两个命令的作用是对循环的执行流程进行控制,使得脚本编写者可以更精细地控制循环的行为。
    • break

      • break 命令用于立即退出包含它的循环,即使循环的结束条件尚未满足。如果循环嵌套,break 只会退出最内层的循环。
        1
        2
        3
        4
        5
        6
        for i in {1..10}; do
        if [ $i -eq 5 ]; then
        break
        fi
        echo "Number $i"
        done
      • 这段代码会打印数字 1 到 4。当 i 等于 5 时,break 命令执行,导致 for 循环提前结束。
    • continue

      • continue 命令用于跳过当前循环的剩余部分,并继续执行下一个循环迭代。
        1
        2
        3
        4
        5
        6
        for i in {1..10}; do
        if [ $i -eq 5 ]; then
        continue
        fi
        echo "Number $i"
        done
      • 这段代码会打印数字 1 到 10,但是不会打印数字 5。当 i 等于 5 时,continue 命令执行,导致 for 循环跳过数字 5 直接继续到数字 6。

函数

  • 在 Linux shell 脚本中,函数是一种复用代码的方式,可以在脚本的任意位置调用以执行特定的任务。下面是一个简单的函数示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/bash

    # 定义一个名为 greet 的函数
    greet() {
    echo "Hello, $1!"
    }

    # 调用函数 greet 并传递一个参数
    greet "World"

    # 再次调用函数 greet 并传递一个不同的参数
    greet "User"
    • 在这个例子中,我们定义了一个名为 greet 的函数,这个函数接受一个参数(通过 $1 访问),当函数被调用时,会打印出 "Hello, " 后跟传递给它的参数。
    • 函数 greet 被调用了两次,第一次传递了 “World” 作为参数,第二次传递了 “User”。这会产生以下输出:
      1
      2
      Hello, World!
      Hello, User!
  • 函数也可以接收多个参数,并在函数体内部通过 $1, $2, $3 等访问这些参数。如果你想访问所有的参数,可以使用 $@$*。函数可以通过 return 命令返回状态码(不是值),或者通过 echo 命令输出结果,然后在函数外部通过命令替换捕获这些输出。
  • 下面是一个更复杂的函数例子,该函数用于计算两个数的和:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/bash

    # 定义一个名为 add 的函数,计算两个数的和
    add() {
    local sum=$(($1 + $2))
    echo $sum
    }

    # 调用函数,并捕获输出(函数的返回值)
    result=$(add 3 5)

    # 打印结果
    echo "The sum is: $result"
    • 这个脚本定义了一个 add 函数,接收两个参数(两个数),然后在局部变量 sum 中计算它们的和。函数通过 echo 输出计算结果,该结果可以在函数外部通过命令替换($(add 3 5))来捕获。运行这个脚本会产生以下输出:
      1
      The sum is: 8
  • 在 Linux shell 脚本中,$? 是一个特殊的变量,它表示最后执行的命令的退出状态。退出状态是一个从 0 到 255 的数值,其中 0 通常表示成功,而非零值通常表示错误或异常状态。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/bash

    arg() {
    echo $1
    echo $2
    echo $#
    echo $@
    echo $*
    echo "$*"
    return 123
    }
    arg 1 2
    echo $?
    1. echo $1 打印出第一个参数,即 1
    2. echo $2 打印出第二个参数,即 2
    3. echo $# 打印出传递给函数的参数个数,这里是 2
    4. echo $@ 打印出所有传递给函数的参数,这里是 1 2。它们是按照传递给函数的原样输出的。
    5. echo $*$@ 类似,也打印出所有传递给函数的参数,这里也是 1 2。不过在被双引号包围时,"$*" 把所有参数看作一个整体,而 "$@" 仍然把它们视为独立的参数。
    6. return 123 设置函数的退出状态为 123。在 bash 中,函数的退出状态可以通过 $? 获取,类似于一个程序或命令的退出状态。
  • 当你在函数中或函数之后使用 $?,你会得到最近执行的命令(这可以是函数调用或任何其他命令)的退出状态。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #!/bin/bash

    # 定义一个函数,它尝试使用一个不存在的命令
    fakeCommand() {
    non_existing_command
    }

    # 调用函数 fakeCommand
    fakeCommand

    # 打印 fakeCommand 函数后的退出状态
    echo "The exit status of the function was: $?"

    # 现在执行一个成功的命令
    ls >/dev/null

    # 打印 ls 命令后的退出状态
    echo "The exit status of 'ls' was: $?"
    • 在此脚本中,fakeCommand 函数尝试执行一个不存在的命令。该命令将失败,并返回一个非零退出状态。当我们打印 $? 时,它将显示那个失败的退出状态。
    • 随后,我们执行 ls 命令并将输出重定向到 /dev/null(忽略输出)。因为 ls 命令通常会成功运行,所以紧接着该命令后打印的 $? 应该是 0,表示成功。
    • 运行这个脚本会产生以下输出(假设脚本名称为 test.sh ):
      1
      2
      3
      ./test.sh: line 5: non_existing_command: command not found
      The exit status of the function was: 127
      The exit status of 'ls' was: 0

服务管理

  • 简介

    • Linux 系统通常使用系统服务守护进程(如 Systemd、SysVinit 或 Upstart)来管理服务。以 Systemd 为例,它是目前大多数 Linux 发行版使用的初始化系统和服务管理器。
    • 请注意,对于大多数服务管理命令,你需要具有管理员权限,因此在下述示例中使用了 sudo 命令。此外,服务名称可能因不同的 Linux 发行版而异,如 sshd 服务在某些发行版中可能被称为 ssh.service
  • 管理服务

    以下是一些基本的 Systemd 命令,用于管理服务:

    1. 启动服务

      1
      sudo systemctl start [service_name]

      例如,要启动 SSH 服务,使用:

      1
      sudo systemctl start sshd
    2. 停止服务

      1
      sudo systemctl stop [service_name]

      例如,停止 SSH 服务:

      1
      sudo systemctl stop sshd
    3. 重启服务

      1
      sudo systemctl restart [service_name]

      重启 SSH 服务的命令如下:

      1
      sudo systemctl restart sshd
    4. 查看服务状态

      1
      sudo systemctl status [service_name]

      查看 SSH 服务的状态:

      1
      sudo systemctl status sshd
    5. 使服务在启动时自动运行

      1
      sudo systemctl enable [service_name]

      使 SSH 服务开机自启:

      1
      sudo systemctl enable sshd
    6. 禁用服务自动启动

      1
      sudo systemctl disable [service_name]

      禁止 SSH 服务开机自启:

      1
      sudo systemctl disable sshd
    7. 重载服务配置

      1
      sudo systemctl reload [service_name]

      如果服务支持不中断服务进行配置重载,那么可以用此命令。例如,对 Apache 服务进行配置文件的重载:

      1
      sudo systemctl reload apache2
    8. 查看所有服务的状态

      1
      systemctl list-units --type service

      这将列出所有系统服务及其状态。

    9. 查看已启用的服务

      1
      systemctl list-unit-files --type=service --state=enabled

      这显示了所有设置为开机自启的服务。

    10. 检查服务是否启动

      1
      systemctl is-active [service_name]

      例如,检查 SSH 服务是否运行中:

      1
      systemctl is-active sshd
  • 自建服务

    • 简介

      • 在 Linux 中创建自己的服务通常意味着编写一个 systemd 服务单元文件。
      • 确保在实际部署之前,在一个安全的环境中测试你的服务脚本。任何时候,如果服务脚本或单元文件需要更新,都需要重新加载 systemd 守护进程,并重启服务以使更改生效。
    1. 创建服务单元文件

      1. 编写一个服务单元文件:首先,你需要创建一个服务单元文件,通常放在 /etc/systemd/system/ 目录下。文件通常以 .service 结尾。以下是一个基本的服务单元文件示例:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        [Unit]
        Description=My Custom Service
        After=network.target

        [Service]
        ExecStart=/usr/local/bin/my-custom-script.sh
        Restart=on-failure
        User=nobody
        Group=nogroup

        [Install]
        WantedBy=multi-user.target
        这个服务单元文件定义了一个服务,它将在 network.target 之后启动,运行位于 /usr/local/bin/ 的脚本,并在失败时自动重启,以 nobody 用户的身份运行。
      2. 编写你的脚本:你需要创建并编辑 /usr/local/bin/my-custom-script.sh 文件,将以下内容加入脚本:
        1
        2
        3
        4
        5
        6
        7
        #!/bin/bash
        # My custom script

        while true; do
        # 你的逻辑代码
        sleep 60
        done
        记得给脚本执行权限:
        1
        sudo chmod +x /usr/local/bin/my-custom-script.sh
    2. 重新加载 systemd 配置

      使用以下命令让 systemd 重新加载所有配置,使得新服务生效:

      1
      sudo systemctl daemon-reload
    3. 启动服务

      接下来,使用以下命令启动你的服务:

      1
      sudo systemctl start my-custom-service

      这里的 my-custom-service 应该与你的 .service 文件的文件名相匹配。

    4. 查看服务状态

      使用以下命令检查服务的状态,确保它正在运行:

      1
      sudo systemctl status my-custom-service
    5. 使服务开机自启

      如果你的服务工作正常,你可能希望它在系统启动时自动启动:

      1
      sudo systemctl enable my-custom-service
    6. 测试服务的持久性

      重启计算机并检查你的服务是否能够成功启动:

      1
      sudo systemctl status my-custom-service
    7. 日志查看

      如果服务没有按预期工作,你可以检查服务的日志以获取更多信息:

      1
      sudo journalctl -u my-custom-service

      这将显示与你的服务相关的所有日志。

  • service 命令

    • 在较旧的 Linux 系统中,或者在仍然使用 SysVinit 或 Upstart 的系统中,服务是通过 service 命令或直接调用 /etc/init.d/ 脚本来管理的。然而,在使用 Systemd 的系统中,推荐的方法是使用 systemctl 命令,因为 Systemd 已经取代了这些较旧的方法成为大多数现代 Linux 发行版的默认初始化系统和服务管理器。
    • 如果你尝试在一个使用 Systemd 的系统上使用 service 命令,它通常会被重定向到 systemctl 命令,以保持兼容性。例如,当你运行:
      1
      sudo service my-custom-service start
    • 在后台,它可能会被转换为:
      1
      sudo systemctl start my-custom-service
    • 如果你所在的系统确实是使用 Systemd 的话,最好直接使用 systemctl 命令,因为这将保证与系统的服务管理工具直接对接,同时也拥有更多的功能和更好的支持。
    • 不过,如果你的系统确实是使用 SysVinit 或 Upstart 的,那么你将需要:
      1. 将服务脚本放在 /etc/init.d/ 目录下。
      2. 使脚本可执行:sudo chmod +x /etc/init.d/my-custom-service
      3. 使用 update-rc.d 或类似工具来配置脚本的自动启动。
      4. 之后就可以使用 service 命令来管理服务了。
    • 但在现代的 Linux 发行版中,这已经变得不太常见了,Systemd 是大多数发行版的标准。
本文结束 感谢您的阅读
正在加载今日诗词....