Linux学习笔记(三)-shell脚本

        这节主要是shell部分的内容。

基础知识

概念

        这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。

        具体来说,Shell 这个词有多种含义。

        首先,Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(commandline,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。

        其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。

        最后,Shell 是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。

        网上有很多 shell 的概念介绍,其实都很官方化,如果你对 linux 命令很熟悉,那么编写 shell 就不是一个难事,shell 本质上是 linux 命令,一条一条命令组合在一起,实现某一个目的,就变成了 shell 脚本。它从一定程度上减轻了我们的工作量,提高了工作效率。

历史

        Shell 伴随着 Unix 系统的诞生而诞生。

        1969年,Ken Thompson 和 Dennis Ritchie 开发了第一版的 Unix。

        1971年,Ken Thompson 编写了最初的 Shell,称为 Thompson shell,程序名是 sh,方便用户使用 Unix。

        1973年至1975年间,John R. Mashey 扩展了最初的 Thompson shell,添加了编程功能,使得 Shell 成为一种编程语言。这个版本的 Shell 称为 Mashey shell。

        1976年,Stephen Bourne 结合 Mashey shell 的功能,重写一个新的 Shell,称为 Bourne shell。

        1978年,加州大学伯克利分校的 Bill Joy 开发了 C shell,为 Shell 提供 C 语言的语法,程序名是 csh 。它是第一个真正替代 sh 的 UNIX shell,被合并到 Berkeley UNIX 的 2BSD 版本中。

        1979年,UNIX 第七版发布,内置了 Bourne Shell,导致它成为 Unix 的默认 Shell。注意,Thompson shell、Mashey shell 和 Bourne shell 都是贝尔实验室的产品,程序名都是 sh 。对于用户来说,它们是同一个东西,只是底层代码不同而已。

        1983年,David Korn 开发了Korn shell,程序名是 ksh 。

        1985年,Richard Stallman 成立了自由软件基金会(FSF),由于 Shell 的版权属于贝尔公司,所以他决定写一个自由版权的、使用 GNU 许可证的 Shell 程序,避免 Unix 的版权争议。

        1988年,自由软件基金会的第一个付薪程序员 Brian Fox 写了一个 Shell,功能基本上是 Bourne shell 的克隆,叫做 Bourne-Again SHell,简称 Bash,程序名为 bash,任何人都可以免费使用。后来,它逐渐成为 Linux 系统的标准 Shell。

        1989年,Bash 发布1.0版。

        1996年,Bash 发布2.0版。

        2004年,Bash 发布3.0版。

        2009年,Bash 发布4.0版。

        2019年,Bash 发布5.0版。

 种类

        Shell 有很多种,只要能给用户提供命令行环境的程序,都可以看作是 Shell。

        历史上,主要的 Shell 主要有 Bourne Shell(sh)、Bourne Again shell(bash)、C Shell(csh)、TENEX C Shell(tcsh)、Korn shell(ksh)、Z Shell(zsh)、Friendly Interactive Shell(fish)等。

        因为后续都是用bash举例,所以这里在介绍 bash 外只简单延伸一下其他的shell。

Bourne Again Shell

        Bash 是目前最常用的 Shell,也就是Bourne Again Shell。Bash由于易用和免费,在日常工作中被广泛使用,也是大多数Linux操作系统默认的Shell环境。除非特别指明,下文的 Shell 和 Bash 当作同义词使用,可以互换。

        Bash 于 1989 年首次发布,它是大多数 Linux 发行版的默认 Shell 环境。其他发行版,如 Kali Linux,使用 Z Shell 作为默认 shell。

        Bash 也是 Linus Torvalds(Linux 的创建者)移植到 Linux 的首批程序之一。

Bash 的关键点

        1、因为 Bash 是大多数系统上的默认 shell 环境,大多数用户使用它。

        2、Bash 没有内联通配符表达式。通配符表达式是当你想要在 Shell 中搜索模式(pattern)的时候使用,类似于正则表达式(Regex)。三个主要的通配符是 * 、? 和 [] 。

        3、不能自动更改目录名称。

        4、# 在脚本中被视为注释。

        5、Bash 有 shopt(shell option 缩写)设置。

        6、提示符(prompt)有反斜杠转义。

        7、用户配置设置在 .bashrc 中。

Z Shell

        Z Shell 又称 Zsh, 是一个与 Bash 非常相似的 UNIX shell。你还可以使用 Zsh 编写脚本并将 shell 用作命令解释器。

        Zsh 是 Bourne shell 的扩展,因此在此之上很多改进。Zsh 于 1990 年由 Paul Falstad 发布,它具有 Bash、Korn Shell 和 C Shell 共有的一些功能。

        macOS 默认使用 Zsh Shell。

Zsh 的关键点

        1、在终端使用时,Zsh 带有自动补全功能。因此,当你按下 Tab 键以自动补全你想运行的任何命令时,它不仅为你自动补全,而且弹出下拉菜单,包含所有其他可能的文件和目录。

        2、支持内联通配符表达式。

        3、比 Bash 可配置度更高。

        4、支持插件和主题。

        同时,还有围绕 Z Shell 构建的框架。最受欢迎的框架之一是 Oh My Zsh,它是一个社区驱动的开源框架,用于管理 Zsh 配置。

        Zsh 和 Oh My Zsh 很相似,但并不完全相同。另外,Oh My Zsh 是一种管理 Zsh 配置的方式,它不是 Shell 本身。

Fish Shell

        Fish 是一个强调交互性和可用性的 UNIX shell 环境。与 Zsh 不同,Fish 旨在为用户提供交互性,而不是信任用户实现自己的配置。

        它由 Axel Liljencrantz 于 2005 年创建。由于不符合 POSIX shell 标准,Fish 被认为是"奇异的 shell"。

Fish 的关键点

        1、Fish 根据你的命令历史记录和所在目录提供“键入时搜索”自动建议。与 Bash 的历史搜索类似,Fish Shell 的搜索历史始终处于打开状态。这样,用户终端工作时能够获得交互式反馈。

        2、Fish 还倾向将功能作为命令而不是语法。这使得功能的选项和帮助文本可见。

        3、由于默认情况下 Fish 已经预置了很多配置,因此它被认为比 Zsh 等其他 sh 选项更适合初学者。

        4、Fish 的脚本语言不同于 Zsh 和 Bash。 Zsh 使用更多简化指令(alias),而 Fish 避免在脚本语言中使用简化指令。

        如果你只是使用基本命令(如 cd 、cp 、vim 、ssh 等)编写脚本,你将不会注意到 Fish 和 Bash 的脚本语言的工作方式有何不同。

        Bash、Z Shell 和 Fish Shell 各有优点,也有一些相似之处。如果你想要更可配置的 shell,你可以使用 Zsh(甚至安装 Oh My Zsh)。如果你想要更多交互的终端体验,同时不需要大量配置,你可以使用 Fish Shell。如果你想要经典的感觉,你可以保留 Bash。

        下面的命令可以查看当前运行的 Shell,效果图如下。

echo $SHELL

        下面的命令可以查看当前的 Linux 系统安装的所有 Shell,效果图如下。

cat /etc/shells

        Linux允许每个用户使用不同的Shell,但用户的默认Shell 一般都是Bash,或者与Bash兼容。

作用

        Shell 是操作系统中的一个重要组成部分,它充当用户与操作系统之间的接口,下面是 Shell 在操作系统中所扮演的几个主要角色和作用。

命令解释器Shell 是一个命令解释器,用户通过它输入命令,Shell 将这些命令解析并传递给操作系统执行。它能够理解用户输入的命令,并将其转换为操作系统可以执行的操作用户界面Shell 提供了一个用户界面(通常是命令行界面),用户可以通过输入命令来与操作系统进行交互。尽管现代操作系统也有图形用户界面(GUI),但命令行界面仍然在系统管理、开发和自动化任务中被广泛使用
 脚本编程Shell 支持脚本编程,用户可以编写 Shell 脚本来自动执行一系列命令。这使得用户能够创建复杂的任务和自动化流程,从而提高工作效率(例如,系统管理员可以编写脚本来定期备份文件或监控系统状态)进程控制Shell 允许用户启动、停止和管理进程。用户可以在 Shell 中运行程序、将其放入后台或前台、以及终止进程。Shell 还提供了作业控制功能,允许用户在多个进程之间进行切换
环境管理Shell 提供了环境变量的管理功能,用户可以设置和修改环境变量,以影响系统和应用程序的行为。环境变量可以用于存储配置信息,如路径、用户信息等文件操作Shell 为用户提供了丰富的文件操作命令,用户可以轻松地创建、删除、移动、复制和编辑文件和目录。Shell 还支持重定向和管道,允许用户灵活地处理输入和输出
系统管理工具Shell 提供了许多系统管理工具,管理员可以使用 Shell 来监控系统状态、管理用户、配置网络以及执行其他系统管理任务命令组合与脚本控制Shell 支持命令组合、条件执行和循环控制,使得用户能够编写复杂的逻辑。用户可以使用条件语句(如 if ,case)和循环结构(如 for , while )来控制程序的执行流。

基础操作

        基本的命令行操作需要掌握,比如 ls、cd、cp、mv、mkdir、grep等,这里因为在前面的笔记中已经讲过,所以这里不再展开介绍。

基础语法

        这里先介绍一个shell编程注意事项。

        1、Shell 脚本名称命名一般为英文、大写、小写,后缀以 ". sh" 结尾。

        2、不能使用特殊符号、空格。

        3、名称最好可以一下看出功能。

        4、首行需要 #!/bin/bash 开头。

        5、shell 脚本 变量 不能以 数字、特殊符号开头,可以使用下划线—, 但不能 用破折号 - 。

变量

        在 Shell 编程中,变量是用于存储数据值的名称。

        Shell 是一种动态类型语言(不使用显式的数据声明)和弱类型语言(变量的类型操作根据需求而不同)。Shell中的变量是不分类型的(都是字符串类型),但是依赖于具体的上下文,Shell编程也允许比较操作和整数操作。

        Shell变量名在定义时,首个字符必须为字母(a-z,A-Z),不能以数字开头,中间不能有空格,可以使用下划线(_),不能使用(-),也不能使用标点符号等。

        定义变量时,变量名不加美元符号($,PHP语言中变量需要)。

分类

        shell 里的变量可以分为环境变量和自定义变量。

 环境变量

        通常情况下,每个进程都有自己的“环境”,这个环境是由一组变量组成的,这些变量中存有进程可能需要引用的信息。在这种情况下,Shell 与一般的进程没有什么区别。

        环境变量是系统提供的共享变量,是 linux 系统加载 shell 的配置文件中定义的变量共享给所有的 shell 程序使用。环境变量按照其作用范围不同大致可以分为系统级环境变量和用户级环境变量。

        这里的系统级环境变量指的是 shell 环境加载全局配置文件中的变量共享给所有用户所有shell程序使用,实现全局共享;而用户级环境变量是指 shell 环境加载个人配置文件中的变量共享给当前用户的 shell 程序使用,即登录用户使用。

        全局配置文件一般在 /etc/profile、/etc/profile.d/*.sh、/etc/bashrc。

        个人配置文件一般在当前用户/.bash_profile、当前用户/.bashrc。

        查看系统环境变量的方法 - env 或者 set。

# 查看当前Shell系统环境变量
env
# 查看Shell变量(系统环境变量+自定义变量+函数)
set

        效果图如下,这里用我自己虚拟的效果来举例说明,set 的效果太长了这里就不做展示。

        常见的环境变量如下。

PATH设置命令的搜索路径,以冒号为分割LANG字符集以及语言编码
PS1Shell 提示符PS2 输入多行命令时,次要的 Shell 提示符
PWD当前工作目录HOME当前用户主目录
SHELL当前shell解析器类型HISTFILE显示当前用户执行命令的历史列表文件
OLDPWD显示之前的路径HOSTNAME显示当前主机名
HOSTTYPE显示主机的架构TERM终端类型名,即终端仿真器所用的协议

        很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。

        注意,Bash 变量名区分大小写,比如 HOME 和 home 是两个不同的变量。

        查看单个环境变量的值,可以使用 printenv 命令或 echo 命令。

printenv 变量名 # 这里不用加$前缀
echo $变量名

        效果图如下。

 自定义变量

        自定义变量是用户在当前 Shell 里面自己定义的变量,必须先定义后使用,而且仅在当前 Shell 可用。一旦退出当前 Shell,该变量就不存在了。

        在 Shell 编程中,变量可以根据其作用域和使用方式进行分类,自定义变量按照不同标准的分类各不相同,比如数组变量、特殊变量、只读变量、用户变量(包括全局变量和局部变量)、位置变量等。

        这里简单介绍其中几个。

用户变量

        用户在 Shell 编程过程中定义的变量,分为全局变量和局部变量。默认情况下,用户定义的Shell 变量为全局变量,如果要指定局部变量,则需使用 local 限定词。

位置变量

        位置变量也称系统变量或位置参数,是 Shell 脚本运行时传递给脚本的参数,同时也表示Shell 脚本内部的函数参数。

        它们的名称是以数字命名(出于历史原因,直接引用的位置参数只能从0~9,即$0~$9,超过这个范围的必须用括号括起来,如 ${10} 。)

特殊变量

        特殊变量是系统预定义的变量,用于提供关于当前 shell 或进程的信息。

        $#(变量个数)、$?(上条命令的结束值)、$*(所有参数)、$@(与S*同义,在双引号中时,每个参数作为独立的参数)等。

        注意,当 S* 和 $@ 在 "" 中时,他们的意义不同,"$*" 值是一个字串,"$@" 值是N个字串。 

        下面是部分效果图。

        上面的 $0 永远都会是脚本名,或者包含脚本名。

只读变量

        只读变量在定义后不能被修改。使用 readonly 命令设置。

readonly my_var="This is readonly "

使用

变量定义规则

        1、变量名称可以有字母,数字和下划线组成, 但是不能以数字开头。

        2、等号两侧不能有空格。

        3、在 bash 环境中, 变量的默认类型都是字符串类型, 无法直接进行数值运算。

        4、变量的值如果有空格, 必须使用双引号括起来。

        5、不能使用 Shell 的关键字作为变量名称。

        注意,变量名和等号之间不能有空格,这可能和我们之前熟悉的所有编程语言都不一样。

变量名的命名规则

        1、只包含字母、数字和下划线

              变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。

        2、不能以数字开头

              变量名不能以数字开头,但可以包含数字。

        3、避免使用 Shell 关键字

             不要使用 Shell 的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。

        4、使用大写字母表示常量

              习惯上,常量的变量名通常使用大写字母,例如 PI=3.14。

        5、避免使用特殊符号

              尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。

        6、避免使用空格

              变量名中不应该包含空格,因为空格通常用于分隔命令和参数。

        在Bash 没有数据类型的概念,所有的变量值都是字符串。

        通过在变量名前添加 "$" 字符来引用变量的值,此过程也叫变量替换。

        在定义变量时,若 string 包含空格,制表符和换行符,则 string 必须用 'string' 和 "string" 的形式,即用单引号或双引号将其括注,双引号内允许变量替换,而单引号内不可以。

读取变量

        读取一个定义过的变量,只要在变量名前面加美元符号即可,如果变量不存在,Bash 不会报错,而会输出空字符。

        下面是部分效果图。

        这里的 { } 加不加都可以,加主要是为了帮助解释器识别变量的边界,下面这种就需要。

删除变量

        使用 unset 命令可以删除变量。

unset 变量名

        变量被删除后不能再次使用,unset 命令不能删除只读变量。

        因为不存在的 Bash 变量一律等于空字符串,所以即使 unset 命令删除了变量,还是可以读取这个变量,值为空字符串。

        所以,删除一个变量,也可以将这个变量设成空字符串。

输出变量(export 命令)

        用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用 export 命令。

        这样输出的变量,对于子 Shell 来说就是环境变量。

        export 命令用来向子 Shell 新增,修改或删除环境变量,供后续执行的程序使用。

# 这里是安装 nginx 后增加环境变量
export PATH=$PATH:/usr/local/nginx

        子 Shell 如果修改继承的变量,不会影响父 Shell。

字符串

        字符串是 shell 编程中最常用最有用的数据类型,字符串可以用单引号或者双引号,也可以不用引号。(都必须是英文格式的符号)

表示格式

单引号
a='start'

限制

        1、单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。

        2、单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号
a="start"

优点

        1、双引号里可以有变量。

        2、双引号里可以出现转义字符。

拼接字符串

# 双引号拼接
a="alice"
all="hello,$a"
echo $all
all_1="hell0,${a}"
# 单引号拼接
a="alice"
all_2='hello,$a'
echo $all_2
all_3='hello,${a}'
echo $all_3
# 从下面的效果图可以得知单引号无法拼接带有变量的字符串

        这里看到,使用单引号不会成功,只能使用双引号。

获取字符串长度

a="字符串"
echo ${#a} / echo ${#a[0]}
# 变量为字符串时,${#string} 等价于 ${#string[0]}
# 因为 ${#string[0]} 返回的是数组(或字符串的第一个字符)的长度

        具体效果图如下。 

提取子字符串

a="字符串"
echo ${a:数字1:数字2}
# 这里的数字1是指从索引为数字1的字符开始提取
# 这里的数字2是指到索引为数字2的字符结束提取
# 数字为负数则表示从末尾开始倒着找

        效果图如下。

查找子字符串

a="字符串"
echo $(expr index "$a" 子字符串)

        效果图如下。

        还有更多使用方法如下。

搜索和替换

        Bash 提供字符串搜索和替换的多种方法,格式如下。

格式

说明

示例

${变量名/旧字符串/新字符串}

将旧字符串替换成新字符串,仅替换第一个

${path/aaa/AAA}将aaa替换成AAA,仅替换第一个

${变量名//旧字符串/新字符串}

将旧字符串替换成新字符串,替换所有

${path//aaa/AAA}将aaa替换成AAA,所有的都替换

字符串头部的模式匹配

        以下两种语法可以检查字符串开头,是否匹配给定的模式。如果匹配成功,就删除匹配的部分,返回剩下的部分,原始变量不会发生变化。

# 如果 pattern 匹配变量的开头,就删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${变量名#pattern}

# 如果 pattern 匹配变量的开头,就删除最长匹配(贪婪匹配)的部分,返回剩余部分
${变量名##pattern}
# 模式 pattern 可以用 *、?、[] 等通配符。

        上面两种语法会删除变量字符串开头的匹配部分(将其替换为空),返回剩下的部分。

        区别是一个是最短匹配(又称非贪婪匹配),另一个是最长匹配(又称贪婪匹配)。

        从上图中,可以看出一个 # 是最短匹配,适合和 ? 一起使用,两个 ## 是最长匹配,如果开头符合就删除所有,适合和 * 一起使用。

        匹配不成功就会返回原始字符串。

        要将头部匹配的部分替换成其他内容的话可以参考下面的写法。

a="one"
echo ${a/#o/s}
# 输出 sne

         部分效果图如下。

        上面例子中,被替换的部分必须出现在字符串头部。

字符串尾部的模式匹配

        下面两种语法可以检查字符串结尾是否匹配给定的模式。

        如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。

# 如果 pattern 匹配变量的结尾,
# 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${变量名%pattern}

# 如果 pattern 匹配变量的结尾,
# 删除最长匹配(贪婪匹配)的部分,返回剩余部分
${变量名%%pattern}

        上面两种语法会删除变量字符串结尾的匹配部分(将其替换为空),返回剩下的部分。区别是一个是最短匹配(又称非贪婪匹配),另一个是最长匹配(又称贪婪匹配)。

        如果匹配不成功,则返回原始字符串。

        要替换尾部匹配的部分,用法和上面的头部替换差不多。

b="two"
echo ${b/%o/n}
# 输出 twn

        上面例子中,被替换的部分必须出现在字符串尾部。

任意位置的模式匹配

        下面种语法可以检查字符串内部,是否匹配给定的模式。

        如果匹配成功,就删除匹配的部分,换成其他的字符串返回。原始变量不会发生变化。

# 如果 pattern 匹配变量的一部分,
# 最长匹配(贪婪匹配)的那部分被新字符串替换,但仅替换第一个匹配
${变量名/pattern/替换字符串}

# 如果 pattern 匹配变量的一部分,
# 最长匹配(贪婪匹配)的那部分被新字符串替换,所有匹配都替换
${变量名//pattern/替换字符串}
        上面两种语法都是最长匹配(贪婪匹配)下的替换,区别是前一个语法仅仅替换第一个匹配,后一个语法替换所有匹配。

 

改变大小写

        下面的语法可以改变变量的大小写,效果图如下。

echo ${变量名^^} # 转大写
echo ${变量名,,} # 转小写

数组

        数组中可以存放多个值。

        Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。

        这里的数组和大部分编程语言类似,里面元素的下标都从 0 开始。

        Shell 数组用括号来表示,元素用"空格"符号分割开。

all=(a b c d e ...) # 形式类似,注意空格
all[索引数]=自定义 # 这是单个定义的方法
echo ${all[索引数]} # 这是读取方法

        部分效果图如下。

运算符

算术运算符

运算符说明运算符说明
+加法-减法
*乘法/除法
%取余=赋值
==相等!=不相等

        实现算术运算可以用下面的两种办法,部分效果图如下。

# 第一种
echo $((...))
# 第二种
a = `expr ...`
echo $a
# 这里的 ... 都是具体的运算表达式,比如 3+2、8*3 等,a 也是自定义变量名。

关系运算符

运算符说明运算符说明
-eq检测两个数是否相等-ne检测两个数是否不相等
-gt检测左边的数是否大于右边的-lt检测左边的数是否小于右边的
-ge检测左边的数是否大于等于右边的-le检测左边的数是否小于等于右边的

·        满足说明的就会返回 true,部分效果图如下。

布尔运算符

运算符说明
非运算,表达式为 true 则返回 false,否则返回 true
-o或运算,有一个表达式为 true 则返回 true
-a与运算,两个表达式都为 true 才返回 true

        部分效果图如下。

        温馨提示, [] 里一定要记得空格。

逻辑运算符

运算符说明运算符说明
&&逻辑的 and||逻辑的 or

        部分效果图如下。

        注意这里需要用 [[]] , 不能用 [] 。

字符串运算符

运算符说明
=检测两个字符串是否相等,相等返回 true
!=检测两个字符串是否不相等,不相等返回 true
-z检测字符串长度是否为0,为0返回 true
-n检测字符串长度是否不为 0,不为 0 返回 true
$检测字符串是否不为空,不为空返回 true

        部分效果图如下。

文件测试运算符

操作费说明操作费说明
-b file检测文件是否是块设备文件,如果是,则返回 true-c file检测文件是否是字符设备文件,如果是,则返回 true
-d file检测文件是否是目录,如果是,则返回 true

-f

file

检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
-g file检测文件是否设置了 SGID 位,如果是,则返回 true-k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true

-r

file

检测文件是否可读,如果是,则返回 true-w file检测文件是否可写,如果是,则返回 true
-s file检测文件是否为空(文件大小是否大于0),不为空返回 true-e file检测文件(包括目录)是否存在,如果是,则返回 true
-p file检测文件是否是有名管道,如果是,则返回 true-u file检测文件是否设置了 SUID 位,如果是,则返回 true

        部分效果图如下。

条件判断

if 结构

        if 是最常用的条件判断结构,通常以 if 开头,fi 结尾,只有符合给定条件时,才会执行指定的命令。

# 单分支语句
if 条件表达式 ; then
   语句
fi
# 也可以写成单行
if 条件表达式 ; then 语句 ; fi
# 双分支语句
if 条件表达式 ; then
   语句
else
   语句
fi
# 多分支语句
if 条件表达式 ; then
   语句
elif 条件表达式 ; then
     语句
elif 条件表达式 ; then
     语句
fi

        这里 if 关键字后面是主要的判断条件,elif 用来添加在主条件不成立时的其他判断条件,else则是所有条件都不成立时要执行的部分。

        注意,在命令中用到定义的变量时必须在前面加上 $ 符号,还有涉及到变量的条件表达式必须是 test 命令形式,下面会介绍,还有比较运算符因为在前面已经介绍过,这里就不讲了。

        下面是一个例子的效果图。

test 命令

        if 结构的判断条件,一般使用 test 命令,有三种形式。

# 写法一
test 表达式
# 写法二
[ 表达式 ]
# 写法三
[[ 表达式 ]]
# 后面两种都要注意有空格

       上面三种形式是等价的,但是第三种形式还支持正则判断,前两种不支持。

        单独 test 命令作用检查某个条件是否成立,返回值为0(真)或者其他值(假),可通过 echo $? 查看返回值,和 if 、for 结合也常用于条件和循环语句。

        一般用于判断文件是否存在、比较字符串和数值。

        判断文件或者文件夹的参数如下。

参数说明参数说明参数说明
-e当路径存在时返回真-f路径存在且为文件时返回真-d路径存在且为文件夹时返回真

        部分效果图如下。

        比较字符串的参数如下。

参数说明参数说明
-z当字符串为空时返回真-n当字符串为非空时返回真
=/==两个字符串相等时返回真!=两个字符串不等时返回真

         部分效果图如下。

        比较数值的参数如下,就是前面的算术运算符,后面的参数应该都能在前面找到,就不再多介绍一遍了,部分效果图如下。

判断表达式

        if 关键字后面,跟的是一个命令。这个命令可以是 test 命令,也可以是其他命令。命令的返回值为 0 表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。

        下面列举几个常用的表达式,判断运算符都在前面可以找到。

文件判断

字符串判断

整数判断
正则判断

        具体的正则表达式会在后面介绍,这里先简单举个例子。

^[a-zA-Z]+$
# 下面是解析
# ^
# 这个符号表示字符串的开始。它确保匹配的字符串从开头开始
# [a-zA-Z]
# 方括号 [] 表示一个字符类,表示可以匹配方括号内的任意一个字符
# a-z 表示所有小写字母(从 a 到 z)
# A-Z 表示所有大写字母(从 A 到 Z)
# 所以[a-zA-Z] 可以匹配任意单个字母(不论是大写还是小写)
# +
# 这个符号表示前面的字符类(即 [a-zA-Z])可以出现一次或多次。也就是说,至少需要一个字母,但可以有多个字母
# $
# 这个符号表示字符串的结束。它确保匹配的字符串在结尾处结束
# 结合这些部分,正则表达式 ^[a-zA-Z]+$ 的整体含义如下
# 匹配一个由字母组成的字符串,该字符串可以包含一个或多个字母,但不能包含其他字符(如数字、符号或空格),并且该字符串必须从开始到结束都是字母

        效果图如下。

算术判断

        这里主要就是用 (( )) 来实现。

逻辑判断

        主要用到的就是 &&(AND)和 ||(OR),还有 !(NOT)。

case 结构

        case 结构用于多值判断,可以为每个值指定对应的命令,跟包含多个 elif 的 if 结构等价,但是语义更好。

case 条件表达式 in
  表达式的值/一种模式 ) 语句
                    ;;
  表达式的值/一种模式 ) 语句
                    ;;
  ...
esac

        具体效果图如下。

循环结构

        Bash 提供三种循环语法 for、while、until 。

while 循环

        while 循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。

while 条件表达式
do
  语句
done
# do 在第一行的话就需要在前面加一个分号,注意空格

        条件表达式可以使用 test 命令,跟 if 结构的判断条件写法一致。

until 循环

        until 循环与 while 循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。

until 条件表达式
do
  语句
done
# do 在第一行的话就需要在前面加一个分号,注意空格

        until 循环都可以转为 while 循环,只要把条件设为否定即可,但一般 until 用得比较少。

for 循环

        for 循环除了和上面相似的使用架构外,还有一个经常使用的 for ... in ... 循环架构。

for ((表达式)) ; do
  语句
done

for 变量名 in 列表(比如数组); do
  语句
done

# do 在第二行就可以不要分号

break 和 continue

        Bash 提供了两个内部命令 break 和 continue 用来在循环内部跳出循环。

        break 命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。

       上面只要变量N等于10就跳出循环,并输出此时的变量值。

        continue 命令立即终止本轮循环,开始执行下一轮循环。

        这里主要是为了排除和跳过某些特定的值。

select 结构

        select 结构主要用来生成简单的菜单,它的语法与 for ... in ... 大致相同。

        在 Bash 脚本中,select 语句会自动为每个选项分配一个数字索引,并将其显示在菜单中。这使得用户可以通过输入对应的数字来选择相应的选项。

        这里的 fruit 是用于存储用户选择的变量,选项是数组中的所有元素。

函数

        函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。

        函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。

# 第一种定义语法
自定义名() {
  # 代码
}
# 第二种定义语法
function 自定义名() {
  # 代码
}

        上面是效果图。

参数变量

        函数体内可以使用参数变量获取函数参数,而函数的参数变量,与脚本参数变量是一致的。

$1~$9函数的第一个到第九个参数
$0函数所在脚本名
$#函数的参数总和
$@函数的全部参数,彼此间有空格
$*函数的全部参数,参数之间使用变量 $IFS 值的第一个字符分隔,默认为空格,但是可以自定义

        如果函数的参数多于9个,那么第10个参数可以用 ${10} 的形式引用,以此类推。

return 命令

        return 命令用于从函数返回一个值,函数执行到这条命令,就不再往下执行直接返回了。

全局变量和局部变量

        Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。

        函数里面可以用 local 命令声明局部变量。

        这里因为 b 是局部变量,所以在函数结构体外调用时就无法找到。

输入输出

标准输入输出

        在 Linux 系统中,标准输入(stdin)、标准输出(stdout)和标准错误(stderr)是处理输入和输出的三个基本概念。它们在 Shell 脚本编程中非常重要,理解这些概念可以帮助我们更有效地编写和调试脚本。

标准输入(stdin)

        标准输入是程序接收输入数据的默认源,通常是键盘。

        标准输入的文件描述符是 `0`,可以使用重定向符 `<` 将文件内容作为标准输入传递给程序。

# 从文件读取输入
cat < input.txt
```
在这个示例中,`cat` 命令从 `input.txt` 文件中读取内容

 标准输出(stdout)

        标准输出是程序输出数据的默认目标,通常是终端(屏幕)。

        标准输出的文件描述符是 `1`,可以使用重定向符 `>` 将标准输出重定向到文件,使用 `>>` 追加到文件。

# 将输出写入文件
echo 'Hello, World!' > output.txt
```
在这个示例中,`echo` 命令的输出被重定向到 `output.txt` 文件中,而不是显示在终端上

标准错误(stderr)

        标准错误是程序输出错误信息的默认目标,通常也是终端。

        标准错误的文件描述符是 `2`,可以使用重定向符 `2>` 将标准错误重定向到文件,使用 `2>>` 追加到文件。

# 将错误输出写入错误.log
ls nonexistentfile 2> error.log
```
在这个示例中,`ls` 命令尝试列出一个不存在的文件,错误信息被重定向到 `error.log` 文件中

结合使用

        在 Shell 脚本中,可以同时处理标准输入、输出和错误。

#!/bin/bash

# 读取用户输入
echo "请输入一个文件名:"
read file

# 检查文件是否存在
if [ -e "$file" ]; then
    echo "文件 '$file' 存在"  # 标准输出
else
    echo "错误:文件 '$file' 不存在" >&2  # 标准错误
fi
# 增加执行权限
chmod +x 脚本名

# 将输出和错误都重定向到文件
./脚本名> output.log 2>&1

        效果图如下,注意如果将结果重定向到文件中,命令行的提示就会消失,只能直接输入文件名进行判断。 

        当然,还有另一种办法可以解决这个问题。

#!/bin/bash

# 读取用户输入
echo "请输入一个文件名:"
read file

# 检查文件是否存在
if [ -e "$file" ]; then
    echo "文件 '$file' 存在" | tee -a output.log
else
    echo "错误:文件 '$file' 不存在。" >&2  | tee -a output.log
fi

        tee 命令是一个非常有用的工具,它可以将标准输入的数据同时输出到标准输出和一个或多个文件中。

总结

        1、stdin(标准输入)用于接收输入,默认来源是键盘。

        2、stdout(标准输出)用于输出结果,默认目标是终端。

        3、stderr(标准错误)用于输出错误信息,默认目标也是终端。

        4、通过重定向,可以将这些输入输出流转发到文件或其他程序。

重定向

        重定向指的是将命令行输出写入指定位置。

        下面是一些常用的规范。

cmd1 | cmd2

管道(将 cmd1 的标准输出作为 cmd2 的标准输入)

> file将标准输出重定向到文件< file从文件获取标准输入

>> file

将标准输出重定向到文件;如果文件已存在,则追加到文件末尾> | file强制将标准输出重定向到文件,即使设置了 noclobber
n> | file强制从文件描述符 n 将输出重定向到文件,即使设置了 noclobber<> file将文件用作标准输入和标准输出
n<> file将文件用作文件描述符 n 的输入和输出<< label这里文档;参见文本
n > file将文件描述符 n 的输出重定向到文件n <  file从文件获取文件描述符 n 的输入
n >> file将文件描述符 n 的输出重定向到文件;如果文件已存在,则追加到文件末尾n>&将标准输出复制到文件描述符 n
n<&将标准输入从文件描述符 n 复制n>&m将文件描述符 n 复制为输出文件描述符
n<&m将文件描述符 n 复制为输入文件描述符&>file将标准输出和标准错误都重定向到文件
<&-关闭标准输入>&-关闭标准输出
n>&-关闭文件描述符 n 的输出n<&-关闭文件描述符 n 的输入

n>&word如果未指定 n,则使用标准输出(文件描述符 1);如果 word 中的数字未指定一个打开用于输出的文件描述符,则会发生重定向错误;作为特例,如果省略 n,并且 word 不展开为一个或多个数字,则标准输出和标准错误将按照上面描述的方式重定向n<&word如果 word 展开为一个或多个数字,则文件描述符 n 将成为该文件描述符的副本;如果 word 中的数字未指定一个打开用于输入的文件描述符,则会发生重定向错误;如果 word 的值为 -,则关闭文件描述符 n;如果未指定 n,则使用标准输入(文件描述符 0)
n>&digit-将文件描述符 digit 移动到文件描述符 n,如果未指定 n,则移动到标准输出(文件描述符 1)n<&digit-将文件描述符 digit 移动到文件描述符 n,如果未指定 n,则移动到标准输入(文件描述符 0);在复制到 n 后,digit 被关闭

        这四个可能一下看不明白,所以我在这里用几个例子介绍一下。

### 示例 1: `n>&word`

# 基本用法
  exec 3>&1  # 将文件描述符 3 重定向到标准输出
  echo "Hello, World!" >&3  # 将内容输出到文件描述符 3(实际上是标准输出)

# 在这个例子中,`exec 3>&1` 将文件描述符 3 指向标准输出(文件描述符 1)。接下来的 `echo` 命令将字符串 "Hello, World!" 输出到文件描述符 3,这意味着它会显示在终端上

# 重定向错误示例
  exec 4>&-  # 关闭文件描述符 4
  echo "Test" >&4  # 试图将输出重定向到已关闭的文件描述符,发生错误

### 示例 2: `n<&word`

# 基本用法
# 假设有一个文件 `input.txt`,内容为
  ```
  Hello
  World
  ```
# 下面是脚本内容
  exec 5< input.txt  # 将文件描述符 5 指向 input.txt
  read line <&5  # 从文件描述符 5 读取一行
  echo "Read from file: $line"  # 输出读取的内容

# 在这个例子中,`exec 5< input.txt` 将文件描述符 5 指向 `input.txt` 文件。接下来的 `read` 命令从文件描述符 5 中读取一行,最后输出读取的内容

# 关闭文件描述符示例
  exec 6<&-  # 关闭文件描述符 6
  read line <&6  # 试图从已关闭的文件描述符读取,发生错误

### 示例 3: `n>&digit-`

# 基本用法
  exec 7>&1  # 将文件描述符 7 重定向到标准输出
  echo "Standard Output" >&7  # 这将输出 "Standard Output" 到标准输出
  exec 7>&-  # 关闭文件描述符 7

# 在这个例子中,`exec 7>&1` 将文件描述符 7 指向标准输出。然后,`echo` 命令将字符串输出到文件描述符 7(实际上是标准输出)。最后,`exec 7>&-` 关闭文件描述符 7

### 示例 4: `n<&digit-`

# 基本用法
  exec 8<> /dev/tty  # 将文件描述符 8 指向终端
  echo "Enter something: " >&8  # 提示用户输入
  read input <&8  # 从文件描述符 8 读取用户输入
  echo "You entered: $input"  # 输出用户输入
  exec 8<&-  # 关闭文件描述符 8

# 在这个例子中,`exec 8<> /dev/tty` 将文件描述符 8 指向终端设备。然后,`echo` 输出提示信息,接下来使用 `read` 从文件描述符 8 读取用户输入,最后输出用户输入的内容

> 将标准输出重定向到指定文件

        如果重定向后的指定文件已经存在,就会被覆盖,不会有任何提示。

        如果命令没有任何输出,那么重定向之后,得到的是一个长度为0的文件。

        因此, > 具有创建新文件或改写现存文件、将其改为长度0的作用。

>> 将标准输出重定向追加到指定文件

2> 用来将标准错误重定向到指定文件

# 可以用 标准输出和标准错误,可以重定向到同一个文件
$ ls -l /bin/usr > ls-output.txt 2>&1
# 或者
$ ls -l /bin/usr &> ls-output.txt

# 追加到同一个文件
$ ls -l /bin/usr &>> ls-output.txt

# 如果不希望输出错误信息,可以将它重定向到一个特殊文件/dev/null
$ ls -l /bin/usr 2> /dev/null

  用于将一个命令的标准输出,重定向到另一个命令的标准输入

高级语法

脚本结构

脚本头(Shebang)

        脚本的第一行通常是一个称为“shebang”的行,它指明了脚本的解释器。

        在 Bash 脚本中,shebang 行通常是 #!/bin/bash,这告诉系统使用 `/bin/bash` 解释器来执行该脚本。

注释

        在脚本中,注释用于提供关于代码的说明和文档,注释不会被执行,主要用于提高代码的可读性。

        Bash 中的注释以 `#` 开头,直到行尾。

# 这是一个示例脚本
echo "Hello, World!"  # 输出 "Hello, World!"

变量

        在脚本中,可以定义和使用变量。

        变量不需要提前声明,直接赋值即可。

name="Alice"
echo "Hello, $name!"

控制结构

        脚本通常包含控制结构,例如条件语句和循环,上面有详细介绍,这里就不再展开。

函数

        可以在脚本中定义函数来组织代码并提高可重用性。

执行权限

        在 Linux 中,脚本需要具有执行权限才能运行。可以使用 `chmod` 命令来设置权限。

chmod +x 脚本名 # 给 script.sh 添加执行权限

# 设置好权限后,可以通过以下方式执行脚本
./脚本名  # 在当前目录下执行脚本

输入和输出

        脚本可以处理输入和输出,包括从用户获取输入和将输出重定向到文件。

# 用户输入
  read -p "Enter your name: " name
  echo "Hello, $name!"

# 重定向输出
  echo "This will be saved to a file" > output.txt  # 将输出重定向到文件

错误处理

        可以通过检查命令的退出状态($?)来进行错误处理。

mkdir new_directory
if [ $? -ne 0 ]; then
    echo "Failed to create directory!"
fi
# 原理是如果命令执行成功,echo $? 将会返回0的结果,非0则有错误。

退出状态

脚本可以使用 `exit` 命令返回一个退出状态码。通常,返回 0 表示成功,非零值表示错误。

exit 0  # 正常退出
exit 1  # 发生错误

错误处理

        错误处理在脚本编写中非常重要。它可以帮助我们捕获并处理错误,从而使脚本更加健壮和可靠。

        在 Bash 脚本中,常用的错误处理方法包括使用 `$?` 检查命令的退出状态和使用 `trap` 捕获信号和错误。

使用 `$?` 检查退出状态

        如前所述,`$?` 是一个特殊变量,用于获取最近执行命令的退出状态。我们可以通过检查这个状态来判断命令是否成功执行。

#### 示例:基本的错误检查

#!/bin/bash

# 尝试创建一个目录
mkdir my_directory

# 检查 mkdir 命令的退出状态
if [ $? -eq 0 ]; then
    echo "目录创建成功!"
else
    echo "目录创建失败!"
fi

# 在这个例子中,脚本尝试创建一个名为 `my_directory` 的目录
# 如果 `mkdir` 命令成功执行,输出“目录创建成功!”;如果失败,则输出“目录创建失败!”

#### 示例:在多个命令中使用

# 当脚本中有多个命令时,可以使用一个函数来集中处理错误

#!/bin/bash

a() {
    if [ $? -ne 0 ]; then
        echo "错误发生在 $1"
        exit 1
    fi
}

# 尝试创建目录
mkdir my_directory
a "创建目录"

# 尝试移动文件
mv my_file.txt my_directory/
a "移动文件"

# 在这个例子中,`check_error` 函数检查最近命令的退出状态,如果失败则输出错误信息并退出脚本

使用 `trap` 捕获信号和错误

        `trap` 命令用于捕获信号(如 `SIGINT`,即 Ctrl+C)或错误,并在捕获到信号时执行指定的命令,这对于清理资源或执行特定的错误处理逻辑非常有用。

#### 示例:捕获信号

#!/bin/bash

cleanup() {
    echo "清理工作..."
    # 这里可以添加清理代码,比如删除临时文件等
}

# 捕获 SIGINT(Ctrl+C)信号
trap cleanup SIGINT

# 模拟一个长时间运行的任务
echo "按 Ctrl+C 退出..."
while true; do
    sleep 1
done

# 在这个例子中,当用户按下 Ctrl+C 时,`cleanup` 函数会被调用,输出“清理工作...”并执行清理操作

#### 示例:捕获错误

# 我们还可以使用 `trap` 来捕获脚本中的错误。
# 比如,使用 `ERR` 信号

#!/bin/bash

error_handler() {
    echo "发生错误:$1"
    exit 1
}

# 捕获 ERR 信号
trap 'error_handler "$BASH_COMMAND"' ERR

# 尝试执行一些命令
mkdir my_directory
mv nonexistent_file.txt my_directory/  # 这个命令将失败
echo "这条消息不会被输出"

# 在这个例子中,`trap` 捕获到任何命令的错误并调用 `error_handler` 函数,输出错误信息并退出脚本

        使用 `$?` 和 `trap` 是处理 Bash 脚本中错误和异常的有效方法。`$?` 允许你检查命令的执行状态,而 `trap` 则为你提供了捕获信号和错误处理的能力。通过这些方法,我们可以编写出更加健壮和可维护的脚本。

正则表达式

        正则表达式是一种用于匹配模式的技巧,可以在 Linux 系统中执行文本搜索和替换操作,大大提高了工作效率。

        下面是一些常用的字符合集。

.匹配任何单个字符?上一项是可选的,最多匹配一次
*前一项将被匹配零次或多次+前一项将被匹配一次或多次
{N}上一项完全匹配N次{N,}前一项匹配N次或多次
{N,M}前一项至少匹配N次,但不超过M次--结束选项标志,确保后续参数被视为位置参数
^匹配行首的空字符串;也代表不在列表范围内的字符$匹配行尾的空字符串
\b匹配单词边缘的空字符串\B匹配空字符串,前提是它不在单词的边缘
\<匹配单词开头的空字符串\>匹配单词末尾的空字符串

元字符

        元字符是表示特殊函数的字符,包括 ^ 、$ 、. 、[] 、{} 、- 、? 、* 、+ 、() 、| 、\\等。除了元字符,其他字符在正则表达式中,都表示原来的含义。

. # 匹配任意字符,但不含空字符
^ # 匹配文本行开头
$ # 匹配文本行结尾

        在这个例子里,可以看到要注意 ^ 要在前面, $ 要在后面,否则操作无效。

方括号

        方括号之中的字符,表示可以任意匹配其中的一个。

        注意,元字符放入方括号之中,会失去其特殊含义。

        但有两种情况除外,^ 在方括号的开头,表示否定,否则只是一个普通字符,表示原义。

        注意,上面命令不会匹配不和 H 或者 h 连在一起包含 e 的字符串,因为一个否定的字符集仍然要求存在一个字符。

        - 在方括号之中表示一个字符区域。

        上面命令匹配所有以大写字母开头的文本行。类似的,^[A-Z]、^[a-z]、^[0-9]表示以大写字母、小写字母、数字开头的文本行。(上面的[a-Z]则表示所有大写或小写字母)

        注意,连字号如果不构成一个字符区域,则表示其本来的含义。(比如[a-9]、[-AZ]等)

预定义字符类

        由于 locale 设置不同,Shell展开正则表达式 [A-Z] 时,可能不是解释为所有大写字母,而是解释为包括所有字母的字典顺序。

        为了避免这个问题,可以使用正则表达式的预定义字符类。

[:alnum:]字母数字字符(在 ASCII 中,等价于 [A-Za-z0-9])[:word:] 和[:alnum:]相同, 但增加了下划线字符
[:alpha:] 字母字符(在 ASCII 中,等价于 [A-Za-z])[:blank:]包含空格和 tab 字符
[:cntrl:]ASCII 的控制码(包含了0到31,和127的 ASCII 字符)[:graph:]可视字符(在 ASCII 中,它包含33到126的字符)
[:digit:]数字0到9[:lower:]小写字母
[:punct:]标点符号字符[:space:]

空白字符(包括空格,tab,回车,换行,vertical tab, 和 form feed)

在 ASCII 中, 等价于 [\t\r\n\v\f]

[:xdigit:] 用来表示十六进制数字的字符(在 ASCII 中,等价于 [0-9A-Fa-f])[:upper:]大写字母
ls /usr/sbin/[[:upper:]]*
# 上面命令返回所有大写字母开头的文件名

限定符

        限定符用于限制匹配的数量,下面列出一些常见的限定符。

? # 匹配前面的元素出现0次或1次
* # 匹配前面的元素出现0次或多次
+ # 匹配前面的元素出现1次或多次
{n} # 匹配前面的元素出现了n次
{n,m} # 匹配前面的元素它至少出现了n次,但是不多于m次
{n,} # 匹配前面的元素至少出现了n次
{,m} # 匹配前面的元素,如果它出现的次数不多于 m 次

文本处理工具

cat

        将文件的内容显示在标准输出。

cat 参数 文件名

        下面是一些常用参数。

参数作用参数作用参数作用
-n显示行号,会在输出的每一行前加上行号-b显示行号,但只对非空行进行编号-s压缩连续的空行,只显示一个空行
-E在每一行的末尾显示 $ 符号-T将 Tab 字符显示为 ^I-v显示一些非打印字符

        部分效果图如下。

uniq

        uniq 命令用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用。

uniq 参数 文件名

        主要参数如下。

参数作用参数作用
-c显示每行在文本中重复出现的次数-d设置每个重复记录只出现一次
-f跳过对前N个列的比较-D显示所有相邻的重复行
-i忽略大小写-s跳过对前N个字符的比较
-u仅显示没有重复的记录-w仅对前N个字符进行比较

        下面是部分效果图,记得先用 sort 排序,不然 uniq 只能去除相邻的重复行。

sed

        sed 命令是利用脚本来处理文本文件。它可以用来对文本进行替换、插入、删除和其他编辑操作。下面介绍具体用法和一些常见示例。

# 基本语法
sed [options] 'command' file

# `options` 用于改变 `sed` 行为的选项
# `command` 要执行的操作,如替换、删除等
# `file` 要处理的文件名

# 常用选项
# `-n` 抑制默认输出。只有使用 `p` 命令时才会输出
# `-e` 允许在命令行中指定多个 `sed` 表达式
# `-i` 直接修改文件,而不是输出到标准输出
替换

        替换文件中的某个字符串。

sed 's/old/new/' filename
# `s`**: 表示替换操作
# `old`**: 要被替换的字符串
# `new`**: 替换成的新字符串

# 使用 `g` 标志替换所有匹配项
sed 's/old/new/g' filename
抑制默认输出

        使用 `-n` 选项,可以控制输出,结合 `p` 命令输出匹配的行。

sed -n 's/old/new/p' filename

        这将只输出被替换的行。

删除行

        删除特定行或匹配的行。

# 删除第三行
sed '3d' filename 

# 删除包含 "one" 的行
sed '/one/d' filename
插入行

        在指定行之前插入新行。

sed '2i\This is a new line.' filename
# 这将在第二行之前插入 "This is a new line."
追加行

        在指定行之后追加新行。

sed '2a\This is another new line.' filename
# 这将在第二行之后添加 "This is another new line."
修改行

        替换某一行的内容。

sed '2c\This is the new second line.' filename
# 这将把第二行替换为 "This is the new second line."
行号范围

        可以指定行号范围来执行操作。

# 比如替换第二到第四行中的 "old" 为 "new"
sed '2,4s/old/new/g' filename
使用正则表达式

        `sed` 支持基本和扩展正则表达式。使用 `-E` 选项来启用扩展正则表达式。

sed -E 's/(a|b)/c/g' filename
# 这个命令将 "a" 或 "b" 替换为 "c"
直接修改文件

        使用 `-i` 选项直接修改文件内容。

sed -i 's/a/b/g' filename

awk

        awk 是一种处理文本文件的语言,是一个强大的文本分析工具。

        awk 通过提供编程语言的功能,如变量、数学运算、字符串处理等,使得对文本文件的分析和操作变得非常灵活和高效。

        awk 这个名字是因为取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。

# 语法
awk options 'pattern {action}' file

        主要用到的参数如下。

参数作用参数作用
-F 指定字段分隔符-v定义变量(-v var=value 可以在 awk 中使用变量 var)
-V显示 awk 的版本信息-h显示 awk 的帮助信息,包括选项和用法示例

        下面是一些常用方法举例。

# 基本示例
# 打印文件的所有行
awk '{ print }' filename

# 打印特定字段
# 假设有一个以空格分隔的文件 `data.txt`,内容如下
# Alice 25
# Bob 30
# Charlie 22
#打印第二列(年龄)
awk '{ print $2 }' data.txt

# 使用自定义分隔符
# 如果文件以逗号分隔
# Alice,25
# Bob,30
# Charlie,22
# 使用 `-F` 指定分隔符
awk -F, '{ print $1 }' data.csv

# 条件匹配
# 打印年龄大于 25 的行
awk '$2 > 25 { print }' data.txt

# 计算总和
# 计算所有年龄的总和
awk '{ sum += $2 } END { print sum }' data.txt

# 打印行号
# 打印文件的行号和内容
awk '{ print NR, $0 }' data.txt


# 内置变量

# `$0`  当前行的完整内容
# `$1`, `$2`, ...  当前行的第 1、2、... 列
# `NR`  当前记录的行号
# `NF`  当前行的字段数量
# `FILENAME`  当前文件的名称

# 控制结构
# `awk` 支持控制结构,如 `if`、`for` 和 `while`
# `if` 语句
awk '{ if ($2 > 25) print $1 " is older than 25." }' data.txt

# `for` 循环
# 打印每个字段的值
awk '{ for (i = 1; i <= NF; i++) print $i }' data.txt

# 函数
# `awk` 提供了一些内置函数,如字符串操作、数学计算等

# 字符串函数
# `length()`  获取字符串长度
awk '{ print $1, length($1) }' data.txt

# `substr()`  获取子字符串
awk '{ print substr($1, 1, 2) }' data.txt

# 数学函数
# `sqrt()`  计算平方根
awk '{ print $2, sqrt($2) }' data.txt

# 比如处理 CSV 文件
# 假设有一个 CSV 文件 `data.csv`,内容如下
# Name,Age,Score
# Alice,25,88
# Bob,30,75
# Charlie,22,90
# 打印所有学生的名字和成绩
awk -F, 'NR > 1 { print $1, $3 }' data.csv

# 组合使用
# `awk` 可以与其他 Unix 工具结合使用,如 `grep` 和 `sort`
# 与 `grep` 结合
grep "Alice" data.txt | awk '{ print $2 }'

# 与 `sort` 结合
awk '{ print $2 }' data.txt | sort -n
# `awk -v` 选项用于在执行 `awk` 命令时定义变量
# 这些变量可以在 `awk` 脚本中使用,以便更灵活地处理数据
# 通过 `-v` 选项设置的变量可以在模式和操作中使用,类似于其他内置变量

# 基本语法
awk -v var=value 'pattern { action }' file
# var 变量的名称
# value 变量的值,可以是字符串、数字或表达式

# 示例
# 1. 设置简单变量
# 假设有一个文件 `data.txt`,内容如下
# Alice 25
# Bob 30
# Charlie 22
# 要求打印每个人的名字和年龄,并在每行前面加上一个固定的前缀
awk -v prefix="Name:" '{ print prefix, $1, "is", $2, "years old." }' data.txt
# 输出
# Name: Alice is 25 years old.
# Name: Bob is 30 years old.
# Name: Charlie is 22 years old.

# 2. 使用变量进行比较
# 我们可以通过 `-v` 传递一个阈值,然后根据这个阈值来过滤数据
awk -v age_limit=25 '$2 > age_limit { print $1 " is older than " age_limit }' data.txt
# 输出
# Bob is older than 25

# 3. 计算带变量的总和
# 假设有一个包含多个学生成绩的文件 `scores.txt`,内容如下
# Alice 88
# Bob 75
# Charlie 90
# 我们可以定义一个变量表示额外的分数,并计算总分
awk -v extra_points=5 '{ total = $2 + extra_points; print $1, "total score:", total }' scores.txt
# 输出
# Alice total score: 93
# Bob total score: 80
# Charlie total score: 95

# 4. 结合使用多个变量
# 我们可以同时使用多个变量。例如,假设我们想要根据不同的分数阈值进行分类
awk -v pass_score=80 -v fail_score=60 '{
    if ($2 >= pass_score) {
        status = "Pass"
    } else if ($2 >= fail_score) {
        status = "Fail"
    } else {
        status = "Retake"
    }
    print $1, ":", status
}' scores.txt
# 输出
# Alice : Pass
# Bob : Fail
# Charlie : Pass

# 小结
# 使用 `awk -v` 可以方便地在脚本中定义和使用变量,从而使 `awk` 脚本更加灵活和易于维护
# 通过将外部变量传递给 `awk`,你可以根据不同的条件和数据动态调整处理逻辑

grep

        grep 是一个强大的命令行工具,用于搜索文本文件中的特定模式或字符串。它支持正则表达式,可以用于查找、过滤和处理文本数据。

grep [options] pattern [file...]
# options : 用于改变 grep 行为的选项
# pattern : 要搜索的模式或字符串
# file : 要搜索的文件名。如果不指定文件,grep 将从标准输入读取

        常用参数如下。

参数作用参数作用
-i忽略所有大小写-v反向匹配,显示不匹配的行
-r递归搜索目录-l只输出包含匹配字符串的文件名
-n显示匹配行的行号-c统计匹配行的数量
-A n显示匹配行及其后n行-B n显示匹配行及其前n行
-C n显示匹配行及其前后各n行-s不显示错误信息

        常见用法如下。

基本搜索        

        搜索文件中包含特定字符串的行。

grep 'pattern' filename

        比如查找文件 1.txt 中包含 "alice" 的行。

grep 'alice' 1.txt

忽略大小写

        使用 -i 选项忽略大小写。

grep -i 'ALICE' 1.txt

反向匹配

        使用 -v 选项显示不匹配模式的行。

grep -v 'alice' 1.txt

显示行号

        使用 -n 选项显示匹配行的行号。

grep -n 'alice' 1.txt

递归搜索

        使用 -r 选项递归搜索目录中的文件。

grep -r 'alice' /path/to/directory
只输出文件名

        使用 -l 选项只输出包含匹配字符串的文件名。

grep -l 'alice' *.txt

统计匹配行数

        使用 -c 选项统计匹配的行数。

grep -c 'alice' 1.txt

显示匹配行及上下文

        显示匹配行及其后 n 行。

grep -A 1 'alice' 1.txt

        显示匹配行及其前 n 行。

grep -B 2 'alice' 1.txt

        显示匹配行及其前后各 n 行。

grep -C 1 'alice' 1.txt

使用正则表达式

        grep 支持基本和扩展的正则表达式。使用 -E 选项启用扩展正则表达式。

grep -E 'alice|bob' 1.txt

sort

        sort 是一个用于排序文本文件内容的命令行工具,广泛用于 Unix/Linux 系统中。

        它可以按字母顺序、数字顺序、时间顺序等对文件中的行进行排序。

# 基本语法
sort [options] [file...]
# options 用于改变 sort 行为的选项
# file 要排序的文件名。如果不指定文件,sort 将从标准输入读取

        主要参数如下。

参数作用参数作用
-n按数字排序-r反向排序(从大到小)
-k知道排序关键字(列)-t指定字段分隔符
-u去重-o将输出写入指定文件
-f忽略大小写-M按月份排序

        常见用法如下。

基本排序

        按字母顺序排序文件的行。

sort filename

数字排序

        按数字进行排序(而不是按字母)。

sort -n 2.txt

反向排序

        使用 -r 选项进行反向排序。

sort -r 1.txt

按关键字排序

        使用 -k 选项指定排序的列(关键字)。按第二列(年龄)排序:

sort -k 2 3.txt

指定字段分隔符

        使用 -t 选项指定字段分隔符。(例如使用逗号作为分隔符)

sort -t ',' -k 2 filename
去重

        使用 -u 选项只保留唯一的行。

sort -u 1.txt

输出到文件

        使用 -o 选项将结果写入指定文件。

sort 1.txt -o 5.txt

cut

        cut  命令用于从文本文件中提取特定的列或字段。

        它可以根据字符位置、字节位置或分隔符来切割文本。

cut [options] [file...]

        主要参数如下。

参数作用参数作用
-f指定要提取的字段(列)-d指定字段分隔符(默认为制表符)
-c按字符位置提取-b按字节位置提取

wc

        wc(word count)命令用于统计文本文件中的行数、单词数和字节数。

wc [options] [file...]

        主要参数如下。

参数作用参数作用
-l统计行数-w统计单词数
-c统计字节数-m统计字符数

nl

        nl 命令用于给文本文件的每一行添加行号。它可以方便地查看文本文件的行号。

nl [options] [file...]
# 常用选项
# -b 指定行号的类型(a:所有行,t:只对非空行,n:行号格式)
# -w 指定行号的宽度
# -s 指定行号与行内容之间的分隔符

进程管理

        管理进程是 Unix/Linux 系统中的一个重要任务,涉及到前台和后台进程的控制与查看。

        下面将介绍如何管理进程,包括查看、控制、启动和停止进程的常用命令。

进程的概念

前台进程

        在终端中运行的进程,用户可以直接与其交互。前台进程会占用终端,直到它结束。

后台进程

        在终端中运行但不占用终端的进程。用户可以继续使用终端,后台进程在后台运行。

查看进程

ps

        ps 命令用于查看当前运行的进程。

# 基本用法
ps

# 查看所有进程
ps -aux

# 查看特定用户的进程
ps -u username

# 查看进程树
ps -ejH

        不带任何参数时,ps 只列出与当前 Session 相关的进程。输出结果中,PID 是进程 ID、TTY 是进程的终端号(如果显示 ?,则表示进程没有终端),TIME 是消耗的 CPU 时间,CMD 是触发进程的命令。

        x 参数列出所有进程的详细信息,包括不在当前 Session 的信息。

# 这时的输出结果,会多出STAT一栏,表示状态
# 它的各种值如下

R 
# 正在运行或准备运行
S 
# 正在睡眠,即没有运行,正在等待一个事件唤醒
D 
# 不可中断睡眠。进程正在等待 I/O,比如磁盘驱动器的I/O
T 
# 已停止,即进程停止运行
Z 
# “僵尸”进程。即这是一个已经终止的子进程,但父进程还没有清空它(没有把子进程从进程表中删除)
< 
# 高优先级进程。这可能会授予一个进程更多重要的资源,给它更多的 CPU 时间
N 
# 低优先级进程。一个低优先级进程(一个“好”进程)只有当其它高优先级进程执行之后,才会得到处理器时间

        aux 参数可以显示更多信息。

# 输出结果包含的列的含义如下
USER 
# 用户ID,表示进程的所有者
%CPU 
# 百分比表示的 CPU 使用率
%MEM 
# 百分比表示的内存使用率
VSZ 
# 虚拟内存大小
RSS 
# 进程占用的物理内存的大小,以千字节为单位
START 
# 进程运行的起始时间。若超过24小时,则用天表示
top

        top 命令提供实时的系统监控,显示当前运行的进程及其资源使用情况。

top
# 在 top 界面中,可以按 `q` 退出。

        它的输出结果分为两部分,最上面是系统概要,下面是进程列表,以 CPU 的使用率排序。

        输出结果是动态更新的,默认每三分钟更新一次。

htop

        htop 是 top 命令的增强版,提供了更友好的界面和交互功能,可以使用方向键选择进程。

htop

控制进程

启动进程

        前台进程,直接在终端中运行命令。

sleep 60

        后台进程,在命令后加上 `&` 符号。

sleep 60 &
查看后台进程

        使用 jobs 命令查看当前用户的所有后台进程。

jobs
将前台进程放入后台

        如果已经在前台运行了一个进程,可以使用 Ctrl + Z 将其暂停,然后使用 bg 命令将其放入后台。

Ctrl + Z  # 暂停进程
bg        # 将其放入后台
将后台进程带回前台

        使用 fg 命令将后台进程带回前台。

fg %1  # 带回第一个后台进程

终止进程

        使用 kill 命令,通过进程 ID (PID) 终止进程。

kill PID

        强制终止,如果进程不响应,可以使用 -9 选项强制终止。

kill -9 PID

        通过进程名称终止,使用 pkill 命令可以通过进程名称终止进程。

pkill process_name

进阶优化

脚本优化

        优化 Linux 脚本的性能和可读性是提升脚本效率和维护性的关键。下面是一些实用的技巧和最佳实践,可以帮助在编写和优化脚本时提升性能和可读性。

1、选择合适的解释器

        确保使用适合脚本的解释器。

        对于 Bash 脚本,使用 `#!/bin/bash` 或 `#!/usr/bin/env bash` 来确保脚本在正确的环境中运行。

2、使用函数

        将重复的代码块封装成函数,以提高可读性和可重用性。

3、使用变量

        避免重复计算相同的值,使用变量存储结果,以提高性能。

# 不推荐
for i in $(seq 1 1000); do
    echo $((i * 2))
done

# 推荐
limit=1000
for i in $(seq 1 $limit); do
    echo $((i * 2))
done

4、避免使用外部命令

        尽量使用内置命令而不是外部命令,因为内置命令的性能通常更好。

# 不推荐
count=$(wc -l < file.txt)

# 推荐
count=0
while read -r line; do
    ((count++))
done < file.txt

5、使用数组

        在需要处理多个相关值时,使用数组可以提高可读性和性能。

#!/bin/bash

fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
    echo $fruit
done

6、使用双引号

        在处理字符串时,使用双引号以避免意外的单词拆分和路径扩展。

# 不推荐
echo $var

# 推荐
echo "$var"

7、使用条件语句的简洁写法

        在处理条件时,使用简洁的写法可以提高可读性。

# 不推荐
if [ "$var" = "yes" ]; then
    echo "Yes"
else
    echo "No"
fi

# 推荐
[ "$var" = "yes" ] && echo "Yes" || echo "No"

8、性能分析

        使用工具如 time、bash -x 或 set -x 来分析脚本的性能和调试信息。

time ./1.sh

9、使用 shellcheck 检查脚本

        使用 shellcheck 工具可以帮助你发现脚本中的潜在问题和提高可读性。

# 安装 shellcheck
sudo apt install shellcheck

# 检查脚本
shellcheck 1.sh

10、编写文档

        在脚本开头添加注释,说明脚本的用途、参数和使用方法,提高可读性。

        通过这些技巧和最佳实践,可以有效地优化 Linux 脚本的性能和可读性。优化脚本不仅可以提高执行效率,还能使脚本更易于理解和维护。

定时任务

        在 Linux 系统中,cron 和 at 是两个常用的工具,用于设置定时任务。

1、使用 cron 设置定时任务

        cron 是一个基于时间的作业调度器,可以定期执行任务。

1.1、编辑 `cron` 任务

        要编辑当前用户的 `cron` 任务,可以使用 crontab 命令。

crontab -e

        这将打开一个文本编辑器,我们可以在其中添加定时任务。

1.2、cron 任务的格式

# cron 任务的格式如下

* * * * * 命令

# 五个星号分别代表(从左至右)
# 分钟 (0-59)
# 小时 (0-23)
# 日 (1-31)
# 月 (1-12)
# 星期 (0-7)(0和7都代表星期日)
# 每天凌晨 2 点执行 `/path/to/script.sh`:

   0 2 * * * /path/to/script.sh

# 每小时的第 15 分钟执行 `/path/to/script.sh`:

   15 * * * * /path/to/script.sh

# 每周一的中午 12 点执行 `/path/to/script.sh`:

   0 12 * * 1 /path/to/script.sh

1.4、查看 cron 任务

crontab -l

1.5、删除 cron 任务

crontab -r # 删除当前用户的所以定时任务

2、使用 at 设置定时任务

        at 是一个用于在指定时间执行一次性任务的命令。

2.1 安装 at

        在某些 Linux 发行版中,at 可能需要单独安装。

sudo yum install at

2.2、启动 atd 服务

        在使用 at 之前,确保 atd 服务正在运行。

# 启动 atd 服务
sudo systemctl start atd

# 设置为开机自启
sudo systemctl enable atd

2.3、使用 at 命令

        要使用 at 创建定时任务,可以使用以下格式。

at [time]
# 在今天的 15:00 执行 `/path/to/script.sh`:

   echo "/path/to/script.sh" | at 15:00

# 在明天的中午 12:30 执行 `/path/to/script.sh`:

   echo "/path/to/script.sh" | at 12:30 tomorrow

# 在 5 分钟后执行 `/path/to/script.sh`:

   echo "/path/to/script.sh" | at now + 5 minutes

2.5、查看 at 任务

atq

2.6、删除 at 任务

        要删除某个 at 任务,可以使用 atrm 命令,后面跟上任务的 ID(可以通过 `atq` 查看)。

atrm job_id

总结

        cron 适用于定期执行的任务,适合每天、每周或每月执行的作业。
        at 适用于一次性任务,适合在特定时间执行的作业。

脚本编写

        这里编写了几个实用的 Shell 脚本以作参考。

# 备份脚本
# 这个脚本会将指定目录的内容备份到指定的备份目录,并在备份文件名中添加时间戳

#!/bin/bash

# 备份源目录
SOURCE_DIR="/path/to/source"
# 备份目标目录
BACKUP_DIR="/path/to/backup"
# 当前日期
CURRENT_DATE=$(date +"%Y%m%d_%H%M%S")

# 创建备份
tar -czf "$BACKUP_DIR/backup_$CURRENT_DATE.tar.gz" -C "$SOURCE_DIR" .

# 输出备份结果
echo "Backup of $SOURCE_DIR completed at $CURRENT_DATE."
# 日志分析脚本
# 这个脚本分析 Apache 日志文件,统计访问量最多的 IP 地址

#!/bin/bash

# 日志文件路径
LOG_FILE="/var/log/apache2/access.log"

# 输出结果文件
OUTPUT_FILE="top_ips.txt"

# 统计 IP 地址并输出到文件
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n 10 > "$OUTPUT_FILE"

# 输出结果
echo "Top 10 IP addresses saved to $OUTPUT_FILE."
# 系统监控脚本
# 这个脚本监控系统的 CPU 使用率和内存使用情况,并将结果输出到一个日志文件

#!/bin/bash

# 日志文件
LOG_FILE="system_monitor.log"

# 获取系统信息
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
MEMORY_USAGE=$(free | grep Mem | awk '{print $3/$2 * 100.0}')

# 当前日期
CURRENT_DATE=$(date +"%Y-%m-%d %H:%M:%S")

# 输出到日志文件
echo "$CURRENT_DATE - CPU Usage: $CPU_USAGE% - Memory Usage: $MEMORY_USAGE%" >> "$LOG_FILE"

# 输出结果
echo "System monitoring logged to $LOG_FILE."
# 定时清理临时文件脚本
# 这个脚本会定期清理指定目录下的临时文件

#!/bin/bash

# 临时文件目录
TEMP_DIR="/path/to/temp"

# 删除临时文件
find "$TEMP_DIR" -type f -mtime +7 -exec rm -f {} \;

# 输出结果
echo "Temporary files older than 7 days in $TEMP_DIR have been deleted."

        个人学习的部分介绍完毕,如果有错误希望大家指出,谢谢。

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值