Linux Shell 脚本学习手册(一)

原文:zh.annas-archive.org/md5/77969218787D4338964B84D125FE6927

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Shell 脚本允许我们以链式编程命令,并让系统将它们作为脚本事件执行,就像批处理文件一样。这本书将从 Linux 和 Bash shell 脚本的概述开始,然后迅速深入帮助你设置本地环境,然后介绍用于编写 shell 脚本的工具。接下来的一系列章节将重点帮助你了解 Linux 的内部机制以及 Bash 为用户提供的内容。很快,你将开始沿着命令行进行旅程。你现在将开始编写实际的脚本而不是命令,并将介绍脚本的实际应用。最后一系列章节将深入探讨 shell 脚本中的更高级主题。这些高级主题将把你从简单的脚本带到现实世界中存在的可重用的有价值的程序。最后一章将为你提供一些方便的技巧和窍门,以及包含最有趣的标志和选项的备忘单,涉及最常用的命令。

完成这本书后,你应该对开始自己的 shell 脚本项目感到自信,无论之前的任务看起来多么简单或复杂。我们的目标是教会你如何编写脚本和需要考虑什么,以补充你在日常脚本挑战中可以使用的清晰模式。

这本书是为谁写的

这本书面向新的和现有的 Linux 系统管理员,以及对自动化管理任务感兴趣的 Windows 系统管理员或开发人员。不需要先前的 shell 脚本经验,但如果你有一些经验,这本书将迅速让你成为专家。读者应该对命令行有(非常)基本的理解。

这本书涵盖了什么

第一章,介绍,为你做好了本书的余下部分。在 Linux 和 Bash 的一些背景知识的帮助下,你应该更能够理解为什么以及如何 shell 脚本可以为你提供明显的好处。

第二章,设置本地环境,帮助你准备好本地机器,以便在本书的其余部分中进行示例和练习。你将学会如何在本地机器上使用 VirtualBox 设置 Ubuntu 18.04 Linux 虚拟机。这个虚拟机将用于在本书中编写、运行和调试命令和脚本。

第三章,选择合适的工具,介绍了用于编写 shell 脚本的工具。将描述两种不同类型的工具:IDE 编辑器(Atom,Notepad++)和基于终端的编辑器(vim 和 nano)。你将被鼓励最初在 IDE 中编写脚本,并在基于终端的编辑器中排除故障,以最接近真实世界的使用。

第四章,Linux 文件系统,通过探索前几章创建的虚拟机,介绍了 Linux 文件系统的组织方式。你将通过执行第一个命令行操作(如cdpwdls)来实现这一点。将提供有关不同结构的上下文,以便你在编写脚本时使用这些信息。最重要的是,将解释一切都是文件的概念。

第五章,理解 Linux 权限方案,让你熟悉 Linux 下的权限,再次通过探索虚拟机。诸如sudochmodchown之类的命令将被用来交互式地学习文件和目录权限。在本章中获得的技能将在 shell 脚本中大量使用,因此你必须接触到命令的成功执行以及失败消息。

第六章,文件操作,向您介绍了最相关的文件操作命令,包括这些命令的最常用标志和修饰符。这将通过虚拟机内的命令实现。

第七章,Hello World!,在撰写脚本时,教育您提前思考和养成良好习惯。在本章中,您将编写您的第一个实际的 shell 脚本。

第八章,变量和用户输入,向您介绍了变量和用户输入。您将看到 Bash 如何使用参数,以及参数和参数之间的区别。用户输入将被处理并用于在您的脚本中生成新函数。最后,将澄清和讨论交互式和非交互式脚本之间的区别。

第九章,错误检查和处理,使您熟悉(用户)输入,错误检查和处理。将用户输入引入脚本中很可能会导致更多的错误,除非脚本专门处理用户提交的不正确或意外的输入可能性。您将学习如何最好地处理这个问题。

第十章,正则表达式,使您熟悉常用于 shell 脚本的正则表达式。将介绍这些正则表达式的最常见模式和用法。本章还将介绍sed的基本用法,以补充正则表达式的解释。

第十一章,条件测试和脚本循环,讨论了在 Bash 中使用的不同类型的循环和相关控制结构。

第十二章,在脚本中使用管道和重定向,向您介绍了 Linux 上的重定向。本章将从基本的输入/输出重定向开始,然后继续流重定向和管道。

第十三章,函数,向您介绍了函数。函数将被呈现为代码块,这些代码块以这样的方式组合在一起,以便它们可以被重复使用,通常使用不同的参数,以产生稍微不同的最终结果。您将学会理解重用代码的好处,并相应地规划脚本。

第十四章,调度和日志记录,教您如何安排脚本,并确保这些计划的脚本执行它们旨在执行的任务,方法是使用 crontab 和at命令,以及适当的日志记录。

第十五章,使用 getopts 解析 Bash 脚本参数,帮助您通过添加标志而不是位置参数来改进脚本,从而使脚本更容易使用。

第十六章,Bash 参数替换和扩展,展示了如何通过参数扩展、替换和变量操作来优化先前在早期脚本中使用的模式。

第十七章,Cheat Sheet 中的技巧和技巧,为您提供了一些方便的技巧和技巧,这些技巧和技巧不一定在 Bash 脚本中使用,但在终端上工作时非常方便。对于最常用的命令,将提供包含最有趣的标志和选项的备忘单,以便您在编写脚本时将本章用作参考。

为了充分利用本书

您需要一个 Ubuntu 18.04 Linux 虚拟机来跟随本书。我们将在第二章中指导您设置这一点。只有当您跟随所有代码示例时,您才能真正学会 shell 脚本编写。整本书都是以此为出发点编写的,所以请务必遵循这些建议!

下载示例代码文件

您可以从您在www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. www.packtpub.com登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

一旦文件下载完成,请确保您使用最新版本的解压缩或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Learn-Linux-Shell-Scripting-Fundamentals-of-Bash-4.4。如果代码有更新,将会在现有的 GitHub 仓库上进行更新。

我们还有其他代码包,来自我们丰富的书籍和视频目录,可在github.com/PacktPublishing/上找到。快去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781788995597_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“让我们试着将/tmp/目录复制到我们的home目录中。”

代码块设置如下:

#!/bin/bash

echo "Hello World!"

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

reader@ubuntu:~/scripts/chapter_10$ grep 'use' grep-file.txt 
We can use this regular file for testing grep.
but in the USA they use color (and realize)!

任何命令行输入或输出都写成如下形式:

reader@ubuntu:~/scripts/chapter_10$ grep 'e.e' character-class.txt 
eee
e2e
e e

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种形式出现在文本中。这是一个例子:“点击安装按钮,然后观看安装过程。”

警告或重要说明会出现在这样的形式中。

提示和技巧会以这种形式出现。

联系我们

我们始终欢迎读者的反馈。

一般反馈:发送电子邮件至feedback@packtpub.com,并在主题中提及书名。如果您对本书的任何方面有疑问,请发送电子邮件至questions@packtpub.com与我们联系。

勘误:尽管我们已经非常注意确保内容的准确性,但错误是难免的。如果您在本书中发现错误,我们将不胜感激。请访问www.packtpub.com/submit-errata,选择您的书,点击勘误提交表格链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何形式的非法副本,我们将不胜感激,如果您能向我们提供位置地址或网站名称。请通过copyright@packtpub.com与我们联系,并附上材料链接。

如果您有兴趣成为作者:如果您在某个专题上有专业知识,并且有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评论

请留下评论。阅读并使用本书后,为什么不在购买它的网站上留下评论呢?潜在的读者可以看到并使用您的客观意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们书籍的反馈。谢谢!

有关 Packt 的更多信息,请访问 packtpub.com

免责声明

本书中的信息仅供以合乎道德的方式使用。如果您没有设备所有者的书面许可,请不要使用本书中的任何信息。如果您进行非法行为,您可能会被逮捕并依法起诉。Packt Publishing 不对您滥用本书中的任何信息承担任何责任。本书中的信息必须在获得适当人员的书面授权的测试环境中使用。

第一章:介绍

在我们开始编写 shell 脚本之前,我们需要了解一些关于我们最相关的两个组件的背景:LinuxBash。我们将解释 Linux 和 Bash,探讨这两种技术背后的历史,并讨论它们的当前状态。

本章将涵盖以下主题:

  • 什么是 Linux?

  • 什么是 Bash?

什么是 Linux?

Linux 是一个通用术语,指的是基于 Linux 内核的不同开源操作系统。Linux 内核最初由 Linus Torvalds 于 1991 年创建,并于 1996 年开源。内核是一种设计用于在低级硬件(如处理器、内存和输入/输出设备)与高级软件(如操作系统)之间充当中间层的软件。除了 Linux 内核外,大多数 Linux 操作系统在很大程度上依赖 GNU 项目实用工具;例如,Bash shell 是一个 GNU 程序。因此,一些人将 Linux 操作系统称为 GNU/Linux。GNU 项目,其中 GNU 代表GNU’s Not Unix!(一个递归缩写),是一个自由软件集合,其中大部分在大多数 Linux 发行版中都可以找到。这个集合包括许多工具,还有一个名为 GNU HURD 的替代内核(并不像 Linux 内核那样广泛使用)。

为什么我们需要一个内核?由于内核位于硬件和操作系统之间,它提供了与硬件交互的抽象。这就是为什么 Linux 生态系统变得如此庞大:内核可以自由使用,并且它处理了许多类型硬件的低级操作。因此,操作系统的创建者可以花时间为用户制作易于使用、美观的体验,而不必担心用户的图片将如何被写入连接到系统的物理磁盘。

Linux 内核是所谓的类 Unix软件。正如你可能猜到的那样,这意味着它类似于最初由 Ken Thompson 和 Dennis Ritchie 在贝尔实验室于 1971 年至 1973 年间创建的原始 Unix 内核。然而,Linux 内核只是基于Unix 原则,并不与 Unix 系统共享代码。著名的 Unix 系统包括 BSD(FreeBSD、OpenBSD 等)和 macOS。

Linux 操作系统广泛用于两个目的之一:作为桌面或作为服务器。作为桌面,Linux 可以替代更常用的 Microsoft Windows 或 macOS。然而,大多数 Linux 使用是用于服务器领域。据估计,目前约 70%的互联网服务器使用 Unix 或类 Unix 操作系统。下次当你浏览新闻、阅读邮件或在你最喜欢的社交媒体网站上滚动时,请记住,你所看到的页面很有可能是由一个或多个 Linux 服务器处理的。

Linux 有许多发行版或版本。大多数 Linux 操作系统属于发行版家族。发行版家族基于一个共同的祖先,并且通常使用相同的软件包管理工具。其中一个较为知名的 Linux 发行版Ubuntu基于Debian发行版家族。另一个著名的 Linux 发行版Fedora基于Red Hat家族。其他值得注意的发行版家族包括SUSEGentooArch

很多人并不意识到有多少设备在运行 Linux 内核。例如,如今使用最普遍的智能手机操作系统 Android(市场份额约为 85%)使用了修改版的 Linux 内核。许多智能电视、路由器、调制解调器和其他各种嵌入式设备也是如此。如果我们将 Unix 和其他类 Unix 软件包括在内,我们可以肯定地说世界上大多数设备都在运行这些内核!

什么是 Bash?

Linux 系统中最常用的 shell 是Bourne-again shell,或者称为 Bash。Bash shell 基于Bourne shell,也就是sh。但 shell 到底是什么呢?

shell 本质上是一个用户界面。最常见的是指文本界面,也称为命令行界面CLI)。但它被称为shell,是因为它可以被看作是内核周围的外壳;这意味着它不仅适用于 CLI,同样也适用于图形用户界面GUI)。当我们在本书中提到 shell 时,我们指的是 CLI,除非另有说明,我们指的是 Bash shell。

shell 的目的,无论是 CLI 还是 GUI,都是允许用户与系统进行交互。毕竟,一个没有交互功能的系统很难被证明存在的,更不用说难以使用了!在这种情况下,交互意味着许多事情:在键盘上输入会在屏幕上显示字母,移动鼠标会改变屏幕上光标的位置,给出删除文件的命令(无论是使用 CLI 还是 GUI)都会从磁盘中删除字节,等等。

在 Unix 和计算机的早期,没有 GUI 可用,因此所有工作都是通过 CLI 执行的。要连接到运行中的机器上的 shell,通常会使用视频终端:通常这是一个非常简单的监视器,配合键盘使用,通过 RS-232 串行电缆连接。在这个视频终端上输入的命令由运行在 Unix 机器上的 shell 处理。

幸运的是,自第一台计算机以来,事情发生了很大的变化。今天,我们不再使用专用硬件连接到 shell。一个运行在 GUI 中的软件,即终端仿真器,用于与 shell 进行交互。让我们快速看一下通过终端仿真器连接到 Bash shell 的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在前面的屏幕截图中,我们通过安全外壳SSH)协议,使用终端仿真器(GNOME Terminal)连接到了 Linux 虚拟机(我们将在下一章中设置这个),收到了一些有趣的信息:

  • 我们处于 CLI 界面;我们既无法访问鼠标,也不需要鼠标

  • 我们连接到了一个 Ubuntu 机器,但是我们是在另一个操作系统(本例中是 Arch Linux)中运行的

  • 我们在 Ubuntu 18.04 中收到了一条欢迎消息,显示了关于系统的一些常规信息

除了直接使用 Bash shell 与系统进行交互,它还提供了另一个重要的功能:按特定目标顺序执行多个命令,无论是否需要用户交互。这听起来可能很复杂,但实际上非常简单:我们谈论的是Bash 脚本,也就是本书的主题!

总结

在这一章中,您已经了解了 GNU/Linux 操作系统和 Linux 内核,了解了内核的真正含义,以及 Linux 发行版对日常生活产生的巨大影响。您还了解了 shell 是什么,以及最常见的 Linux shell——Bash,既可以用于与 Linux 系统交互,也可以用于编写 shell 脚本。

在下一章中,我们将设置一个本地环境,这将在本书的其余部分中使用,包括示例和练习。

第二章:设置您的本地环境

在上一章中,我们探讨了 Linux 和 Bash 的美妙世界的一些背景知识。由于这是一本实践驱动的书,我们将利用本章来设置一台机器,您可以在其中跟随示例并完成每章末尾的练习。这可以是虚拟机,也可以是物理安装;这取决于您。我们将在本章的第一部分讨论这个问题,然后继续安装 VirtualBox,最后创建一个 Ubuntu 虚拟机。

本章将介绍以下命令:sshexit

本章将涵盖以下主题:

  • 在虚拟机和物理安装之间进行选择

  • 设置 VirtualBox

  • 创建 Ubuntu 虚拟机

技术要求

要完成本章(以及以下章节)中的练习,您需要一台至少拥有 2 GHz CPU 功率、10 GB 硬盘空间和大约 1 GB 可用内存的 PC 或笔记本电脑。在过去 5 年中制造的几乎所有硬件都应该足够。

在虚拟机和物理安装之间进行选择

虚拟机是物理机的仿真。这意味着它在物理机内部运行,而不是直接在硬件上运行。物理机可以直接访问所有硬件,如 CPU、RAM 和其他设备,如鼠标、键盘和显示器。然而,无法在多个物理机之间共享 CPU 或 RAM。虚拟机不能直接访问硬件,而是通过仿真层访问,这意味着资源可以在多个虚拟机之间共享。

因为我们正在讨论 Bash shell 脚本编程,理论上进行何种安装并不重要。只要该安装运行兼容的 Linux 操作系统,并且具有 Bash 4.4 或更高版本,所有练习都应该可以运行。然而,使用虚拟机而不是物理安装有许多优势:

  • 无需删除您当前首选的操作系统,或设置复杂的双启动配置

  • 虚拟机可以进行快照,这允许从关键故障中恢复

  • 您可以在一台机器上运行(许多)不同的操作系统

不幸的是,虚拟机使用也有一些缺点:

  • 因为您在已经运行的操作系统中运行虚拟操作系统,所以与运行裸机安装相比,虚拟化会带来一些开销

  • 由于同时运行多个操作系统,您将需要比裸机安装更多的资源

在我们看来,现代计算机的速度足够快,使得缺点几乎可以忽略不计,而在虚拟机中运行 Linux 所提供的优势非常有帮助。因此,我们将在本章的其余部分中只解释虚拟机设置。如果您对将 Linux 作为物理安装感到足够自信(或者您可能已经在某个地方运行了 Linux!),请随时使用该机器探索本书的其余部分。

您可能在家里有一个树莓派或另一个运行 Linux 的单板计算机,来自以前的项目。虽然这些机器确实运行着 Linux 发行版(Raspbian),但它们可能是在不同的架构上运行的:ARM 而不是 x86。因为这可能会导致意想不到的结果,我们建议本书只使用 x86 设备。

如果您想确保所有示例和练习都能像本书中所示那样工作,请在 VirtualBox 中运行一个 Ubuntu 18.04 LTS 虚拟机,建议的规格为 1 个 CPU、1GB RAM 和 10GB 硬盘:这个设置在本章的其余部分中有描述。即使许多其他类型的部署也应该可以工作,但当练习不起作用时,您可能不希望在发现是由于您的设置引起的之前,花费数小时撞墙。

设置 VirtualBox

要使用虚拟机,我们需要一种称为虚拟化软件的软件。虚拟化软件在主机机器和虚拟机之间管理资源,提供对磁盘的访问,并具有管理所有这些的接口。有两种不同类型的虚拟化软件:类型 1 和类型 2。类型 1 虚拟化软件是所谓的裸机虚拟化软件。这些软件是安装在硬件上而不是像 Linux、macOS 或 Windows 等常规操作系统上的。这些类型的虚拟化软件用于企业服务器、云服务等。在本书中,我们将使用类型 2 虚拟化软件(也称为托管虚拟化软件):这些软件安装在另一个操作系统中,就像一个浏览器一样。

有许多类型 2 的虚拟化软件。在撰写本文时,最受欢迎的选择是 VirtualBox、VMware 工作站播放器,或者 OS 特定的变体,如 Linux 上的 QEMU/KVM,macOS 上的 Parallels Desktop,以及 Windows 上的 Hyper-V。因为我们将在整本书中使用虚拟机,我们不假设主机机器的任何情况:您应该可以舒适地使用您喜欢的任何操作系统。因此,我们选择使用 VirtualBox 作为我们的虚拟化软件,因为它可以在 Linux、macOS 和 Windows 上运行(甚至其他系统!)。此外,VirtualBox 是免费和开源软件,这意味着您可以只需下载并使用它。

目前,VirtualBox 由 Oracle 公司拥有。您可以从www.virtualbox.org/下载 VirtualBox 的安装程序。安装不应该很难;按照安装程序的说明进行操作。

安装类型 2 的虚拟化软件(如 VirtualBox)后,请务必重新启动计算机。虚拟化软件通常需要加载一些内核模块,最简单的方法是通过重新启动实现。

创建 Ubuntu 虚拟机

在本书中,我们使用 Bash 进行脚本编写,这意味着我们的 Linux 安装不需要 GUI。我们选择使用Ubuntu Server 18.04 LTS作为虚拟机操作系统,原因有很多:

  • Ubuntu 被认为是一种适合初学者的 Linux 发行版。

  • 18.04 是一个长期支持LTS)版本,这意味着它将在 2023 年 4 月之前接收更新

  • 因为 Ubuntu 服务器提供了仅 CLI 安装,它对系统资源消耗较小,并且代表了真实服务器的情况。

在撰写本文时,Ubuntu 由 Canonical 公司维护。您可以从www.ubuntu.com/download/server下载 ISO 镜像。现在下载文件,并记住您保存此文件的位置,因为您很快就会需要它。

如果前面的下载链接不再有效,您可以转到您喜欢的搜索引擎,搜索“Ubuntu Server 18.04 ISO 下载”。您应该会找到 Ubuntu 存档的参考,其中将包含所需的 ISO。

在 VirtualBox 中创建虚拟机

首先,我们将开始创建虚拟机来托管我们的 Ubuntu 安装:

  1. 打开 VirtualBox,选择菜单工具栏中的 Machine | New。

  2. 作为参考,在下面的截图中给出了菜单工具栏中的 Machine 条目。为虚拟机选择一个名称(这可以是与服务器名称不同的名称,但出于简单起见,我们喜欢保持相同),将类型设置为 Linux,版本设置为 Ubuntu(64 位)。然后点击下一步:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 在这个屏幕上,我们确定内存设置。对于大多数服务器来说,1024 MB 的 RAM 是一个很好的起点(也是 VirtualBox 为虚拟机推荐的)。如果你有强大的硬件,可以设置为 2048 MB,但 1024 MB 应该也可以。做出你的选择,然后按下一步:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 再次,VirtualBox 推荐的值对我们的需求非常完美。按下“创建”开始创建虚拟硬盘:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 虚拟硬盘可以是许多不同的类型。VirtualBox 默认使用自己的格式VDI,而不是 VMware 使用的VMDK格式(另一个流行的虚拟化提供商)。最后一个选项是VHD(虚拟硬盘),这是一个更通用的格式,可以被多个虚拟化提供商使用。由于我们在本书中将专门使用 VirtualBox,所以保持选择VDI(VirtualBox 磁盘映像)并按下“下一步”:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 在这个屏幕上,我们有两个选项:我们可以立即在物理硬盘上为虚拟硬盘分配全部空间,或者我们可以使用动态分配,它不会保留虚拟磁盘的全部大小,而只保留已使用的部分。

这些选项之间的区别通常在许多虚拟机运行在单个主机上的情况下最相关。创建的磁盘总量大于物理可用的情况,但假设并非所有磁盘都会被充分使用,允许我们在单台机器上放置更多的虚拟机。这被称为过度配置,只有在并非所有磁盘都被填满的情况下才能工作(因为我们永远不能拥有比物理磁盘空间更多的虚拟磁盘空间)。对我们来说,这个区别并不重要,因为我们将只运行一个虚拟机;我们保持默认的动态分配并进入下一个屏幕:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 在这个屏幕上,我们可以做三件事:命名虚拟磁盘文件,选择位置,并指定大小。如果你关心位置(默认为你的home/user目录中的某个位置),你可以按下下面截图中圈出的图标。对于名称,我们喜欢保持与虚拟机名称相同。最后,10GB 的大小对本书中的练习已经足够了。设置好这三个值后,按下“创建”。恭喜,你刚刚创建了你的第一个虚拟机,如下面的截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 然而,在我们可以在虚拟机上安装 Ubuntu 之前,我们还需要做两件事:将虚拟机指向安装 ISO,并设置网络。选择新创建的虚拟机,点击“设置”。导航到存储部分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你应该在磁盘图标上看到一个带有“Empty”字样的图标(在前面截图左侧圈出的位置)。选择它并通过点击选择磁盘图标(在右侧圈出的位置)挂载一个 ISO 文件,选择虚拟光盘文件,然后选择你之前下载的 Ubuntu ISO。如果你做对了,你的屏幕应该和前面的截图一样:你不再看到磁盘图标旁边的“Empty”字样,信息部分应该也填写了。

  1. 一旦你验证了这一点,就去到网络部分。

  2. 配置应该默认为 NAT 类型。如果不是,请现在设置为 NAT。NAT 代表网络地址转换。在这种模式下,主机机器充当虚拟机的路由器。最后,我们将设置一些端口转发,以便稍后使用 SSH 工具。点击“端口转发”按钮:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 设置 SSH 规则就像我们所做的那样。这意味着在虚拟机上的端口22被暴露为主机上的端口2222。我们选择端口2222有两个原因:低于 1024 的端口需要 root/administrator 权限,我们可能没有。其次,有可能主机上已经有一个 SSH 进程在监听,这意味着 VirtualBox 将无法使用该端口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过这一步,我们已经完成了虚拟机的设置!

在虚拟机上安装 Ubuntu

现在您可以从 VirtualBox 主屏幕启动虚拟机。右键单击该虚拟机,选择启动,然后选择正常启动。如果一切顺利,一个新窗口将弹出,显示虚拟机控制台。过一会儿,您应该在该窗口中看到 Ubuntu 服务器安装屏幕:

  1. 在下一个截图中显示的屏幕上,使用箭头键选择您喜欢的语言(我们使用英语,所以如果您不确定,英语是一个不错的选择),然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 选择您正在使用的键盘布局。如果不确定,可以使用交互式识别键盘选项来确定哪种布局最适合您。设置正确的布局后,将焦点移动到“完成”然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 现在我们选择安装类型。因为我们使用的是服务器 ISO,所以我们看不到与 GUI 相关的任何选项。在前面的截图中,选择安装 Ubuntu(其他两个选项都使用 Canonical 的Metal-As-A-ServerMAAS)云服务,这对我们不相关),然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 您将看到网络连接屏幕。安装程序应默认使用虚拟机创建的默认网络接口上的 DHCP。验证该接口是否已分配 IP,然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 配置代理屏幕对我们不相关(除非您正在使用代理设置,但在这种情况下,您很可能不需要我们的安装帮助!)。将代理地址留空,然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 有时手动分区 Linux 磁盘以满足特定需求是有帮助的。在我们的情况下,使用整个磁盘的默认值非常合适,所以按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 在选择了使用整个磁盘之后,我们需要指定要使用的磁盘。由于我们在配置虚拟机时只创建了一个磁盘,选择它然后按Enter键。

  2. 现在您将遇到有关执行破坏性操作的警告。因为我们使用的是整个(虚拟!)磁盘,该磁盘上的所有信息都将被删除。我们在创建虚拟机时创建了这个磁盘,因此它不包含任何数据。我们可以安全地执行此操作,所以选择继续然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 文件系统设置,再次默认值非常适合我们的需求。验证我们至少有 10GB 的硬盘空间(可能会少一点,比如在下面的示例中是 9.997GB:这没问题),然后按Enter键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. Ubuntu 服务器现在应该开始安装到虚拟磁盘。在这一步中,我们将设置服务器名称并创建一个管理用户。我们选择了服务器名称ubuntu,用户名reader和密码password。请注意,这是一个非常弱的密码,我们只会在这台服务器上使用它以简化操作。这是可以接受的,因为服务器只能从我们的主机访问。在配置接受来自互联网的流量的服务器时,永远不要使用这么弱的密码!选择任何您喜欢的内容,只要您能记住它。如果您不确定,我们建议使用ubuntureaderpassword相同的值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,您已经选择了服务器名称并配置了一个管理用户,请按Enter完成安装。

  1. 根据上一个屏幕完成所花费的时间以及主机的速度,Ubuntu 要么仍在安装中,要么已经完成。如果您仍然看到屏幕上的文本在移动,那么安装仍在进行。一旦安装完全完成,您将看到“立即重启”按钮出现。按Enter

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 几秒钟后,应该会出现一条消息,指出“请移除安装介质,然后按 Enter”。按照说明操作,如果一切顺利,您应该会看到一个终端登录提示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通常,VirtualBox 足够智能,会尝试从硬盘而不是 ISO 进行第二次引导。如果在之前的步骤之后,重新启动将您送回安装菜单,则从 VirtualBox 主屏幕关闭虚拟机。右键单击该机器,选择关闭,然后选择关机。在完全关闭电源后,编辑该机器并删除 ISO。这应该强制 VirtualBox 从包含 Ubuntu Server 18.04 LTS 安装的磁盘引导。

  1. 现在是真相的时刻:尝试使用您创建的用户名和密码登录。如果成功,您应该会看到一个类似于以下的屏幕:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

给自己一个鼓励:您刚刚创建了一个虚拟机,安装了 Ubuntu Server 18.04 LTS,并通过终端控制台登录。干得好!要退出,请输入exitlogout,然后按Enter

通过 SSH 访问虚拟机

我们已成功连接到 VirtualBox 提供给我们的终端控制台。但是,这个终端连接实际上非常基本:例如,我们无法向上滚动,无法粘贴复制的文本,也没有彩色的语法高亮。幸运的是,我们有一个不错的替代方案:安全外壳SSH)协议。 SSH 用于连接到虚拟机上运行的 shell。通常,这是通过网络完成的:这就是企业维护其 Linux 服务器的方式。在我们的设置中,我们实际上可以在主机机器上使用 SSH,使用我们之前设置的电源转发。

如果您遵循了安装指南,主机上的端口2222应该被重定向到虚拟机上的端口22,这是 SSH 进程运行的端口。从 Linux 或 macOS 主机,我们可以使用以下命令进行连接(根据需要替换用户名或端口号):

$ ssh reader@localhost -p 2222

然而,有很大的可能性您正在运行 Windows。在这种情况下,您可能无法在命令提示符中访问本机 SSH 客户端应用程序。幸运的是,有许多好的(而且免费的!)SSH 客户端。最简单和最知名的客户端是PuTTY。 PuTTY 是在 1999 年创建的,虽然它绝对是一个非常稳定的客户端,但它的年龄开始显现。我们建议一些更新的 SSH 客户端软件,比如MobaXterm。这为您提供了更多的会话管理,更好的图形用户界面,甚至本地命令提示符!

无论您决定使用哪种软件,请确保使用以下值(再次更改端口或用户名,如果您偏离了安装指南):

  • 主机名:localhost

  • 端口:2222

  • 用户名:reader

如果您使用 SSH 连接到虚拟机,可以以无头模式启动它。这样做时,VirtualBox 不会为您创建一个带有终端控制台的新窗口,而是在后台运行虚拟机,您仍然可以通过 SSH 连接(就像在实际的 Linux 服务器上发生的情况)。这个无头启动选项,在右键单击虚拟机并选择启动时,位于早期的正常启动下方。

摘要

在本章中,我们已经开始准备我们的本地机器,以便进行本书的其余部分。我们现在知道虚拟机和物理机之间的区别,以及为什么我们更喜欢在本书的其余部分使用虚拟机。我们已经了解了两种不同类型的 hypervisors。我们已经安装和配置了 VirtualBox,并在其中安装了 Ubuntu 18.04 操作系统的虚拟机。最后,我们已经使用 SSH 连接到正在运行的虚拟机,而不是使用 VirtualBox 终端,这样可以获得更好的可用性和选项。

在本章中引入了以下命令:sshexit

在下一章中,我们将通过查看一些不同的工具来完成设置我们的本地机器,这些工具将帮助我们在 GUI 和虚拟机 CLI 上进行 bash 脚本编写。

问题

  1. 运行虚拟机比裸机安装更可取的原因是什么?

  2. 与裸机安装相比,运行虚拟机的一些缺点是什么?

  3. 类型 1 和类型 2 hypervisor 之间有什么区别?

  4. 在 VirtualBox 上有哪两种方式可以启动虚拟机?

  5. Ubuntu LTS 版本有什么特别之处?

  6. 如果在 Ubuntu 安装后,虚拟机再次引导到 Ubuntu 安装屏幕,我们应该怎么办?

  7. 如果在安装过程中意外重启,最终没有进入 Ubuntu 安装界面(而是看到错误),我们应该怎么办?

  8. 为什么我们为虚拟机设置了 NAT 转发?

进一步阅读

如果您想更深入地了解本章的主题,以下资源可能会有所帮助:

第三章:选择合适的工具

本章将介绍一些在编写 Bash 脚本时会帮助我们的工具。我们将专注于两种类型的工具:基于 GUI 的编辑器(Atom 和 Notepad++)和基于终端的编辑器(Vim 和 nano)。我们将描述这些工具以及如何使用它们,它们的优势和劣势,以及如何同时使用基于 GUI 和基于终端的编辑器以获得最佳结果。

本章将介绍以下命令:vimnanols

本章将涵盖以下主题:

  • 使用图形编辑器进行 shell 脚本编写

  • 使用命令行编辑器进行 shell 脚本编写

  • 在编写 shell 脚本时将图形编辑器与命令行编辑器结合使用

技术要求

在使用 Vim 或 nano 时,您将需要我们在上一章中创建的虚拟机。如果要使用 Notepad++,您将需要 Windows 主机。对于 Atom,主机可以运行 Linux、macOS 或 Windows。

使用图形编辑器进行 shell 脚本编写

自 Unix 和类 Unix 发行版问世以来,工具已经发展了很长一段路。在早期,编写 shell 脚本比今天要困难得多:shell 功能较弱,文本编辑器仅限于命令行,诸如语法高亮和自动补全之类的功能都是不存在的。今天,我们有非常强大的图形用户界面编辑器,可以帮助我们进行脚本编写。为什么我们要等到运行脚本才发现错误,当图形用户界面编辑器可以提前显示错误?今天,使用高级编辑器进行 shell 脚本编写几乎是一种必需品,我们不想离开。

在接下来的页面中,我们将描述两个文本编辑器:Atom 和 Notepad++。两者都是基于 GUI 的,我们可以用它们进行高效的 shell 脚本编写。如果您已经对其中一个有偏好,请选择那个。如果不确定,我们建议使用 Atom。

Atom

我们将首先考虑的图形编辑器是由 GitHub 制作的 Atom。它被描述为“21 世纪可修改的文本编辑器”。在这里,“可修改”意味着虽然 Atom 的默认安装与任何文本编辑器一样完整,但这个应用程序真正闪耀的地方在于它非常可配置和可扩展。任何 GitHub 未集成的功能都可以作为扩展包编写。通过使用这些扩展,您可以使 Atom 安装完全成为您自己的;如果您不喜欢某些东西,就改变它。如果不能直接改变,就找一个可以做到的扩展包。即使没有符合您期望的扩展包,您仍然可以选择创建自己的扩展包!

Atom 的另一个很好的功能是与 Git 和 GitHub 的默认集成。Git 目前是最流行的版本控制系统。版本控制系统在编写代码或脚本时使用。它们确保文件的历史记录被保留,并使多个甚至许多贡献者能够同时在同一文件上工作,而不会因冲突管理而负担过重。GitHub,顾名思义,目前是最重要的面向开源软件的基于 Web 的 Git 提供者。

关于 Atom 的最后一件伟大的事情是,它默认支持许多脚本和编程语言。当我们说“支持”时,我们是指它可以通过文件扩展名识别文件类型,并提供语法高亮(这样编写脚本就更容易了!)。这种功能是通过核心包提供的,它们的工作方式与普通包相同,但从一开始就包含在内。对于我们的目的,核心包language-shellscript将帮助我们进行 shell 脚本编写。

Atom 安装和配置

让我们继续安装 Atom。 只要您运行 Linux、macOS 或 Windows,您可以转到atom.io/并获取安装程序。 运行安装程序,如果需要,可以跟随提示直到 Atom 安装完成。 现在,启动 Atom,您将会看到欢迎屏幕,写作时看起来像下面这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一定要查看 Atom 提供的所有屏幕。 当您觉得已经足够探索时,让我们向 Atom 添加一个可以补充我们的 shell 脚本的软件包。 如果您仍然打开欢迎指南屏幕,请从中选择安装软件包。 否则,您可以使用键盘快捷键Ctrl + *,*来打开设置屏幕。 您将在那里看到一个安装选项,它将带您到安装软件包屏幕。 搜索bash,您应该会看到以下软件包:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

单击安装按钮并观看安装过程。 安装后可能会提示您重新启动 Atom;一定要这样做。 如果没有提示但看到任何错误,重新启动 Atom 绝对不是一个坏主意。 安装软件包后,您现在在编写 shell 脚本时将具有自动完成功能。 这意味着您可以开始输入,Atom 将尝试预测您想要的内容,方式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在右侧,您可以看到我们开始输入echo shell 命令,前两个字母后,Atom 给出了包含这两个字母的两个选项。 一旦它提出建议,我们可以按Enter,命令就完全插入了。 虽然在这种情况下节省的时间不会太多,但有两个主要原因可以很好地使用它:

  • 如果您不确定命令的确切名称,您可能可以通过自动完成找到它。

  • 一旦您开始编写条件和循环(在本书的第二部分),自动完成将跨越多行,为您节省了输入许多单词和记住所有语法的时间。

最后,让我们看看当您打开一个 Git 项目并且正在处理文件时,Atom 的外观是什么样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 Atom 中工作时,屏幕大部分时间看起来会像这样。 在左侧,您将看到树视图,您可以通过按Ctrl + **来切换其开/关。 树视图包含当前项目中的所有文件(即您打开的目录)。 双击这些文件可以将它们打开,它们将出现在中间:编辑器视图。 这是您将花费大部分时间在其中编写 shell 脚本的地方。 即使当前没有打开文件,编辑器视图也将始终可见。

默认情况下,还有一个最后的视图,Git 视图,位于右侧。 您可以通过按*Ctrl *+*Shift *+ 9来切换此视图。 本书的代码托管在 GitHub 上,您将下载(或者,正如 Git 所称的那样,克隆)一次,而无需在远程服务器上进行编辑。 因此,在本书中不需要 Git 视图,但我们提到它,因为您可能会在其他项目中使用它。

Notepad++

Atom 更接近于集成开发环境IDE)而不是文本编辑器,Notepad++基本上就是其名字的含义:带有一些附加功能的老式记事本。 其中一些附加功能包括能够同时打开多个文件,语法高亮显示和有限的自动完成。 它最初是在 2003 年发布的,只能在 Windows 上运行。

Notepad++以其简单性而闻名。如果您熟悉任何一种记事本软件(谁不熟悉?),那么 Notepad++应该是立即可识别的。虽然我们建议在本书中使用 Atom,但使用诸如 Notepad++之类的简单解决方案绝对不会让您退步。但是,在商业环境中,您几乎总是会在已存在的版本控制存储库中创建脚本,这就是 Atom 的附加功能真正发挥作用的地方。

如果您想尝试 Notepad++,请从notepad-plus-plus.org/download下载并运行安装程序(请记住,仅当您使用 Windows 时!)。保持默认选项并在安装后运行 Notepad++。您应该会看到以下屏幕:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如您所看到的,当您打开以.sh结尾的文件时,您将看到语法高亮显示。这是因为.sh扩展名保留用于 shell 脚本文件。在编写脚本时,这可以帮助您很多。缺少引号导致脚本出错的示例将通过基于颜色的语法高亮显示变得非常明显,可能为您节省了很多故障排除时间。

Notepad++还有许多其他功能,使其成为一个出色的增强记事本。您可以使用宏执行脚本化任务,可以安装插件以扩展功能,并且还有许多其他独特功能,使 Notepad++成为一个吸引人的选择。

使用命令行编辑器

能够使用命令行编辑器是任何与 Linux 一起工作的人迟早都应该学会的技能。对于带有图形用户界面(GUI)的 Linux 安装,这可能会被替换为诸如 Atom 或分发内置的 Notepad 变体之类的 GUI 工具。但是,服务器安装几乎永远不会有 GUI,您将不得不依赖命令行文本编辑器。虽然这可能听起来令人生畏,但实际上并非如此!为了给您一个关于命令行编辑器的简要介绍,我们将介绍大多数 Linux 发行版上都存在的两个最受欢迎的应用程序:VimGNU nano

Vim

我们将讨论的第一个命令行文本编辑器可能是 Linux 中最受欢迎的:Vim。Vim 源自术语Vi Improved,因为它是 Unix 编辑器 Vi 的更新克隆。它由 Bram Moolenaar 创建,并仍在维护,他于 1991 年首次公开发布了 Vim。Vim(或者在非常老的系统上是 Vi)应该存在于您遇到的所有 Unix 或类 Unix 机器上。

Vim 被认为是一种难以学习的工具。这主要是因为它的工作方式与大多数人习惯的文本编辑器非常不同。但是,一旦初始学习曲线结束,许多人同意在 Vim 中可以更快地完成许多操作,而不是在普通文本编辑器(如 Microsoft 的 Notepad++)中。

让我们开始吧!登录到您的虚拟机:

$ ssh reader@localhost -p 2222

登录后,打开 Vim 到一个空文件:

$ vim

您应该会看到大致如下的东西:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Vim 启动一个使用整个终端的新进程(不用担心,一旦退出 Vim,一切都还会在您离开的地方!)。当您启动 Vim 时,您将进入normal模式。Vim 有许多模式,其中正常和insert是最有趣的探索。在正常模式下,您不能像在记事本或 Word 中那样开始输入。由于 Vim 设计为无需鼠标即可使用,因此它需要一种方式来操作文本。一些应用程序决定使用修改器(例如在记事本中按住Shift键),而 Vim 决定使用模式。让我们首先进入插入模式,以便我们可以开始输入一些文本。按I键,您的屏幕应该切换到插入模式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们已经在插入模式下输入了一些文本。确保您也这样做,完成后按Esc返回到正常模式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你比较两个屏幕截图,你应该会看到一个很大的区别:在左下角,文本-- INSERT --消失了!当你处于正常模式以外的模式时,那个模式会清晰地显示在那里。如果你什么都看不到,你可以安全地假设你处于正常模式。在正常模式下,我们可以使用箭头键进行导航。我们还可以使用几个按键来操作字符、单词,甚至(多个)行!例如,按下dd,注意到你整行都被删除了。如果你想要恢复它,按u进行撤销。

还有一个挑战:退出 Vim。通常,你可能会想要使用Esc按钮退出程序。如果你对 Linux 有一些了解,你可能甚至知道一个不错的Ctrl + C也可以退出大多数程序。然而,这两种方法对 Vim 都不起作用:Esc只会让你进入正常模式,而Ctrl + C 则不会有任何作用。要退出 Vim,请确保你处于正常模式,并输入以下内容:

:q!

这将退出你当前的文档,而不保存任何内容。如果你想保存并退出,请使用以下命令:

:x filename.txt

这将保存你当前的文档为filename.txt并返回到你的终端。请注意,通常你会使用以下命令在已经存在的文件上启动 Vim:

$ vim filename.txt

在这种情况下,当保存和退出时,你不需要输入文件名;在这种情况下,使用:x就足够了。:x实际上是:wq的缩写。:w操作,用于保存文件,:q用于退出。结合起来,它们用于保存并退出。如果你在编辑过程中想随时保存文件,你可以使用:w来完成这个操作。

Vim 摘要

Vim 有许多命令,受力用户欣赏。现在,记住有两个重要的模式,正常和插入。你可以通过按I从正常模式切换到插入模式,你可以通过按Esc返回到正常模式。在插入模式下,Vim 的行为就像记事本或 Word 一样,但在正常模式下,你可以进行简单的文本操作,例如删除当前选择的整行。如果你想退出 Vim,进入正常模式,然后输入:q!:x,取决于你是否想保存更改。

不要害怕开始使用 Vim。虽然一开始可能会让人望而生畏,但一旦你掌握了它,你就可以更快地在服务器上执行与文件相关的任务。如果你想提前了解,花 30 分钟的时间通过vimtutor。这个命令行工具会让你迅速掌握 Vim 的基本用法!要开始,只需导航到你的虚拟机,输入vimtutor,然后按Enter

.vimrc

.vimrc文件可用于为 Vim 设置一些持久选项。使用这个文件,你可以自定义你的 Vim 体验。有许多定制的可能性:常见的例子包括设置颜色方案,转换制表符和空格,以及设置搜索选项。

要创建一个在启动 Vim 时使用的.vimrc文件,请执行以下操作:

$ cd
$ vim .vimrc

第一个命令将你放在你的home目录中(不用担心,这将在本书的后面更详细地解释)。第二个命令为.vimrc文件启动了一个 Vim 编辑器。不要忘记前面的点,因为这是 Linux 处理隐藏文件的方式(同样,稍后会更详细地介绍)。我们在.vimrc文件中使用以下配置:

set expandtab
set tabstop=2
syntax on
colo peachpuff
set ignorecase
set smartcase
set number

按顺序,通过这个配置实现了以下几件事:

  • set expandtab:将制表符转换为空格。

  • set tabstop=2:每个制表符转换为两个空格。

  • syntax on:打开语法高亮(使用不同的颜色)。

  • colorscheme peachpuff:使用 peachpuff 颜色方案。

  • set ignorecase:在搜索时忽略大小写。

  • set smartcase:在搜索时不忽略一个或多个大写字母的大小写。

  • set number:显示行号。

Vim 速查表

为了让您熟悉一些 Vim 的常用命令,我们提供了一个速查表。通过vimtutor学习后,有了这个速查表,几乎可以保证您能够正确地使用 Vim!

按键直接输入。请注意,按键区分大小写,因此aA不同。您可以按住Shift键输入大写字母,或使用大写锁定键。然而,最实用的方法是使用Shift

按键效果
Esc退出插入模式,返回命令模式。
i在光标当前位置之前进入插入模式。
a在光标当前位置之后进入插入模式。
I在当前行的开头进入插入模式。
A在当前行的末尾进入插入模式。
o在当前行下方插入新行进入插入模式。
O在当前行上方插入新行进入插入模式。
dd删除当前行。
u撤消上一个插入模式中所做的更改。
Ctrl + r重做撤消。
yy'复制’当前行。
p在当前行下方粘贴最后复制的行。
P在当前行上方粘贴最后复制的行。
H导航到文件开头。
M导航到文件中间。
G导航到文件末尾。
dH删除直到文件开头的所有行(包括当前行)。
dG删除直到文件末尾的所有行(包括当前行)。

nano

GNU nano,通常简称为 nano,是另一个默认存在于大多数 Linux 安装中的命令行编辑器。正如名称所示,它是 GNU 项目的一部分,与构成 Linux 发行版的许多其他部分并无不同(请记住,Bash 也是 GNU 项目软件)。Nano 于 1999 年首次发布,旨在取代 Pico 文本编辑器,Pico 是为 Unix 系统创建的简单文本编辑器。

与 Vim 相比,Nano 远不止是一个所见即所得(WYSIWYG)工具。类似于记事本和 Word,nano 不使用不同的模式;它总是准备好开始输入您的文档或脚本。

在您的虚拟机上,打开一个 nano 编辑器屏幕:

$ nano

应该出现类似以下的屏幕:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随意开始输入一些内容。它应该看起来像以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如您所看到的,屏幕底部保留用于显示 nano 所称的控制键。虽然一开始可能不太明显,但^Ctrl的简写。如果您想退出,您可以按住Ctrl并按X

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您将被提示是否要保存或不保存文件并退出。在这种情况下,我们按Y表示是。如果我们使用文件名启动 nano,保存和退出将立即完成,但因为我们没有使用文件名启动 nano,另一个选择将被呈现给我们:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入文件名并按Enter。您将回到之前的终端屏幕,在您启动 nano 的目录中。如果一切顺利,您可以使用以下命令查看文件:

$ ls -l

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

尽管 nano 具有更多高级功能,但对于基本用法,我们已经讨论了最重要的功能。虽然最初使用比 Vim 更容易,但它也没有那么强大。简而言之,nano 很简单,Vim 很强大。

如果您没有任何经验和/或偏好,我们建议您花点时间学习 Vim 并坚持使用它。在花费更多时间学习 Linux 和 Bash 脚本之后,Vim 的高级功能变得不可或缺。但是,如果您无法习惯 Vim,不要害羞地使用 nano:这是一个很好的编辑器,可以在不太麻烦的情况下完成大部分工作!

在编写 shell 脚本时,将图形编辑器与命令行编辑器相结合

为了让您了解我们喜欢如何将 GUI 工具与命令行编辑器结合使用,我们给出了以下示例工作流程。不要担心现在不理解所有步骤;在本书结束时,您应该回到这个示例并准确理解我们在谈论什么。

在编写 shell 脚本时,通常会经历几个阶段:

  1. 收集 shell 脚本的要求。

  2. 设计 shell 脚本。

  3. 编写 shell 脚本。

  4. 测试和调整 shell 脚本。

  5. (可选)将工作的 shell 脚本提交到您的版本控制系统。

阶段 1 和 2 通常在不编写实际代码的情况下完成。您会思考脚本的目的,它如何实现,以及创建脚本会带来什么好处。这些步骤通常涉及研究和寻找最佳实践。当您觉得对为什么、什么以及如何编写 shell 脚本有了很好的想法时,就可以进入第 3 阶段:编写脚本。在这一点上,您会打开您最喜欢的基于 GUI 的编辑器并开始输入。由于 GUI 编辑器具有自动完成、语法高亮和其他内置的生产力功能,您可以高效地编写大部分 shell 脚本代码。在您觉得脚本准备好进行测试之后,您需要离开 GUI:脚本必须在其设计的系统上进行测试。

第 4 阶段开始。您可以使用 Vim 或 nano 将脚本复制并粘贴到服务器上。一旦脚本在服务器上,您就可以运行它。大多数情况下,它实际上不会做您期望它做的一切。小错误很容易犯错,也很容易修复,但是如果要回到 GUI 编辑器更改、保存、传输到服务器并再次运行,这将是一个小麻烦!幸运的是,我们可以使用 Vim 或 nano 在服务器上进行微小更改以修复脚本,并再次尝试。一个丢失的;"将使 shell 脚本无法使用,但可以快速修复(尽管 GUI 编辑器通常会突出显示这样的错误,因此这些错误不太可能出现在服务器上,即使是第一个版本)。

最后,在经过多次迭代后,您的脚本将按预期工作。现在,您必须确保完整且正确的脚本已上传到您的版本控制系统。建议将脚本从 GUI 传输到服务器最后一次,以查看您是否已将服务器上所做的所有更改应用到您的 GUI 会话中。完成后,提交它,您就完成了!

总结

在本章中,我们讨论了四种文本编辑工具,分为两种类型:基于 GUI 的编辑器(Atom 和 Notepad++)和命令行编辑器(Vim 和 GNU nano),然后展示了如何将这些工具结合使用。

Atom 是一个功能强大的文本编辑器,可以按照您的要求进行配置。默认情况下,它支持许多不同的编程语言,包括 shell。它还具有 Git 和 GitHub 集成。我们还简要讨论了 Notepad++。虽然不如 Atom 强大,但它也适用于我们的目的,因为它基本上是一个增强版的记事本,具有所有 shell 脚本的重要功能。

Vim 和 nano 是两种最流行的 Linux 命令行文本编辑器。我们已经了解到,虽然 Vim 非常强大,但比 nano 更难学习。然而,学会如何正确使用 Vim 将加快您在 Linux 系统上的许多操作,并且是一项非常有价值的技能。要了解 Vim 的实际操作,请通过 vimtutor 进行实践性的介绍。Nano 更容易使用,因为它更接近所见即所得的编辑风格,这种风格也在 Microsoft Word 和记事本中找到。

我们以一个 shell 脚本编写的示例结束了本章。我们简要概述了如何在命令行编辑器中使用基于 GUI 的编辑器。

本章介绍了以下命令:vimnanols

问题

  1. 为什么语法高亮是文本编辑器的重要特性?

  2. 我们如何扩展 Atom 已提供的功能?

  3. 编写 shell 脚本时,自动补全有什么好处?

  4. Vim 和 GNU nano 之间的区别如何描述?

  5. Vim 中最有趣的两种模式是哪两种?

  6. 什么是.vimrc文件?

  7. 当我们称 nano 为所见即所得的编辑器时,我们是什么意思?

  8. 为什么我们希望将图形界面编辑器与命令行编辑器结合使用?

进一步阅读

如果您想更深入地了解本章的主题,以下资源可能会很有趣:

第四章:Linux 文件系统

在本章中,我们将花一些时间探索 Linux 文件系统。我们将解释文件系统是什么,以及什么使 Linux 文件系统独特。我们将描述 Linux 文件系统的结构以及在 Linux 下(几乎)一切都是文件。我们将以交互方式进行,让您首次近距离了解一些常见的 Linux 命令,这些命令将在后面的脚本中使用。

本章将介绍以下命令:pwdcddfechotypecatless

本章将涵盖以下主题:

  • Linux 文件系统解释

  • Linux 文件系统的结构

  • 一切都是文件

技术要求

我们将使用在第二章中创建的虚拟机来探索 Linux 文件系统,设置本地环境

如果在连接到虚拟机时遇到问题,请确保 VirtualBox 正在运行,并且虚拟机已启动。虽然有许多可能导致问题的原因,但确保虚拟机监控程序和虚拟机正在运行应始终是故障排除的第一步。

Linux 文件系统解释

本章将介绍 Linux 文件系统的基础知识。由于文件系统很复杂,我们不会深入探讨技术的细节;相反,我们将提供足够相关的信息,以便进行 shell 脚本编写。

什么是文件系统?

文件系统本质上是数据在物理介质上存储和检索的方式(可以是硬盘、固态硬盘,甚至是 RAM)。它是一个软件实现,管理位的写入和再次找到的位置和方式,并可能包括各种增强可靠性、性能和功能的高级功能。

文件系统的概念是抽象的:有许多文件系统实现,令人困惑的是它们经常被称为文件系统。我们发现最容易理解的方法是按照家族对文件系统进行排序,就像 Linux 发行版一样:有 Linux 文件系统、Windows 文件系统、macOS 文件系统以及许多其他文件系统。Windows 文件系统家族从最早的FAT文件系统一直延伸到最新的ReFS,目前最广泛使用的是NTFS

在撰写本文时,Linux 家族中最重要的文件系统实现如下:

  • ext4

  • XFS

  • Btrfs

目前最常用的 Linux 文件系统实现是 ext4。它是 Linux 文件系统扩展文件系统ext)系列的第四个版本。它于 2008 年发布,被认为非常稳定,但并非最先进;可靠性是最重要的考虑因素。

XFS 最著名的用途是在 Red Hat 发行版(Red Hat Enterprise Linux、CentOS 和 Fedora)中。它包含一些比 ext4 更先进的功能,如并行 I/O、更大的文件大小支持和更好地处理大文件。

最后是 Btrfs。这个文件系统实现最初是在 Oracle 设计的,截至 2014 年被认为是稳定的。Btrfs 具有许多先进的功能,这使得它可能比 ext4 和 XFS 更可取;甚至 ext4 的主要开发人员表示,ext4 最终应该被 Btrfs 取代。Btrfs 最有趣的特性是它使用写时复制COW)原则:复制的文件实际上并没有完全写入物理介质,而只是创建了指向相同数据的新指针。只有在复制或原始文件被修改时才会写入新数据。

正如你可能已经猜到的那样,文件系统实现只不过是软件而已。对于 Linux,前面描述的三种实现都存在于所有更新的 Linux 内核中。这意味着只要在操作系统中安装了正确的驱动程序,这些都可以使用。更好的是,所有这些甚至可以同时使用!我们将在本章的后面进一步讨论这一点。

另一个有趣的事情是,虽然 ext4 是 Linux 原生的文件系统,但在驱动程序的帮助下,它也可以在 Windows 下使用。你不会将 ext4 用作 Windows 下的主要驱动器文件系统,但你可以在 Windows 下挂载一个 Linux 格式的 ext4 文件系统并与其中的内容交互。反过来,在 Linux 下挂载 Windows 文件系统也是大多数实现所支持的。虽然我们在这里使用 ext4 作为例子,但 XFS 和 Btrfs 也是一样的。

Linux 文件系统的独特之处是什么?

现在应该清楚的是,实际上并不存在the Linux 文件系统。然而,这些文件系统共享某些特征,使它们成为可行的 Linux 文件系统。

Linux 文件系统遵循文件系统层次结构标准FHS)。这个 FHS 由 Linux 基金会维护,目前已经更新到 3.0 版。与 Linux 生态系统中的许多其他事物一样,它是基于 Unix 的前身:Unix 文件系统标准UFS)。它指定了目录结构及其内容。我们将在本章的下一部分一起探讨这个结构。

由于 Linux 最常用于服务器,Linux 文件系统实现(通常)在文件完整性和灾难恢复方面具有非常先进的功能。这种灾难的一个例子是,当系统在写入一个业务关键文件时遇到停电。如果写入操作存储在内存中并在中途中止,文件将处于不一致的状态。当系统再次启动时,操作系统不再在内存中有写入操作(因为内存在每次重启时都会被清除),只有部分文件会被写入。显然,这是不希望发生的行为,可能会导致问题。由于 COW 的特性,Btrfs 不会出现这个问题。然而,ext4 和 XFS 不是 COW 文件系统。它们以另一种方式处理这个问题:通过日志记录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,文件被写入磁盘分为三个步骤:

  1. 文件系统请求从日志中写入磁盘

  2. 日志写入磁盘

  3. 文件写入后,更新日志

如果服务器在步骤 2 和 3 之间崩溃,那么在上电后将再次进行写入,因为日志仍然包含该条目。日志只包含有关操作的一些元数据,而不是整个文件。由于日志包含对磁盘上实际位置(驱动器扇区)的引用,它将覆盖先前写入的内容,即文件的一部分。如果这次成功完成,日志条目将被删除,文件/磁盘的状态得到保证。如果服务器在步骤 1 和 2 之间失败,那么实际的写入磁盘指令从未被给出,给出指令的软件应该考虑到这种可能性。

免责声明:关于日志记录的部分有点过于简化,但是文件系统很复杂,我们想要专注于与 shell 脚本相关的内容。如果你对文件系统在更低层次上的工作原理感兴趣,一定要找一本其他的书来看,因为这确实是一个非常有趣的主题!

Linux 文件系统的结构

虽然还有许多更高级的文件系统功能非常有趣,但我们想要专注于使 Linux 文件系统与众不同的东西:文件系统结构。如果您习惯于 Windows,这可能是两个操作系统之间最令人困惑的区别。如果您来自 macOS,差异仍然明显,但要小得多:这是 macOS 作为 Unix 操作系统的结果,它与类 Unix 的 Linux 结构有明显的相似之处。

从这一点开始,我们将交互式地探索 Linux 文件系统。我们建议您跟随后面的代码示例,因为这会显著增加信息的保留。此外,如果您选择不使用 Ubuntu 18.04 LTS 进行本书学习,您的系统可能与我们使用的系统有所不同。无论如何,启动虚拟机并与我们一起开始探索吧!

树结构

让我们首先通过 SSH 登录到我们的虚拟机:

ssh -p 2222 reader@localhost

在提示处输入密码,然后您应该到达默认的 Ubuntu 18.04 登录横幅,应该看起来类似于以下内容:

reader@localhost's password: 
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)
<SNIPPED>
  System information as of Sat Jul 28 14:15:19 UTC 2018

  System load:  0.09              Processes:             87
  Usage of /:   45.6% of 9.78GB   Users logged in:       0
  Memory usage: 15%               IP address for enp0s3: 10.0.2.15
  Swap usage:   0%
<SNIPPED>
Last login: Sat Jul 28 14:13:42 2018 from 10.0.2.2
reader@ubuntu:~$

登录时(无论是通过 SSH 还是终端控制台),您将最终进入用户的home目录。您可以始终使用pwd命令来确定您的确切位置。pwd代表print working directory:

reader@ubuntu:~$ pwd
/home/reader

所以,我们最终进入了/home/reader/目录。这是大多数 Linux 发行版的默认设置:/home/$USERNAME/。由于我们创建了主用户reader,这就是我们期望的位置。对于那些来自 Windows 的人来说,这可能看起来非常陌生:驱动器名称(C:D:等)在哪里?为什么我们使用(正斜杠)而不是反斜杠?

Linux 以及 Unix 和其他类 Unix 系统使用树结构。它被称为树,因为它从单个起始点root(位于/)开始。目录从那里嵌套(就像树的分支一样),与其他操作系统并没有太大不同。最后,树结构以被视为树的叶子的文件结束。这可能听起来仍然非常复杂,但实际上相对简单。让我们继续探索,以确保我们完全理解这个结构!在 Linux 下,我们使用cd命令来更改目录。它通过输入cd,后跟我们想要去的文件系统位置作为命令的参数来工作。导航到文件系统根目录:

reader@ubuntu:~$ cd /    
reader@ubuntu:/$

如您所见,似乎没有发生太多事情。但是,您的终端提示中有一个微小的区别:~字符已被/替换。在 Ubuntu 下,默认配置显示文件系统的位置,无需使用pwd命令。提示构建如下:<username>@<hostname>**:**<location>**$**。那么为什么是~呢?简单:波浪符号是用户主目录的简写!如果简写不存在,登录时的提示将是reader@ubuntu:/home/reader$

由于我们已经导航到了文件系统的根目录,让我们看看我们可以在那里找到什么。要列出当前目录的内容,我们使用ls命令:

reader@ubuntu:/$ ls
bin dev home initrd.img.old lib64 media opt root sbin srv sys usr vmlinuz
boot etc initrd.img lib lost+found mnt proc run snap swap.img tmp var vmlinuz.old

如果您使用 SSH,您很可能会看到一些颜色来区分文件和目录(甚至可以看到目录权限的颜色,如果您以不同方式看到tmp;这将在下一章中讨论)。然而,即使有颜色的帮助,这仍然感觉不清晰。让我们通过在ls命令上使用一个选项来清理一下:

reader@ubuntu:/$ ls -l
total 2017372
drwxr-xr-x  2 root root       4096 Jul 28 10:31 bin
drwxr-xr-x  3 root root       4096 Jul 28 10:32 boot
drwxr-xr-x 19 root root       3900 Jul 28 10:31 dev
drwxr-xr-x 90 root root       4096 Jul 28 10:32 etc
drwxr-xr-x  3 root root       4096 Jun 30 18:20 home
lrwxrwxrwx  1 root root         33 Jul 27 11:39 initrd.img -> boot/initrd.img-4.15.0-29-generic
lrwxrwxrwx  1 root root         33 Jul 27 11:39 initrd.img.old -> boot/initrd.img-4.15.0-23-generic
drwxr-xr-x 22 root root       4096 Apr 26 19:09 lib
drwxr-xr-x  2 root root       4096 Apr 26 19:07 lib64
drwx------  2 root root      16384 Jun 30 17:58 lost+found
drwxr-xr-x  2 root root       4096 Apr 26 19:07 media
drwxr-xr-x  2 root root       4096 Apr 26 19:07 mnt
drwxr-xr-x  2 root root       4096 Apr 26 19:07 opt
dr-xr-xr-x 97 root root          0 Jul 28 10:30 proc
drwx------  3 root root       4096 Jul  1 09:40 root
drwxr-xr-x 26 root root        920 Jul 28 14:15 run
drwxr-xr-x  2 root root      12288 Jul 28 10:31 sbin
drwxr-xr-x  4 root root       4096 Jun 30 18:20 snap
drwxr-xr-x  2 root root       4096 Apr 26 19:07 srv
-rw-------  1 root root 2065694720 Jun 30 18:00 swap.img
dr-xr-xr-x 13 root root          0 Jul 28 10:30 sys
drwxrwxrwt  9 root root       4096 Jul 28 14:32 tmp
drwxr-xr-x 10 root root       4096 Apr 26 19:07 usr
drwxr-xr-x 13 root root       4096 Apr 26 19:10 var
lrwxrwxrwx  1 root root         30 Jul 27 11:39 vmlinuz -> boot/vmlinuz-4.15.0-29-generic
lrwxrwxrwx  1 root root         30 Jul 27 11:39 vmlinuz.old -> boot/vmlinuz-4.15.0-23-generic

ls命令的选项-l(连字符小写 l,如long)提供了长列表格式。除其他外,这会打印权限、文件/目录的所有者、文件类型和大小。请记住,权限和所有权将在下一章中讨论,所以现在不需要担心这些。从中最重要的是,每个文件/目录都会单独打印在自己的一行上,该行的第一个字符表示文件类型:d表示目录,-表示普通文件,l表示符号链接(在 Linux 下是快捷方式)。

让我们深入树结构,回到我们的home目录。此时,你有两个选择。你可以使用相对路径(即:相对于当前位置)或全路径相对于当前目录)。让我们都试一下:

reader@ubuntu:/$ cd home
reader@ubuntu:/home$

上述是进入相对目录的示例。我们位于根目录/,然后从那里导航到 home,最终到达/home。我们可以使用全路径从任何地方导航到那里:

reader@ubuntu:/$ cd /home
reader@ubuntu:/home$

你注意到了区别吗?在全路径示例中,cd的参数以斜杠开头,但在相对示例中没有。让我们看看如果你使用这两种类型时会发生什么错误:

reader@ubuntu:/home$ ls
reader
reader@ubuntu:/home$ cd /reader
-bash: cd: /reader: No such file or directory

我们使用ls列出了/home目录的内容。如预期的那样,我们看到(至少)当前用户的主目录reader。然而,当我们尝试使用cd /reader导航到它时,我们得到了臭名昭著的错误No such file or directory。尽管这并不令人意外:实际上并没有一个目录/reader。我们要找的目录是/home/reader,可以使用命令cd /home/reader全路径到达:

reader@ubuntu:/home$ cd home
-bash: cd: home: No such file or directory
reader@ubuntu:/home$

如果我们尝试使用不正确的相对路径,也会出现相同的错误。在上面的示例中,我们当前位于/home目录,然后使用cd home命令。实际上,这会把我们放在/home/home,就像我们在/home目录中使用ls时看到的那样,这个目录并不存在!

在 Linux 中安全地导航的最佳方式是全路径:只要你有正确的目录,它总是有效的,无论你当前位于文件系统的何处。然而,特别是当你深入文件系统时,你需要输入更多。我们始终建议初学者从全路径导航开始,一旦他们熟悉了cdlspwd命令,就可以切换到相对路径。

尽管全路径更安全,但比起相对路径效率要低得多。你看到了我们如何可以深入树结构的分支,但如果你需要向下一级,回到根呢?幸运的是,这并不强迫我们使用全路径。我们可以使用..符号,这意味着向上一级,朝着/

reader@ubuntu:/home$ cd ..
reader@ubuntu:/$

这里需要注意一下术语。虽然我们将文件系统构想为一棵树,但在谈到根目录时,我们将其视为文件系统中的最高点。因此,从/移动到/home时,我们是在向下移动。如果我们使用cd ..命令移回/,我们是在向上移动。虽然我们认为这实际上与树的图像不太匹配(根实际上是最低点),但请记住这个约定!

使用cd ..向上移动会使我们回到文件系统的根目录。此时,你可能会想*如果我在文件系统的最高级别再次这样做,会发生什么?*试一试:

reader@ubuntu:/$ cd ..
reader@ubuntu:/$

对我们来说幸运的是,我们没有收到错误,也没有崩溃的机器;相反,我们只是最终到达(或者,取决于你的看法,停留在)文件系统的根目录。

Linux 新用户经常困惑的一个术语是root。它可以代表以下三种情况之一:

  1. 文件系统中的最低点,在/

  2. 默认的超级用户,名为root

  3. 默认超级用户的主目录,在/root/

通常,读者需要根据上下文来确定指的是这三者中的哪一个。当谈论文件系统的上下文时,可能是:

  1. 如果它似乎是在提到用户,你可以期望它指的是 root 用户

  2. 只有在谈论根用户的主目录或/root/时,你应该考虑

  3. 最常见的是,你会发现 root 指的是 1 或 2!

顶级目录概述

现在我们已经掌握了使用cd移动和使用ls列出目录内容的基础知识,让我们开始探索文件系统的其他部分。让我们从根文件系统下直接的每个目录的概述开始,如 FHS 所指定的:

位置目的
/bin/包含普通用户使用的基本工具(=工具)
/boot/包含启动过程中使用的文件:kernelinitramfsbootloader
/dev/包含用于访问设备的特殊文件
/etc/软件配置文件的默认位置
/home/包含普通用户的主目录
/lib/包含系统
/lib64/包含64位系统
/media/可移动设备,如 USB 和 DVD,可以在这里找到
/mnt/默认为空,可以用来挂载其他文件系统
/opt/可以安装可选软件的目录
/proc/存储有关进程的信息的目录
/root/root用户的主目录
/run/包含关于运行时数据的可变数据,每次启动都不同
/sbin/包含管理员用户使用的基本系统工具(=工具)
/srv/放置服务器服务的数据的目录
/sys/包含有关系统的信息,如驱动程序和内核功能
/tmp/用于临时文件的目录,通常在重新启动时清除(因为它存储在 RAM 中,而不是在磁盘上)
/usr/包含只读用户数据的非必要文件和二进制文件
/var/包含变量文件,如日志

虽然每个顶级目录都有重要的功能,但有一些我们将更仔细地检查,因为我们肯定会在我们的 shell 脚本中遇到它们。这些是/bin//sbin//usr//etc//opt//tmp//var/

多个分区呢?

但首先,我们想简要解释一些可能让你感到困惑的事情,特别是如果你来自 Windows 背景,习惯于C:\D:\E:\等形式的多个磁盘/分区。有了前面的目录结构和最高点在/的信息,Linux 如何处理多个磁盘/分区?

答案实际上相当简单。Linux 在树结构中的某个位置挂载文件系统。第一个挂载点位于我们已经介绍过的主分区上:它被挂载在/上!让我们看看在我们检查新的df工具时它是什么样子:

reader@ubuntu:~$ df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  464M     0  464M   0% /dev
tmpfs          tmpfs      99M  920K   98M   1% /run
/dev/sda2      ext4      9.8G  4.4G  5.0G  47% /
tmpfs          tmpfs     493M     0  493M   0% /dev/shm
tmpfs          tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs          tmpfs     493M     0  493M   0% /sys/fs/cgroup
/dev/loop0     squashfs   87M   87M     0 100% /snap/core/4917
/dev/loop1     squashfs   87M   87M     0 100% /snap/core/4486
/dev/loop2     squashfs   87M   87M     0 100% /snap/core/4830
tmpfs          tmpfs      99M     0   99M   0% /run/user/1000

虽然这是df报告文件系统磁盘空间使用情况)的大量输出,但最有趣的是之前突出显示的:类型为ext4(记得吗?)的分区/dev/sda2被挂载在/上。你将在本章后面看到一切都是文件的预览:/dev/sda2被处理为文件,但实际上是对磁盘上的分区的引用(在这种情况下是虚拟磁盘)。我们的 Arch Linux 主机的另一个示例提供了更多信息(如果你没有 Linux 主机,不用担心,我们以后会解释):

[root@caladan ~]# df -hT
Filesystem                          Type      Size  Used Avail Use% Mounted on
dev                                 devtmpfs  7.8G     0  7.8G   0% /dev
run                                 tmpfs     7.8G  1.5M  7.8G   1% /run
/dev/mapper/vg_caladan-lv_arch_root ext4       50G   29G   19G  60% /
tmpfs                               tmpfs     7.8G  287M  7.5G   4% /dev/shm
tmpfs                               tmpfs     7.8G     0  7.8G   0% /sys/fs/cgroup
tmpfs                               tmpfs     7.8G  212K  7.8G   1% /tmp
/dev/sda1                           vfat      550M   97M  453M  18% /boot
tmpfs                               tmpfs     1.6G   16K  1.6G   1% /run/user/120
tmpfs                               tmpfs     1.6G   14M  1.6G   1% /run/user/1000
/dev/sdc1   vfat       15G  552M   14G   4% /run/media/tammert/ARCH_201803
/dev/mapper/vg_caladan-lv_data      btrfs      10G   17M  9.8G   1% /data

你可以看到我有一个ext4文件系统挂载在我的根目录。然而,我还有一个额外的btrfs分区挂载在/data/上,以及一个vfat引导分区(在裸机安装时需要,但在虚拟机上不需要)挂载在/boot/上。最后,还有一个连接着 Arch Linux 安装程序的vfat USB 设备,它被自动挂载在/run/media/下。因此,Linux 不仅可以优雅地处理多个分区或磁盘,甚至不同类型的文件系统也可以在同一树结构下并存!

/bin/、/sbin/和/usr/

让我们回到顶级目录。我们将首先讨论/bin//sbin//usr/,因为它们非常相似。正如概述中所述,所有这些目录都包含了系统的普通用户和管理员使用的二进制文件。让我们看看这些二进制文件在哪里,以及我们的用户会话如何在进程中找到它们。我们将使用echo命令来管理这个过程。它的简短描述只是显示一行文本。让我们看看它是如何工作的:

reader@ubuntu:~$ echo

reader@ubuntu:~$ echo 'Hello'
Hello
reader@ubuntu:~$

如果我们使用echo而没有传递参数,将显示一行空白文本(基本上就像简短描述所承诺的那样!)。如果我们传递文本,将其用单引号括起来,那么该文本将被打印出来。在这种情况下,包含字母、数字或其他字符的文本被称为字符串。因此,我们传递给echo的任何字符串都将在我们的终端中打印出来。虽然这可能看起来不那么有趣,但当你开始考虑变量时,它就变得有趣了。变量是一个值会随时间变化的字符串,正如其名称所暗示的那样。让我们使用echo来打印变量BASH_VERSION的当前值:

reader@ubuntu:~$ echo BASH_VERSION
BASH_VERSION
reader@ubuntu:~$ echo $BASH_VERSION
4.4.19(1)-release
reader@ubuntu:~$

你应该注意到我们没有使用echo BASH_VERSION命令,因为那样会打印出文字BASH_VERSION,而是我们用$符号开始了变量名。在 Bash 中,$表示我们正在使用一个变量(我们将在第八章中进一步解释变量变量插值)。为什么我们告诉你这个?因为我们可以从我们的终端使用的二进制文件是通过使用一个变量找到的,具体来说是PATH变量:

reader@ubuntu:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin <SNIPPED>
reader@ubuntu:~$

如你所见,二进制文件需要在/usr/local/sbin//usr/local/bin//usr/sbin//usr/bin//sbin//bin/目录中才能使用(使用当前值的PATH,我们可以更改,但这暂时超出了范围)。这意味着我们到目前为止使用的二进制文件(cdlspwdecho)需要在这些目录中的一个中才能使用,对吗?不幸的是,这就是事情变得稍微复杂的地方。在 Linux 上,我们基本上使用两种类型的二进制文件:在磁盘上找到的(在PATH变量指定的目录中),或者它们可以内置到我们正在使用的 shell 中,然后称为shell 内置。一个很好的例子实际上是我们刚学到的echo命令,它两者都是!我们可以使用type来查看我们正在处理的命令的类型:

reader@ubuntu:~$ type -a echo
echo is a shell builtin
echo is /bin/echo
reader@ubuntu:~$ type -a cd
cd is a shell builtin
reader@ubuntu:~$

如果一个命令既是内置命令又是PATH中的二进制文件,则使用二进制文件。如果它只存在于内置命令中,比如cd,则使用内置命令。一般来说,你使用的大多数命令都是磁盘上的二进制文件,在你的PATH中找到。此外,这些命令中大多数都存在于/usr/bin/目录中(在我们的 Ubuntu 虚拟机上,超过一半的二进制文件都存在于/usr/bin/中!)。

因此,二进制目录的总体目标应该是清楚的:为我们提供执行工作所需的工具。问题仍然存在,为什么有(至少)六个不同的目录,它们为什么分为binsbin?对于问题的最后一部分的答案很容易:bin包含用户使用的常规实用程序,而sbin包含系统管理员使用的实用程序。在最后一类中,可以找到与磁盘维护、网络配置和防火墙等相关的工具。bin目录包含用于文件系统操作(例如创建和删除文件/目录)、存档和列出系统信息等的实用程序。

顶级目录/(s)bin//usr/(s)bin/之间的区别有点模糊。一般来说,基本工具可以在/(s)bin中找到,而系统特定的二进制文件则放在/usr/(s)bin目录中。因此,如果你安装了一个用于运行 Web 服务器的软件包,它将被放置在/usr/bin//usr/sbin/中,因为它是系统特定的。最后,根据我们的经验,/usr/local/(s)bin/目录最常用于手动安装的二进制文件,而不是从软件包管理器中安装。但你可以将它们放在PATH的任一目录中工作;这主要是一种惯例问题。

最后,/usr/包含的不仅仅是二进制文件。其中包括一些库(与/lib//lib64/顶级目录具有相同关系)和一些杂项文件。如果你感兴趣,我们绝对建议使用cdls来查看/usr/目录的其余部分,但最重要的是要记住二进制文件可以在这里找到。

/etc/

接下来是 Linux 文件系统中另一个有趣的顶级目录:/etc/目录。发音为et-c,用于存储系统软件和用户软件的配置文件。让我们看看它包含了什么:

reader@ubuntu:/etc# ls
acpi console-setup ethertypes inputrc logrotate.conf network python3 shadow ucf.conf
...<SNIPPED>:

我们剪掉了前面的输出,只保留了我们系统的顶行。如果你跟着这个例子(你应该这样做!)你会看到超过 150 个文件和目录。我们将使用cat命令打印一个特别有趣的文件:

reader@ubuntu:/etc$ cat fstab 
UUID=376cd784-7c8f-11e8-a415-080027a7d0ea / ext4 defaults 0 0
/swap.img    none    swap    sw    0    0
reader@ubuntu:/etc$

我们在这里看到的是文件系统表,或者fstab文件。它包含了 Linux 在每次启动时挂载文件系统的指令。正如我们在这里看到的,我们通过通用唯一标识符UUID)引用一个分区,并将其挂载在/上,作为根文件系统。它的类型是ext4,使用defaults选项挂载。最后的两个零处理系统启动时的备份和检查。在第二行,我们看到我们正在使用一个文件作为交换空间。交换空间用于在系统没有足够的内存可用时使用,可以通过将其写入磁盘来补偿(但会导致严重的性能损失,因为磁盘比 RAM 慢得多)。

/etc/目录中的另一个有趣的配置文件是passwd文件。虽然听起来像密码,但别担心,密码并没有存储在那里。让我们使用less命令查看内容:

reader@ubuntu:/etc$ less passwd

这将以只读模式在所谓的分页器中打开文件。less使用 Vim 命令,所以你可以通过在键盘上按Q来退出。如果文件比你的屏幕大,你可以使用 Vim 的按键:箭头键或使用JK来上下导航。在less中,屏幕应该看起来像下面这样:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...<SNIPPED>:
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
reader:x:1000:1004:Learn Linux Shell Scripting:/home/reader:/bin/bash

这个文件包含了系统上所有用户的信息。按顺序,由:分隔的字段表示以下内容:

用户名密码用户 ID(UID)组 ID(GID)用户真实姓名主目录用户默认 shell

尽管这里有一个密码字段,但这是出于传统原因;(哈希!)密码已经移动到/etc/shadow文件中,只有 root 超级用户才能读取。我们将在下一章中讨论 UID 和 GID;其他字段现在应该是清楚的。

这些只是在/etc/目录中找到的配置文件的两个例子(尽管很重要!)。

/opt//tmp//var/

在 Ubuntu 的新安装中,/opt/目录是空的。虽然这又是一个惯例问题,但根据我们的经验,这个目录最常用于安装来自发行版软件包管理器之外的软件。但是,一些使用软件包管理器安装的应用程序确实使用/opt/来存储它们的文件;这完全取决于软件包维护者的偏好。在我们的情况下,我们将使用这个目录来保存我们将要创建的 shell 脚本,因为这些绝对属于可选软件。

/tmp/目录用于临时文件(谁会猜到呢?)。在一些 Linux 发行版中,/tmp/不是根分区的一部分,而是作为单独的tmpfs文件系统挂载的。这种类型的文件系统是在 RAM 中分配的,这意味着/tmp/的内容在重新启动后不会存活。由于我们处理临时文件,这有时不仅是一个很好的功能,而且是特定用途的先决条件。例如,对于桌面 Linux 用户,可以用来保存只在活动会话期间需要的笔记,而无需担心在完成后清理它。

最后,/var/目录稍微复杂一些。让我们来看看:

reader@ubuntu:~$ cd /var/
reader@ubuntu:/var$ ls -l
total 48
drwxr-xr-x  2 root root   4096 Jul 29 10:14 backups
drwxr-xr-x 10 root root   4096 Jul 29 12:31 cache
drwxrwxrwt  2 root root   4096 Jul 28 10:30 crash
drwxr-xr-x 35 root root   4096 Jul 29 12:30 lib
drwxrwsr-x  2 root staff  4096 Apr 24 08:34 local
lrwxrwxrwx  1 root root      9 Apr 26 19:07 lock -> /run/lock
drwxrwxr-x 10 root syslog 4096 Jul 29 12:30 log
drwxrwsr-x  2 root mail   4096 Apr 26 19:07 mail
drwxr-xr-x  2 root root   4096 Apr 26 19:07 opt
lrwxrwxrwx  1 root root      4 Apr 26 19:07 run -> /run
drwxr-xr-x  3 root root   4096 Jun 30 18:20 snap
drwxr-xr-x  4 root root   4096 Apr 26 19:08 spool
drwxrwxrwt  4 root root   4096 Jul 29 15:04 tmp
drwxr-xr-x  3 root root   4096 Jul 29 12:30 www
reader@ubuntu:/var$

正如您所看到的,/var/包含许多子目录和一些符号链接(由->字符表示)。在这种情况下,/var/run/实际上是指向顶级目录/run的快捷方式。/var/中最有趣的子目录(目前)是log/mail/

/var/log/通常用于保存大多数系统和用户进程的日志文件。根据我们的经验,在 Linux 系统上安装的大多数第三方软件都会遵守这个惯例,并将日志文件输出到/var/log/目录,或者在/var/log/中创建一个子目录。让我们看一个使用less的完全限定路径的日志文件的例子:

reader@ubuntu:~$ less /var/log/kern.log

less分页器中,您将遇到类似以下内容的东西:

Jun 30 18:20:32 ubuntu kernel: [    0.000000] Linux version 4.15.0-23-generic (buildd@lgw01-amd64-055) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #25-Ubuntu SMP Wed May 23 18:02:16 UTC 2018 (Ubuntu 4.15.0-23.25-generic 4.15.18)
Jun 30 18:20:32 ubuntu kernel: [    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-4.15.0-23-generic root=UUID=376cd784-7c8f-11e8-a415-080027a7d0ea ro maybe-ubiquity
Jun 30 18:20:32 ubuntu kernel: [    0.000000] KERNEL supported cpus:
Jun 30 18:20:32 ubuntu kernel: [    0.000000]   Intel GenuineIntel
Jun 30 18:20:32 ubuntu kernel: [    0.000000]   AMD AuthenticAMD
...<SNIPPED>:

这个日志文件包含有关内核引导过程的信息。您可以看到对磁盘上实际内核的引用,/boot/vmlinuz-4.15.0-23-generic,以及挂载在根目录的文件系统的 UUID,UUID=376cd784-7c8f-11e8-a415-080027a7d0ea。如果您的系统在启动时出现问题或某些功能似乎无法正常工作,您将检查此文件!

在 Unix 和 Linux 的早期,发送邮件不仅仅是在互联网上使用(当时互联网还处于萌芽阶段),还用于在服务器或同一服务器上的用户之间中继消息。在您的新 Ubuntu 虚拟机上,/var/mail/目录及其符号链接/var/spool/mail/将是空的。但是,一旦我们开始讨论调度和日志记录,我们将看到该目录将用于存储消息。

这就是关于默认 Linux 文件系统中顶级目录的简要描述。我们讨论了我们认为与 shell 脚本相关的最重要的目录。然而,随着时间的推移,您将对所有目录有所了解,并且在 Linux 文件系统中找到任何东西肯定会变得更容易,尽管现在可能听起来很困难。

一切都是文件

在 Linux 下,有一个众所周知的表达:

在 Linux 系统中,一切都是文件;如果某物不是文件,那就是一个进程。

虽然这并不是严格意义上的 100%真实,但至少对于您在 Linux 上遇到的 90%的事情来说是真实的,特别是如果您还不是很高级的话。尽管一般来说,这个规则是成立的,但它还有一些额外的注意事项。尽管 Linux 上的大部分东西都是文件,但有不同的文件类型,确切地说是七种。我们将在接下来的页面上讨论它们。您可能不会使用所有七种;但是,对它们都有基本的了解可以让您更好地理解 Linux,这绝对是一件好事!

不同类型的文件

这七种文件类型如下,用 Linux 用来表示它们的字符:

类型解释
-: 常规文件一个包含文本或字节的常规文件
d: 目录一个目录,可以包含其他目录和常规文件
l: 符号链接用作快捷方式
s: 套接字用于通信的通道
c: 特殊文件主要用于设备处理程序
b: 块设备表示存储硬件的类型,如磁盘分区
p: 命名管道用于进程之间进行通信

在这七种文件类型中,您首先会遇到常规文件(-)和目录(d)。接下来,您可能会更多地与符号链接(l)、块设备(b)和特殊文件(c)进行交互。很少会使用最后两种:套接字(s)和命名管道(p)。

/dev/ 中遇到最常见的文件类型是一个不错的地方。让我们使用 ls 命令来查看它包含了什么:

reader@ubuntu:/dev$ ls -l /dev/
total 0
crw-r--r-- 1 root root     10, 235 Jul 29 15:04 autofs
drwxr-xr-x 2 root root         280 Jul 29 15:04 block
drwxr-xr-x 2 root root          80 Jul 29 15:04 bsg
crw-rw---- 1 root disk     10, 234 Jul 29 15:04 btrfs-control
drwxr-xr-x 3 root root          60 Jul 29 15:04 bus
lrwxrwxrwx 1 root root           3 Jul 29 15:04 cdrom -> sr0
drwxr-xr-x 2 root root        3500 Jul 29 15:04 char
crw------- 1 root root      5,   1 Jul 29 15:04 console
lrwxrwxrwx 1 root root          11 Jul 29 15:04 core -> /proc/kcore
...<SNIPPED>:
brw-rw---- 1 root disk      8,   0 Jul 29 15:04 sda
brw-rw---- 1 root disk      8,   1 Jul 29 15:04 sda1
brw-rw---- 1 root disk      8,   2 Jul 29 15:04 sda2
crw-rw---- 1 root cdrom    21,   0 Jul 29 15:04 sg0
crw-rw---- 1 root disk     21,   1 Jul 29 15:04 sg1
drwxrwxrwt 2 root root          40 Jul 29 15:04 shm
crw------- 1 root root     10, 231 Jul 29 15:04 snapshot
drwxr-xr-x 3 root root         180 Jul 29 15:04 snd
brw-rw---- 1 root cdrom    11,   0 Jul 29 15:04 sr0
lrwxrwxrwx 1 root root          15 Jul 29 15:04 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root          15 Jul 29 15:04 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root          15 Jul 29 15:04 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root tty       5,   0 Jul 29 17:58 tty
crw--w---- 1 root tty       4,   0 Jul 29 15:04 tty0
crw--w---- 1 root tty       4,   1 Jul 29 15:04 tty1
...<SNIPPED>:
reader@ubuntu:/dev$

正如您从输出中看到的那样,/dev/ 包含了大量文件,其中大多数类型如上所述。具有讽刺意味的是,它不包含最常见的文件类型:常规文件。但是,因为我们一直在与常规文件交互,所以您应该对它们有所了解(否则本书的其余部分肯定会给您一个概念)。

因此,让我们看看除了常规文件之外的任何东西。让我们从最熟悉的开始:目录。任何以 d 开头的行都是一个目录,如果您使用 SSH,它很可能也会以不同的颜色表示。不要低估这种视觉辅助的重要性,因为当您在 Linux 机器上导航时,它会为您节省大量时间。记住,您可以使用相对路径或绝对路径(始终从文件系统的根目录开始)来进入目录,其中相对路径使用 cd 命令。

接下来,您将看到以 b 开头的文件。这些文件用于表示 设备,最常见的用途是磁盘设备或分区。在大多数 Linux 发行版中,磁盘通常被称为 /dev/sda/dev/sdb 等。这些磁盘上的分区用数字表示:/dev/sda1/dev/sda2 等。正如您在前面的输出中所看到的,我们的系统只有一个磁盘(只有 /dev/sda)。但是该磁盘有两个分区:/dev/sda1/dev/sda2。再次尝试使用 df -hT 命令,您会注意到 /dev/sda2 被挂载为根文件系统(除非您的虚拟机配置不同,否则可能是 /dev/sda1 或甚至 /dev/sda3)。

在 Linux 上经常使用符号链接。在前面的输出中查找条目 cdrom,您会看到它以 l 开头。术语 cdrom 具有上下文意义:它指的是 CD(或更可能是在新系统中,DVD)驱动器。但是,它链接到处理交互的实际块设备 /dev/sr0,它以 b 开头表示块设备。使用符号链接可以轻松找到您需要的项目(磁盘驱动器),同时仍然保留 Linux 配置,调用设备处理程序 sr0

最后,您应该看到一个名为tty的文件的长列表。这些文件的开头标有c,表示特殊文件。为了简单起见,您应该将tty视为连接到 Linux 服务器的终端。这些是 Linux 用来允许用户与系统进行交互的一种虚拟设备。许多虚拟和物理设备在它们出现在 Linux 文件系统上时使用特殊文件处理程序。

本章向您介绍了许多命令。也许您已经厌倦了一切都要手动输入,也许没有。无论如何,我们有一些好消息:Bash 有一个叫做自动完成的功能。我们不想过早介绍它以避免混淆,但在使用 Linux 系统时,它被广泛使用,如果我们不解释它,我们就会欺骗您。

实际上很简单:如果在命令的第一部分(如cdls)后按下Tab键,如果只有一个选择,它将完成您的命令,或者如果再次按下Tab,它将向您呈现一个选项列表。转到/,输入cd,然后按两次Tab键,看看它是如何工作的。进入/home/目录并在输入cd后按一次Tab键将使其自动完成,只有一个目录,节省时间!

摘要

在本章中,我们介绍了 Linux 文件系统的概述。我们首先简要介绍了一般文件系统,然后解释了 Linux 文件系统的独特之处。讨论了 Ext4、XFS 和 Btrfs 文件系统实现,以及这些文件系统的日志记录功能。接下来,解释了 Linux 遵循的 FHS,然后详细介绍了 Linux 文件系统的更重要部分。这是通过探索构成 Linux 文件系统的树结构的部分来完成的。我们解释了可以在树的某个地方挂载不同的文件系统。最后,我们解释了在 Linux 上(几乎)一切都被处理为文件,并讨论了使用的不同文件类型。

本章介绍了以下命令:pwdcddfechotypecatless。作为提示,解释了 Bash 自动完成功能。

问题

  1. 什么是文件系统?

  2. 哪些 Linux 特定的文件系统最常见?

  3. 真或假:Linux 上可以同时使用多个文件系统实现?

  4. 大多数 Linux 文件系统实现中存在的日志记录功能是什么?

  5. 根文件系统挂载在树的哪个位置?

  6. PATH变量用于什么?

  7. 根据 FHS,配置文件存储在哪个顶级目录中?

  8. 进程日志通常保存在哪里?

  9. Linux 有多少种文件类型?

  10. Bash 自动完成功能是如何工作的?

进一步阅读

如果您想更深入地了解本章的主题,可以参考以下资源:

第五章:理解 Linux 权限方案

在本章中,我们将探讨 Linux 权限方案是如何实现的。文件和目录的读取、写入和执行权限将被讨论,我们将看到它们如何不同地影响文件和目录。我们将看到多个用户如何使用组一起工作,以及一些文件和目录也对其他人可用。

本章将介绍以下命令:idtouchchmodumaskchownchgrpsudouseraddgroupaddusermodmkdirsu

本章将涵盖以下主题:

  • 读取,写入和执行

  • 用户,组和其他人

  • 与多个用户一起工作

  • 高级权限

技术要求

我们将使用我们在第二章中创建的虚拟机来探索 Linux 权限方案,设置您的本地环境。在本章中,我们将向该系统添加新用户,但目前只有作为第一个用户(具有管理或root权限)的访问权限就足够了。

读取,写入和执行

在上一章中,我们讨论了 Linux 文件系统以及 Linux 实现“一切皆文件”哲学的不同类型。然而,我们没有看文件的权限。你可能已经猜到,在一个多用户系统,比如 Linux 服务器中,用户可以访问其他用户拥有的文件并不是一个特别好的主意。隐私在哪里呢?

Linux 权限方案实际上是 Linux 体验的核心,就我们而言。就像在 Linux 中(几乎)一切都被处理为文件一样,所有这些文件都有一组不同的权限。在上一章中探索文件系统时,我们限制了自己只能查看所有人或当前登录用户可见的文件。然而,有许多文件只能被root用户查看或写入:通常,这些是一些敏感文件,比如/etc/shadow(其中包含所有用户的哈希密码),或者在启动系统时使用的文件,比如/etc/fstab(确定哪些文件系统在启动时被挂载)。如果每个人都可以编辑这些文件,很快就会导致系统无法启动!

RWX

Linux 下的文件权限由三个属性处理:读取写入执行,或者 RWX。虽然还有其他权限(我们将在本章后面讨论一些),但大多数权限交互将由这三个属性处理。尽管这些名称似乎不言自明,但它们在(普通)文件和目录方面的行为是不同的。以下表格应该说明这一点:

允许用户使用任何支持此操作的命令查看文件的内容,比如vimnanolesscat等。

权限对普通文件对目录
读取允许用户使用ls命令列出目录的内容。这甚至会列出用户没有其他权限的目录中的文件!
写入允许用户对文件进行更改。允许用户替换或删除目录中的文件,即使用户对该文件没有直接权限。但这不包括对目录中所有文件的读取权限!
执行允许用户执行文件。当文件是应该被执行的东西,比如二进制文件或脚本时,这是相关的;否则,这个属性什么也不做。允许用户使用 cd 进入目录。这是与内容列表不同的权限,但它们几乎总是一起使用;能够列出而不能进入(反之亦然)大多数情况下是无效的配置。

这个概述应该为这三种不同的权限提供了一个基础。请仔细看一看,看看你是否完全理解了那里呈现的内容。

现在,情况将变得更加复杂。虽然文件和目录上的这些权限显示了用户可以做什么和不能做什么,但 Linux 如何处理多个用户?Linux 如何跟踪文件的所有权,文件如何被多个用户共享?

用户、组和其他

在 Linux 下,每个文件都由一个用户和一个组拥有。每个用户都有一个标识号,用户 IDUID)。组也是一样:它由一个组 IDGID)解析。每个用户有一个 UID 和一个主要GID;然而,用户可以是多个组的成员。在这种情况下,用户将有一个或多个附加 GID。您可以通过在 Ubuntu 机器上运行id命令来自己看到这一点:

reader@ubuntu:~$ id
uid=1000(reader) gid=1004(reader) groups=1004(reader),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd),1000(lpadmin),1001(sambashare),1002(debian-tor),1003(libvirtd)
reader@ubuntu:~$

在前面的输出中,我们可以看到以下内容:

  • reader用户的uid1000;Linux 通常从1000开始对普通用户进行编号

  • gid1004,对应于reader组;默认情况下,Linux 会创建一个与用户同名的组(除非明确告知不要创建)

  • 其他组包括 admsudo 和其他

这意味着什么?当前登录的用户具有uid1000,主要gid1004,以及一些附加组,这确保它具有其他特权。例如,在 Ubuntu 下,cdrom组允许用户访问光驱。sudo组允许用户执行管理命令,adm组允许用户读取管理文件。

虽然我们通常按名称引用用户和组,但这只是 Linux 为我们提供的 UID 和 GID 的表示。在系统级别上,只有 UID 和 GID 对权限很重要。这使得例如,有两个具有相同用户名但不同 UID 的用户成为可能:这些用户的权限将不同。反之亦然也是可能的:两个不同用户名但相同 UID 的用户,这将导致两个用户的权限在 UID 级别上至少是相同的。然而,这两种情况都非常令人困惑,不应该使用!正如我们将在后面看到的那样,使用组来共享权限是共享文件和目录的最佳解决方案。

另一件需要记住的事情是,UID 和 GID 是本地的。所以,如果我在机器 A 上有一个名为 bob 的用户,UID 为 1000,在机器 B 上 UID 为 1000 映射到用户 alice,将 bob 的文件从机器 A 传输到机器 B 将导致文件在系统 B 上被 alice 拥有!

之前解释的 RWX 权限与我们现在讨论的用户和组有关。实质上,每个文件(或目录,这只是一种不同类型的文件)都有以下属性:

  • 文件由一个用户拥有,该用户具有(部分)RWX 权限

  • 文件也由一个拥有,再次,有(部分)RWX 权限

  • 文件最终对其他人具有 RWX 权限,这意味着所有不共享该组的不同用户

要确定用户是否可以读取、写入或执行文件或目录,我们需要查看以下属性(不一定按照这个顺序):

  • 用户是否是文件的所有者?所有者有什么 RWX 权限?

  • 用户是否是拥有文件的组的一部分?为组设置了什么 RWX 权限?

  • 文件在其他属性上有足够的权限吗?

在变得太抽象之前,让我们看一些简单的例子。在您的虚拟机上,按照以下命令操作:

reader@ubuntu:~$ pwd
/home/reader
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
reader@ubuntu:~$ touch testfile
reader@ubuntu:~$

首先,我们确保我们在reader用户的home目录中。如果不是,我们可以使用cd /home/reader命令或者只输入cd(没有参数时,cd默认为用户的home目录!)。我们继续使用ls -l以长格式列出目录的内容,其中显示了一个文件:nanofile.txt,来自第二章,设置您的本地环境(如果您没有跟随那里并且没有该文件,不用担心;我们稍后将创建和操作文件)。我们使用一个新命令touch来创建一个空文件。我们指定给touch的参数被解释为文件名,当我们再次列出文件时可以看到。

reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rw-rw-r-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$

您将看到权限后面跟着两个名称:用户名和组名(按顺序!)。对于我们的testfile,用户readerreader组的成员都可以读取和写入文件,但不能执行(在x的位置上,有一个-,表示没有该权限)。所有其他用户,例如既不是读者也不是读者组的成员(在这种情况下,实际上是所有其他用户),由于其他人的权限,只能读取文件。这也在下表中描述:

文件类型 (第一个字符)用户权限(第 2 到 4 个字符)组权限(第 5 到 7 个字符)其他权限(第 8 到 10 个字符)用户所有权组所有权
-(普通文件)rw-,读和写,无执行rw-,读和写,无执行r--,只读读者读者

如果一个文件对每个人都有完全权限,它会是这样的:-rwxrwxrwx。对于所有者和组都有所有权限,但其他人没有任何权限的文件,它将是-rwxrwx---。对于用户和组都有所有权限,但其他人没有任何权限的目录,表示为drwxrwx---

让我们看另一个例子:

reader@ubuntu:~$ ls -l /
<SNIPPED>
dr-xr-xr-x 98 root root          0 Aug  4 10:49 proc
drwx------  3 root root       4096 Jul  1 09:40 root 

drwxr-xr-x 25 root root        900 Aug  4 10:51 run
<SNIPPED>
reader@ubuntu:~$

系统超级用户的home目录是/root/。我们可以从行的第一个字符看到它是一个d,表示目录。它对所有者root具有 RWX(最后一次:读取、写入、执行)权限,对组(也是root)没有权限,对其他人(由---表示)也没有权限。这些权限只能意味着一件事:只有用户 root **可以进入或操作此目录!**让我们看看我们的假设是否正确。请记住,进入目录需要x权限,而列出目录内容需要r权限。我们既不是root用户也不在 root 组,因此我们既不能做任何一件事。在这种情况下,将应用其他人的权限,即---

reader@ubuntu:~$ cd /root/
-bash: cd: /root/: Permission denied
reader@ubuntu:~$ ls /root/
ls: cannot open directory '/root/': Permission denied
reader@ubuntu:~$

操作文件权限和所有权

阅读本章的第一部分后,您应该对 Linux 文件权限有了相当好的理解,以及如何在用户、组和其他级别上使用读取、写入和执行来确保文件的暴露正好符合要求。然而,直到这一点,我们一直在处理静态权限。在管理 Linux 系统时,您很可能会花费大量时间调整和解决权限问题。在本书的这一部分中,我们将探讨可以用来操作文件权限的命令。

chmod,umask

让我们回到我们的testfile。它具有以下权限:-rw-rw----。用户和组可读/写,其他人可读。虽然这些权限对大多数文件来说可能是合适的,但对于所有文件来说肯定不是一个很好的选择。私人文件呢?您可能不希望这些文件被所有人读取,甚至可能不希望被组成员读取。

在 Linux 中,更改文件或目录的权限的命令是chmod,我们喜欢将其读作change file mode。chmod有两种操作模式:符号模式和数字/八进制模式。我们将首先解释符号模式(更容易理解),然后再转到八进制模式(更快速使用)。

我们还没有介绍的是查看命令手册的命令。该命令就是man,后面跟着你想要查看手册的命令。在这种情况下,man chmod会将我们放入chmod手册分页器中,它使用与你学习 Vim 相同的导航控件。记住,退出是通过输入:q来完成的。在这种情况下,只需输入q就足够了。现在看一下chmod手册,至少读一下description标题;这将使接下来的解释更清晰。

符号模式使用了我们之前看到的 UGOA 字母的 RWX 构造。这可能看起来很新,但实际上并不是!Users、Groups、Others 和All 用于表示我们正在更改的权限。

要添加权限,我们告诉chmod我们(用户、组、其他或所有)为谁做这个操作,然后是我们想要添加的权限。例如,chmod u+x <filename>将为用户添加执行权限。类似地,使用chmod删除权限的方法如下:chmod g-rwx <filename>。请注意,我们使用+号来添加权限,使用-号来删除权限。如果我们没有指定用户、组、其他或所有,所有将默认使用。让我们在我们的 Ubuntu 机器上试一下:

reader@ubuntu:~$ cd
reader@ubuntu:~$ pwd
/home/reader
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rw-rw-r-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ chmod u+x testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxrw-r-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ chmod g-rwx testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwx---r-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ chmod -r testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
--wx------ 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$

首先,我们为testfile添加了用户的执行权限。接下来,我们从组中移除了读取、写入和执行权限,结果是-rwx---r--。在这种情况下,组成员仍然可以读取文件,然而,因为每个人仍然可以读取文件。可以说这不是隐私的完美权限。最后,我们在-r之前没有指定任何内容,这实际上移除了用户、组和其他的读取权限,导致文件最终变成了--wx------

能够写和执行一个你无法阅读的文件有点奇怪。让我们来修复它,看看八进制权限是如何工作的!我们可以使用chmodverbose选项,通过使用-v标志来打印更多信息:

reader@ubuntu:~$ chmod -v u+rwx testfile
mode of 'testfile' changed from 0300 (-wx------) to 0700 (rwx------)
reader@ubuntu:~$

正如你所看到的,我们现在从chmod得到了输出!具体来说,我们可以看到八进制模式。在我们改变文件之前,模式是0300,在为用户添加读取权限后,它跳到了0700。这些数字代表什么?

这一切都与权限的二进制实现有关。对于所有三个级别(用户、组、其他),在结合读取、写入和执行时,有 8 种不同的可能权限,如下所示:

符号八进制
---0
--x1
-w-2
-wx3
r--4
r-x5
rw-6
rwx7

基本上,八进制值在 0 和 7 之间,总共有 8 个值。这就是为什么它被称为八进制:来自于拉丁语/希腊语中 8 的表示,octo。读取权限被赋予值 4,写入权限被赋予值 2,执行权限被赋予值 1。

通过使用这个系统,0 到 7 的值总是可以唯一地与 RWX 值相关联。RWX 是4+2+1 = 7,RX 是4+1 = 5,依此类推。

现在我们知道了八进制表示是如何工作的,我们可以使用它们来使用chmod修改文件权限。让我们在一个命令中为用户、组和其他给予测试文件完全权限(RWX 或 7):

reader@ubuntu:~$ chmod -v 0777 testfile 
mode of 'testfile' changed from 0700 (rwx------) to 0777 (rwxrwxrwx)
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxrwxrwx 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$

在这种情况下,chmod接受四个数字作为参数。第一个数字涉及到一种特殊类型的权限,称为粘滞位;我们不会讨论这个,但我们已经在Further reading部分中包含了相关材料,供感兴趣的人参考。在这些示例中,它总是设置为0,因此没有设置特殊位。第二个数字映射到用户权限,第三个映射到组权限,第四个,不出所料,映射到其他权限。

如果我们想要使用符号表示法来做到这一点,我们可以使用chmod a+rwx命令。那么,为什么八进制比我们之前说的更快呢?让我们看看如果我们想要为每个级别设置不同的权限会发生什么,例如-rwxr-xr--。如果我们想要用符号表示法来做到这一点,我们需要使用三个命令或一个链接的命令(chmod的另一个功能):

reader@ubuntu:~$ chmod 0000 testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
---------- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ chmod u+rwx,g+rx,o+r testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxr-xr-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$

chmod u+rwx,g+rx,o+r testfile命令中可以看出,事情变得有点复杂。然而,使用八进制表示法,命令要简单得多:

reader@ubuntu:~$ chmod 0000 testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
---------- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ chmod 0754 testfile 
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxr-xr-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$

基本上,主要区别在于使用命令式表示法(添加或删除权限)与声明式表示法(将其设置为这些值)。根据我们的经验,声明式几乎总是更好/更安全的选择。使用命令式,我们需要首先检查当前的权限并对其进行变异;而使用声明式,我们可以在单个命令中指定我们想要的内容。

现在可能很明显了,但我们更喜欢使用八进制表示法。除了从更短、更简单的命令中受益,这些命令是以声明方式处理的,另一个好处是大多数您在网上找到的示例也使用八进制表示法。要完全理解这些示例,您至少需要了解八进制。而且,如果无论如何都需要理解它们,那么在日常生活中使用它们是最好的!

早些时候,当我们使用touch命令时,我们最终得到了一个文件,该文件可以被用户和组读取和写入,并且对其他人是可读的。这些似乎是默认权限,但它们是从哪里来的?我们如何操纵它们?让我们来认识一下umask

reader@ubuntu:~$ umask
0002
reader@ubuntu:~$

umask会话用于确定新创建的文件和目录的文件权限。对于文件,执行以下操作:取文件的最大八进制值0666,然后减去umask(在本例中为0002),这给我们0664。这意味着新创建的文件是-rw-rwr--,这正是我们在testfile中看到的。你可能会问,为什么我们使用0666而不是0777?这是 Linux 提供的一种保护措施;如果我们使用0777,大多数文件将被创建为可执行文件。可执行文件可能是危险的,因此设计决策是文件只有在明确设置为可执行时才能执行。因此,根据当前的实现,没有意外创建可执行文件这样的事情。对于目录,使用正常的八进制值0777,这意味着目录以0775-rwxrwxr-x权限创建。我们可以通过使用mkdir命令创建一个新目录来验证这一点:

reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxr-xr-- 1 reader reader  0 Aug  4 13:44 testfile
reader@ubuntu:~$ umask
0002
reader@ubuntu:~$ mkdir testdir
reader@ubuntu:~$ ls -l
total 8
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
reader@ubuntu:~$

因为目录的执行权限要少得多(记住,它用于确定您是否可以进入目录),所以这个实现与文件不同。

关于umask,我们还有一个技巧要展示。在特定情况下,我们想要自己确定文件和目录的默认值。我们也可以使用umask命令来做到这一点:

reader@ubuntu:~$ umask
0002
reader@ubuntu:~$ umask 0007
reader@ubuntu:~$ umask
0007
reader@ubuntu:~$ touch umaskfile
reader@ubuntu:~$ mkdir umaskdir
reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader reader    0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

在上面的例子中,您可以看到运行umask命令而不带参数会打印当前的 umask。如果以有效的 umask 值作为参数运行它,将会改变 umask 为该值,然后在创建新文件和目录时使用。将上述输出中的umaskfileumaskdir与之前的testfiletestdir进行比较。如果我们想要默认创建私有文件,这将非常有用!

sudo、chown 和 chgrp

到目前为止,我们已经看到了如何操纵文件和目录的(基本)权限。然而,我们还没有处理更改文件的所有者或组。总是必须按照创建时的用户和组来工作有点不切实际。对于 Linux,我们可以使用两个工具来更改所有者和组:change owner(chown)和change groupchgrp)。然而,有一件非常重要的事情要注意:这些命令只能由具有 root 权限的人执行(通常是root用户)。因此,在我们向你介绍chownchgrp之前,让我们先看看sudo

sudo

sudo命令最初是为superuser do命名的,正如其名字所暗示的,它给了你一个机会以 root 超级用户的身份执行操作。sudo命令使用/etc/sudoers文件来确定用户是否被允许提升到超级用户权限。让我们看看它是如何工作的!

reader@ubuntu:~$ cat /etc/sudoers
cat: /etc/sudoers: Permission denied
reader@ubuntu:~$ ls -l /etc/sudoers
-r--r----- 1 root root 755 Jan 18  2018 /etc/sudoers
reader@ubuntu:~$ sudo cat /etc/sudoers
[sudo] password for reader: 
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults    env_reset
Defaults    mail_badpass
Defaults  secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
<SNIPPED>
# User privilege specification
root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo    ALL=(ALL:ALL) ALL
<SNIPPED>
reader@ubuntu:~$

我们首先尝试以普通用户的身份查看/etc/sudoers的内容。当这给我们一个Permission denied错误时,我们查看文件的权限。从-r--r----- 1 root root这一行,很明显只有root用户或root组的成员才能读取该文件。为了提升到 root 权限,我们使用sudo命令我们想要运行的命令前面,即cat /etc/sudoers。为了验证,Linux 会始终要求用户输入密码。默认情况下,这个密码会被保存在内存中大约 5 分钟,所以如果你最近输入过密码,你就不必每次都输入密码。

输入密码后,/etc/sudoers文件就会被打印出来!看来sudo确实给了我们超级用户权限。这是如何工作的也可以通过/etc/sudoers文件来解释。# Allow members of group sudo to execute any command这一行是一个注释(因为它以#开头;稍后会详细介绍),告诉我们下面的行给了sudo组的所有用户任何命令的权限。在 Ubuntu 上,默认创建的用户被认为是管理员,并且是这个组的成员。使用id命令来验证这一点:

reader@ubuntu:~$ id
uid=1000(reader) gid=1004(reader) groups=1004(reader),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd),1000(lpadmin),1001(sambashare),1002(debian-tor),1003(libvirtd)
reader@ubuntu:~$

sudo命令还有另一个很好的用途:切换到root用户!为此,使用--login标志,或者它的简写,-i

reader@ubuntu:~$ sudo -i
[sudo] password for reader: 
root@ubuntu:~#

在提示符中,你会看到用户名已经从reader变成了root。此外,你的提示符中的最后一个字符现在是#而不是$。这也用于表示当前的提升权限。你可以使用内置的exit shell 退出这个提升的位置:

root@ubuntu:~# exit
logout
reader@ubuntu:~$

记住,root用户是系统的超级用户,可以做任何事情。而且,我们真的是指任何事情!与其他操作系统不同,如果你告诉 Linux 删除根文件系统和其下的一切,它会乐意遵从(直到它破坏了太多以至于无法正常工作为止)。也不要指望有Are you sure?的提示。对于sudo命令或者 root 提示中的任何东西都要非常小心。

chown,chgrp

经过一小段sudo的绕道之后,我们可以回到文件权限:我们如何改变文件的所有权?让我们从使用chgrp来改变组开始。语法如下:chgrp <groupname> <filename>

reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader reader    0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ chgrp games umaskfile 
chgrp: changing group of 'umaskfile': Operation not permitted
reader@ubuntu:~$ sudo chgrp games umaskfile 
reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

首先,我们使用ls列出内容。接下来,我们尝试使用chgrpumaskfile文件的组更改为 games。然而,由于这是一个特权操作,我们没有以sudo开头启动命令,所以它失败了,显示Operation not permitted错误消息。接下来,我们使用正确的sudo chgrp games umaskfile命令,这通常是 Linux 中的一个好迹象。我们再次列出文件,确保情况是这样,我们可以看到umaskfile的组已经更改为games

让我们做同样的事情,但现在是为了用户,使用chown命令。语法与chgrp相同:chown <username> <filename>

reader@ubuntu:~$ sudo chown pollinate umaskfile 
reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader    reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader    reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader    reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader    reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 pollinate games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

正如我们所看到的,我们现在已经将文件所有权从reader:reader更改为pollinate:games。然而,有一个小技巧非常方便,我们想立刻向您展示!您实际上可以使用chown通过以下语法更改用户和组:chown <username>:<groupname> <filename>。让我们看看这是否可以将umaskfile恢复到其原始所有权:

reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader    reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader    reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader    reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader    reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 pollinate games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ sudo chown reader:reader umaskfile 
reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader reader    0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

在前面的示例中,我们使用了随机用户和组。如果要查看系统上存在哪些组,请检查/etc/group文件。对于用户,相同的信息可以在/etc/passwd中找到。

与多个用户一起工作

正如我们之前所说,Linux 本质上是一个多用户系统,特别是在 Linux 服务器的情况下,这些系统通常不是由单个用户,而是经常是(大型)团队管理。服务器上的每个用户都有自己的权限集。例如,想象一下,一个服务器需要开发、运营和安全三个部门。开发和运营都有自己的东西,但也需要共享其他一些东西。安全部门需要能够查看所有内容,以确保符合安全准则和规定。我们如何安排这样的结构?让我们实现它!

首先,我们需要创建一些用户。对于每个部门,我们将创建一个单一用户,但由于我们将确保组级别的权限,因此对于每个部门中的 5、10 或 100 个用户来说,这也同样有效。我们可以使用useradd命令创建用户。在其基本形式中,我们可以只使用useradd <username>,Linux 将通过默认值处理其余部分。显然,与 Linux 中的几乎所有内容一样,这是高度可定制的;有关更多信息,请查看 man 页面(man useradd)。

chownchgrp一样,useradd(以及后来的usermod)是一个特权命令,我们将使用sudo来执行:

reader@ubuntu:~$ useradd dev-user1
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
reader@ubuntu:~$ sudo useradd dev-user1
[sudo] password for reader: 
reader@ubuntu:~$ sudo useradd ops-user1
reader@ubuntu:~$ sudo useradd sec-user1
reader@ubuntu:~$ id dev-user1
uid=1001(dev-user1) gid=1005(dev-user1) groups=1005(dev-user1)
reader@ubuntu:~$ id ops-user1 
uid=1002(ops-user1) gid=1006(ops-user1) groups=1006(ops-user1)
reader@ubuntu:~$ id sec-user1 
uid=1003(sec-user1) gid=1007(sec-user1) groups=1007(sec-user1)
reader@ubuntu:~$

最后提醒一下,我们向您展示了当您忘记sudo时会发生什么。虽然错误消息在技术上是完全正确的(您需要 root 权限才能编辑/etc/passwd,其中存储了用户信息),但可能不太明显命令失败的原因,特别是因为误导性的稍后重试!错误。

然而,使用sudo,我们可以添加三个用户:dev-user1ops-user1sec-user1。当我们按顺序检查这些用户时,我们可以看到他们的uid每次增加一个。我们还可以看到与用户同名的组被创建,并且这是用户的唯一组成员。组也有它们的gid,每次下一个用户都会增加一个。

因此,现在我们已经有了用户,但我们需要共享组。为此,我们有一个类似的命令(在名称和操作上都相同):groupadd。查看groupadd的 man 页面,并添加三个对应于我们部门的组:

reader@ubuntu:~$ sudo groupadd development
reader@ubuntu:~$ sudo groupadd operations
reader@ubuntu:~$ sudo groupadd security
reader@ubuntu:~$

要查看系统上已有哪些组,可以查看/etc/group文件(例如使用lesscat)。一旦满意,我们现在已经有了用户和组。但是我们如何使用户成为组的成员?输入usermod(表示user modify)。设置用户的主要组的语法如下:usermod -g <groupname> <username>

reader@ubuntu:~$ sudo usermod -g development dev-user1 
reader@ubuntu:~$ sudo usermod -g operations ops-user1 
reader@ubuntu:~$ sudo usermod -g security sec-user1 
reader@ubuntu:~$ id dev-user1 
uid=1001(dev-user1) gid=1008(development) groups=1008(development)
reader@ubuntu:~$ id ops-user1 
uid=1002(ops-user1) gid=1009(operations) groups=1009(operations)
reader@ubuntu:~$ id sec-user1 
uid=1003(sec-user1) gid=1010(security) groups=1010(security)
reader@ubuntu:~$

我们现在已经实现的更接近我们的目标,但我们还没有到达那里。到目前为止,我们只确保多个开发人员可以通过所有在开发组中的文件共享文件。但是开发和运营之间的共享文件夹呢?安全性如何监视所有内容?让我们创建一些具有正确组的目录(使用mkdir,表示make directory),看看我们能走多远:

reader@ubuntu:~$ sudo mkdir /data
[sudo] password for reader:
reader@ubuntu:~$ cd /data
reader@ubuntu:/data$ sudo mkdir dev-files
reader@ubuntu:/data$ sudo mkdir ops-files
reader@ubuntu:/data$ sudo mkdir devops-files
reader@ubuntu:/data$ ls -l
total 12
drwxr-xr-x 2 root root 4096 Aug 11 10:03 dev-files
drwxr-xr-x 2 root root 4096 Aug 11 10:04 devops-files
drwxr-xr-x 2 root root 4096 Aug 11 10:04 ops-files
reader@ubuntu:/data$ sudo chgrp development dev-files/
reader@ubuntu:/data$ sudo chgrp operations ops-files/
reader@ubuntu:/data$ sudo chmod 0770 dev-files/
reader@ubuntu:/data$ sudo chmod 0770 ops-files/
reader@ubuntu:/data$ ls -l
total 12
drwxrwx--- 2 root development 4096 Aug 11 10:03 dev-files
drwxr-xr-x 2 root root        4096 Aug 11 10:04 devops-files
drwxrwx--- 2 root operations  4096 Aug 11 10:04 ops-files
reader@ubuntu:/data

我们现在有以下结构:一个/data/顶级目录,其中包含dev-filesops-files目录,分别由developmentoperations组拥有。现在,让我们满足安全部门可以进入这两个目录并管理文件的要求!除了使用usermod来更改主要组,我们还可以将用户追加到额外的组中。在这种情况下,语法是usermod -a -G <groupnames> <username>。让我们将sec-user1添加到developmentoperations组中:

reader@ubuntu:/data$ id sec-user1
uid=1003(sec-user1) gid=1010(security) groups=1010(security)
reader@ubuntu:/data$ sudo usermod -a -G development,operations sec-user1 
reader@ubuntu:/data$ id sec-user1
uid=1003(sec-user1) gid=1010(security) groups=1010(security),1008(development),1009(operations)
reader@ubuntu:/data$

安全部门的用户现在是所有新组的成员:安全、开发和运维。由于/data/dev-files//data/ops-files/都没有其他人的权限,我们当前的用户不应该能够进入其中任何一个,但sec-user1应该可以。让我们看看这是否正确:

reader@ubuntu:/data$ sudo su - sec-user1
No directory, logging in with HOME=/
$ cd /data/
$ ls -l
total 12
drwxrwx--- 2 root development 4096 Aug 11 10:03 dev-files
drwxr-xr-x 2 root root        4096 Aug 11 10:04 devops-files
drwxrwx--- 2 root operations  4096 Aug 11 10:04 ops-files
$ cd dev-files
$ pwd
/data/dev-files
$ touch security-file
$ ls -l
total 0
-rw-r--r-- 1 sec-user1 security 0 Aug 11 10:16 security-file
$ exit
reader@ubuntu:/data$

如果您跟着这个例子,您应该会发现我们引入了一个新命令:su。它是switch user 的缩写,它允许我们在用户之间切换。如果您在前面加上sudo,您可以切换到一个用户,而不需要该用户的密码,只要您有这些权限。否则,您将需要输入密码(在这种情况下很难,因为我们还没有为用户设置密码)。您可能已经注意到,新用户的 shell 是不同的。这是因为我们还没有加载任何配置(这是为默认用户自动完成的)。不过,不用担心——它仍然是一个完全功能的 shell!我们的测试成功了:我们能够进入dev-files目录,即使我们不是开发人员。我们甚至能够创建一个文件。如果您愿意,可以验证在ops-files目录中也是可能的。

最后,让我们创建一个新组devops,我们将使用它来在开发人员和运维之间共享文件。创建组后,我们将像将sec-user1添加到developmentoperations组一样,将dev-user1ops-user1添加到这个组中:

reader@ubuntu:/data$ sudo groupadd devops
reader@ubuntu:/data$ sudo usermod -a -G devops dev-user1 
reader@ubuntu:/data$ sudo usermod -a -G devops ops-user1 
reader@ubuntu:/data$ id dev-user1 
uid=1001(dev-user1) gid=1008(development) groups=1008(development),1011(devops)
reader@ubuntu:/data$ id ops-user1 
uid=1002(ops-user1) gid=1009(operations) groups=1009(operations),1011(devops)
reader@ubuntu:/data$ ls -l
total 12
drwxrwx--- 2 root development 4096 Aug 11 10:16 dev-files
drwxr-xr-x 2 root root        4096 Aug 11 10:04 devops-files
drwxrwx--- 2 root operations  4096 Aug 11 10:04 ops-files
reader@ubuntu:/data$ sudo chown root:devops devops-files/
reader@ubuntu:/data$ sudo chmod 0770 devops-files/
reader@ubuntu:/data$ ls -l
total 12
drwxrwx---  2 root development 4096 Aug 11 10:16 dev-files/
drwxrwx---  2 root devops      4096 Aug 11 10:04 devops-files/
drwxrwx---  2 root operations  4096 Aug 11 10:04 ops-files/
reader@ubuntu:/data$

我们现在有一个共享目录,/data/devops-files/dev-user1ops-user1都可以进入并创建文件。

作为练习,可以执行以下任何一项:

  • sec-user1添加到devops组,以便它也可以审计共享文件

  • 验证dev-user1ops-user1是否可以在共享目录中写入文件

  • 了解为什么dev-user1ops-user1只能读取devops目录中的对方文件,但不能编辑它们(提示:本章的下一节高级权限将告诉您如何使用 SGID 解决这个问题)

高级权限

这涵盖了 Linux 的基本权限。然而,还有一些高级主题,我们想指出,但我们不会详细讨论它们。有关这些主题的更多信息,请查看本章末尾的进一步阅读部分。我们已经包括了文件属性、特殊文件权限和访问控制列表的参考。

文件属性

文件也可以具有以不同于我们目前所见的权限表达的属性。一个例子是使文件不可变(一个花哨的词,意思是它不能被更改)。不可变文件仍然具有正常的所有权和组以及 RWX 权限,但它不允许用户更改它,即使它包含可写权限。另一个特点是该文件不能被重命名。

其他文件属性包括不可删除仅追加压缩。有关文件属性的更多信息,请查看lsattrchattr命令的 man 页面(man lsattrman chattr)。

特殊文件权限

正如您可能已经在八进制表示部分注意到的那样,我们总是以零开头(0775,0640 等)。如果我们不使用它,为什么要包括零?该位置保留用于特殊文件权限:SUID、SGID 和粘滞位。它们具有类似的八进制表示法(其中 SUID 为 4,SGID 为 2,粘滞位为 1),并且以以下方式使用:

文件目录
SUID文件以所有者的权限执行,无论哪个用户执行它。什么也不做。
SGID文件以组的权限执行,无论哪个用户执行它。在此目录中创建的文件获得与目录相同的组。
粘滞位什么也不做。用户只能删除他们在这个目录中的文件。查看/tmp/目录以了解其最著名的用途。

访问控制列表(ACL)

ACL 是增加 UGO/RWX 系统灵活性的一种方式。使用setfaclset file acl)和getfaclget file acl),您可以为文件和目录设置额外的权限。因此,例如,使用 ACL,您可以说,虽然/root/目录通常只能由root用户访问,但也可以被reader用户读取。另一种实现这一点的方法是将reader用户添加到root组中,这也给了reader用户系统上的许多其他特权(任何对 root 组有权限的东西都已经授予了 reader 用户!)。尽管根据我们的经验,ACL 在实践中并不经常使用,但对于边缘情况,它们可能是复杂解决方案和简单解决方案之间的区别。

总结

在本章中,我们已经了解了 Linux 权限方案。我们已经学到了权限安排的两个主要轴:文件权限和文件所有权。对于文件权限,每个文件都有对执行权限的允许(或不允许)。这些权限的工作方式对文件和目录有所不同。权限是通过所有权应用的:文件始终由用户和组拥有。除了用户之外,还有其他人的文件权限,称为其他所有权。如果用户是文件的所有者或文件组的成员,那么这些权限对用户是可用的。否则,其他人需要有权限才能与文件交互。

接下来,我们学习了如何操纵文件权限和所有权。通过使用chmodumask,我们能够以所需的方式获取文件权限。使用sudochownchgrp,我们操纵了文件的所有者和组。对于sudoroot用户的使用给出了警告,因为两者都可以在很少的努力下使 Linux 系统无法操作。

我们继续以一个与多个用户一起工作的示例。我们使用useradd添加了三个额外的用户到系统,并使用usermod为他们分配了正确的组。我们看到这些用户可以成为相同组的成员,并以这种方式共享对文件的访问。

最后,我们简要介绍了 Linux 下高级权限的一些基础知识。进一步阅读部分包含了这些主题的更多信息。

本章介绍了以下命令:idtouchchmodumaskchownchgrpsudouseraddgroupaddusermodmkdirsu

问题

  1. Linux 文件使用了哪三种权限?

  2. 为 Linux 文件定义了哪三种所有权类型?

  3. 哪个命令用于更改文件的权限?

  4. 什么机制控制了新创建文件的默认权限?

  5. 以下符号权限如何用八进制描述:rwxrw-r--

  6. 以下八进制权限如何用符号描述:0644

  7. 哪个命令允许我们获得超级用户权限?

  8. 我们可以使用哪些命令来更改文件的所有权?

  9. 我们如何安排多个用户共享文件访问?

  10. Linux 有哪些类型的高级权限?

进一步阅读

如果您想深入了解本章主题,以下资源可能会很有趣:

第六章:文件操作

本章专门讨论文件操作。就像一切都是文件系统一样,文件操作被认为是与 Linux 工作中最重要的方面之一。我们将首先探讨常见的文件操作,比如创建、复制和删除文件。接着我们会介绍一些关于存档的内容,这是在命令行工作时的另一个重要工具。本章的最后部分将致力于在文件系统中查找文件,这是 shell 脚本工具包中的另一个重要技能。

本章将介绍以下命令:cprmmvlntarlocatefind

本章将涵盖以下主题:

  • 常见的文件操作

  • 存档

  • 查找文件

技术要求

我们将使用我们在第二章中创建的虚拟机进行文件操作。此时不需要更多资源。

常见的文件操作

到目前为止,我们主要介绍了与 Linux 文件系统导航相关的命令。在早期的章节中,我们已经看到我们可以使用mkdirtouch分别创建目录和空文件。如果我们想给文件一些有意义的(文本)内容,我们使用vimnano。然而,我们还没有讨论过删除文件或目录,或者复制、重命名或创建快捷方式。让我们从复制文件开始。

复制

实质上,在 Linux 上复制文件非常简单:使用cp命令,后面跟着要复制的文件名和要复制到的文件名。它看起来像这样:

reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ cp testfile testfilecopy
reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 testfile
-rwxr-xr-- 1 reader reader    0 Aug 18 14:00 testfilecopy
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

正如你所看到的,在这个例子中,我们复制了一个(空的)文件,它已经是我们拥有的,而我们在相同的目录中。这可能会引发一些问题,比如:

  • 我们是否总是需要在源文件和目标文件的相同目录中?

  • 文件的权限呢?

  • 我们是否也可以使用cp复制目录?

正如你所期望的,在 Linux 下,cp命令也是非常多才多艺的。我们确实可以复制不属于我们的文件;我们不需要在与文件相同的目录中,我们也可以复制目录!让我们尝试一些这些事情:

reader@ubuntu:~$ cd /var/log/
reader@ubuntu:/var/log$ ls -l
total 3688
<SNIPPED>
drwxr-xr-x  2 root      root               4096 Apr 17 20:22 dist-upgrade
-rw-r--r--  1 root      root             550975 Aug 18 13:35 dpkg.log
-rw-r--r--  1 root      root              32160 Aug 11 10:15 faillog
<SNIPPED>
-rw-------  1 root      root              64320 Aug 11 10:15 tallylog
<SNIPPED>
reader@ubuntu:/var/log$ cp dpkg.log /home/reader/
reader@ubuntu:/var/log$ ls -l /home/reader/
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
-rwxr-xr-- 1 reader reader      0 Aug 18 14:00 testfilecopy
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:/var/log$ cp tallylog /home/reader/
cp: cannot open 'tallylog' for reading: Permission denied
reader@ubuntu:/var/log$

那么,发生了什么?我们使用cd命令将目录更改为/var/log/。我们使用带有选项的ls列出了那里的文件。我们复制了一个相对路径的文件,我们能够读取它,但它是由root:root拥有的,复制到了完全限定的/home/reader/目录。当我们使用完全限定路径列出/home/reader/时,我们看到复制的文件现在由reader:reader拥有。当我们尝试对tallylog文件做同样的操作时,我们得到了错误cannot open 'tallylog' for reading: Permission denied。这并不意外,因为我们对该文件没有任何读取权限,所以复制会很困难。

这应该回答了三个问题中的两个。但是对于目录呢?让我们尝试将/tmp/目录复制到我们的home目录中:

reader@ubuntu:/var/log$ cd
reader@ubuntu:~$ cp /tmp/ .
cp: -r not specified; omitting directory '/tmp/'
reader@ubuntu:~$ ls -l
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
-rwxr-xr-- 1 reader reader      0 Aug 18 14:00 testfilecopy
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ cp -r /tmp/ .
cp: cannot access '/tmp/systemd-private-72bcf47b69464914b021b421d5999bbe-systemd-timesyncd.service-LeF05x': Permission denied
cp: cannot access '/tmp/systemd-private-72bcf47b69464914b021b421d5999bbe-systemd-resolved.service-ApdzhW': Permission denied
reader@ubuntu:~$ ls -l
total 556
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
-rwxr-xr-- 1 reader reader      0 Aug 18 14:00 testfilecopy
drwxrwxr-t 9 reader reader   4096 Aug 18 14:38 tmp
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

对于这样一个简单的练习,实际上发生了很多事情!首先,我们使用cd命令返回到我们的home目录,而不带任何参数;这本身就是一个很巧妙的小技巧。接下来,我们尝试将整个/tmp/目录复制到.(你应该记得,.当前目录的缩写)。然而,这次失败了,出现了错误-r not specified; omitting directory '/tmp/'。我们列出目录来检查,确实,似乎什么都没发生。当我们添加了错误指定的-r并重新尝试命令时,出现了一些Permission denied的错误。这并不意外,因为并非所有/tmp/目录中的文件都对我们可读。尽管我们得到了错误,但当我们现在检查我们的home目录的内容时,我们可以看到tmp目录在那里!因此,使用-r选项(它是--recursive的缩写)允许我们复制目录和其中的所有内容。

删除

在将一些文件和目录复制到我们的home目录之后(这是一个安全的选择,因为我们确信可以在那里写入!),我们留下了一些混乱。让我们使用rm命令来删除一些重复的项目:

reader@ubuntu:~$ ls -l
total 556
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
-rwxr-xr-- 1 reader reader      0 Aug 18 14:00 testfilecopy
drwxrwxr-t 9 reader reader   4096 Aug 18 14:38 tmp
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ rm testfilecopy
reader@ubuntu:~$ rm tmp/
rm: cannot remove 'tmp/': Is a directory
reader@ubuntu:~$ rm -r tmp/
reader@ubuntu:~$ ls -l
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

使用rm后跟文件名将删除它。您可能会注意到,这里没有“您确定吗?”的提示。实际上,可以通过使用-i标志来启用此功能,但默认情况下不是这样。请注意,rm还允许您使用通配符,例如*(匹配所有内容),这将删除所有匹配的文件(并且可以被用户删除)。简而言之,这是一种非常快速丢失文件的好方法!但是,当我们尝试使用rm命令和目录名称时,它会给出错误cannot remove 'tmp/': Is a directory。这与cp命令非常相似,幸运的是,解决方法也是一样的:添加-r进行递归删除!同样,这是一种丢失文件的好方法;一个命令就可以让您删除整个home目录及其中的所有内容,而不需要任何警告。请把这当作是您的警告!特别是在与-f标志结合使用时,它是--force的缩写,这将确保rm永远不会提示并立即开始删除。

重命名、移动和链接

有时,我们不仅想要创建或删除文件,还可能需要重命名文件。奇怪的是,Linux 没有任何听起来像重命名的东西;但是,mv命令(用于move)确实实现了我们想要的功能。与cp命令类似,它接受源文件和目标文件作为参数,并且看起来像这样:

reader@ubuntu:~$ ls -l
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 testdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 testfile
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ mv testfile renamedtestfile
reader@ubuntu:~$ mv testdir/ renamedtestdir
reader@ubuntu:~$ ls -l
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

正如您所看到的,mv命令非常简单易用。它甚至适用于目录,无需像cprm那样需要-r这样的特殊选项。但是,当我们引入通配符时,它会变得更加复杂,但现在不用担心。我们在前面的代码中使用的命令是相对的,但它们也可以完全限定或混合使用。

有时,您可能需要将文件从一个目录移动到另一个目录。如果您仔细考虑,这实际上是对完全限定文件名的重命名!没有触及任何数据,但您只是想在其他地方访问文件。因此,使用mv umaskfile umaskdir/umaskfile移动到umaskdir/

reader@ubuntu:~$ ls -l
total 16
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ mv umaskfile umaskdir/
reader@ubuntu:~$ ls -l
total 16
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader   4096 Aug 19 10:37 umaskdir
reader@ubuntu:~$ ls -l umaskdir/
total 0
-rw-rw---- 1 reader games 0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

最后,我们有ln命令,它代表linking。这是 Linux 创建文件之间链接的方式,最接近 Windows 使用的快捷方式。有两种类型的链接:符号链接(也称为软链接)和硬链接。区别在于文件系统的工作原理:符号链接指向文件名(或目录名),而硬链接链接到存储文件或目录内容的inode。对于脚本编写,如果您使用链接,您可能正在使用符号链接,因此让我们看看这些符号链接的操作:

reader@ubuntu:~$ ls -l
total 552
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$ ln -s /var/log/auth.log 
reader@ubuntu:~$ ln -s /var/log/auth.log link-to-auth.log
reader@ubuntu:~$ ln -s /tmp/
reader@ubuntu:~$ ln -s /tmp/ link-to-tmp
reader@ubuntu:~$ ls -l
total 552
lrwxrwxrwx 1 reader reader     17 Aug 18 15:07 auth.log -> /var/log/auth.log
-rw-r--r-- 1 reader reader 550975 Aug 18 14:20 dpkg.log
lrwxrwxrwx 1 reader reader     17 Aug 18 15:08 link-to-auth.log -> /var/log/auth.log
lrwxrwxrwx 1 reader reader      5 Aug 18 15:08 link-to-tmp -> /tmp/
-rw-rw-r-- 1 reader reader     69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader   4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader      0 Aug  4 13:44 renamedtestfile
lrwxrwxrwx 1 reader reader      5 Aug 18 15:08 tmp -> /tmp/
drwxrwx--- 2 reader reader   4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games       0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

我们使用ln -s(这是--symbolic的缩写)创建了两种类型的符号链接:首先是到/var/log/auth.log文件,然后是到/tmp/目录。我们看到了两种不同的使用ln -s的方式:如果没有第二个参数,它将创建与我们要链接的内容相同名称的链接;否则,我们可以将我们自己的名称作为第二个参数(如link-to-auth.loglink-to-tmp/链接所示)。现在,我们可以通过与/home/reader/auth.log/home/reader/link-to-auth.log交互来读取/var/log/auth.log的内容。如果我们想要导航到/tmp/,我们现在可以使用/home/reader/tmp//home/reader/link-to-tmp/cd结合使用。虽然这个例子在日常工作中并不特别有用(除非输入/var/log/auth.log而不是auth.log可以为您节省大量时间),但链接可以防止重复复制文件,同时保持易于访问。

链接(以及 Linux 文件系统一般)中的一个重要概念是inode。每个文件(无论类型如何,包括目录)都有一个 inode,它描述了该文件的属性和磁盘块位置。在这个上下文中,属性包括所有权和权限,以及最后的更改、访问和修改时间戳。在链接中,软链接有它们自己的 inode,而硬链接指的是相同的 inode。

在继续本章的下一部分之前,使用rm清理四个链接和复制的dpk.log文件。如果你不确定如何做到这一点,请查看rm的 man 页面。一个小提示:删除符号链接就像rm <name-of-link>一样简单!

存档

现在我们对 Linux 中的常见文件操作有了一定的了解,我们将继续进行存档操作。虽然听起来可能很花哨,但存档简单地指的是创建存档。你们大多数人熟悉的一个例子是创建 ZIP 文件,这是一个存档。ZIP 并不是特定于 Windows 的;它是一种存档文件格式,在 Windows、Linux、macOS 等不同的实现中都有。

正如你所期望的,有许多存档文件格式。在 Linux 上,最常用的是tarball,它是通过使用tar命令创建的(这个术语来源于tape archive)。以.tar结尾的 tarball 文件是未压缩的。在实践中,tarball 几乎总是使用 Gzip 进行压缩,Gzip 代表GNU zip。这可以直接使用tar命令(最常见)或之后使用gzip命令(不太常见,但也可以用于压缩除 tarball 以外的文件)。由于tar是一个复杂的命令,我们将更详细地探讨最常用的标志(描述取自tar手册页):

-c--create创建一个新的存档。参数提供要存档的文件的名称。除非给出--no-recursion选项,否则将递归存档目录。
-x--extract--get从存档中提取文件。参数是可选的。给定时,它们指定要提取的存档成员的名称。
-t--list列出存档的内容。参数是可选的。给定时,它们指定要列出的成员的名称。
-v--verbose详细列出处理的文件。
-f--file=ARCHIVE使用存档文件或设备 ARCHIVE。
-z--gzip--gunzip--ungzip通过 Gzip 过滤存档。
-C--directory=DIR在执行任何操作之前切换到 DIR。这个选项是有顺序的,也就是说,它影响后面的所有选项。

tar命令在指定这些选项的方式上非常灵活。我们可以逐个呈现它们,一起呈现,带有或不带有连字符,或者使用长选项或短选项。这意味着创建存档的以下方式都是正确的,都可以工作:

  • tar czvf <archive name> <file1> <file2>

  • tar -czvf <archive name> <file1> <file2>

  • tar -c -z -v -f <archive name> <file1> <file2>

  • tar --create --gzip --verbose --file=<archive name> <file1> <file2>

虽然这看起来很有帮助,但也可能令人困惑。我们的建议是:选择一个格式并坚持使用它。在本书中,我们将使用最简短的形式,因此这是所有没有连字符的短选项。让我们使用这种形式来创建我们的第一个存档!

reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
reader@ubuntu:~$ tar czvf my-first-archive.tar.gz \
nanofile.txt renamedtestfile
nanofile.txt
renamedtestfile
reader@ubuntu:~$ ls -l
total 16
-rw-rw-r-- 1 reader reader  267 Aug 19 10:29 my-first-archive.tar.gz
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader 4096 Aug  4 16:18 umaskdir
-rw-rw---- 1 reader games     0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

使用这个命令,我们verbosely created 了一个名为my-first-archive.tar.gz的 gzipped file,其中包含了文件nanofile.txt umaskfilerenamedtestfile

在这个例子中,我们只存档了文件。实际上,通常很好的存档整个目录。语法完全相同,只是你会给出一个目录名,整个目录将被存档(在-z选项的情况下也会被压缩)。当你解压存档了一个目录的 tarball 时,整个目录将被再次提取,而不仅仅是内容。

现在,让我们看看解压它是否能还原我们的文件!我们将 gzipped tarball 移动到renamedtestdir,并使用tar xzvf命令在那里解压它:

reader@ubuntu:~$ ls -l
total 16
-rw-rw-r-- 1 reader reader  226 Aug 19 10:40 my-first-archive.tar.gz
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug  4 16:16 renamedtestdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader 4096 Aug 19 10:37 umaskdir
reader@ubuntu:~$ mv my-first-archive.tar.gz renamedtestdir/
reader@ubuntu:~$ cd renamedtestdir/
reader@ubuntu:~/renamedtestdir$ ls -l
total 4
-rw-rw-r-- 1 reader reader 226 Aug 19 10:40 my-first-archive.tar.gz
reader@ubuntu:~/renamedtestdir$ tar xzvf my-first-archive.tar.gz 
nanofile.txt
renamedtestfile
reader@ubuntu:~/renamedtestdir$ ls -l
total 8
-rw-rw-r-- 1 reader reader 226 Aug 19 10:40 my-first-archive.tar.gz
-rw-rw-r-- 1 reader reader  69 Jul 14 13:18 nanofile.txt
-rwxr-xr-- 1 reader reader   0 Aug  4 13:44 renamedtestfile
reader@ubuntu:~/renamedtestdir$

正如我们所看到的,我们在renamedtestdir中找回了我们的文件!实际上,我们从未删除原始文件,所以这些是副本。在你开始提取和清理所有东西之前,你可能想知道 tarball 里面有什么。这可以通过使用-t选项而不是-x来实现:

reader@ubuntu:~/renamedtestdir$ tar tzvf my-first-archive.tar.gz 
-rw-rw-r-- reader/reader 69 2018-08-19 11:54 nanofile.txt
-rw-rw-r-- reader/reader  0 2018-08-19 11:54 renamedtestfile
reader@ubuntu:~/renamedtestdir$

tar广泛使用的最后一个有趣选项是-C--directory选项。这个命令确保我们在提取之前不必移动存档。让我们使用它将/home/reader/renamedtestdir/my-first-archive.tar.gz从我们的home目录提取到/home/reader/umaskdir/中:

reader@ubuntu:~/renamedtestdir$ cd
reader@ubuntu:~$ tar xzvf renamedtestdir/my-first-archive.tar.gz -C umaskdir/
nanofile.txt
renamedtestfile
reader@ubuntu:~$ ls -l umaskdir/
total 4
-rw-rw-r-- 1 reader reader 69 Jul 14 13:18 nanofile.txt
-rwxr-xr-- 1 reader reader  0 Aug  4 13:44 renamedtestfile
-rw-rw---- 1 reader games   0 Aug  4 16:18 umaskfile
reader@ubuntu:~$

通过在存档名称后指定-C和目录参数,我们确保tar将 gzipped tarball 的内容提取到指定的目录中。

这涵盖了tar命令的最重要方面。然而,还有一件小事要做:清理!我们在home目录下搞得一团糟,而且那里没有任何真正有用的文件。以下是一个实际示例,展示了带有rm -r命令的通配符有多危险:

reader@ubuntu:~$ ls -l
total 12
-rw-rw-r-- 1 reader reader   69 Jul 14 13:18 nanofile.txt
drwxrwxr-x 2 reader reader 4096 Aug 19 10:42 renamedtestdir
-rwxr-xr-- 1 reader reader    0 Aug  4 13:44 renamedtestfile
drwxrwx--- 2 reader reader 4096 Aug 19 10:47 umaskdir
reader@ubuntu:~$ rm -r *
reader@ubuntu:~$ ls -l
total 0
reader@ubuntu:~$

一个简单的命令,没有警告,所有文件,包括更多文件的目录,都消失了!如果你在想:不,Linux 也没有回收站。这些文件已经消失了;只有高级硬盘恢复技术可能还能够恢复这些文件。

确保你执行了前面的命令,以了解rm有多具有破坏性。然而,在你执行之前,请确保你在你的home目录下,并且不要意外地有任何你不想删除的文件。如果你遵循我们的示例,这不应该是问题,但如果你做了其他事情,请确保你知道自己在做什么!

查找文件

在学习了常见的文件操作和存档之后,还有一个在文件操作中至关重要的技能我们还没有涉及:查找文件。你知道如何复制或存档文件是非常好的,但如果你找不到你想要操作的文件,你将很难完成你的任务。幸运的是,有专门用于在 Linux 文件系统中查找和定位文件的工具。简单来说,这些工具就是findlocatefind命令更复杂,但更强大,而locate命令在你确切知道你要找的东西时更容易使用。首先,我们将向你展示如何使用locate,然后再介绍find更广泛的功能。

定位

在 locate 的 man 页面上,描述再合适不过了:“locate - 按名称查找文件”。locate命令默认安装在您的 Ubuntu 机器上,基本功能就是使用locate <filename>这么简单。让我们看看它是如何工作的:

reader@ubuntu:~$ locate fstab
/etc/fstab
/lib/systemd/system-generators/systemd-fstab-generator
/sbin/fstab-decode
/usr/share/doc/mount/examples/fstab
/usr/share/doc/mount/examples/mount.fstab
/usr/share/doc/util-linux/examples/fstab
/usr/share/doc/util-linux/examples/fstab.example2
/usr/share/man/man5/fstab.5.gz
/usr/share/man/man8/fstab-decode.8.gz
/usr/share/man/man8/systemd-fstab-generator.8.gz
/usr/share/vim/vim80/syntax/fstab.vim
reader@ubuntu:~$

在前面的例子中,我们搜索了文件名fstab。我们可能记得我们需要编辑这个文件来进行文件系统更改,但我们不确定在哪里可以找到它。locate向我们展示了磁盘上包含fstab的所有位置。如你所见,它不必是一个精确的匹配;包含fstab字符串的所有内容都将被打印出来。

你可能已经注意到locate命令几乎立即完成。这是因为它使用一个定期更新的数据库来存储所有文件,而不是在运行时遍历整个文件系统。因此,信息并不总是准确的,因为更改不会实时同步到数据库中。为了确保你使用的是文件系统的最新状态与数据库进行交互,请确保在运行locate之前运行sudo updatedb(需要 root 权限)。这也是在系统上首次运行locate之前所需的,否则就没有数据库可供查询!

Locate 有一些选项,但根据我们的经验,只有当你知道确切的文件名(或文件名的一部分)时才会使用它。对于其他搜索,最好默认使用find命令。

find

find 是一个非常强大但复杂的命令。你可以使用find做以下任何一件事情:

  • 按文件名搜索

  • 按权限搜索(用户和组)

  • 按所有权搜索

  • 按文件类型搜索

  • 按文件大小搜索

  • 按时间戳搜索(创建时间,最后修改时间,最后访问时间)

  • 仅在特定目录中搜索

解释find命令的所有功能需要一整章的篇幅。我们只会描述最常见的用法。真正的教训在于了解find的高级功能;如果你需要查找具有特定属性的文件,一定要首先考虑使用find命令,并查看man file页面,看看是否可以利用find进行搜索(剧透:几乎总是这样!)。

让我们从 find 的基本用法开始:find <位置> <选项和参数>。如果没有任何选项和参数,find 将打印出位置内找到的每个文件:

reader@ubuntu:~$ find /home/reader/
/home/reader/
/home/reader/.gnupg
/home/reader/.gnupg/private-keys-v1.d
/home/reader/.bash_logout
/home/reader/.sudo_as_admin_successful
/home/reader/.profile
/home/reader/.bashrc
/home/reader/.viminfo
/home/reader/.lesshst
/home/reader/.local
/home/reader/.local/share
/home/reader/.local/share/nano
/home/reader/.cache
/home/reader/.cache/motd.legal-displayed
/home/reader/.bash_history
reader@ubuntu:~$

你可能以为你的home目录是空的。实际上,它包含了相当多的隐藏文件或目录(以点开头),这些文件被find找到了。现在,让我们使用-name选项应用一个过滤器:

reader@ubuntu:~$ find /home/reader/ -name bash
reader@ubuntu:~$ find /home/reader/ -name *bash*
/home/reader/.bash_logout
/home/reader/.bashrc
/home/reader/.bash_history
reader@ubuntu:~$ find /home/reader/ -name .bashrc
/home/reader/.bashrc
reader@ubuntu:~$

与你可能期望的相反,findlocate在部分匹配文件方面的工作方式不同。除非在-name参数的参数周围添加通配符,否则它只会匹配完整的文件名,而不是部分匹配的文件。这绝对是需要记住的事情。那么,仅查找文件而不是目录呢?为此,我们可以使用-type选项和d参数表示目录,或者使用f表示文件:

reader@ubuntu:~$ find /home/reader/ -type d
/home/reader/
/home/reader/.gnupg
/home/reader/.gnupg/private-keys-v1.d
/home/reader/.local
/home/reader/.local/share
/home/reader/.local/share/nano
/home/reader/.cache
reader@ubuntu:~$ find /home/reader/ -type f
/home/reader/.bash_logout
/home/reader/.sudo_as_admin_successful
/home/reader/.profile
/home/reader/.bashrc
/home/reader/.viminfo
/home/reader/.lesshst
/home/reader/.cache/motd.legal-displayed
/home/reader/.bash_history
reader@ubuntu:~$

第一个结果显示了/home/reader/内的所有目录(包括/home/reader/!),而第二个结果打印了所有文件。你可以看到,没有重叠,因为在 Linux 下,文件总是只有一种类型。我们还可以组合多个选项,比如-name-type

reader@ubuntu:~$ find /home/reader/ -name *cache* -type f
reader@ubuntu:~$ find /home/reader/ -name *cache* -type d
/home/reader/.cache
reader@ubuntu:~$

我们首先在/home/reader/中寻找包含字符串 cache 的文件find命令没有打印任何内容,这意味着我们没有找到任何东西。然而,如果我们寻找包含 cache 字符串的目录,我们会看到/home/reader/.cache/目录。

最后一个例子,让我们看看如何使用find来区分不同大小的文件。为此,我们将使用touch创建一个空文件,使用vim(或nano)创建一个非空文件:

reader@ubuntu:~$ ls -l
total 0
reader@ubuntu:~$ touch emptyfile
reader@ubuntu:~$ vim textfile.txt
reader@ubuntu:~$ ls -l
total 4
-rw-rw-r-- 1 reader reader  0 Aug 19 11:54 emptyfile
-rw-rw-r-- 1 reader reader 23 Aug 19 11:54 textfile.txt
reader@ubuntu:~

从屏幕上的023可以看出,emptyfile包含 0 字节,而textfile.txt包含 23 字节(这不是巧合,它包含了 23 个字符的句子)。让我们看看如何使用find命令找到这两个文件:

reader@ubuntu:~$ find /home/reader/ -size 0c
/home/reader/.sudo_as_admin_successful
/home/reader/.cache/motd.legal-displayed
/home/reader/emptyfile
reader@ubuntu:~$ find /home/reader/ -size 23c
/home/reader/textfile.txt
reader@ubuntu:~$

为此,我们使用了-size选项。我们给出了我们要查找的数字,后面跟着一个表示我们正在处理的范围的字母。c用于字节,k用于千字节,M用于兆字节,依此类推。您可以在手册页上找到这些值。正如结果所示,有三个文件的大小正好为 0 字节:我们的emptyfile就是其中之一。有一个文件的大小正好为 23 字节:我们的textfile.txt。您可能会想:23 字节,那非常具体!我们怎么知道文件的确切字节数呢?好吧,您不会知道。find的创建者还实现了大于小于的结构,我们可以使用它们来提供更多的灵活性:

reader@ubuntu:~$ find /home/reader/ -size +10c
/home/reader/
/home/reader/.gnupg
/home/reader/.gnupg/private-keys-v1.d
/home/reader/.bash_logout
/home/reader/.profile
/home/reader/.bashrc
/home/reader/.viminfo
/home/reader/.lesshst
/home/reader/textfile.txt
/home/reader/.local
/home/reader/.local/share
/home/reader/.local/share/nano
/home/reader/.cache
/home/reader/.bash_history
reader@ubuntu:~$ find /home/reader/ -size +10c -size -30c
/home/reader/textfile.txt
reader@ubuntu:~$

假设我们正在寻找一个至少大于 10 字节的文件。我们在参数上使用+选项,它只打印大于 10 字节的文件。然而,我们仍然看到了太多的文件。现在,我们希望文件也小于 30 字节。我们添加另一个-size选项,这次指定-30c,意味着文件将小于 30 字节。并且,毫不意外,我们找到了我们的 23 字节的testfile.txt

所有前述选项以及更多选项都可以组合在一起,形成一个非常强大的搜索查询。您是否正在寻找一个文件,它至少有 100 KB,但不超过10 MB,在/var/中的任何位置,在上周创建,并且对您是可读的?只需在find中组合选项,您肯定会在短时间内找到该文件!

总结

本章描述了 Linux 中的文件操作。我们从常见的文件操作开始。我们解释了如何在 Linux 中使用cp复制文件,以及如何使用mv移动或重命名文件。接下来,我们讨论了如何使用rm删除文件和目录,以及如何使用ln -s命令在 Linux 下创建符号链接。

在本章的第二部分中,我们讨论了归档。虽然有许多不同的工具可以进行归档,但我们专注于 Linux 中最常用的工具:tar。我们向您展示了如何在当前工作目录和文件系统的其他位置创建和提取归档。我们描述了tar可以归档文件和整个目录,并且我们可以使用-t选项在不实际提取它的情况下查看 tarball 中的内容。

我们以使用filelocate查找文件结束了本章。我们解释了locate是一个在特定情况下有用的简单命令,而find是一个更复杂但非常强大的命令,可以为掌握它的人带来巨大的好处。

本章介绍了以下命令:cprmmvlntarlocatefind

问题

  1. 我们在 Linux 中使用哪个命令来复制文件?

  2. 移动和重命名文件之间有什么区别?

  3. 用于在 Linux 下删除文件的rm命令为什么可能很危险?

  4. 硬链接和符号(软)链接之间有什么区别?

  5. tar的三种最重要的操作模式是什么?

  6. tar用于选择输出目录的选项是什么?

  7. 在搜索文件名时,locatefind之间最大的区别是什么?

  8. find有多少个选项可以组合使用?

进一步阅读

以下资源可能会很有趣,如果您想深入了解本章的主题:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值