Linux 终端基础提示和技巧(一)
原文:Basic Linux Terminal Tips and Tricks
协议:CC BY-NC-SA 4.0
一、Linux 优先
今天成长起来的大多数人都是通过图形界面接触计算机的,无论是通过视频游戏控制台、笔记本电脑还是 iPad。因为大多数人与计算机的交互是通过某种图形界面来完成的。尽管图形界面很流行,但大多数严肃的编程和系统管理仍然是在命令行级别完成的。
图形越来越好。语音激活计算、可穿戴设备和物联网等创新正在引入更多与计算机交互的方式。桌面和移动设备上流行的操作系统的新版本在不断变化。然而,在从物联网到安卓的大多数新系统下,似乎都有一个命令行世界,它以稳定的状态存在,而构建在其上的一切都在变化。
Linux 操作系统和终端命令行作为输入方法的持久性证明了它的效率和有用性。虽然黑色背景上简单的绿色文本可能看起来过时了,但它实际上是通向神奇效率的大门。每个命令都像一个咒语。只需敲几下键盘,我们就可以完成使用鼠标和图形用户界面需要花费很长时间才能完成的任务和技巧。在某些情况下,我们甚至可以在命令行上做一些用 GUI 做不到的事情。
这本书是为那些想从命令行的角度探索 Linux 的人而写的——无论你是一个完全的新手,从零开始学习命令行,还是你已经熟悉了 Linux 机器,但想学习一些新的命令和实用程序,这些都会派上用场。
在这一章中,我们将看看什么是 Linux,回顾一些流行的发行版(或发行版),并看看一些处理文件和目录的基本命令。
什么是 Linux
在本书中,我们将探讨不同的命令行应用、内置命令和技术。在我们开始之前,有必要先了解一下“什么是 Linux”一些技术定义将只包括 Linux 内核(与计算机底层硬件一起工作的核心部分)。
通常这样的定义将发行版定义为 GNU/Linux。GNU 是一个递归的首字母缩写词,代表“GNU 不是 Linux”它指的是所有(或几个)开源应用,这些应用普遍与 Linux 内核捆绑在一起。这些工具包括bash、coreutils、grep、groff、grub和readline,这里仅举几个例子。也就是说,并不是所有 Linux 内核附带的工具都是由 GNU 组织创建的。
在普通语言中,Linux 指的是围绕 Linux 内核构建的操作系统。这包括内核、预装的软件以及两者之间的一切。为了简单和与通用语言兼容,我们将 Linux 称为整个操作系统,而不仅仅是内核。
Unix 与 Linux
Linux 实际上是被称为“类 Unix”操作系统的更大的操作系统组的一部分。这些操作系统的灵感都来自于 1970 年发布的最初的 Unix 操作系统。除了 Linux 之外,它还包括几个操作系统家族:
-
MacOS(自 2015 年起)
-
Android(构建在修改后的 Linux 内核上)
-
Linux 操作系统
-
操作系统
-
加州大学伯克利分校软件(Berkeley Software Distribution)
-
NetBSD
看到 MacOS 和 Android 等流行的操作系统列在这里,你可能会感到惊讶。最初的 Unix 操作系统的影响是深远的。最初的 Unix 操作系统有一些至今仍然存在的关键特性,包括
-
硬件和用户空间之间的内核
-
所有数据存储为文件
-
用户和权限系统
-
至今仍在使用的目录布局(因操作系统而异)
在图 1-1 中,显示了利用 PDP-11/20 ( 设备,在实验显示系统标签的最右下方)的设置。这是 20 世纪 70 年代运行 Unix 的早期系统的一个例子。
图 1-1
PDP 11/35,PDP-11/20 的微程序后继者;设计团队由吉姆·奥洛夫林领导
基于 Unix 的标准的广泛采用产生了深远的影响。本书中涉及的许多核心命令和实用程序实际上可以在 Linux 之外的系统上运行。如果你在 Mac 甚至 Android 上打开一个终端会话,你会发现这里的许多命令都工作得很好。甚至 Windows 现在也包括一个可选的 Linux 子系统,以及它们自己的命令行系统上的各种别名,这些别名将 Linux 命令定向到它们的 Windows 等价物,例如'ls',它是 Windows 上'dir'的别名。
POSIX 标准
POSIX 代表便携式操作系统接口。它定义了脚本的标准语法和应该可用的实用程序列表。它用于保证类 Unix 系统之间的兼容性。如果一个程序或操作系统是 POSIX 兼容的,那么 bash 脚本可以在其上运行。
POSIX 还保证您可以访问一系列实用程序,其中包括cat、awk、cut、grep和kill,这里仅举几例。它还定义了指定实用程序的行为方式。以前,一些实用程序有相互竞争的实现,这给可移植性带来了问题。
选择发行版
大多数人在切换到 Linux 时面临的第一个重大决定是使用什么发行版*(通常称为 distro)* 。通常,人们最终会使用朋友或同事正在使用的产品,或者只是我们第一次听说的产品。有几十种流行的发行版,每一种都有它擅长的优点、缺点和特定的用例。
在这本书里,我将使用安装在 Ubuntu 和其他基于 Debian 的操作系统上的软件包管理器apt-get。也就是说,几乎所有这些都可以在任何 Linux 发行版上很好地工作;只需要找到您的发行版中有问题的包,并通过提供的包管理器安装它,手动安装它,甚至从源代码构建它。
对于许多流行的 Linux 发行版来说,这是有争议的。我个人选择使用 Ubuntu 主要归结于兼容性方面。Ubuntu 是桌面用户使用最广泛的 Linux 发行版。如果你在一家使用 Linux 的公司工作,你会发现使用那里使用的发行版很有用。尽管 bash 和 Linux 的其他方面具有可移植性,但发行版和 bug 之间还是存在差异,它们可能存在于一个发行版中,而不存在于另一个发行版中。因此,如果你在使用 Linux Mint,但其他人都在使用 Fedora,你可能会引入不必要的摩擦。
在我为 Linux 开发基于 GUI 的应用的经验中,我发现如果我在开发过程中使用一个不同于最终用户的发行版,通常会有视觉上的差异,这将极大地影响设计,或者在某些情况下只在一个发行版中出现错误。例如,最终用户系统和开发系统之间的系统字体系列或字体大小可能不同。尽管在大多数情况下,核心功能可以在大多数 Linux 系统上运行。Linux 发行版之间的高度可变性是许多游戏行业仍然没有提供对基于 Linux 的操作系统的完全支持的一个原因。
从一个流行的发行版开始的另一个好处是,当阅读在线教程和项目文档时,你经常会看到更流行的 Linux 发行版(如 Ubuntu)的说明,但对于不太流行的发行版却没有。此外,如果您确实有问题,并且您想提交一个 bug,该项目可能不会为较少使用的发行版提供支持。
也就是说,运行较少使用的发行版有很大的好处。作为一个年轻的业余爱好者,我发现运行像 Arch Linux 这样的最小发行版迫使我学习关于 Linux 发行版的组成部分以及如何使用命令行导航和修复我的系统的概念。通常,这种学习过程表现为实验、破坏系统、修复系统,在某些情况下,不得不重新安装所有东西并从头开始。
如果探索发行版和 Linux 的内部工作方式让你兴奋,我鼓励你去探索那些很少被使用并且可能更难安装的 Linux 发行版。Gentoo 甚至要求用户从代码中编译所有使用的程序。在像 Gentoo 或 Arch Linux 这样的发行版上启动和运行本身就是一项成就和一个学习过程。
除了便利性和学习过程的因素,考虑一个发行版优于另一个发行版的地方也很重要。例如,Arch Linux 对于为嵌入式或低端机器编译定制操作系统特别有用。Kali Linux 因其在渗透测试中的使用而臭名昭著。基于 Red Hat 的发行版通常用于企业服务器。如果您对渗透测试感兴趣,Kali 是显而易见的选择;如果你想成为一家企业公司的系统管理员,你可能想熟悉 Fedora。
开放源码软件的分支
在接下来的几节中,我们将看看一些更流行的 Linux 发行版。我包含了一个显示操作系统家族分支的树,让您了解不同流行操作系统之间的关系。请记住,父/子操作系统之间的关系可能会有很大不同(参见图 1-2 )。
图 1-2
Linux 发行系列
一种自由操作系统
Debian 于 1993 年首次发布,是几个流行的 Linux 发行版的主干,包括 Ubuntu、Kali 和 Linux Mint。它以拥有非常好的打包系统apt而闻名,是 Advanced Package Tool 的简称。我们将在整本书中使用apt,它可以在后面列出的所有基于 Debian 的操作系统上使用,尽管通过管理器可用的包在不同的操作系统之间会有所不同。
要用apt安装软件包,只需运行下面的命令,用<package>替换您要安装的软件包:
sudo apt-get install <package>
Note
sudo在前面的命令中是一个关键字,允许非根用户对文件或系统方面进行可能影响其他用户的更改。使用sudo时,系统会提示您输入密码,然后才能以 root 用户身份执行命令。如果您运行一个命令并得到“权限被拒绝”的消息,您可以简单地在命令前面加上sudo,它应该会工作。也就是说,在使用sudo之前,请确保您理解相关命令,因为 root 权限允许您修改对操作系统运行至关重要的系统文件。
人的本质
Ubuntu 是桌面上最流行的 Linux 发行版,尽管它通常也用于服务器。它建立在 Debian 的基础上,增加了对几个非自由软件二进制文件和编解码器的支持,改善了用户在线观看视频和玩游戏的体验。
它拥有 Debian 的许多优点,如强大的包管理器和稳定性,但也是为了提供良好的桌面体验。目前最新版本的 Ubuntu 使用的是 GNOME 界面(即操作系统的 GUI 桌面,位于底层软件之上)。
Ubuntu 有几个版本使用其他桌面用户界面,可能更适合低端硬件,比如使用xfce的 Xubuntu 和使用lxqt的 Lubuntu。这些发行版通常只是切换出了界面,而没有改变太多。如果你看一下图 1-3 ,它显示了 Linux 操作系统的堆栈,Xubuntu/Lubuntu 之间的区别在很大程度上仅仅是切换出接口层,以及交换一些预装的应用。
一个比较是,像脸书这样的网络应用通常有不同的接口来访问相同的核心功能。当使用我的手机时,我可以通过他们的网站、他们的应用,甚至是一个名为脸书 Lite 的轻量级版本访问脸书。所有这些界面看起来不同,可能有增强或限制,但最终所有的功能(发布、查看、喜欢等。)访问相同的核心功能。
图 1-3
Linux 操作系统堆栈
迦利
Kali 是一个基于 Debian 的发行版,主要关注攻击性安全工具。它包括几个用于数字取证、渗透测试和逆向工程的预装工具。它带有 600 多个预装工具,包括 Wireshark、Aircrack-ng 和 Burp Suite。这使得它对渗透测试器特别有用。也就是说,不建议日常使用。
同样值得注意的是,这些工具可以通过包管理器或者直接下载安装在其他系统上。对于包管理者来说,这个过程相当简单,但是当直接下载时,这个过程会因工具而异。例如,有些包可能是通过 Python 的包管理器 Pip 安装的,而其他包则需要您编译一个二进制文件或下载一个预编译的二进制文件,并将其放在系统可以找到可执行文件的文件夹中,例如/usr/bin。
使用 Kali 的另一个好处是,其他发行版上的软件包管理器上的工具版本可能是旧版本。由于这些包只是需要维护者打包、审查和更新的众多包中的一个,所以它们可能会落后,而在 Kali 上,与 Linux 安全相关的工具是焦点,因此需要付出更多的努力来保持它们的最新。
铸造
Mint 是另一个基于 Ubuntu 的发行版,它已经变得相对流行。Linux Mint 是基于 Ubuntu 的,但是它有一个利用 Cinnamon 桌面环境的替代界面。它和 Ubuntu 有相同的库,所以本书中的任何apt-get install命令都应该提供和 Ubuntu 相同的结果。
波波!波波
PopOS 是另一个基于 Ubuntu 的发行版,它获得了广泛的关注。它提供了一个替代 Ubuntu 的桌面体验,有相同的软件包,加上一些通过 PPAs(个人软件包档案)提供的额外的软件包。这些额外的软件包包括 Nvidia 图形驱动程序、Steam 和 Spotify 等其他流行程序。操作系统由 System76 维护,system 76 是一家总部位于科罗拉多州的计算机制造商,专注于生产高质量的硬件并重视开源软件。
一种男式软呢帽
Fedora 是由 Red Hat Linux 支持的社区驱动的操作系统。Fedora 是许多功能的试验场,这些功能最终被引入 RHEL (Red Hat Enterprise Linux),这是 Red Hat 的主要产品。对于 Fedora、RHEL 和 CentOS,使用命令安装软件包
dnf install <package>
在某些情况下,如果你使用的是 Fedora/RHEL 系统,你可以简单地用apt-get替换dnf。在其他情况下,包的名称可能会稍有不同,或者它可能不作为包提供。
RHEL(红帽企业 Linux)
虽然 Ubuntu 可能是桌面上最流行的发行版,但 RHEL 可能是企业服务器上使用最多的。它既是开源的,也是付费的产品,包含企业级的支持。如果你打算在企业环境中做系统管理员,你很可能会和 RHEL 一起工作。
重点是安全性、稳定性和速度。由于这个原因,与 Ubuntu 或 Red Hat 支持的更开放的版本 Fedora 相比,可用的包可能更少。
CentOS
虽然 Fedora 是 RHEL 的一个更具实验性和开放性的免费版本,但 CentOS 本质上与 RHEL 是相同的操作系统,但完全免费。购买 RHEL 许可证时,会附带 CentOS 不包含的支持。CentOS 为学习 RHEL 提供了一个很好的操作系统,或者不需要外部支持就可以简单地使用它。
斯拉克语
Slackware 是一个 Linux 发行版,可以追溯到 1993 年。它有一小群忠实的粉丝,但已经超过 3 年没有发行了,而以前它每年至少发行一次。
OpenSUSE
OpenSUSE 最初源自 Slackware,但它已经长出了自己的一套腿,如今几乎没有什么联系。虽然 Slackware 一直缺乏更新,但 OpenSUSE 仍然非常活跃,并拥有与 RHEL 类似的大公司支持。
拱门
Arch Linux 是 Linux 的一个分支,它是高度可定制的,专注于滚动发布包管理器。滚动发布包管理器意味着所提供的包可能是最新的。与大多数操作系统不同,没有主要版本。这是通过去掉在其他发行版的主要版本发布之前审查和确认任何变更的包维护人员来实现的。滚动发布意味着有可能获得最新版本的应用,但不利的一面是,在审核方面投入的精力较少,这可能会导致稳定性和安全性问题。
使用pacman软件包管理器安装软件包:
sudo pacman -S <package>
Arch Linux 的另一个值得注意的方面是,默认情况下,它不附带运行桌面体验所需的软件。取而代之的是由用户来选择特定的程序,比如声音、窗口管理器和图形界面。
曼哈罗
Manjaro 是 Arch Linux 的一个版本,它通过预配置的桌面体验解决了入门的困难。Ubuntu 有几种变体,比如 Xubuntu 和 Kubuntu。
巴布亚企鹅
Gentoo 是一个高度可定制的 Linux 版本,它允许定制到内核级别。Gentoo 不是下载预编译的应用,而是在本地计算机上编译源代码。当需要高度定制的体验时,它特别有用。
阿尔卑斯 Linux
Alpine Linux 是一个发行版,基本上没有人将其作为桌面或服务器的主要发行版,但它作为 Docker 容器的基础映像却非常受欢迎。如果您使用或修改 Docker 容器,您可能会遇到 Alpine Linux。它非常小,默认情况下几乎没有应用,尽管它有自己的包管理器apk。
如果您有一个应用或进程想要用 Docker 进行容器化,那么可以考虑 Alpine Linux。这里的许多程序和脚本都是兼容的,尽管如果使用任何程序,你必须先用apk安装它们。
常见命令
在接下来的小节中,我们将会看到一些常见的命令,其中大部分都是默认安装在大多数 Linux 系统上的。
使用 man 命令阅读手册
我将在本书中提到许多程序。对于其中的大部分,我最多只会谈到它们的 5–10%的使用情况。如果您想更深入地研究这些程序,学习几乎所有 Linux 操作系统上都有的man命令是很重要的。
man是手动的简称。它的使用方法是运行命令并传入另一个 Linux 命令行程序的名称。例如,如果我们想获得关于命令ls的更多信息,我们可以运行以下命令:
man ls
这将返回程序的描述以及如何使用它,如图 1-4 所示。
图 1-4
手册页的示例
我鼓励你在探索 Linux 操作系统时经常使用man,因为它通常可以节省你进行冗长的互联网搜索的时间。您可以使用箭头键和 page up 或 page down 按钮来浏览手册页。
如果您需要搜索手册页来查找某个特定的关键字,有一个内置的搜索功能。要进行搜索,请按/键,然后键入您的术语并按 enter 键。如果存在,您将被带到第一个事件。若要前往下一个事件,请轻按n;每次点击n都会将你带到下一个实例。如果你想返回一个实例,按下大写N,和n一样,每按一次都返回一个实例。
编号手册页
在某些情况下,一个程序可能有多个手册页。例如,用程序stat,我们可以运行
man 1 stat
或者我们可以跑
man 2 stat
这些命令将把我们带到涉及程序不同方面的不同手册页。参见表 1-1 了解不同页码的列表及其包含的信息。并非所有程序都包含每种类型的页面。例如,printf有一个包含 C 库函数信息的第 3 页,但没有第 2 页。
表 1-1
编号手册页中的信息描述
|页码
|
描述
| | --- | --- | | one | 用户命令 | | Two | 系统调用 | | three | c 库函数 | | four | 设备和特殊文件 | | five | 文件格式和约定 | | six | 比赛 | | seven | 多方面的 | | eight | 系统管理工具和守护程序 |
在大多数情况下,我们会从用户命令的角度对程序感兴趣,所以我们可以只运行man而不使用默认为 1 的编号或找到的编号最小的页面。
man man
man -s 6 --regex -k '.*'
如果您想在自己的系统上看到表 1-1 中显示的列表,您可以运行“man man”。你可能想知道他们真的是游戏专用的页码吗?答案是肯定的。至少从 20 世纪 80 年代的 Unix System V 开始,游戏部分就已经包含在内了。它很少被使用,但是如果你运行下面的命令,你会得到一个在你的系统上使用它的包的列表。你可能会发现一些有趣的复活节彩蛋程序,比如“??”,这是一个安装在许多机器上的玩笑程序,声称可以读取用户的想法。
用于导航的有用命令
表 1-2 中列出了一些您想要熟悉的用于导航和创建新文件夹的命令。
表 1-2
用于导航和处理文件/目录的命令
|命令
|
描述
| | --- | --- | | 限位开关(Limit Switch) | 列出目录内容 | | 激光唱片 | 更改目录 | | 显示当前工作目录 | 打印工作目录 | | 创建目录 | 制作目录 | | 是吗 | 删除目录(仅在空的情况下有效 |
用 ls 和 cd 导航文件系统
大多数用户在进入文件系统时学习的第一个命令是ls,它是“列出目录内容”的缩写,还有cd,它是“更改目录”的缩写。
只知道这两个命令就可以导航文件系统——首先通过运行ls来查看当前目录中的文件和文件夹,然后使用带有其中一个文件夹名称的cd来导航。
使用cd时要记住一点。任何时候你都可以不带任何文件夹名运行cd返回到你的主目录。如果你用cd进入一个目录,并想返回到包含你所在的目录,你可以使用..,例如:
cd ..
或者,如果您想向上返回两个文件夹:
cd ../..
你可能想用ls做的一些常见的事情是列出文件和文件夹名称之外的其他细节;这可以通过-l标志来实现:
ls -l
如果您想按最后修改时间排序,您可以使用-t标志,它最好与-l结合使用:
ls -lt
如果您想反转结果,使最旧的文件在顶部,添加-r标志进行反转:
ls -ltr
您应该得到类似于图 1-5 所示的输出。
图 1-5
运行 ls -ltr 的结果,l 表示附加信息,t 表示按修改时间排序,r 表示以相反的顺序显示结果
不可见文件(点文件)
重要的是要知道,在 Linux 中以.开头的文件在使用ls或图形文件浏览器时通常不会出现。这些文件是用于配置的,为了方便起见被隐藏起来了。我们经常想要编辑或查看这些文件,所以了解ls的-a标志是很重要的。-a代表全部,将显示所有文件,包括隐藏的文件。
ls -a
用密码获取当前目录
有了所有这些导航,很容易忘记您在文件系统上的确切位置。如果发生这种情况,有一个简单的方法来确定你的确切位置。只需运行pwd即可返回您当前位置的完整路径。
pwd
制作目录
导航文件系统的一部分是创建新的目录来存放文件和子文件夹。使用mkdir命令相对容易,它接受文件夹名,并根据您的当前位置创建目录。例如,如果我们在主目录中运行以下命令:
mkdir music
我们将以一个名为“音乐”的文件夹结束。一次可以创建多少个是没有限制的。假设我们想要创建另外两个子文件夹,我们可以运行
mkdir music/rock music/classical
也可以使用完整路径而不是相对路径。例如,如果我在我的主目录中,我想在我的/tmp文件夹中创建一个新文件夹:
mkdir /tmp/test
这不是mkdir独有的;基本上所有可以使用相对路径的程序也允许你使用完整路径;它只要求路径以“/”开头。
递归创建目录
通常在创建一个文件夹时,你已经有了一个由多个目录组成的结构。例如,假设我们要创建一个名为 movies 的新文件夹,其中一个子文件夹是关于恐怖片的,另一个子文件夹是关于 2012 年的。如果我们逃跑
mkdir movies/horror/2012
我们将返回一个错误,说“没有这样的文件或目录”。标签提供了一种解决方法。-p代表创建父目录,意思是如果我们要创建的目录的父目录不存在,就会被创建。运行以下命令可以像预期的那样工作,给我们留下三个新文件夹:
mkdir -p movies/horror/2012
删除目录
创建目录后,您可能决定要删除它。一种方法是使用与mkdir类似的rmdir命令;只需将您想要删除的目录的名称传递给它:
rmdir music/classical
不幸的是,rmdir有一个主要的限制,它只能删除一个完全空的目录。试图在任何包含文件或子目录的目录上使用rmdir将返回“目录不为空”。因此,实际上许多人总是使用这个命令
rm -r music
该命令中的-r代表递归。这个命令很实用,因为不管目录是否包含任何内容,它都可以对文件和目录起作用。
使用文件
一旦你可以浏览目录,下一件你想做的事就是处理文件——创建、删除、复制文件,以及读取文件和比较它们的内容。
编辑文件
我们在上一节简单提到了nano;这是一个简单的文本编辑器,类似于大多数人熟悉的记事本。您只需通过将文件位置作为命令参数传递来打开文本文件:
nano /tmp/myFile.txt
文件将打开,如果不存在,将创建一个文件。您可以像在大多数文本编辑器上一样输入文本,按 backspace 删除文本,并使用箭头键导航。在屏幕的底部,显示了可以执行的操作列表,例如 ctrl+x 退出。
在后面的章节中,我们将会看到更强大的编辑器 Vim 和 Emacs,但是如果你发现它们很难,并且妨碍了你学习或者做你想做的事情,你总是可以求助于nano或者基于 GUI 的文本编辑器。
用于处理文件的命令
表 1-3 中列出了一些使用 Linux 的最有用和最基本的命令。这些命令在处理文件时非常方便。大多数是通过提供文件名作为参数来使用的。您可以在任何列出的命令上使用man <command>来获得额外的信息。我们将详细了解这些命令是如何工作的,其用法如下。
表 1-3
用于处理文件的命令
|命令
|
描述
| | --- | --- | | 触控 | 创建文件或更新现有文件的时间戳 | | 猫 | 输出文件的全部内容 | | 头 | 从顶部开始返回文件的前 X 行 | | 尾巴 | 从底部开始返回文件的前 X 行 | | 丙酸纤维素 | 复制文件或目录 | | 空间 | 删除文件或目录 | | 平均变化 | 移动文件或文件夹 | | 较少的 | 显示文件内容,同时允许轻松上下滚动 | | 差速器 | 比较两个文件的差异 | | 金属波纹管 | 逐字节检查两个文件是否相同 | | 文件 | 获取有关文件类型的信息 |
使用触摸实用程序创建文件或更新时间戳
有时您希望创建一个空白文件,作为您计划以后编辑的占位符,或者可能作为一个指示器,例如,一个锁定文件。touch命令允许您快速创建一个或多个空白文件。只需运行命令并使用所需的文件名作为参数,例如:
touch notes.txt
或对于多个:
touch file1 file2 file3
touch 命令还可以用来更新文件的时间戳。运行脚本后,您可能希望用 touch 更新一个未使用的文件,以便您可以留下脚本完成时的一些痕迹。例如,您可能会更新日志文件的时间戳,尽管没有添加任何新的日志,因此其他人*(人* *或程序)*可以推断脚本运行并且没有生成日志。这与创建文件的方式完全相同,只不过不是提供要创建的文件的路径,而是提供现有文件的路径:
touch log.txt
在对现有文件执行touch之后,您可以在该文件的目录中使用ls -l来确认时间戳已经更新。需要注意的是touch永远不会修改现有文件的内容,所以不要担心用空白文件覆盖任何现有内容。
使用 Cat 获取文件内容
使用命令行时,cat是需要知道的最有用的命令之一。cat简单地获取一个文件的内容并将它们输出到命令行。这允许您或者直观地看到该文件的内容(,而不需要打开和关闭程序),或者使用管道将该文件的内容作为其他程序的输入(,这是 bash shell 的一个方面,我们将在本书的后面部分中详细介绍)。
作为使用cat的一个例子,您可以运行以下命令,它将输出您系统上一个文件的内容:
cat /etc/passwd
这个文件列出了系统中的用户和一些相关信息,但是理解内容并不重要。这里重要的是,您可以使用cat获取系统上任何文件的内容,并将其显示为终端输出。
对头部或尾部不太满意
如果你理解了cat是做什么的,你就能很容易地理解head和tail命令。当您使用cat时,会返回一个文件的全部内容。对于大文件,这意味着你可以一次得到几页内容,并把你以前的所有工作和命令推到屏幕上。
如果你想得到一个文件的预览,但不想要整个文件,你可以使用head,它将返回一个文件的前 X 行。默认情况下,X 是 10,所以如果你运行
head /etc/passwd
你应该得到文件的前十行*(假设文件至少有十行)*。tail函数的工作方式与head完全相同,但它不是获取前 X 行,而是获取最后 X 行。因此,如果我们运行以下命令,我们将获得文件的最后十行:
tail /etc/passwd
如果您想要修改返回的行数,您可以使用-n 标志指定返回的行数,例如,如果我们想要前五行:
head -n 5 /etc/passwd
除了不要用大量文本填满屏幕之外,head和tail命令在编写脚本时也很有用,如果你确切知道某个文件中需要多少行的话。例如,您可能有一个脚本想要查看日志文件中的最后 20 行,以分析文本中的某些特定错误;在这种情况下,我们可以利用tail -n 20 filename将输出通过管道传输到您的解析脚本中(稍后将详细介绍管道和脚本)。
使用 cp 复制文件
如果你正在做系统管理或软件开发,很可能你会经常使用cp命令。这是一个非常简单但非常有用的命令,代表复制。使用该命令时,第一个参数是要复制的文件,第二个参数是要复制到的位置,例如,将file1复制到位置file2将使用:
cp file1 file2
运行前面的命令会产生一个名为file2的新文件,它包含与file1相同的内容。
除了复制文件,cp还可以用来复制整个文件夹。要对文件夹使用cp,您需要指定-r 标志,它代表递归(类似于对文件夹使用 rm 命令)。因此,复制文件夹与复制文件非常相似,例如:
cp -r folder1 folder2
使用 rm 删除文件
由于目录一节中rmdir的限制,我们已经使用了rm。请注意,rm命令主要用于删除文件,这样做时,不需要包含-r标志,例如:
rm file1
用 mv 移动文件
另一个非常流行的内置命令,mv允许您将文件或目录移动到一个新的位置。它的用法与cp非常相似,除了你最终只能得到一个文件,例如,如果我们使用
mv file1 file2
我们名为file1的文件现在将被命名为file2——类似于在 Mac 或 Windows 等图形桌面操作系统上移动文件。也像我们看到的许多其他命令一样,您可以对目录使用mv,但是使用mv不需要使用特殊的标志,您可以简单地使用
mv folder1 folder2
请注意,如果文件已经存在,mv将会在没有警告的情况下覆盖该文件。例如,如果我将文件 1 移动到文件 2,但文件 2 已经存在,我的原始文件 2 将永远丢失。如果你担心发生这种情况,有一个特殊的标志-i会在覆盖任何内容之前提示你。
用更少的资源交互式查看文件内容
我们提到了使用cat是如何变得令人头疼的,因为大文件输出最终会挤满您的 shell。我们提到了head和tail,它们允许你查看一小部分,但是在大多数情况下,我们希望选择查看整个文件,但是要慢慢滚动。这就是less的作用。
less不是输出文件的内容,而是打开一个独立于您终端的交互式浏览器,您可以在其中以自己的速度滚动浏览内容。与cat、tail和head一样,您只需将目标文件作为输入来运行命令:
less /etc/passwd
您将从文件的顶部开始,并能够使用箭头键和 page up/page down 按钮向下和向上滚动。这很像滚动手册页,你甚至可以访问相同的搜索方法 (vim 风格搜索)。也就是说,按下“/”,键入搜索词,然后按回车键。你将被带到这个术语的第一个实例,从那里你可以按n进入下一个实例,或者按N返回上一个实例(这种搜索方法也在 Vim 中使用,Vim 是一个文本编辑器,我们将在本书的后面讨论)。
Note
当你在 Linux 上探索不同的程序时,你可能会遇到more,并认为它与less相似,但又不同;毕竟命令head和tail就是这种情况。more实际上是less所基于的一个更老的程序。more功能较少,不可用,例如,可以向下滚动,但不能向上滚动。很可能你会在你的系统上找到more,但是我们建议在所有可能考虑more的情况下使用less。
比较文件
比较文件是一项你可能需要不时完成的任务,当然比mv或cat要少得多,但尽管如此,对于软件开发来说,它是与文件相关且有用的命令。有几个程序可以用来比较文件。
默认情况下,cmp和comm安装在大多数系统上。然而,在实践中,diff更容易使用,colordiff甚至更好*(与 diff 相同,但有颜色编码)*。出于实用目的,建议使用diff或colordiff。在后面的章节中,我们将会看到如何别名diff来使用colordiff。
为了演示比较文件,让我们转到/tmp目录并创建两个相同的文件。为此,请运行以下命令:
cd /tmp
cp /etc/passwd file1
cp file1 file2
接下来用nano或你喜欢的文本编辑器打开file2,改变一个字母;它可以是添加一个字母这样小的变化。进行更改后,保存并关闭文件。
与 Comm 命令比较
现在您有了两个几乎相同的文件,我们可以测试几个命令来比较差异。我们将尝试的第一个是comm,它可以通过传递两个文件名作为参数*(最好是用于演示目的的类似文件)*来运行:
comm file1 file2
这将返回文件的内容,这些内容相互重叠,具有三层深度。将用于文件中大部分行的最右侧深度是包含在两个文件中的行。然后,当您到达有差异的行时,您将有两个不同的缩进,一个用于仅file1行,另一个用于仅file2行。
它并不漂亮,但它完成了工作,可以在大多数系统上找到。尽管如前所述,我们建议安装 diff 或 colordiff。
请比较 Cmp 命令
虽然comm可以完全被diff代替,但命令cmp实际上略有不同。它不是比较文件的文本,而是逐字节比较文件。我们可以通过传递命令 2 文件名来测试程序:
cmp file1 file2
使用cmp,您将得到一行,它指定了文件之间第一个差异出现的行和字节。在您只想比较文件是否相同的脚本中,cmp可能是最快的选项,因为只要发现一个差异它就返回,而不是解析整个文件。
与 Diff 命令比较
diff命令类似于comm,但是可读性更强,并且有额外的特性和标志。默认情况下,大多数系统都不会安装它,所以您必须先安装它:
sudo apt-get install diff
安装了diff之后,我们就可以比较我们的文件了,这可以类似于comm和cmp来完成:
diff file1 file2
diff将只返回不同的行,而不是返回文件中的所有行。这意味着每一行都有两个不同的副本。第一个文件中的行将被加上一个<,第二个文件中的行将被加上一个>,允许您查看哪些行属于哪个文件。在这些行之前,您还会看到一个指示器,指示正在比较哪些行号。这使您能够发现不同之处,并在文本编辑器中快速找到它。
ColorDiff 甚至比 Diff 更好
与comm相比,diff的主要优势是可用性,这是由于差异是如何显示的。如果你的终端支持彩色*(大多数桌面终端都支持)*,你可能要安装colordiff来代替。colordiff是diff的一个包装器,它通过对不同之处进行颜色编码来进一步增强体验,因此您可以快速看到哪些行属于哪些文件。像diff一样,需要安装:
sudo apt-get install diff
安装colordiff后,比较两个文件,观察输出的不同:
colordiff file1 file2
获取文件类型
如果您来自 Windows,您可能习惯于这样的概念,即文件的扩展名决定了文件的类型及其运行的程序。在 Linux 上,经常使用文件扩展名,但这只是为了方便读者。文件扩展名不是强制性的,在某些情况下不使用。
你可以找到一个有名字但没有扩展名的文本文件或程序。在这种情况下,您可能会发现file命令很有用。给定一个文件作为输入,它将返回关于文件类型的信息。例如,如果我们在上一节中创建的file1上运行 file 命令,将文件位置作为参数传递,如下所示:
file file1
您应该恢复“ASCII 文本”类型如果你的电脑上有一个图像文件,试着在上面运行file。除了像 JPG 这样的图像类型之外,您还将获得像照片尺寸这样的额外元数据。
命令信息,包括类型、类型、位置或位置
与使用file获取文件信息类似,我们可以使用type、which、whereis或locate获取命令信息。第一个命令type内置于 bash 本身,它搜索您的路径并在找到时获取关于该命令的信息,例如:
type ls
在我的系统上,它返回一个别名(更多别名在后面的章节),如图 1-6 所示。
图 1-6
检查ls类型的输出
然后用which我们可以找到可执行文件的位置:
which ls
类似地,我们可以使用whereis并找到命令的可执行位置、源位置和手册页面文件。whereis命令应该返回多个文件位置,如图 1-7 所示。
图 1-7
使用which和whereis显示程序的位置
whereis ls
在某些情况下,您可能不记得确切的命令,所以在使用which时它不会出现;在这种情况下,您也可以尝试locate,它将搜索文件系统的数据库索引:
locate samba
定位有两个问题;首先,它可以返回大量结果,找到系统上每个文件的完整路径的文本输入的每个匹配项。例如,给定一个用户名,ubuntu,locate ubuntu将返回主目录*(因为每个文件都包含文件路径中的用户名)*中的每个文件。第二个问题是支持locate的数据库(比用find手动搜索文件系统更快)每天只通过 cron 更新一次。如果您想手动更新它,您可以运行 sudo updatedb (运行时间可能需要几秒到几分钟,取决于系统和文件系统的大小)。
更多关于须藤的信息
通常情况下,当你登录到你的操作系统时,你会得到一个用户名,它拥有特定文件夹的权限。文件夹位置通常是
/home/<username>/
通常,每个用户都有一个专用的主目录,他们对该目录拥有完全的管理权限。有时您需要使用个人文件夹之外的文件和文件夹。如果您试图做一些需要超出您的用户帐户权限的事情,您会得到一条消息说“权限被拒绝”或“您是 root 吗?”。
在这种情况下,您必须重试该命令,首先附加sudo,指定您希望作为 root 用户运行该命令。例如,该命令
cat /etc/sudoers
反而变成了
sudo cat /etc/sudoers
使用sudo时,会提示您输入密码。当然,sudo的成功依赖于你的主用户账户能够使用sudo。在/etc/sudoers中定义了用户可以使用sudo的策略。例如,在我默认安装的 Ubuntu 中,有这样一行代码
%sudo ALL=(ALL:ALL) ALL
这指定了组sudo中的所有用户都可以使用sudo。要查看用户所在的组,您可以运行
groups <username>
用你账户的用户名替换<username>,你将得到你所在的群组列表。
如果您需要连续运行多个命令,这些命令都使用了sudo,那么您可能需要切换到 root。通过这样做,您可以在没有它的情况下运行通常需要sudo的命令。要切换到 root,请运行以下命令,并在出现提示时输入您的密码:
sudo -i
现在你可以自由地运行任何你想要的命令。要返回到您的普通用户,请按ctrl+d。
更少管道
当我们谈论文件类型检测时,值得一提的是 less pipe,它是许多系统上预装的命令less的文件类型预处理器。Less pipe 可让您在终端中查看通常无法在终端中访问的文件,例如 PDF 文件。
要查看是否安装了较少的管道,请运行以下命令:
echo $LESSOPEN
如果您得到一个后跟文件位置的管道,例如|/usr/local/bin/lesspipe.sh %s,那么它就安装在您的系统上。如果您发现运行该命令返回一个空字符串,那么您的系统没有lesspipe。如果是这种情况,不要担心,因为我们将在下一节介绍安装*(或更新)* less 管道。
更新/安装更少的管道
Ubuntu 和其他操作系统将会安装一个足够好的版本lesspipe。因此,如果您不想更改默认值,请随意跳过这一部分。
为了充分利用这里列出的所有特性,您可能需要更新 lesspipe。在我的系统 Ubuntu 18.04 上,我发现lesspipe的版本有点过时,没有给我关于最新版本中可用的照片元数据的深入细节。旧版本也可能不支持下一节中列出的所有文件格式,尽管它应该适用于 PDF 等常见格式。
首先,需要安装git和make。Git 是一个用于编程的版本控制程序,make 用于编译源代码。在本书中,我们将使用git作为从 GitHub 下载公开可用代码的一种方式。您可以通过运行以下命令来安装它:
sudo apt-get install git
如上所述,我们还将使用make命令。make用于编译经常用 C 语言编写的程序(虽然不限于任何语言)。如果你下载了一个程序,它包含一个名为Makefile的文件,这是一个好迹象,表明这个程序可以用make编译。make utility通常与其他工具捆绑在一起,比如用于 C 和 C++的gcc编译器和公共库。要在 Ubuntu 上安装make,运行:
sudo apt-get install build-essential
安装了git和make,我们就可以开始更新lesspipe;这个过程从下载项目代码、移入文件夹、编译代码和测试设置开始:
git clone https://github.com/wofr06/lesspipe
cd lesspipe
make
make test
运行make test后,观察结果和任何缺失的程序。例如,在我的例子中,如图 1-8 所示,我得到了各种建议安装的程序。如果不安装上述程序,您可能无法打开相关的文件类型。您可以根据自己使用的文件类型来决定要安装哪些文件,不要安装哪些文件。
图 1-8
编译lesspipe后运行make test的输出
基于来自测试脚本的反馈,安装丢失的包 ( 反馈 来自测试 s 脚本 可能因您的系统而异):
sudo apt-get install antiword unrtf rpm2cpio
如果你得到一个没有找到包的消息,你必须忽略它或者在你的操作系统包管理器上寻找正确的名字。比如我发现sxw2txt可以用名字odt2txt安装。
接下来,运行
sudo make install
这将取代你的旧版本lesspipe或安装它,如果你没有它。最后一步是打开您的~/.bashrc文件,并在底部添加以下几行:
LESSOPEN="|/usr/local/bin/lesspipe.sh %s"; export LESSOPEN
完成这些步骤后,您将充分利用 less pipe 来处理尽可能多的文件类型。
Note
.bashrc文件包含可以从命令行访问的帐户范围的配置和变量。例如,如果我们添加一行export FAVORITE_COLOR="Blue",然后打开一个新的终端,我们就可以访问该变量。例如,运行echo $FAVORITE_COLOR会将“蓝色”打印到屏幕上。一些程序允许你基于这样的变量来改变设置,例如,一个基于 GUI 的程序可能会寻找$FAVORITE_COLOR 来设置布局的颜色。这个特殊的变量并不常用,但是它演示了如何以这种方式配置程序。我们将在后面的章节中更多地讨论.bashrc,以及如何使用它来改善您的命令行体验。
定期使用较少
如前所述,less用于查看文件文本数据,允许您从顶部开始慢慢向下滚动。在打开其他文件类型之前,让我们回顾一下如何正常使用less。首先使用seq命令创建一个包含几行文本的长文件,这是序列的缩写。seq 命令将起始数字和结束数字作为参数,并返回它们之间的数字序列:
seq 1 999
这会输出 1 到 999 的数字(seq 对定制脚本或测试很有用)。现在再次运行相同的命令,但是使用特殊的>字符将输出定向到一个文件,该字符用于将文本输出定向到一个文件:
seq 1 999 > /tmp/numbers.txt
Note
当创建测试文件时,我通常会将位置设为/tmp;这个文件夹有一个特殊的属性,当你重新启动计算机时,里面的所有内容都会被删除。如果你知道你以后会删除一个文件,比如我们的numbers.txt文件,你应该在/tmp文件夹中创建它。这样,如果你忘记删除垃圾文件,你就不必担心垃圾文件到处都是。只是注意不要把任何重要的东西留在你的/tmp 文件夹中。有时候,一个开始时被扔掉的脚本可以发展成您想要保存起来以备后用的东西。
现在我们已经为测试目的创建了文件,使用less /tmp/numbers.txt打开它。这将从顶部开始打开less文件,如图 1-9 所示。您可以使用箭头键或 page down 和 page up 按钮上下滚动。按下q退出。
图 1-9
在less中查看长文件
用更少的管道打开 pdf
更少的管道也使得less能够打开和读取 PDF 文件。类似于图像,运行less <filename.pdf>,你将在你的终端中得到 PDF 的文本版本。
用较少的管道打开压缩文件夹
当你安装了较少的管道时,压缩文件和文件夹可以用less打开。为了演示,创建一个包含一些文件的文件夹,并使用 tar *(一个压缩和解压缩文件的常用工具)*对它们进行压缩:
cd /tmp
mkdir folder
cd folder
touch file1 file2 file3
cd ..
tar -zcvf folder.tar.gz folder
运行这些命令后,您将有一个包含三个空文件的压缩文件夹。接下来我们试着用less打开它。你应该得到一个文件夹和文件的列表,包括每个文件的权限,如图 1-10 所示。
图 1-10
使用less打开压缩文件夹创建的输出
使用较少管道的图像元数据
对于下一个例子,您需要下载一个图像或者在您的系统上找到一个现有的图像。导航到包含图像的文件夹,并使用less打开它;如果你已经安装了最新版本,当你打开一个更少的图片时,你会得到详细的元数据,如图 1-11 所示。
图 1-11
使用 lesspipe 以更少的时间查看图像数据
Lesspipe 的其他文件
有各种各样的文件可以用 lesspipe 打开和查看。我们不会对所有这些进行深入探讨,但这里有一些其他的,所以你知道什么是可能的:
-
各种压缩文件夹,包括 zip、g zip、7-zip 等等
-
Java JAR 文件
-
RAR 档案
-
RPM (Red Hat 软件包管理器文件)
-
Microsoft Word、PowerPoint 和 Excel
-
ePub 图书
-
超文本标记语言
-
便携文档格式
-
MP4
要获得完整和最新的列表,以及您可能需要为某个文件类型安装的任何其他配套程序,请查看位于 https://github.com/wofr06/lesspipe 的官方存储库。
Note
这里列出的一些文件类型取决于您安装了一些附加软件包的系统。如果你发现一个你想读的包不工作,回头参考运行make test的安装步骤。如果你打开的文件类型经过测试,返回“忽略”并列出要安装的软件包,你需要安装上述程序。如果文件类型显示“不正常”或显示“正常”,但仍然不起作用,您需要访问前面列出的 GitHub 页面,并检查问题选项卡,查看其他有类似问题的人(或打开您自己的问题,如果没有找到)。
使用 Cron 作业调度进程
另一个需要了解的重要工具是 cron jobs。cron 作业是在特定时间或间隔运行的脚本或进程。这对于清理日志文件夹或在设定的时间间隔备份文件是很有用的(我们将在第六章中讨论)。
首先,运行带有-e标志的 crontab,它是“edit的缩写
crontab -e
第一次运行时,会要求您选择一个编辑器。如果你不习惯命令行编辑器*(我们将在后面的章节中讨论 Vim 和 Emacs)*,你应该选择nano,因为它最容易使用。如果您稍后决定要更改所使用的编辑器,您需要修改~/.selected_editor或删除它以恢复提示。
一旦crontab -e将你带到一个文件,转到最底部并创建如图 1-12 所示的示例作业。五个*符号中的每一个都可以用一个数字来代替,以表示它们应该何时运行。*符号表示通配符,意味着它匹配任何值。当所有 5 个值都是通配符时,意味着该命令将在每分钟、每小时、每天等等运行。图 1-12 所示的命令将每分钟使用 touch 创建或更新文件/tmp/hello的时间戳。
图 1-12
cron 作业的每个元素的标签
添加 cron 作业后,等待一两分钟,运行ls /tmp;您应该看到一个名为hello的新文件。在确认 cron 作业正常工作后,一定要删除该作业以保持系统干净。
表 1-4 包含使用各种列的 cron 计划示例,包括分钟、小时、工作日、日历日和月。
表 1-4
cron 中的时间间隔示例
|克朗时间
|
描述
| | --- | --- | | * * * * * | 每一分钟 | | 5 * * * * | 每小时的第五分钟 | | */5 * * * * | 每 5 分钟一次 | | 0 0 0 0 1 | 每周一午夜 | | 0 2 1 1 * | 1 月 1 日,凌晨两点 |
摘要
在这一章中,我们学习了选择一个 Linux 发行版,使用 man 查找关于一个程序的信息,公共命令,创建脚本和文件权限。我们只是简单地触及了这些主题作为开始。随着我们的继续,我们将更深入地探讨列出的几个主题。
二、文件/文件夹导航
无论您在终端中做什么,您都希望知道自己在系统文件结构中的位置。您还想知道如何导航到其他文件夹,这些文件夹中有您可能需要处理的文件。在这一章中,我们将重申基础知识,并看看导航文件系统的其他工具和方法。
基础
终端上的任何人都应该知道的最基本的命令是列出结构的ls和改变目录的cd。输入ls将会返回你当前目录下的文件和文件夹列表,然后你可以移动到带有cd后跟目录名的目录。表 2-1 列出了一些可以和-ls一起使用的有用选项。
表 2-1
ls 的选项
|命令
|
描述
| | --- | --- | | [构成动植物的古名或拉丁化的现代名] | 显示隐藏的文件和目录 | | -颜色 | 彩色高亮输出 | | -福 | 文件名末尾表示类型的符号 | | [构成来自拉丁语、结尾为-us 的名词的复数] | 显示文件索引号(索引节点号 | | -我 | 带细节的长格式 | | 相当于-ED | 按日期时间排序 | | 构成名词复数 | 按文件大小排序 | | -r | 倒序 | | -右 | 递归列出当前文件夹和子文件夹 |
虽然ls有几个常用的选项,但cd几乎从不与选项一起使用,尽管它有两个选项:-P不跟随符号链接,-L强制跟随符号链接。虽然你在使用cd时不需要选项,但有几个符号你应该知道。
导航时,有一些全局符号可用作表 2-2 所示路径的一部分。
表 2-2
目录符号
|命令
|
描述
| | --- | --- | | 。 | 表示当前工作目录 | | .. | 表示包含工作目录的文件夹 | | ~ | 代表当前用户的主文件夹 |
当使用ls或cd时,这些简短的形式很方便;我们可以在文件系统的任何地方,如果我们想回到我们的主文件夹,我们可以简单地运行
cd ~
节点
我们提到过ls -i会返回一个文件索引号或者 inode 号,但是 inode 到底是什么?每次在 Linux 系统上创建文件时,都会在后台为它分配一个 inode。每个索引节点指向内存中文件所在的位置,以及与文件相关的元数据,包括文件大小、文件所有者和上次访问时间。
系统上的所有 inodes 都存储在一个表中,该表预先分配了一定数量的内存。这样做的一个有趣的副作用是,您可以在不耗尽磁盘空间的情况下耗尽文件空间。为此,您需要创建足够小的(或空的)文件来填充 inode 表,这几乎从来不会发生。要想知道可以存储多少个索引节点,可以运行
df -i
您将得到一个列表,其中包含系统中每个驱动器的一个名为IFree的列;这表示驱动器上的可用信息节点数量。在我的例子中,我有超过 650 万个空闲索引节点;因此,为了达到索引节点的最大数量,我必须创建超过 650 万个文件。
虽然可能性不大,但还是有可能的。如果您很好奇,想要模拟用完空闲的 inode,这里有一个将用完所有 inode 的 liner。在使用它之前,请确保您在/tmp文件夹中,这样如果您需要重新启动,所有文件将在下次启动时消失。
cd /tmp
mkdir test
cd test
for i in $(seq 1 7000000) ; do touch $i ; done
您需要用一个大于您的驱动器上的空闲 inodes 总数的数字来替换7000000。该命令纯粹是出于教育目的,可能需要几个小时才能完成。用完索引节点的情况非常罕见,但是在长时间运行且内存有限的系统上尤其会发生。
获取当前位置
每当你打开一个新的终端,你可能会在你的用户的主目录。因此对于用户ubuntu,您将在/home/ubuntu/中。情况并非总是如此,有时你会发现自己忘记了自己的位置。您可以通过运行以下命令找到您的当前位置
pwd
这将返回到您当前位置的完整路径。它代表“打印工作目录”
符号链接
在某些情况下,目录本身不是文件夹,而是另一个目录的快捷方式。这些被称为符号链接,或软链接。您可以通过运行以下命令为现有文件创建符号链接
ln -s original_file link_file
这将在指向original_file的工作目录中创建一个名为link_file的文件。这个新的符号链接文件本身不包含任何数据。符号链接只包含它作为别名的文件的文件系统地址。这意味着当移动或重命名别名文件时你必须小心,因为系统链接仍然指向原始位置。
当使用列表结构命令ls -l的详细版本时,你会看到一个从link_file指向实际文件位置的箭头(图 2-1 )。在这里,-l旗实际上代表“长”。
图 2-1
系统链接文件的详细信息
或者如果你使用ls -F,你会在文件末尾看到一个@符号,它是符号链接,如图 2-2 所示。
图 2-2
符号@指定系统链接
符号链接也可以应用于文件夹,使一个文件夹成为另一个文件夹的快捷方式。
硬链接
除了符号链接,还有硬链接。硬链接是指向文件的信息节点的文件克隆。删除只有一个硬链接的文件的硬链接(目录条目)也会删除该文件。多个硬链接可以指向同一个 inode,只要它们都在同一个文件系统中。删除一个或多个指向某个信息节点的硬链接不会删除该信息节点或它所指向的文件,直到所有硬链接都被删除。另一方面,符号链接只是指向原始文件的快捷方式。与符号链接不同,硬链接不能应用于文件夹,只能应用于文件。
创建硬链接类似于创建符号链接,但没有-s标志:
ln original_file link_file
使用ls -l或ls -F,您将无法将硬链接识别为特殊类型的文件。本质上,它是一个与原始文件平等的普通文件;改变一个就会改变另一个。这是因为两个文件都指向同一个 inode,而这个 inode 又指向文件的一个实例。这意味着不像软链接,你可以移动任何一个文件的位置而不影响链接。
如前所述,Linux 系统上的每个文件都有一个关联的 inode。通过使用ls -i,我们可以看到当前目录中每个文件的 inode。图 2-3 显示了在硬链接和原始文件上使用ls -i的示例;请注意,inode 是相同的。
图 2-3
'ls -i'的输出显示了具有相同信息节点号的两个文件
即使移动文件,inode 也保持不变。指向 inode 的目录条目从一个目录移动到另一个目录。索引节点保持不变,索引节点和属于该文件的数据的位置也保持不变。
带有 pushd 和 popd 的导航堆栈
cd和ls是众所周知的,但是一旦你熟悉了导航文件目录,还有一些命令可以派上用场。第一个是pushd。pushd的行为类似于cd,但是它创建了一个目录堆栈,这样你以后可以很容易地返回到你当前的目录。例如,假设你在目录/tmp/中,你使用pushd ~,这将把你移动到主目录,就像cd一样,接下来做pushd /usr/local/bin。这又一次改变了你的位置,就像'cd',但是图 2-4 中返回了我们访问过的位置列表。
图 2-4
pushd 堆栈中显示的文件夹位置列表
当前目录显示在左边,堆栈目录的最下面显示在右边(在我们的例子中是*/tmp)。现在,如果我们运行popd,我们将从堆栈中弹出当前目录,并向右移动一个目录,在本例中是~;然后再次运行它,我们将返回到/tmp。当你想跟踪一组要返回的目录时,这可以是一个有用的选择cd。*
*另一个相关的命令只是运行cd -。当您在cd后使用减号时,您实际上将导航到您之前所在的任何目录;你可以重复这几次回溯所有你访问过的目录。
看守人
我最常用的另一个 Linux 命令行程序是 Ranger。Ranger 是一个命令行程序,它使文件和目录浏览变得快速而简单,尤其是在没有基于 GUI 的目录浏览器的服务器或设备上。
通过运行以下命令安装 ranger
sudo apt-get install ranger
安装完成后,只需在要启动的目录中运行命令即可启动它:
ranger
您将得到如图 2-5 所示的三窗格视图。按向上和向下将改变您在中间窗格的选择。按右进入右侧显示的目录,按左浏览父目录。
图 2-5
使用 Ranger 导航
以这种方式航行将很快成为第二天性。受 Vim 中绑定的启发,Ranger 还提供了几个键盘快捷键。我最喜欢的包括
-
s——输入大写字母
S将打开在最左侧窗格中选择的目录,该目录将在 bash 会话中打开。从那时起,如果您按 ctrl+d 或手动运行exit,您将返回到 Ranger。 -
s——输入小写字母
s将在屏幕左下方打开一个小文本框,可以在其中输入 shell 命令。例如,导航到/tmp,按下s后,输入命令mkdir hello,然后按回车键。您将看到一个名为hello的新目录出现在/tmp中。 -
q-输入大写
Q将退出 Ranger 并返回到命令行。 -
@–键入
@符号将允许您在不离开 Ranger 的情况下输入 bash 命令,例如,您输入touch hi并按 enter,您会看到您所在的当前目录添加了一个同名的空文件。 -
~–键入
~符号将在三级目录视图和一个只关注当前视图的视图之间切换;再按一次返回。当您处理长文件夹名称或不想分心时,较大的视图非常有用。 -
o–输入小写字母
o将显示当前目录中文件排序的可能方式列表,例如,按时间变化或字母顺序。
用树可视化文件结构
除了ls,ranger是我最常用的查看文件结构的程序。然而,另一个值得一提的是tree,它需要安装在大多数发行版上。tree也是非常轻量级的,不用像ranger那样打开一个完整的程序来探索文件结构,tree可以用来立即创建文件结构的可视化——例如,如果我导航到一个项目并运行以下命令
tree -L 2
Note
这里的两个表示深度显示了多少层(或目录);要深入了解,只需增加数量。
该命令将产生如图 2-6 所示的两个文件夹深度的文件结构的可视化。
图 2-6
使用树显示文件系统树
用 Vim 导航文件系统
我们将有一个专门的章节来用 Vim 进行编辑,但是它也有一个内置的文件/文件夹浏览器。在正常模式下打开 Vim,运行以下命令:
:Ex
这是同样有效的:Explore的简称。运行该命令将在 Vim 中打开一个文件浏览器,如图 2-7 所示,类似于 Ranger,但没有预览。
图 2-7
Vim 探索
您可以选择传递一个想要在 Vim 中使用 explore 打开的文件夹的参数,例如:
:Ex /home
这将导致浏览模式在主文件夹而不是当前工作目录中打开。您将能够使用正常的 Vim 键绑定j进行向下导航,使用k进行向上导航,或者使用箭头键进行导航。您可以在文件夹或文件上按 enter 键来打开它。
摘要
在本章中,我们探讨了与导航文件目录相关的命令。我们还查看了文件属性,如系统链接、隐藏文件和元数据,如上次修改时间。我们看到了 inodes 如何通过将文件名与元数据和磁盘空间中的底层数据相关联,在底层文件系统的工作方式中发挥关键作用。
除了查看文件系统的属性之外,我们还介绍了一些工具,使得探索文件系统变得更加容易。Ranger 和 Vim Explore 都允许我们快速浏览文件。而ls上的附加标志选项允许我们看到通常隐藏的文件属性。*
三、历史和快捷方式
在这一章中,我们将研究如何使用 shell 历史、bash 终端的内置键盘快捷键和文件 globbing。这些技巧将帮助您在输入新命令、重复以前的命令或修改部分编写的命令时动作更快。
历史
指尖上有许多有用的命令是很好的,但是有了这么多,很容易迷失方向。这就是history命令派上用场的地方。大多数 Linux 系统都应该预装了history命令。运行该命令将返回上次运行命令的列表。
默认情况下,大多数系统在删除旧的历史记录之前只会保留大约 2000 条命令。我建议增加这个数字。你可以通过修改你的~/.bashrc;搜索包含HISTSIZE和HISTFILESIZE的行:
# see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=10000
HISTFILESIZE=10000
也可以将您的历史记录设置为无限制;只需声明一个空值:
HISTSIZE=
HISTFILESIZE=
为了在保存大量命令时节省空间,我喜欢打开ignoreboth和erasedups。ignoreboth是一个由ignoredups和ignorespace组合而成的简写。ignoredups选项使连续运行多次的命令只被记录一次。ignorespace选项导致以空格开头的命令不被保存到历史记录中。因此,如果出于某种原因你不想保存一个命令,只需在它前面加上一个空格。erasedups选项实际上会在你每次运行一个命令时检查你的整个历史,并删除它的任何其他实例。erasedups的一个潜在缺点是,如果您获得历史记录,然后运行多个命令,删除一个命令会改变数字,在这种情况下,您必须再次运行history来更新正确的数字,或者意外运行错误的命令。
HISTFILESIZE=10000
# don't save duplicate lines or lines starting with space
# See bash(1) for more options
HISTCONTROL=ignoreboth:erasedups
另一个值得打开的历史选项是histappend,当你使用多个终端会话时,它有助于跟踪历史。默认情况下,当终端实例关闭时,历史文件将被覆盖,而不是被追加。这将导致仅保存上次关闭会话的历史记录。您可以通过以下方式打开histappend
# append to the history file, don't overwrite it
shopt -s histappend
滚动 10000 行会花很长时间;这就是grep派上用场的地方。假设您记得使用ffmpeg来剪切视频,但是您不记得确切的标志和输入。简单地跑
history | grep ffmpeg
一旦你看到你正在寻找的命令,你可以使用左边的数字快速再次运行它。历史输出的另一个例子如图 3-1 所示。
图 3-1
命令历史
给定图 3-1 中所示的历史输出,我们可以通过输入
!2007
此工作流程将大大加快您输入命令的速度。在某些情况下,您可以通过使用!?而不是仅仅!。例如,如果我们想运行图 3-1 中的命令 2012,我们可以运行
!?vi
这样做的目的是搜索历史中包含vi的最近命令。只要字符串在命令中,它也不必引用命令的开头。因此,以下内容也会导致命令 2012 运行:
!?bash
使用前面的使用history的方法可以大大提高你输入命令的速度。但是,您必须小心,因为如果文本出现在您想要的命令之外的命令中,它将自动运行。
Bash 快捷方式
键盘快捷键在大多数程序中都很方便,bash 也是如此。您应该知道 bash shell 有大量的键盘快捷键。就我个人而言,我只使用其中的一些命令,但是我使用的那些命令非常有用。
当你第一次开始时,你可能会发现你只用一两个。你可能会慢慢地、逐渐地学习命令,一旦适应了,你可能会决定在你的常规工作流程中添加更多的命令。从这里列出的几个有用的命令开始,当你习惯了它们之后,再回来尝试合并更多的命令。
使用 bash 时最基本的快捷键是 tab 键。双击 tab 键激活自动完成功能,如图 3-2 所示。为了进行演示,请尝试编写ls命令,后跟一个空格,并在按 enter 键之前,轻按 tab 键两次。
图 3-2
双击可用于自动完成的文件夹
您应该会看到工作目录中的所有文件,如图 3-2 所示。Tab 可以以这种方式用于几乎任何以文件作为输入的命令。
Note
使用ctrl加一个字母的快捷键是基于大多数发行版的默认组合键。如果您发现这些快捷键在您的机器上不起作用,您会希望看到下一节“Emacs 与 Vim 键盘绑定”。因为默认模式可能在你的系统上不同或者已经被改变。如果你的发行版给绑定分配了一些全局行为,这些快捷方式也可能不起作用。例如,在使用 Xubuntu 时,我发现一些我最常用的 bash 键盘快捷键不起作用。我最后做了一些研究,发现了一个特定于发行版的设置面板,在那里我可以删除一些全局快捷键,这些快捷键会导致应用特定的快捷键再次激活。
除了 tab,我用的最多的键盘快捷键是ctrl+b和ctrl+a。
ctrl+a =将文本光标移动到命令的开头
ctrl+b =将文本光标移动到命令的末尾
因此,如果你写了一个很长的命令,并在按 enter 键之前发现了一个打字错误,你可以使用ctrl+a快速返回并修复它,然后返回到使用ctrl+b时的位置。
我经常发现自己把这个和&&结合使用。这告诉 bash 只有在第一个命令成功运行之后,才在&&之后运行该命令。或者,如果您希望不管第一个命令是否成功都运行第二个命令,您可以使用单个&(例如,如果tmp.txt不存在,使用&&时git add *不会运行,而使用&时会运行)。
比方说,你已经输入了命令git add *,但是你意识到你有一个你想首先删除的文件。只需按下ctrl+a将光标移动到终端输入的第一个字符,并将命令改为
rm tmp.txt && git add *
不要删除已经写好的内容,以后再重新输入,只需在开头写下先决条件命令,并用&&将其链接起来。将几个命令链接在一起并让它们都成功运行是一件令人满意的事情。我可能会这样想,因为在输入一连串命令后的那一瞬间,可能是在等待命令处理的时候倒一杯咖啡的最佳时机。
我使用的另外两个命令是ctrl+c和ctrl+d。
ctrl+c =取消当前命令
ctrl+d =关闭当前端子
按下ctrl+d只是关闭当前的终端实例,产生与运行命令exit相同的结果。显而易见的用途是在结束时快速关闭终端窗口,但是您也可以使用它来关闭其他程序,如 tmux 或结束 ssh 会话。
你是否曾经写了一个很长的命令,然后意识到你想做一些完全不同的事情?当这种情况发生时,我们的本能是按住退格键,因为这似乎是永远的。下次试试ctrl+c,清除输入的简称。它会给你一个新的输入,而不需要执行命令。
如果您经常发现自己在运行命令clear,您会想要记下ctrl+l。
ctrl+l =清除屏幕上的所有文本,在终端会话顶部留下一个新的命令行
表 3-1 列出了所有 Emacs 键盘快捷键(大多数系统的默认模式)。
表 3-1
默认(Emacs 风格)bash 键盘快捷键列表
|顺序
|
描述
|
| --- | --- |
| ctrl+a | 转到行首 |
| ctrl+e 组合键 | 走到这条线的尽头 |
| alt+b | 将光标向后移动一个单词 |
| Ctrl+b | 将光标向后移动一个字符 |
| alt+f | 将光标向前移动一个单词 |
| ctrl+f | 将光标向前移动一个字符 |
| alt+t | 交换最后两个单词 |
| Ctrl+t | 交换最后两个字符 |
| alt+r | 如果您从历史记录中修改了命令,重置更改 |
| ctrl+k | 删除光标后的所有内容 |
| ctrl+u | 删除光标前的所有内容 |
| ctrl+w 组合键 | 删除最后一个单词 |
| ctrl+y | 粘贴删除的单词(相当于 ctrl+w 的撤消) |
| Ctrl+l | 清除过去的终端输出(与clear命令相同) |
| ctrl+z 组合键 | 后台运行进程 |
Emacs 与 Vim 键盘绑定
一个有趣的事实是,bash 键盘快捷键实际上是基于 Emacs(一个流行的开源文本编辑器)中的键绑定。许多在 bash 中工作的键绑定也可以在 Emacs 中工作。然而,可以在 bash 中启用类似 Vim 的键绑定。为此,请运行以下命令:
set -o vi
运行set -o vi将只为您当前的会话设置 Vim 键绑定;要永久启用它,您应该将它添加到您的.bashrc文件中。通过在您的.inputrc文件中添加下面一行,您可以设置 bash 甚至更多的程序同时使用 Vim 绑定:
set editing-mode vi
或者,如果您想显式地指定 Emacs 样式的绑定,您可以添加
set editing-mode emacs
.inputrc文件影响所有使用 GNU readline 库的程序的输入,这是一个流行的库,被一些实用程序使用,包括 bash 和其他操作系统,如 OpenBSD。一些使用 GNU readline 库的程序包括但不限于
- Abiword,Amanda,Atari800,Bacula,Bareos,GNU bc,BlueZ,Cdecl,ConnMan,Freeciv,FreeRADIUS,GNU ftp,NetKit ftp,FVWM,GDB,GPG,guile,Hatari,hunspell,LFP,NetworkManager,nfss,parted,rc shell,Samba,SQLite,GNU Units,VICE,wesnoth,WPA Supplicant,Lua REPL,Python REPL,Ruby REPL …】
因此,在.inputrc中改变的任何设置都会影响它们。默认情况下,.inputrc文件并不存在,但是如果添加的话,将会影响 bash 接收输入的方式。除了在缺省 emacs 模式和 vi 模式之间改变之外,终端的其他行为也可以被修改。我们将在下一章深入探讨.inputrc。
vi风格的键绑定没有一组等价的快捷键,而是模仿了分别输入和运行命令的模式。如果您正在使用vi模式并按下esc,您将切换到命令模式,在那里您可以使用一些(但不是全部)vi命令,如0前进到行首、$前进到行尾、w前进一个单词、b后退一个单词。
如果您不熟悉 Vim 或vi以及各种命令,我们建议坚持使用默认的 Emacs 键绑定,尽管 Vim 绝对值得学习,在本书中将有一章专门介绍 Vim。在阅读完它并熟悉使用 Vim 之后,您可能希望返回到 bash 设置并尝试使用vi风格的快捷方式。我使用 Vim 作为编辑器,但仍然更喜欢 bash 上的 Emacs 风格的键绑定,因为它们很简单,也是常见的默认设置。
反向搜索
上一节我们没有讨论的另一个快捷方式是ctrl+r用于反向搜索。我更喜欢使用history命令,但是许多人更喜欢使用交互式反向搜索。
按下ctrl+r后,你将进入一个交互模式,如果你开始键入一个先前写好的命令,它将显示在自动完成中,如图 3-3 所示。
图 3-3
终端中的反向搜索
一旦看到想要运行的命令,就可以按 enter 键运行它。或者,您可以按 tab 键返回到正常的 shell 模式,命令已准备好运行或修改。例如,给定前面的命令,我可以按 tab,然后按ctrl+e转到行尾,删除cups,然后编写一个不同的服务来获取状态。
如果你已经开始在反向搜索中编写一个命令,而自动完成不是你想要的,你可以按ctrl+r再返回。所以在所示的例子中,按下ctrl+r将显示历史上以sys开始的下一场比赛。
文件成组或通配符
文件 globbing 是 Linux 中的一个特性,它允许通过使用通配符来表示多个文件。最广为人知的通配符是*,它代表一个或多个任意字符。例如,运行
echo *
这将对当前目录中的所有文件运行命令echo。通配符也可以与其他字符结合使用,例如:
ls /dev/sd*
它不会返回/dev/中的所有文件,而只会返回文件夹中以“sd”开头的文件。
*不是唯一可以使用的字符,尽管它是最常用的。globbing 的另一个通配符是?。?字符与*相似,它可以表示任何字符,但它只是单个字符,而不是任何数量。如果我们将前面的命令修改为
ls /dev/sd?
我们现在只取回以“sd”开头并有一个额外字符的文件,而不是取回所有以“sd”开头的文件。注意图 3-4 中两者的输出差异。
图 3-4
比较*和?通配符
最后一个可以使用的字符,或者更确切地说是字符组合,是方括号[],这是一个内部经常使用字符的集合。例如,如果我们想重复前面的命令,该命令使用了?,但也包括其他驱动器,如sdb和sdc(如果存在,我们可以这样做
ls /dev/sd[abc]
这将匹配单个字符,只要它是括号内指定的字符,在本例中是 a、b 或 c。
摘要
在这一章中,我们研究了 bash 历史、快捷方式和文件打包的使用。通过使用这些技术,您可以大大加快工作流程,因为您输入命令时需要写得更少。
四、脚本和管道
创建脚本
一旦您熟悉了处理文件和使用各种命令,您很快就会发现您想要组合几个命令,有时会创建较长的序列,这可能会有些耗时。我们稍后会更详细地讨论,但现在最好知道这是可能的。创建一个命令列表来一个接一个地运行,就像写一个你需要购买的东西的购物清单一样简单(一旦你知道了基本的步骤)。
你只需使用任何编辑器打开一个文本文件(前面提到过 nano,但是如果更简单的话,你甚至可以使用桌面文本编辑器)。在文本文件的第一行,你写或粘贴一个特殊的行,称为“shebang ”,表示这个文件是一个脚本(更多细节在下一节*)。然后,您开始逐行列出要运行的命令。*
为经常运行的命令序列创建脚本会很方便。您可以将命令保存为文本文件,然后以单步方式运行该序列。从鸟瞰图创建脚本所需的步骤如下:
-
创建包含命令的文本文件。
-
使文件的第一行成为一个 shebang(稍后解释)。
-
保存文件。
-
使用权限使文件可执行(稍后解释)。
-
运行命令
./myScript.sh。
下面是一个名为name.sh的简单脚本的例子:
#!/usr/bin/env bash
echo First name: $1
echo Last name: $2
这个脚本有两个参数,一个用于名,一个用于姓。这些参数在代码中用$1和$2表示。它将通过运行以下命令来执行
./name.sh Philip Kirkbride
运行时将输出两行,第一行是“名字:Philip”,第二行是“姓氏:Kirkbride”。当然,除非您将输入换成您自己的名字,在这种情况下,这些名字将被换出。
在接下来的小节中,我们将更多地关注前面列表中的步骤 2 和 4,它们是实际运行脚本之前所需要的。
事情
shebang 指的是脚本中的第一行,当该行以#!开始时。这个词来自于音乐符号术语“升”和“??”,有时被称为“砰”;将这两者结合起来就成了“sharp-bang”或简称 shebang。
shebang 用作文件的第一行时,指定将用于解释脚本的程序。与编写 Linux 脚本相关的最流行的方法是
#!/bin/bash
同样的事情可以用/usr/bin/env来表达,它通过使用在用户路径中找到的任何 bash 版本来增加可移植性。
#!/usr/bin/env bash
shebang 并不局限于 bash 脚本。在用其他脚本语言如python、ruby或perl编写脚本时,它也应该是第一行。
#!/usr/bin/env python
文件权限
如前所述,制作可执行脚本的第四步是更改文件的权限以允许执行。最简单快捷的方法就是跑步
chmod +x name.sh
这只是为当前用户添加了文件的执行权限。运行该命令后,您只需运行以下命令就可以使用它(假设您与该文件在同一个目录中):
./name.sh
理解 Linux 上权限的概念是值得的,因为它是操作系统的一个重要方面。每个文件都有三种不同类型的权限:
-
阅读
-
写
-
执行
这三种权限中的每一种都可以为三个组单独设置:
-
用户
-
组
-
其他人
使用ls -l时,可以看到左侧表示的每个文件的设置权限,如图 4-1 所示。
图 4-1
运行 ls -l 时第一列中显示的文件的权限
Note
这个十个字母序列中的第一个字母用于表示特殊的文件类型。可能的值是 D =目录,c =字符设备,s =符号链接,p =命名管道,s =套接字,b =块设备,D =门。我们不必处理这些特殊类型,但知道第一个字母是什么是值得的。
在表示特殊文件类型的第一个字母之后,还有九个字母。我们可以将这九个字母分成三组,如图 4-2 所示——第一组是文件所有者的文件权限,第二组是用户组的文件权限,第三组是所有其他用户的文件权限。
图 4-2
文件权限的组件
对于这三个部分,我们有三个不同的字母,如果存在,则表示组具有上述权限:
-
r =读取
-
w =写
-
x =执行
在给出的例子中,我们有一个文件,它的所有者拥有所有权限,用户组拥有读和写权限,而其他可以访问该文件的用户只有读权限。
权限数据也可以显示为一组三个数字符号,其中一个数字代表权限的组合。每种权限类型都有一个数值:
-
4 =读取
-
2 =写入
-
1 =执行
对于任何组,我们将权限相加,得到一个代表允许权限的数字。例如,读取和写入将为 6 (2 + 4),写入和执行将为 3 (1 + 2),无权限将为 0。
使用这种符号,我们将把rxw rw- r--表示为764。更改文件权限时,可以使用这两种表示法中的任何一种。例如,我们可以跑步
chmod 777 numbers.sh
这将所有权限授予所有用户。或者,如果我们想使用带字母的符号,我们可以运行下面的命令来取消所有组的执行权限(注意-;如果要加的话,应该是 +):
chmod -x numbers.sh
如果我们希望对特定的列(用户、组或其他)使用数字表示法,我们可以首先指定组,例如,添加回执行权限,但仅限于所有者:
chmod u+x numbers.sh
文件类型
虽然我们已经使用文件类型.sh定义了我们的脚本,但这在 Linux 中实际上并不是必需的。我们可以很容易地将它命名为name而不是name.sh,它的工作方式也是一样的。
Note
一些团队更喜欢没有“.sh”扩展名的脚本,例如,Google Shell 风格指南实际上指定了不应该使用扩展名.sh。尽管如此,谷歌管理的几个公共存储库包含了包含.sh的 shell 脚本。这只是表明,即使在一个声明偏好的公司,你也不能确定脚本是否会包含.sh扩展。
检测文件类型的一个有用命令是file。为了进行试验,首先将文件名name.sh改为name。接下来运行以下命令:
file name
您应该得到一条消息,说明文件类型是 Bourne-Again shell 脚本。接下来,尝试打开文件,将 shebang 编辑成 python 的 shebang,如 she bang 部分所列。
#!/usr/bin/env python
保存后,再次尝试运行file。重复这个过程,尝试不同的 shebangs,包括 python、ruby 和 perl。您应该会得到类似于图 4-3 所示的结果。
图 4-3
通过编辑菜单更改文件类型的结果
管道
管道是基本语法最常见的特性之一。如果你对它们很熟悉,可以跳过这一部分。管道只是将一个命令的输出作为输入连接到另一个命令。我们将用一个有趣的例子来演示这个概念。
开始安装fortune和cowsay:
sudo apt-get install fortune-mod cowsay
Fortune 是一个完整的小命令行程序,其历史可以追溯到 20 世纪 90 年代早期的 Linux 版本 7。它只是生成一个随机的引用,例如,在我的电脑上运行该命令现在返回
“你永远不知道你有多少朋友,直到你在海滩上租了一所房子。”
自己跑几次fortune试试吧。每次运行时,都会从一个长列表中随机输出一个报价。现在为了好玩,让我们将输出通过管道传输到cowsay:
fortune | cowsay
现在我们在图 4-4 所示的一小段文字艺术中找回一份随机的财富。
图 4-4
给考赛输送财富
由于产生的财富来自随机列表,您看到的应该与图 4-4 中的不同。这里发生的事情是,fortune 代码的输出被用作cowsay命令的输入。接收命令完全不知道生成所述文本的过程。
例如,我们可以用一个简单的echo替换我们的fortune命令:
echo hello world | cowsay
在这种情况下,如你所料,奶牛会说“你好,世界”。可以使用的管道数量没有限制。在将文本发送给 cow say 之前,我们可以在两个命令之间使用另一个管道来进一步处理我们的文本:
echo hello world | rev | cowsay
在这种情况下,我们的“hello world”文本在到达cowsay命令之前被反转为“dlrow olleh”。在下一节中,我们将更多地研究如何使用多个管道。
多管道
在本书中,除了简单地探索命令行程序之外,我们还将编写 bash 脚本。随着您开始编写更多的 bash 脚本,您会经常发现您需要使用 pipe,不是一次而是多次。
对于用管道将命令行程序串在一起的复杂脚本,您会发现您的脚本开始看起来有点乱。最有用的格式化技术之一是多行管道。这只是当你使用\将一系列管道分成多条线时。
如果您的命令管道可以很好地放在一行中,如下所示,您就不需要担心将它分散在多行中:
# All fits on one line
command1 | command2
但是,如果您在一个链中使用多个命令,并且它超出了一行或看起来难以阅读,请将它分散到多行,如下所示:
# Long commands
command1 \
| command2 \
| command3 \
| command4 \
前面的例子来自 Google Shell Style Guide,这是一个很好的资源,提供了让 Shell 脚本更具可读性的技巧。一些指导原则与公司的内部偏好有关(例如,使用两个空格而不是制表符),而其他提示通常适用于所有 shell 脚本。
一旦您对编写 shell 脚本变得更加熟悉,并且发现您经常这样做,那么您应该看看 Google Style 脚本风格指南。它将帮助您考虑使您的脚本对可能遇到您的脚本的其他开发人员或系统管理员更具可读性的因素。
https://google.github.io/styleguide/shell.xml
用&&和||链接命令
在这一节中,我们将讨论 bash 中内置的一些可以派上用场的逻辑语法——具体来说,可以用作 AND 的&&和可以用作 OR 的||。
&& the operator for "and"
|| the operator for "or"
当您需要使用一个长时间运行的命令后跟另一个命令时,这非常有用。例如,假设你连接到一个互联网连接缓慢的物联网设备,你需要更新系统,一旦完成,安装一个新程序。你可以简单地跑
sudo apt-get update \
&& sudo apt-get install -y program-x
注意使用的-y标志;这告诉 apt-get 在被要求确认时回答 yes。
这在与tmux(一个用于在终端实例之间快速切换的程序,我们将在后面的章节中深入探讨)结合使用时非常有用。在之前的一份工作中,我经常发现自己在现场不得不 SSH 到五到十个不同的物联网设备。将多个命令串在一起,我可以给一个设备足够的工作,让它忙碌 15 分钟,然后立即切换到已经通过 SSH 连接到另一个设备的另一个 tmux 会话,并立即开始工作。
当您知道需要运行第二个命令时,or 运算符||同样有用,但只有在第一个命令失败的情况下。例如,假设我们的物联网设备有一个常见问题,如果一个命令失败,我们可能会用完磁盘空间;在上述情况下,我们希望删除所有日志:
sudo apt-get install -y program-x \
|| sudo rm -rf /var/log/*
&和||的退出代码
应注意的是,是否触发||或&&取决于其之前命令的退出代码。仅仅输出标准误差是不够的。例如,让我们在/tmp/err.sh写一个如下的文件:
#!/usr/bin/env bash
>&2 echo Error
用chmod +x /tmp/err.sh使文件可执行,然后用如下的||语句运行命令:
/tmp/err.sh || echo error
注意,您得到了标准的错误文本,但是echo error命令从未运行过。这是因为我们的程序仍然返回一个退出代码 0。我们可以通过在脚本底部添加以下内容来查看退出代码:
echo $?
现在,当运行脚本时,您应该会看到一个额外的“0”输出。如果你想改变退出命令,你可以使用exit命令。在我们脚本的底部,添加
exit 1
现在,如果我们用我们的||语句再次运行脚本,我们将看到echo error命令触发。这里我们不局限于退出代码 1,数字 0-255 都是有效的退出代码。退出代码 0 表示执行成功,而代码 1–255 表示错误。其中一些退出代码通常用于特定的错误;其他的留给程序特定的错误。标准错误代码编号及其含义见表 4-1 。
表 4-1
退出代码的标准含义
|密码
| | | --- | --- | | Zero | 默认情况下,命令运行没有问题 | | one | 所有非特定错误的总称 | | One hundred and twenty-six | 调用的命令不可执行 | | One hundred and twenty-seven | 找不到命令 | | One hundred and twenty-eight | 退出的参数无效 | | 128+n | 致命错误信号“n” | | One hundred and thirty | 按 ctrl+c 结束脚本 | | 255* | 退出状态超出范围 |
如您所见,即使有这些保留的误差范围,也有足够的空间让您定义自己的定制误差。如果您的脚本有多种可能失败的方式,并且您想要一种以编程方式检测这些特殊情况的方法,那么这将非常有用。
将&&与||一起使用
对于更复杂的用例,您还可以将操作符混合在一起。比方说,我们想使用grep检查一个字符串是否在文本文件中,然后将单词“true”或“false”传递给cowsay程序。在这种情况下,我们需要引入括号的用法:
( grep -q dog /tmp/test && echo true || echo false ) \
| cowsay
类似于在 math 中使用括号,括号内的语句将被求值并通过管道传递给 cowsay,如图 4-5 所示。如果文件/tmp/test存在,并且包含单词 dog,我们应该看到如下内容:
图 4-5
根据一个条件说对或错
当然会返回 false,因为文件/tmp/test不存在。尝试创建包含“狗”的文本文件。您可以使用命令快速完成这项工作
echo dog > /tmp/test
一旦运行了这个命令,运行前一个命令应该会返回true。这里使用的>符号是一个重定向,我们将在下一节中更仔细地研究它。
重新寄送
正如我们在上一节中看到的,我们可以使用>字符将文本发送到一个文件中,而不是通过管道发送到另一个程序中。这可以通过任何程序的输出来完成。使用标准重定向时,您应该知道该文件中的任何现有内容都将被覆盖。运转
echo dog > /tmp/test
echo cat > /tmp/test
将导致/tmp/test只包含文本“cat”。如果您想在文件中添加文本而不是替换内容,您应该使用>>:
echo dog >> /tmp/test
echo cat >> /tmp/test
这将产生一个包含两行的文件,一行是“狗”,一行是“猫”。
默认情况下,重定向中的输出包含输出和任何错误。相反,我们可以通过添加
echo cat > /tmp/test 2> /tmp/error
但是,在前面的示例中,没有创建任何错误。要在单个命令中生成标准输出和标准错误,请在现有文件和不存在的文件上使用ls:
ls /tmp/test /tmp/nope777 > /tmp/test 2> /tmp/error
运行前面的命令后,您应该在/tmp/test文件和/tmp/error中都有内容。和普通的重定向一样,我们可以使用>>来追加而不是替换文本:
ls /tmp/test /tmp/nope777 >> /tmp/test 2>> /tmp/error
如果多次运行前面的命令,每次运行时,每个文件中都会有几行。
用三通立即重新定向和输送
将输出重定向到文件和管道都是强大的工具,但是如果您想同时做这两项工作呢?一个名为tee的流行实用程序就是为此而存在的。它复制输入并将其发送到文件和输出,如图 4-6 所示。
图 4-6
tee 命令的输出图
tee命令从标准输出中获取输出,将其保存到您选择的文件中,然后将该输出传递给它自己的标准输出。例如,假设我们有以下命令,使用重定向将输出“hello”写入一个名为greeting的文件:
echo hello > greeting
运行前面的命令,我们将在我们的greeting文件中以“hello”结束,但是在我们的标准输出中将看不到任何内容。为使用tee而修改的相同程序应该是
echo hello | tee greeting
对于tee,我们将在greeting文件中以“hello”结束,但是我们在标准输出中也会看到“hello”。如果您想让tee像>>一样追加到文件中,而不是像>那样替换文本,您可以使用-a标志。
另一个使用 tee 的例子,假设我们想将一个数学方程传递给数学实用程序bc进行处理。我们将把结果输出到一个名为math的文件中,但是我们也想显示得出结果的等式。我们可以使用下面的命令来利用tee:
echo "7 * 7" | tee math.txt | bc >> math.txt
这导致文件math.txt被写入两次。一次使用三通和输入,第二次通过''。文件math.txt应该包含:
7 * 7
49
xargs
虽然在多个文件上运行命令时通配符有利于文件扩展,但有时您可能希望在另一个命令的每一行输出上运行一个命令。为此我们可以用xargs来演示;我们将使用一个可以通过通配符轻松完成的命令:
ls | xargs cat
前面的命令与cat *的输出相同;它输出当前目录中每个文件的内容。区别在于如何做。我们没有扩展通配符并将每个文件传递到一个单独的cat命令中,而是从ls命令中取出每一行输出,并将其用作每一行单独的cat命令的输入。
使用xargs允许你做用通配符不可能做的事情。例如,假设我想删除某一类型的所有文件。我将使用的一个例子是.swp文件;这些是文本编辑器 Vim 的恢复文件。在我的例子中,它们不包含任何有用的数据,而是由于突然退出程序而留下的(例如,在没有关闭编辑器的情况下关闭终端窗口)。我可以通过运行以下命令在我的主目录上运行查找和删除
find ~/ -name "*.swp" | xargs rm
这将获取由find返回的每个结果,并对其运行rm。我在命令前后跑了find,演示所有的.swp文件都被删除了,如图 4-7 所示。
图 4-7
使用find搜索 swp 文件的结果
Bash 中的条件表达式
当您开始通过命令行使用&&和||组合程序的几个组件时,您可能会发现编写专用脚本比从命令行手动输入一长串命令更容易。当您从命令行转向编写脚本时,使用一些更复杂的语法工具会更容易。
其中一个工具是 if 语句,它更像是一系列可能的测试,每个测试都有自己特定的选项。例如,如果我们想检查一个文件是否存在,我们可以使用-e选项。创建一个脚本并添加以下内容:
if [ -e /etc/passwd ]; then
echo passwd exists
fi
当您运行脚本时,您应该得到输出“passwd exists”。尝试将/etc/passwd更改为不存在的文件。或者如果您想测试该文件是否不存在,您可以添加一个!,如下所示:
if [ ! -e /etc/passwd ]; then
与其他语言一样,我们可以在 if 语句中添加一个else:
if [ -e /etc/passwd ]; then
echo passwd exists
else
touch /etc/passwd
fi
前面的语法适用于几种不同的可能的测试,这些测试可以通过替换-e来运行。列表很长,可以通过运行man bash并向下滚动到条件表达式部分找到。表 4-2 中显示了一些更常用的标志。
表 4-2
条件表达式选项
|密码
| | | --- | --- | | -d | 如果存在并且是目录,则为 True | | -f | 如果存在并且是常规文件,则为 True | | -e | 如果存在,则为真 | | 构成名词复数 | 如果文件存在并且大小大于 0,则为 True | | [加在以-u 结尾的法语词源的名词之后构成复数] | 如果存在并且是可执行文件,则为 True |
是一个带有-d 的目录
-d标志可用于确认文件存在并且是一个目录。如果您想使用一个目录,但不确定它是否存在,或者如果它存在,它是一个目录而不是一个文件,这将非常有用。这里显示了一个使用-d的例子:
mkdir /tmp/test
if [ -d /tmp/test ]; then
rmdir /tmp/test
fi
是一个带有-f 的普通文件
标志-f类似于-d,但是告诉我们一个文件是否是一个常规文件,而不是一个目录。同样,这可以在使用文件之前使用,以确保它存在并且是正确的类型。这里显示了一个-e的例子:
touch /tmp/test
if [ -f /tmp/test ]; then
rm /tmp/test
fi
用-e 检查文件是否存在
-e是-f和-d的一种组合,因为它只测试文件是否存在,而不考虑文件的类型。这里显示了一个使用-e的例子:
touch /tmp/test
if [ -e /tmp/test ]; then
rm -rf /tmp/test
fi
使用-s 检查是否存在以及大小是否大于 0
如果您正在处理一个文件的内容,您可能还想知道这个文件中是否有内容。在这种情况下,您可以使用-s,只有当文件存在并且大小大于 0 时,它才会返回 true。这里显示了一个使用-s的例子:
touch /tmp/test
if [ -s /tmp/test ]; then
echo “doesn’t run”
fi
echo data > /tmp/test
if [ -s /tmp/test ]; then
echo “does run”
fi
使用-x 检查是否存在以及是否是可执行文件
如果您对文件的使用实际上是将其作为程序执行,您可能希望在执行之前确认该文件是否存在。这就是-x派上用场的地方,它检查文件的文件权限以确认它是可执行的。尽管请记住,这只是检查文件权限以确认文件是可执行的,但它实际上并不检查文件是否包含脚本。在下面的例子中,我们的可执行文件实际上只是一个空白文件,然而在运行了chmod +x /tmp/executable之后,-x标志将它识别为可执行文件:
touch /tmp/executable
if [ -x /tmp/executable ]; then
echo “doesn’t run”
fi
chmod +x /tmp/executable
if [ -x /tmp/executable ]; then
echo “does run”
bash /tmp/executable
fi
也可以使用类似的语法来比较字符串。用于比较字符串的标志列表如表 4-3 所示。
表 4-3
字符串比较条件
|密码
| | | --- | --- | | -z S1 | 如果 S1 是长度为 0 的字符串,则为 True | | S1 北部 | 如果 S1 是长度大于 0 的字符串,则为 True | | S1 == S2 | 如果 S1 和 S2 是同一字符串,则为 True | | S1!= S2 | 如果 S1 和 S2 不是一个字符串,则为真 | | S1 < S2 | 如果 S1 排在 S2 之前,则为真 | | S1 > S2 | 如果 S2 排在 S1 之前,则为真 |
检查值是长度为 0 的字符串,带有-z
在编程或编写脚本时,一个未设置的或空的变量通常会带来麻烦。Bash 提供了一种使用-z标志检查变量是否为空的方法。这里显示了一个示例:
S1=""
if [ -z $S1 ]; then
echo "is empty string"
S1=”something”
else
echo "not empty"
fi
检查值是带有-n 的非空字符串
如果不是检查空值,而是想检查值是否不为空,可以使用-n。它本质上与-z相反,对于任何非空字符串都将返回 true。下面是一个例子,其中我们只在变量$S1不为空时才使用它:
S1="something"
if [ -n $S1 ]; then
echo $S1
else
echo "variable is empty"
fi
检查字符串是否相等
像许多编程语言一样,bash 也提供了检查字符串是否相等的方法。这可以用双等号==来完成。一个简单的例子如下:
S1="something"
S2="something"
if [ $S1 == $S2 ]; then
echo "same"
else
echo "not the same"
fi
检查字符串是否不相等
正如您所料,我们可以通过使用!=以类似的方式测试字符串是否相等。下面是一个示例,它将返回文本“same”:
S1="something"
S2="something"
if [ $S1 != $S2 ]; then
echo "not the same"
else
echo "same"
fi
检查字符串排序顺序
在处理字符串时,我们也可以使用>和<符号进行比较。第一次看到这些,你可能会认为它们比较的是数值或者哪个字符串更长。实际上,字符串使用的大于号和小于号检查排序顺序。
默认情况下,这是按字母顺序。为了演示排序,我们可以运行以下命令;请随意用数字或符号替换字母:
letters='a y b v b c'
echo "$letters" | tr ' ' '\n' | sort | tr '\n' ' '
运行前面的命令应该会返回“a b b c v y”。您可以忽略tr命令,它只是将空格转换成换行符,并在排序后用空格替换换行符。您可以尝试使用前面的命令来了解事情是如何排序的。
该排序顺序用于>和<符号。在下面,我们有一个使用<的例子:
S1="a"
S2="b"
if [ $S1 < $S2 ]; then
echo $S1 sorts before $S2
else
echo $S2 sorts before $S1
fi
除了测试文件和字符串,还支持测试整数。表 4-4 概述了比较整数的几种方法。注意在表 4-4 中,N1 和 N2 是可以包含任何整数的变量。
表 4-4
算术运算符
|密码
| | | --- | --- | | N1-情商 N2 | 如果 N1 等于 N2,则为真 | | n1-N2 | 如果 N1 不等于 N2,则为真 | | N1-N2 中尉 | 如果 N1 小于 N2,则为真 | | N1 勒 N2 | 如果 N1 小于或等于 N2,则为 True | | N1 -gt N2 | 如果 N1 比 N2 更伟大 | | N1-葛 N2 | 如果 N1 大于或等于 N2,则为真 |
检查数字是否相等
在比较数字时,有一套完全不同的标志可以使用。其中之一是-eq标志,它检查两个数字是否相等。-eq的使用示例如下:它应该返回“1 和 1 相等”:
N1=1
N2=1
if [ $S1 -eq $S2 ]; then
echo $N1 and $N2 are equal
else
echo $N1 and $N2 are not equal
fi
检查数字是否不相等
为了检查数字是否不相等,使用-ne标志。这和-eq本质上是一样的但是相反。使用-ne的例子如下:它应该返回“1 和 2 不相等”:
N1=1
N2=2
if [ $S1 -ne $S2 ]; then
echo $N1 and $N2 are not equal
else
echo $N1 and $N2 are equal
fi
检查数字是否小于
我们还可以使用一个标志来检查一个数字是否小于另一个数字。使用-lt的例子如下:它应该返回“1 小于 2”:
N1=1
N2=2
if [ $S1 -lt $S2 ]; then
echo $N1 is less than $N2
else
echo $N1 is not less than $N2
fi
检查数字是否小于或等于
标志-le与-lt几乎相同,唯一的例外是,如果数字彼此相等,它也返回 true。下面显示了一个例子,其中相等的数字触发 true,尽管如果$N1小于$S2,它也会触发 true。运行代码应返回“2 小于或等于 2”:
N1=2
N2=2
if [ $S1 -le $S2 ]; then
echo $N1 is less than or equal to $N2
else
echo $N1 is not less than or equal to $N2
fi
检查数字是否大于
每当您有能力检查一个数是否小于时,您很可能也有能力检查它是否大于。bash 就是这种情况,您可以使用-gt标志来检查一个数字是否大于。下面是一个使用-gt标志的例子。以下应返回“3 大于 2”:
N1=3
N2=2
if [ $S1 -gt $S2 ]; then
echo $N1 is greater than $N2
else
echo $N1 is not greater than $N2
fi
检查数字是否大于或等于
如果你想匹配大于或等于,你可以使用-ge。其用法示例如下。运行以下代码应返回“3 大于或等于 3”:
N1=3
N2=3
if [ $S1 -ge $S2 ]; then
echo $N1 is greater than or equal to $N2
else
echo $N1 is not greater than or equal to $N2
fi
虽然前面的代码很方便,但是还有一些使用双括号的算术表达式的语法,我们将在下一节中讨论。
带双括号的算术
虽然在 bash 中使用方括号括起来的测试比较整数是可能的,但是最好使用双括号,它允许使用任何使用另一种语言编程的人都熟悉的语法,例如:
if ((2 < 3)); then
echo 3 is greater than 2
fi
在前面的代码中,恰好((2 < 3))的计算结果为 true。我们在这里使用硬数字,但在大多数情况下,你会比较变量,可以用在他们的地方。
((N1 < N2))
如果你想用数学来设置一个变量,你还需要使用双括号,前面加一个美元符号,例如:
N1=$((1+1))
if ((N1<3)); then
echo 3 is greater than $N1
fi
无论您是否将结果设置为变量,无论何时您想要使用结果,都需要使用$,即使我们只是想要echo结果:
echo $((1+3))
请注意,如果您尝试使用不带双括号的算术,它会将数字附加为字符串,例如:
N1=2
N1+=1
echo $N1
前面的代码返回“21”,而
N1=2
((N1+=1))
echo $N1
将返回 3。
带括号的子 Shell
我们已经看到了双括号的作用,但是单括号呢?当代码放在括号内时,它作为一个子 shell 运行。复制当前的 shell 运行时并创建一个新的。这样做的效果是,子 Shell 中发生的任何事情都不会影响 Shell,例如:
S=Hi
(
echo $S
S=Hello
echo $S
)
echo $S
在前面的脚本中,我们创建了一个变量S。然后我们打开一个 subshell 并echo这个值;注意,subshell 可以看到创建时就存在的S变量的值。然后我们在 subshell 中改变S的值。
在 subshell 的外面,我们再次echo这个值。该值仍然是“Hi”而不是“Hello ”,因为更改是在 subshell 内部进行的。
子 shell 本身又可以产生自己的子 shell,作为子进程。
用花括号展开
bash 内置的另一个符号是花括号{}。花括号可以用于列表的 Shell 扩展。例如
echo {1..100}
将扩展为 1 到 100 之间的所有数字。
图 4-8
使用扩展回显 1 到 100
我们还可以指定增量。例如,如果我们想每次增加 2,我们可以这样做
echo {1..100..2}
现在我们将只得到奇数( 1,3,5 等)。)。同样的技术可以应用于字母和数字。
echo {a..z..2}
前面的代码将返回所有奇数字母( a,c,e 等)。)。
我们甚至可以把这两个结合起来,比如说我们想要每个字母的两个版本:
echo {a..c}{1..2}
这将返回一个包含( a1 a2 b1 b2 c1 c2 )的小集合,尽管我们可以根据自己的喜好将它变得复杂和长。例如,假设我们想要打印总共五位数的二进制数的所有组合:
echo {0..1}{0..1}{0..1}{0..1}{0..1}
另一个用例,假设我们想在/tmp中创建一个文件列表:
touch /tmp/{file1,file2,file3}
或者更好:
touch /tmp/file{1..3}
这两个命令都将在/tmp目录中生成三个文件。
Bash 中的循环
像其他语言一样,bash 也提供了一种对一组项目进行循环的方法。例如,给定一组姓名,我们可以为每个姓名打印“hello ”:
for name in jesse james jen
do
echo "Hello $name"
done
这对于我们在上一节中看到的扩展非常有用,例如:
for i in {1..100}
do
echo "Hello $i"
done
我们也可以用一个i++语句做一个传统风格的循环,你可能在其他语言中见过:
for ((i=1;i<=100;i++));
do
echo "Hello $i"
done
也有可能形成无限循环:
for (( ; ; ))
do
echo "Hello [CTRL+C to stop]"
done
在某些情况下,您可能希望尽早脱离循环。
for i in {1..100}
do
if((i==10))
then
break
fi
echo "Hello $i"
done
在前面的例子中,第 1–9 次运行,但是在第 10 次运行之前,我们点击了break关键字来提前停止循环。或者,我们可以使用continue关键字来结束当前的迭代,而不退出循环。作为一个例子,我们将使continue在所有偶数上触发。注意 if 行的空格,因为缺少空格会导致脚本无法正常运行。
for i in {1..100}
do
if [ $((i%2)) -eq 0 ];
then
continue
fi
echo "Hello $i"
done
前面的脚本将对数字 1 到 100 运行循环,但是任何被发现是偶数的数字将提前退出循环,为下一个数字让路。这是因为在i为偶数的迭代中,会读取continue关键字,导致迭代提前结束。
也可以使用数组作为循环的数据提供者。数组是使用 bash 定义的,如下所示:
array=( 1 39 47 )
然后,要使用该数组,需要使用花括号对其进行扩展:
for i in ${array[@]}
do
echo "Hello $i"
done
虽然我们在这里使用了整数,但是您也可以在数组中使用字符串或其他数据类型。
While 循环
在某些情况下,使用while循环可能比使用for循环更好。您可能希望根据与迭代次数无关的值来结束循环,而不是设定迭代次数。例如,假设我们想知道在 7 秒钟内我们可以循环多少次代码。
Note
对于下面的例子,您需要将代码放入一个脚本文件,并运行chmod +x来添加执行权限。这是因为我们使用了特殊变量$SECONDS。$SECONDS变量包含终端,或者在我们的例子中,脚本,已经运行的秒数。
#!/usr/bin/env bash
i=0
while [ $SECONDS -lt 7 ]; do
i=$((i+1))
done
echo $i
执行该脚本将返回循环在 7 秒内能够运行的次数。你可能会惊讶于这个数字有多高。在我的例子中,循环运行了 927375 次。
while 循环允许我们像 for 循环一样,在没有特定数字的情况下限制代码段的运行。虽然我们使用了时间的例子,但是你也可以使用一些外部值。例如,如果一个网站关闭了,你可能需要每隔几分钟检查一次,直到它恢复。
working=false
while [ $working == false ]; do
curl google.com && working=true
echo $working
sleep 60
done
前面的脚本可能只运行一次,因为我们正在检查 www.google.com 。如果你想知道当一个网站关闭时它是如何工作的,试着把这个网站切换到一个不存在的网站(因此总是会失败)。
while 循环也使得无限循环变得特别容易。要使一个循环永远运行,只需使检查值true如下所示。下面是一个脚本示例,它会每分钟说一次“hello ”,直到关闭:
while [ true ]; do
echo "hello"
sleep 60
done
直到循环
until 循环与 while 循环几乎相同,只是我们不是检查值是否为真,而是检查值是否为假。请注意,下面的脚本与我们的 while 循环几乎相同,但我们不是在 while false 时运行,而是一直运行到 true:
working=false
until [ $working == true ]; do
curl google.com && working=true
echo $working
sleep 60
done
Bash 中的引用
bash 中的引号可以用来防止特殊字符被解释,而是被解释为它们的文字值。例如,我们echo以下符号,如果没有引号,这些符号会导致错误:
echo '$ & * ; |.'
通过用引号将这些特殊字符括起来,我们使它们具有了字面意义。
双引号类似于单引号,但是仍然允许处理美元符号、反引号和反斜线。在下面的示例中,双引号中的变量将被扩展,而单引号中的变量不会被扩展:
greeting=hello
echo '$greeting world'
echo "$greeting world"
另一个例子是如何解释空格;考虑以下两个命令:
touch hello world
touch “hello world”
由于空格是 bash 中的默认分隔符,第一个命令会将输入作为两个独立的参数处理,而使用引号的命令会将输入视为一个文件名。
使用反斜杠的命令替换
反引号与单引号和双引号完全不同。反勾号不会阻止对特殊字符的解释,而是会导致在求值之前对包含的文本进行解释。
为了演示,我们将使用下面的命令,该命令将一个加法语句传送到bc中,以生成一个数字:
echo 5 + 5 | bc
前面的代码将输出数字 10。现在,假设我们想在一个更大的命令中使用这个命令。例如,我们将使用该命令的结果作为要创建的文件的名称。
touch /tmp/`echo 5 + 5 | bc`
在前面的示例中,反勾号中的命令将首先被解释。一旦反斜线被解释,该命令将作为
touch /tmp/10
当您有一个生成文件名或脚本其他方面的动态过程时,这可能会很有用。
定义函数
如果您正在编写一个脚本,您可能希望将一个代码块定义为一个可重用的函数。当您在整个脚本中的多个地方使用同一段代码时,这是非常有用的。通过将一些功能包装成一个命名函数,您可以避免重写相同的代码,如果您决定更改所使用的代码,也可以避免在多个位置进行更新。
在 bash 中创建函数相当容易。我们将创建一个非常简单的例子。首先,我们将创建一个名为greet的函数,它将一个名字作为输入,输出“hello”和这个名字。
greet() {
echo hello $1
}
定义了前面的代码后,我们现在可以运行
greet David
这将把输入“David”传递给我们的greet函数中的echo hello $1。请注意,传递给函数的变量没有命名,而是按照它们的输入顺序指定的。如果我们想处理第二个参数,我们将把它添加为$2,第三个参数将是$3,以此类推。
缺少命名参数并不是 bash 中唯一缺少的特性。在编写函数时,有其他语言经验的人期望发现的另一件事是从函数返回值的能力。不幸的是,没有像您期望的那样可以在函数内部使用 return 关键字。作为一种解决方法,您可以在函数内部定义一个变量,并在函数外部使用它。为了演示这一点,我们将创建一个随机时间生成器,它可以用作测试脚本的一部分。
Note
我们将使用shuf命令来生成随机数。您不需要担心安装它,因为它是 GNU Coreutils,并且几乎存在于所有的 Linux 系统上。
random_time() {
hour=$(shuf -i 0-12 -n 1)
min=$(shuf -i 0-60 -n 1)
hour=$(printf %02d $hour)
min=$(printf %02d $min)
r_time=$min:$hour
}
定义了我们的随机时间生成函数后,让我们运行它并echo结果。我们将这样做两次,以确保每次都获得不同的值。
random_time
echo $r_time
random_time
echo $r_time
来自文件的源代码
如果您来自另一种编程语言,您可能习惯于从外部文件导入源代码。导入相对简单。假设我们已经将上一节中的random_time函数保存为random.sh。首先从上一节中获取random_time函数,我们在这里定义并保存它为一个名为random_time.sh的脚本文件。确保在第一行包含一个 shebang(本节后面的例子作为参考),保存后在上面运行chmod +x。
现在我们已经将random_time函数保存为random_time.sh,我们将在同一目录下的另一个文件中使用它。为此,创建一个名为sourcing.sh的新脚本文件;包括此处显示的代码:
#!/usr/bin/env bash
source random_time.sh
random_time
echo r_time
如果您不在与random_time.sh文件相同的目录中,请确保使用完整路径。文件导入后,您可以使用文件中定义的任何变量或函数。
摘要
在这一章中,我们从管道和重定向开始,它们可以用来将不同的 Unix 实用程序和命令粘合在一起,或者直接处理输出,或者保存到文件中。
然后我们看了 bash 脚本语法的各个方面,包括条件表达式、函数、引号和导入文件。