Linux-脚本编程秘籍第三版-全-
Linux 脚本编程秘籍第三版(全)
原文:
annas-archive.org/md5/f6570e6407789c422c3e68b4adc030ee译者:飞龙
前言
本书将展示如何最大限度地发挥 Linux 计算机的性能。它描述了如何执行常见任务,如查找和搜索文件,解释了复杂的系统管理活动,如监控和调整系统,并讨论了网络、安全、发行版以及如何使用云计算。
普通用户将会喜欢重新格式化照片、从互联网下载视频和音频文件以及归档文件的实用技巧。
高级用户将会发现,解决复杂问题的配方和解释(如备份、版本控制和数据包嗅探)非常有用。
系统管理员和集群管理员将会发现使用容器、虚拟机和云计算的配方可以让他们的工作变得更加轻松。
本书内容概览
第一章,Shell 操作入门,解释了如何使用命令行、编写和调试 bash 脚本,并使用管道和 shell 配置。
第二章,掌握基本命令,介绍了可以在命令行或 bash 脚本中使用的常见 Linux 命令。它还解释了如何从文件中读取数据;通过名称、类型或日期查找文件;以及比较文件。
第三章,文件输入与输出,解释了如何操作文件,包括查找和比较文件、搜索文本、浏览目录层次结构以及处理图像和视频文件。
第四章,文本处理与正则表达式,解释了如何在 awk、sed 和 grep 中使用正则表达式。
第五章,错综复杂的网络?一点也不!,解释了如何在没有浏览器的情况下进行网页交互!它还解释了如何编写脚本来检查网站上的死链接,并下载和解析 HTML 数据。
第六章,版本库管理,介绍了使用 Git 或 Fossil 进行版本控制。跟踪变更并保持历史记录。
第七章,备份计划,讨论了传统和现代的 Linux 备份工具。硬盘越大,你就越需要备份。
第八章,老派网络,解释了如何配置和调试网络问题、共享网络并创建 VPN。
第九章,开启监视模式,帮助我们了解系统的运行情况。它还解释了如何跟踪磁盘和内存使用情况,跟踪登录情况并检查日志文件。
第十章,系统管理呼叫,解释了如何管理任务、向用户发送消息、调度自动任务、记录工作并有效使用终端。
第十一章,追踪线索,解释了如何监控网络以发现网络问题,并跟踪库和系统调用中的问题。
第十二章,调优 Linux 系统,帮助我们了解如何提升系统性能,并高效使用内存、磁盘、I/O 和 CPU。
第十三章,容器、虚拟机和云计算,解释了何时以及如何使用容器、虚拟机和云计算来分发应用程序和共享数据。
本书所需的内容
本书中的食谱适用于任何基于 Linux 的计算机——从 Raspberry Pi 到 IBM 大型计算机。
本书适合谁
从新手用户到经验丰富的管理员,每个人都能从本书中找到有用的信息。它介绍并解释了基础工具和高级概念,以及行业技巧。
章节
在本书中,您会看到几个经常出现的标题(准备就绪、如何实现…、它是如何工作的…、还有更多...和另见)。
为了提供清晰的操作步骤,我们使用如下的章节划分来描述如何完成一个食谱:
准备就绪
本节告诉您该食谱的预期效果,并描述了如何设置任何必要的软件或初步设置。
如何实现…
本节包含完成食谱所需的步骤。
它是如何工作的…
本节通常包含对上一节内容的详细解释。
还有更多…
本节包含有关食谱的额外信息,帮助读者更好地了解食谱。
另见
本节提供了有关该食谱的其他有用信息的链接。
约定
在本书中,您将看到几种不同的文本风格,用以区分不同类型的信息。以下是这些风格的示例及其含义解释。
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名如下所示:“Shebang 是以#!开头的解释器路径。”
代码块如下所示:
$> env
PWD=/home/clif/ShellCookBook
HOME=/home/clif
SHELL=/bin/bash
# ... And many more lines
当我们希望引起您注意某个代码块的特定部分时,相关的行或项目会以粗体显示:
$> env
PWD=/home/clif/ShellCookBook
HOME=/home/clif
SHELL=/bin/bash
# ... And many more lines
任何命令行输入或输出以以下方式写出:
$ chmod a+x sample.sh
新术语和重要词汇以粗体显示。屏幕上看到的词汇,例如在菜单或对话框中,会以如下方式显示:“从管理面板中选择系统信息。”
警告或重要说明会以框体形式显示,如下所示。
提示和技巧以如下方式呈现。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对本书的看法——您喜欢或不喜欢的地方。读者反馈对我们很重要,因为它帮助我们开发出您能够从中获得最大收益的书籍。
要向我们提供一般反馈,只需发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书名。
如果你在某个领域具有专业知识,并且有兴趣撰写或参与书籍的编写,请查看我们的作者指南:www.packtpub.com/authors。
客户支持
现在你已经成为一本 Packt 书籍的骄傲拥有者,我们为你准备了许多资源,帮助你充分利用你的购买。
下载示例代码
你可以从你的账户中下载本书的示例代码文件,访问地址是 www.packtpub.com。如果你是从其他地方购买的这本书,你可以访问 www.packtpub.com/support,并注册以便将文件直接通过电子邮件发送给你。
你可以按照以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的“支持”标签上。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名。
-
选择你想下载代码文件的书籍。
-
从下拉菜单中选择你购买这本书的地点。
-
点击“代码下载”。
你还可以通过点击书籍网页上的代码文件按钮,下载代码文件。该网页可以通过在搜索框中输入书名来访问。请注意,你需要登录你的 Packt 账号才能访问。
文件下载完成后,请确保使用最新版本的工具解压或提取文件夹:
-
适用于 Windows 的 WinRAR / 7-Zip
-
适用于 Mac 的 Zipeg / iZip / UnRarX
-
适用于 Linux 的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,地址是:
github.com/PacktPublishing/Linux-Shell-Scripting-Cookbook-Third-Edition。
我们的丰富图书和视频目录中还有其他代码包可供下载。
github.com/PacktPublishing/。查看一下它们!
下载本书的彩色图片
我们还提供了一份 PDF 文件,包含本书中使用的截图/图表的彩色图片。彩色图片将帮助你更好地理解输出中的变化。你可以通过以下链接下载此文件:
勘误
尽管我们已经尽力确保内容的准确性,但错误仍然会发生。如果你在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感激你能向我们报告。通过这样做,你可以帮助其他读者避免困扰,并帮助我们改进后续版本的书籍。如果你发现任何勘误,请通过访问 www.packtpub.com/submit-errata 报告,选择你的书籍,点击“勘误提交表单”链接,并输入勘误的详细信息。一旦你的勘误得到验证,提交将被接受,勘误将被上传到我们的网站或添加到该书籍勘误列表中。
要查看已提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索框中输入书名。所需的信息将显示在勘误部分。
盗版
网络上盗版受版权保护的材料是一个持续存在的问题,涉及各类媒体。在 Packt,我们非常重视保护我们的版权和许可。如果你在互联网上发现任何我们作品的非法复制品,请立即向我们提供位置地址或网站名称,以便我们采取措施。
请通过copyright@packtpub.com联系我们,并提供涉嫌盗版内容的链接。
我们感谢你帮助保护我们的作者及我们为你提供有价值内容的能力。
问题
如果你对本书的任何内容有问题,可以通过questions@packtpub.com联系我们,我们会尽力解决问题。
第一章:Shell 做一些事
在本章中,我们将涵盖以下内容:
-
在终端中显示输出
-
使用变量和环境变量
-
向环境变量添加前缀的函数
-
使用 shell 进行数学运算
-
玩转文件描述符与重定向
-
数组和关联数组
-
访问别名
-
获取有关终端的信息
-
获取和设置日期与延迟
-
调试脚本
-
函数与参数
-
将一个命令的输出发送到另一个命令
-
在不按回车键的情况下读取
n个字符 -
一直运行命令直到成功
-
字段分隔符和迭代器
-
比较与测试
-
通过配置文件自定义 bash
简介
起初,计算机通过读取卡片或磁带中的程序并生成单个报告来工作。当时没有操作系统、图形显示器,甚至没有交互式提示符。
到了 1960 年代,计算机支持交互式终端(通常是电传打字机或升级版打字机)来调用命令。
当贝尔实验室为全新的 Unix 操作系统创建交互式用户界面时,它具有一个独特的功能。它能够读取和评估来自文本文件(称为 shell 脚本)的相同命令,就像它接受从终端键入的命令一样。
这个功能在生产力上是一次巨大的飞跃。程序员无需输入多个命令来执行一组操作,而是可以将命令保存在文件中,并只需通过几次按键就能运行它们。Shell 脚本不仅节省了时间,还记录了你所做的操作。
最初,Unix 只支持一个交互式 shell,由 Stephen Bourne 编写,并命名为 Bourne Shell(sh)。
1989 年,GNU 项目的 Brian Fox 从许多用户界面中借鉴了特性,并创建了一个新的 shell —— Bourne Again Shell(bash)。bash shell 理解所有 Bourne shell 的构造,并添加了来自 csh、ksh 和其他 shell 的特性。
随着 Linux 成为最流行的类 Unix 操作系统实现,bash shell 已成为 Unix 和 Linux 的事实标准 shell。
本书聚焦于 Linux 和 bash。尽管如此,大多数脚本可以在 Linux 和 Unix 上运行,使用 bash、sh、ash、dash、ksh 或其他 sh 风格的 shell。
本章将为读者提供关于 shell 环境的洞察,并演示一些基本的 shell 特性。
在终端中显示输出
用户通过终端会话与 shell 环境进行交互。如果你使用的是基于 GUI 的系统,那么显示的将是一个终端窗口。如果你在没有 GUI 的环境下工作(例如生产服务器或 ssh 会话),你一登录就会看到 shell 提示符。
在终端中显示文本是大多数脚本和实用工具需要定期执行的任务。Shell 支持多种方法和不同格式来显示文本。
准备工作
命令在终端会话中输入并执行。当终端被打开时,会显示一个提示符。提示符可以通过多种方式进行配置,但通常如下所示:
username@hostname$
另外,也可以将其配置为 root@hostname #,或者仅仅是 $ 或 #。
$ 字符代表普通用户,# 代表管理员用户 root。Root 是 Linux 系统中权限最高的用户。
直接以根用户(管理员)身份使用 shell 执行任务是一个不好的主意。因为当你的 shell 拥有更多权限时,输入错误可能会造成更大的损害。建议以普通用户身份登录(你的 shell 提示符可能会显示为 $),并使用像 sudo 这样的工具来执行特权命令。使用 sudo <command> <arguments> 以 root 身份运行命令。
一个 shell 脚本通常以 shebang 开头:
#!/bin/bash
Shebang 是一行,在该行前缀加上 #!,后面跟着解释器路径。/bin/bash 是 Bash 的解释器命令路径。以 # 符号开头的行被 bash 解释器视为注释。只有脚本的第一行可以包含 shebang 来定义用于评估脚本的解释器。
脚本可以通过两种方式执行:
- 将脚本名称作为命令行参数传递:
bash myScript.sh
- 设置脚本文件的执行权限,使其可以执行:
chmod 755 myScript.sh ./myScript.sh.
如果脚本作为 bash 的命令行参数运行,则不需要 shebang。shebang 使得脚本可以独立运行。可执行脚本使用 shebang 后面的解释器路径来解释脚本。
脚本通过 chmod 命令使其可执行:
$ chmod a+x sample.sh
这个命令使得脚本对所有用户可执行。脚本可以按如下方式执行:
$ ./sample.sh #./ represents the current directory
另外,脚本也可以像这样执行:
$ /home/path/sample.sh # Full path of the script is used
内核会读取第一行,看到 shebang 为 #!/bin/bash,它会识别 /bin/bash 并按如下方式执行脚本:
$ /bin/bash sample.sh
当交互式 shell 启动时,它会执行一组命令来初始化设置,例如提示文本、颜色等。这些命令从位于用户家目录中的 ~/.bashrc(或登录 shell 时的 ~/.bash_profile)脚本中读取。Bash shell 会在 ~/.bash_history 文件中保存用户运行的命令历史记录。
~ 符号表示你的家目录,通常是 /home/user,其中 user 是你的用户名,或者是 /root,对于 root 用户。登录 shell 在你登录计算机时会创建。然而,在图形化环境中登录后创建的终端会话(如 GNOME、KDE 等)并不是登录 shell。通过显示管理器(如 GDM 或 KDM)登录时,可能不会读取 .profile 或 .bash_profile(大多数不会),但通过 ssh 登录远程系统时会读取 .profile。shell 通过分号或新的一行来分隔每个命令或命令序列。请看这个例子:$ cmd1 ; cmd2
这等同于以下内容:
$ cmd1
$ cmd2
注释以#开始,并持续到行尾。注释行通常用于描述代码,或在调试时禁用某行代码的执行:
# sample.sh - echoes "hello world" echo "hello world"
现在让我们继续学习本章的基本示例。
如何做到...
echo命令是终端中最简单的打印命令。
默认情况下,echo在每次调用后会添加换行符:
$ echo "Welcome to Bash" Welcome to Bash
简单来说,使用双引号括起来的文本和echo命令一起打印文本到终端。类似地,未使用双引号的文本也会输出相同的结果:
$ echo Welcome to Bash Welcome to Bash
另一种完成相同任务的方法是使用单引号:
$ echo 'text in quotes'
这些方法看起来相似,但每个方法都有特定的用途和副作用。双引号允许 shell 解释字符串中的特殊字符。单引号则禁用这种解释。
请考虑以下命令:
$ echo "cannot include exclamation - ! within double quotes"
这将返回以下输出:
bash: !: event not found error
如果你需要打印像!这样的特殊字符,必须要么不使用任何引号,要么使用单引号,或者用反斜杠(\)转义特殊字符:
$ echo Hello world !
或者,使用这个:
$ echo 'Hello world !'
或者,可以这样使用:
$ echo "Hello World\!" #Escape character \ prefixed.
当使用不带引号的echo时,不能使用分号,因为分号是 Bash shell 中命令之间的分隔符:
echo hello; hello
从前面的行来看,Bash 将echo hello视为一个命令,第二个hello视为另一个命令。
在下一个示例中讨论的变量替换,在单引号内将不起作用。
另一个在终端中打印的命令是printf。它使用与 C 库中的printf函数相同的参数。请考虑以下示例:
$ printf "Hello world"
printf命令接受用引号括起来的文本或由空格分隔的参数。它支持格式化字符串。格式字符串指定了字符串宽度、左对齐或右对齐等。默认情况下,printf不会附加换行符。当需要时,我们必须显式地指定换行符,如以下脚本所示:
#!/bin/bash #Filename: printf.sh printf "%-5s %-10s %-4s\n" No Name Mark printf "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456 printf "%-5s %-10s %-4.2f\n" 2 James 90.9989 printf "%-5s %-10s %-4.2f\n" 3 Jeff 77.564
我们将收到以下格式化输出:
No Name Mark 1 Sarath 80.35 2 James 91.00 3 Jeff 77.56
它是如何工作的...
%s、%c、%d和%f字符是格式替换字符,用于定义后续参数的打印方式。%-5s字符串定义了一个左对齐的字符串替换(-表示左对齐),并且字符宽度为5。如果没有指定-,字符串将会右对齐。宽度指定了为字符串保留的字符数。对于Name,保留的宽度是10。因此,任何名称都会位于为其保留的 10 个字符宽度内,剩余部分将填充空格,直到总共有 10 个字符。
对于浮点数,我们可以传递附加参数来四舍五入小数位数。
对于 Mark 部分,我们将字符串格式化为%-4.2f,其中.2表示四舍五入到小数点后两位。请注意,对于每一行的格式化字符串,都会发出一个换行符(\n)。
还有更多...
在使用echo和printf的标志时,应将标志放在命令中的任何字符串之前,否则 Bash 会将标志视为另一个字符串。
在 echo 中转义换行符
默认情况下,echo会在输出文本的末尾附加一个换行符。使用-n标志可以禁用换行符。echo命令接受双引号字符串作为参数,并可以处理转义序列。使用转义序列时,使用echo命令为echo -e "包含转义序列的字符串"。考虑以下示例:
echo -e "1\t2\t3" 1 2 3
打印彩色输出
脚本可以使用转义序列在终端上生成彩色文本。
文本的颜色由颜色代码表示,包括 reset = 0,black = 30,red = 31,green = 32,yellow = 33,blue = 34,magenta = 35,cyan = 36,white = 37。
要打印彩色文本,请输入以下命令:
echo -e "\e[1;31m This is red text \e[0m"
这里,\e[1;31m是设置颜色为红色的转义字符串,而\e[0m将颜色重置。将31替换为所需的颜色代码。
对于彩色背景,reset = 0,black = 40,red = 41,green = 42,yellow = 43,blue = 44,magenta = 45,cyan = 46,white = 47,是常用的颜色代码。
要打印彩色背景,请输入以下命令:
echo -e "\e[1;42m Green Background \e[0m"
这些示例涵盖了部分转义序列。文档可以通过man console_codes查看。
使用变量和环境变量
所有编程语言都使用变量来保存数据,以便稍后使用或修改。与编译语言不同,大多数脚本语言在创建变量之前不需要声明类型。类型由使用情况决定。通过在变量名前加上美元符号来访问变量的值。Shell 定义了多个它用于配置和信息的变量,例如可用的打印机、搜索路径等。这些被称为环境变量。
准备中
变量命名规则是字母、数字和下划线的组合,不允许有空格。常见的约定是环境变量使用大写字母(UPPER_CASE),而在脚本中使用的变量使用驼峰命名法或小写字母(camelCase 或 lower_case)。
所有应用程序和脚本都可以访问环境变量。要查看当前 Shell 中定义的所有环境变量,请执行env或printenv命令:
$> env
PWD=/home/clif/ShellCookBook
HOME=/home/clif
SHELL=/bin/bash
# ... And many more lines
要查看其他进程的环境,请使用以下命令:
cat /proc/$PID/environ
设置PID为进程的进程 ID(PID是一个整数值)。
假设一个名为gedit的应用程序正在运行。我们通过pgrep命令获取gedit的进程 ID:
$ pgrep gedit 12501
我们通过执行以下命令查看与进程相关的环境变量:
$ cat /proc/12501/environ GDM_KEYBOARD_LAYOUT=usGNOME_KEYRING_PID=1560USER=slynuxHOME=/home/slynux
注意,前面的输出有许多行被去除以方便阅读。实际输出包含更多的变量。
/proc/PID/environ特殊文件包含环境变量及其值的列表。每个变量以 name=value 对的形式表示,两个部分之间由空字符(\0)分隔。这对于人类来说并不容易阅读。
要生成易于阅读的报告,可以将cat命令的输出通过管道传递给tr,将\0字符替换为\n:
$ cat /proc/12501/environ | tr '\0' '\n'
如何做到…
使用等号操作符为变量赋值:
varName=value
变量的名称是varName,value是要赋给它的值。如果value中不包含空格字符(如空格),则不需要用引号括起来;否则,必须用单引号或双引号括起来。
请注意,var = value和var=value是不同的。将var = value写成var=value是常见错误。没有空格的等号是赋值操作,而使用空格会创建一个相等性测试。
通过在变量名前加上美元符号($)来访问变量的内容。
var="value" #Assign "value" to var echo $var
你也可以这样使用:
echo ${var}
该输出将显示如下:
value
双引号中的变量值可以与printf、echo和其他 Shell 命令一起使用:
#!/bin/bash #Filename :variables.sh fruit=apple count=5 echo "We have $count ${fruit}(s)"
输出将如下所示:
We have 5 apple(s)
由于 Shell 使用空格来分隔单词,我们需要添加花括号,以让 Shell 知道变量名是fruit,而不是fruit(s)。
环境变量是从父进程继承的。例如,HTTP_PROXY是一个环境变量,定义了用于 Internet 连接的代理服务器。
通常,它被设置为如下:
HTTP_PROXY=192.168.1.23:3128 export HTTP_PROXY
export命令声明一个或多个将由子任务继承的变量。变量被导出后,当前 Shell 脚本中执行的任何应用程序都会接收到这些变量。Shell 创建并使用了许多标准环境变量,我们也可以导出自己的变量。
例如,PATH变量列出了 Shell 搜索应用程序的文件夹。一个典型的PATH变量包含以下内容:
$ echo $PATH
/home/slynux/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
目录路径由:字符分隔。通常,$PATH在/etc/environment、/etc/profile或~/.bashrc中定义。
要将新路径添加到PATH环境中,使用以下命令:
export PATH="$PATH:/home/user/bin"
或者,使用这些命令:
$ PATH="$PATH:/home/user/bin"
$ export PATH
$ echo $PATH
/home/slynux/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/user/bin
这里我们将/home/user/bin添加到PATH中。
一些著名的环境变量有HOME、PWD、USER、UID和SHELL。
使用单引号时,变量不会展开,显示的就是原样内容。这意味着,$ echo '$var'将显示$var。
而$ echo "$var"将在$var变量已定义时显示其值,未定义时显示空值。
还有更多...
Shell 还有许多内置功能。以下是一些额外功能:
查找字符串的长度
使用以下命令获取变量值的长度:
length=${#var}
考虑这个例子:
$ var=12345678901234567890$ echo ${#var} 20
length参数表示字符串中的字符数。
识别当前 Shell
要识别当前使用的 Shell,可以使用SHELL environment变量。
echo $SHELL
或者,使用以下命令:
echo $0
考虑这个例子:
$ echo $SHELL /bin/bash
同样,通过执行echo $0命令,我们将得到相同的输出:
$ echo $0 /bin/bash
检查超级用户
UID环境变量保存用户 ID。使用此值可以检查当前脚本是以 root 用户还是普通用户身份运行。考虑这个例子:
If [ $UID -ne 0 ]; then
echo Non root user. Please run as root.
else
echo Root user
fi
请注意,[ 实际上是一个命令,必须与其余字符串用空格分开。我们也可以将前面的脚本写成如下形式:
if test $UID -ne 0:1
then
echo Non root user. Please run as root
else
echo Root User
fi
root 用户的 UID 值为 0。
修改 Bash 提示符字符串(username@hostname:~$)
当我们打开终端或运行 shell 时,会看到类似 user@hostname: /home/$ 的提示符。不同的 GNU/Linux 发行版有不同的提示符和颜色。PS1 环境变量定义了主提示符。默认的提示符由 ~/.bashrc 文件中的一行定义。
- 查看设置
PS1变量的行:
$ cat ~/.bashrc | grep PS1
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
- 要修改提示符,可以输入以下命令:
slynux@localhost: ~$ PS1="PROMPT> " # Prompt string changed
PROMPT> Type commands here.
- 我们可以使用特殊的转义序列来显示彩色文本,例如
\e[1;31(参见本章的 在终端中显示输出 这一小节)。
某些特殊字符会扩展为系统参数。例如,\u 扩展为用户名,\h 扩展为主机名,\w 扩展为当前工作目录。
用于向环境变量添加前缀的函数
环境变量通常用于存储一系列搜索可执行文件、库等的路径。例如,$PATH 和 $LD_LIBRARY_PATH 通常类似于下面的形式:
PATH=/usr/bin;/bin
LD_LIBRARY_PATH=/usr/lib;/lib
这意味着,每当 shell 需要执行一个应用程序(二进制文件或脚本)时,它会首先在 /usr/bin 中查找,然后再搜索 /bin。
在从源代码构建并安装程序时,我们经常需要为新的可执行文件和库添加自定义路径。例如,我们可能会将 myapp 安装在 /opt/myapp 中,其中二进制文件在 /opt/myapp/bin 文件夹,库文件在 /opt/myapp/lib 文件夹。
如何做到这一点...
这个示例展示了如何将新路径添加到环境变量的开头。第一个示例展示了如何使用目前为止讲解的内容来实现,第二个示例展示了如何创建一个函数来简化修改变量的过程。函数的内容将在本章后面介绍。
export PATH=/opt/myapp/bin:$PATH
export LD_LIBRARY_PATH=/opt/myapp/lib;$LD_LIBRARY_PATH
PATH 和 LD_LIBRARY_PATH 变量现在应该类似于以下内容:
PATH=/opt/myapp/bin:/usr/bin:/bin
LD_LIBRARY_PATH=/opt/myapp/lib:/usr/lib;/lib
我们可以通过在 .bashrc 文件中定义一个 prepend 函数,使得添加新路径更加简单。
prepend() { [ -d "$2" ] && eval $1=\"$2':'\$$1\" && export $1; }
可以通过以下方式使用:
prepend PATH /opt/myapp/bin
prepend LD_LIBRARY_PATH /opt/myapp/lib
它是如何工作的...
prepend() 函数首先确认由函数第二个参数指定的目录是否存在。如果存在,eval 表达式会设置变量,变量名为第一个参数的值,等于第二个参数字符串,后跟 :(路径分隔符),然后是变量的原始值。
如果在尝试添加前缀时变量为空,末尾会有一个多余的 :。为了解决这个问题,可以将函数修改为如下形式:
prepend() { [ -d "$2" ] && eval $1=\"$2\$\{$1:+':'\$$1\}\" && export $1 ; }
在这种形式的函数中,我们引入了一种 shell 参数扩展,形式如下:
${parameter:+expression}
如果参数已设置且不为 null,则扩展为表达式。
通过此更改,我们确保在尝试添加前缀时,如果旧值存在,则仅在旧值存在的情况下追加 : 和旧值。
与 shell 进行数学运算
Bash shell 使用let、(( ))和[]命令执行基本的算术操作。expr和bc工具用于执行高级操作。
如何做...
- 数值的赋值与字符串赋值相同。访问它的方法会将其作为数字处理:
#!/bin/bash no1=4; no2=5;
let命令用于直接执行基本操作。在let命令内,我们使用没有$前缀的变量名。请参考这个例子:
let result=no1+no2 echo $result
let命令的其他用法如下:
- 使用此命令进行递增:
$ let no1++
- 递减操作使用此命令:
$ let no1--
- 使用这些来表示简写:
let no+=6 let no-=6
这些等同于let no=no+6和let no=no-6。
- 替代方法如下:
[]操作符的使用方式与let命令相同:
result=$[ no1 + no2 ]
在[]操作符内部使用$前缀是合法的;请参考这个例子:
result=$[ $no1 + 5 ]
(( ))操作符也可以使用。变量名前缀带有$的方式在(( ))操作符内使用:
result=$(( no1 + 50 ))
expr表达式可用于基本操作:
result=`expr 3 + 4` result=$(expr $no1 + 5)
前述方法不支持浮动点数,
仅对整数操作。
bc应用程序,精度计算器,是一个用于数学运算的高级工具。它有广泛的选项,我们可以进行浮点运算并使用高级函数:
echo "4 * 0.56" | bc 2.24 no=54; result=`echo "$no * 1.5" | bc` echo $result 81.0
bc应用程序接受前缀来控制操作。这些前缀通过分号分隔。
- 小数位数使用 bc 调整:在下面的例子中,
scale=2参数将小数位数设置为2。因此,bc的输出将包含一个两位小数的数字:
echo "scale=2;22/7" | bc 3.14
- 使用 bc 进行进制转换:我们可以将一个进制的数字转换为另一个进制。以下代码将数字从十进制转换为二进制,并将二进制转换为十进制:
#!/bin/bash Desc: Number conversion no=100 echo "obase=2;$no" | bc 1100100 no=1100100 echo "obase=10;ibase=2;$no" | bc 100
- 以下示例演示了如何计算平方和平方根:
echo "sqrt(100)" | bc #Square root echo "10¹⁰" | bc #Square
操作文件描述符和重定向
文件描述符是与输入输出流相关联的整数。最常见的文件描述符是stdin、stdout和stderr。一个流的内容可以被重定向到另一个流。这个示例展示了如何操作和重定向文件描述符。
准备就绪
Shell 脚本常常使用标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。脚本可以使用大于号符号将输出重定向到文件。命令生成的文本可以是正常输出或错误消息。默认情况下,正常输出(stdout)和错误消息(stderr)都会显示在屏幕上。通过为每个流指定特定的描述符,两个流可以被分开。
文件描述符是与打开的文件或数据流相关联的整数。文件描述符 0、1 和 2 是保留的,如下所示:
-
0:
stdin -
1:
stdout -
2:
stderr
如何做...
- 使用大于号符号将文本追加到文件:
$ echo "This is a sample text 1" > temp.txt
这会将回显的文本存储到temp.txt中。如果temp.txt已存在,单个大于符号会删除之前的内容。
- 使用双大于符号将文本追加到文件中:
$ echo "This is sample text 2" >> temp.txt
- 使用
cat命令查看文件内容:
$ cat temp.txt This is sample text 1 This is sample text 2
接下来的示例演示了如何重定向stderr。当命令生成错误信息时,会将一条消息打印到stderr流中。考虑以下示例:
$ ls + ls: cannot access +: No such file or directory
这里+是一个无效参数,因此返回错误。
成功与失败的命令
当一个命令因错误退出时,它会返回一个非零的退出状态。命令在成功完成后退出时返回零。返回状态可以在特殊变量$?中获得(在执行命令后立即运行echo $?来打印退出状态)。
以下命令将stderr文本打印到屏幕上,而不是文件中(由于没有stdout输出,out.txt将为空):
$ ls + > out.txt ls: cannot access +: No such file or directory
在以下命令中,我们使用2>(两个大于符号)将stderr重定向到out.txt:
$ ls + 2> out.txt # works
你可以将stderr重定向到一个文件,将stdout重定向到另一个文件。
$ cmd 2>stderr.txt 1>stdout.txt
也可以通过将stderr转换为stdout来将stderr和stdout重定向到同一个文件,使用这种首选方法:
$ cmd 2>&1 allOutput.txt
也可以通过另一种方法来完成此操作:
$ cmd &> output.txt
如果你不想看到或保存任何错误消息,可以将stderr输出重定向到/dev/null,这样可以完全去除它。例如,假设我们有三个文件a1、a2和a3,但是a1对用户没有读写执行权限。为了打印所有以字母a开头的文件内容,我们使用cat命令。可以按如下方式设置测试文件:
$ echo A1 > a1 $ echo A2 > a2 $ echo A3 > a3 $ chmod 000 a1 #Deny all permissions
使用通配符(a*)显示文件内容时,将会因为a1文件没有适当的读权限而生成错误消息:
$ cat a* cat: a1: Permission denied A2 A3
这里,cat: a1: Permission denied属于stderr数据。我们可以将stderr数据重定向到一个文件,同时将stdout输出到终端。
$ cat a* 2> err.txt #stderr is redirected to err.txt A2 A3 $ cat err.txt cat: a1: Permission denied
有些命令生成我们想要处理的输出,并且还希望将其保存以供将来参考或进一步处理。stdout流是一个单一的流,我们可以将其重定向到文件或通过管道传递给另一个程序。你可能认为我们无法做到既得其所,又能两全其美。
然而,也有一种方法可以将数据重定向到文件的同时,将重定向的数据作为stdin提供给管道中的下一个命令。tee命令从stdin读取并将输入数据重定向到stdout和一个或多个文件。
command | tee FILE1 FILE2 | otherCommand
在以下代码中,stdin数据由tee命令接收。它将stdout的副本写入out.txt文件,并将另一副本作为stdin传递给下一个命令。cat -n命令为每一行从stdin接收的内容加上行号,并写入stdout:
$ cat a* | tee out.txt | cat -n cat: a1: Permission denied
1 A2 2 A3
使用cat命令查看out.txt的内容:
$ cat out.txt A2 A3
注意到 cat: a1: Permission denied 不会显示,因为它被发送到了 stderr。tee 命令仅从 stdin 读取。
默认情况下,tee 命令会覆盖文件。加入 -a 选项会强制它将新数据附加到文件末尾。
$ cat a* | tee -a out.txt | cat -n
带有参数的命令遵循格式:command FILE1 FILE2 ... 或简单的 command FILE。
要将输入的两份副本发送到 stdout,请使用 - 作为文件名参数:
$ cmd1 | cmd2 | cmd -
参考此示例:
$ echo who is this | tee - who is this who is this
或者,我们可以使用 /dev/stdin 作为输出文件名来使用 stdin。
同样,使用 /dev/stderr 表示标准错误,/dev/stdout 表示标准输出。这些是特殊的设备文件,分别对应 stdin、stderr 和 stdout。
它是如何工作的…
重定向操作符(> 和 >>)将输出发送到文件,而不是终端。> 和 >> 操作符的行为略有不同。两者都将输出重定向到文件,但单个大于号符号(>)会清空文件并写入新内容,而双大于号符号(>>)会将输出添加到现有文件的末尾。
默认情况下,重定向作用于标准输出。要明确使用特定的文件描述符,必须将描述符数字作为前缀添加到操作符。
> 操作符等同于 1>,类似地,>> 也等同于 1>>。
在处理错误时,stderr 输出会被转储到 /dev/null 文件中。./dev/null 文件是一个特殊的设备文件,接收到的数据会被丢弃。这个空设备通常被称为 黑洞,因为所有进入它的数据都会永远丢失。
还有更多内容...
从 stdin 读取输入的命令可以通过多种方式接收数据。我们可以使用 cat 和管道来指定我们自己的文件描述符。参考此示例:
$ cat file | cmd $ cmd1 | cmd2
从文件重定向到命令
我们可以通过小于符号(<)从文件中读取数据作为 stdin:
$ cmd < file
从脚本中封闭的文本块进行重定向
文本可以从脚本重定向到文件。要在自动生成的文件顶部添加警告,请使用以下代码:
#!/bin/bash
cat<<EOF>log.txt
This is a generated file. Do not edit. Changes will be overwritten.
EOF
出现于 cat <<EOF >log.txt 和下一个 EOF 行之间的内容将作为 stdin 数据显示。log.txt 的内容如下:
$ cat log.txt
This is a generated file. Do not edit. Changes will be overwritten.
自定义文件描述符
文件描述符是访问文件的抽象标识符。每次文件访问都会关联一个称为文件描述符的特殊数字。0、1 和 2 是保留的描述符数字,分别代表 stdin、stdout 和 stderr。
exec 命令可以创建新的文件描述符。如果你熟悉其他编程语言中的文件访问,你可能已经了解了打开文件的模式。这三种模式通常使用:
-
读模式
-
使用追加模式写入
-
使用截断模式写入
< 运算符将文件内容读取到 stdin。> 运算符将数据写入文件并截断(数据写入目标文件,内容会被截断)。>> 运算符将数据追加到文件中(数据附加到现有文件内容,不会丢失目标文件的内容)。文件描述符是通过这三种模式之一创建的。
创建一个用于读取文件的文件描述符:
$ exec 3<input.txt # open for reading with descriptor number 3
我们可以这样使用:
$ echo this is a test line > input.txt $ exec 3<input.txt
现在,你可以使用文件描述符3与命令一起操作。例如,我们将使用cat<&3:
$ cat<&3 this is a test line
如果需要第二次读取,不能重用文件描述符3。我们必须使用exec创建一个新的文件描述符(可能是 4),以从另一个文件读取或重新从第一个文件读取。
创建用于写入的文件描述符(截断模式):
$ exec 4>output.txt # open for writing
考虑以下示例:
$ exec 4>output.txt $ echo newline >&4 $ cat output.txt newline
现在创建一个用于写入的文件描述符(追加模式):
$ exec 5>>input.txt
考虑以下示例:
$ exec 5>>input.txt $ echo appended line >&5 $ cat input.txt newline appended line
数组和关联数组
数组允许脚本通过索引将一组数据存储为独立的实体。Bash 支持两种类型的数组:普通数组(使用整数作为数组索引)和关联数组(使用字符串作为数组索引)。当数据按数字顺序组织时,应使用普通数组,例如一系列连续的迭代。关联数组可以用于数据按字符串组织时,例如主机名。在本例中,我们将看到如何使用这两种数组。
准备工作
要使用关联数组,必须使用 Bash 版本 4 或更高版本。
如何做到...
数组可以使用不同的技术来定义:
- 使用一行值的列表定义数组:
array_var=(test1 test2 test3 test4) #Values will be stored in consecutive locations starting
from index 0.
或者,可以将数组定义为一组索引-值对:
array_var[0]="test1" array_var[1]="test2" array_var[2]="test3" array_var[3]="test4" array_var[4]="test5" array_var[5]="test6"
- 使用以下命令打印数组中给定索引的内容:
echo ${array_var[0]} test1 index=5 echo ${array_var[$index]} test6
- 使用以下命令打印数组中的所有值作为列表:
$ echo ${array_var[*]} test1 test2 test3 test4 test5 test6
或者,你可以使用以下命令:
$ echo ${array_var[@]} test1 test2 test3 test4 test5 test6
- 打印数组的长度(数组中元素的数量):
$ echo ${#array_var[*]}6
还有更多…
关联数组从 Bash 版本 4.0 开始引入。当索引是字符串时(例如站点名称、用户名、非顺序数字等),关联数组比数字索引数组更容易使用。
定义关联数组
关联数组可以使用任何文本数据作为数组索引。需要声明语句来定义变量名为关联数组:
$ declare -A ass_array
在声明后,使用以下两种方法之一向关联数组添加元素:
- 内联索引-值列表方法:
$ ass_array=([index1]=val1 [index2]=val2)
- 分离的索引-值赋值:
$ ass_array[index1]=val1 $ ass_array'index2]=val2
例如,考虑为水果分配价格,使用关联数组:
$ declare -A fruits_value $ fruits_value=([apple]='100 dollars' [orange]='150 dollars')
显示数组的内容:
$ echo "Apple costs ${fruits_value[apple]}" Apple costs 100 dollars
列出数组索引
数组通过索引对每个元素进行索引。普通数组和关联数组在索引类型上有所不同。
获取数组中的索引列表。
$ echo ${!array_var[*]}
另外,我们也可以使用以下命令:
$ echo ${!array_var[@]}
在之前的fruits_value数组示例中,考虑以下命令:
$ echo ${!fruits_value[*]} orange apple
这同样适用于普通数组。
访问别名
别名是一个快捷方式,用来替代输入长命令序列。在本教程中,我们将看到如何使用alias命令创建别名。
如何操作...
以下是你可以对别名执行的操作:
- 创建别名:
$ alias new_command='command sequence'
这个示例为apt-get install命令创建了一个快捷方式:
$ alias install='sudo apt-get install'
一旦定义了别名,我们可以输入install代替sudo apt-get install。
alias命令是临时的:别名存在直到我们关闭当前终端。要使别名在所有 Shell 中有效,可以将此语句添加到~/.bashrc文件中。~/.bashrc中的命令总是在启动新的交互式 Shell 进程时执行:
$ echo 'alias cmd="command seq"' >> ~/.bashrc
-
要删除别名,删除
~/.bashrc文件中的相关条目(如果有),或者使用unalias命令。或者,alias example=可以取消名为example的别名。 -
这个示例创建了一个
rm的别名,它会删除原始文件并在备份目录中保留一份副本:
alias rm='cp $@ ~/backup && rm $@'
当你创建一个别名时,如果被别名的项已经存在,它将会被新创建的别名命令替代,仅对该用户有效。
还有更多...
当以特权用户身份运行时,别名可能会成为安全隐患。为了避免危及系统安全,你应该转义命令。
转义别名
考虑到创建别名伪装成原生命令是多么简单,你不应该以特权用户身份运行有别名的命令。我们可以通过转义我们要运行的命令来忽略当前定义的任何别名。考虑以下示例:
$ \command
\字符用于转义命令,执行时不进行任何别名替换。当在不受信任的环境中运行特权命令时,通过在命令前加\来忽略别名总是一种良好的安全实践。攻击者可能已经将特权命令与其自定义命令别名,借此窃取用户提供给命令的关键信息。
列出别名
alias命令列出当前定义的别名:
$ aliasalias lc='ls -color=auto' alias ll='ls -l' alias vi='vim'
获取终端信息
在编写命令行脚本时,我们经常需要操作关于当前终端的信息,比如列数、行数、光标位置、被隐藏的密码字段等。这个教程帮助收集和操作终端设置。
准备工作
tput和stty命令是用于终端操作的工具。
如何操作...
以下是tput命令的一些功能:
- 返回终端中的列数和行数:
tput cols tput lines
- 返回当前终端名称:
tput longname
- 将光标移动到 100,100 的位置:
tput cup 100 100
- 设置终端背景颜色:
tput setb n
n的值可以是 0 到 7 之间的一个值
- 设置终端前景颜色:
tput setf n
n的值可以是 0 到 7 之间的一个值
一些命令,包括常用的color ls,可能会重置前景和背景颜色。
- 使用此命令使文本加粗:
tput bold
- 执行起始和结束下划线操作:
tput smul tput rmul
- 要删除从光标到行尾的内容,可以使用以下命令:
tput ed
- 在输入密码时,脚本不应显示字符。以下示例展示了使用
stty命令禁用字符回显:
#!/bin/sh #Filename: password.sh echo -e "Enter password: " # disable echo before reading password stty -echo read password # re-enable echo stty echo echo echo Password read.
上述命令中的-echo选项禁用终端输出,而echo选项则启用输出。
获取和设置日期以及延迟
时间延迟用于在程序执行过程中等待设定的时间(例如 1 秒),或每隔几秒钟(或几个月)监控一个任务。处理时间和日期时需要了解时间和日期的表示方式以及如何操作它们。本篇食谱将展示如何处理日期和时间延迟。
准备工作
日期可以以多种格式打印。内部上,日期是以自 1970 年 1 月 1 日 00:00:00 以来的秒数存储的,这被称为纪元时间或Unix 时间。
系统的日期可以通过命令行设置。接下来的食谱展示了如何读取和设置日期。
它是如何做的...
可以以不同的格式读取日期,也可以设置日期。
- 读取日期:
$ date Thu May 20 23:09:04 IST 2010
- 打印纪元时间:
$ date +%s 1290047248
date命令可以将许多格式化的日期字符串转换为纪元时间。这使得你可以使用多种日期格式作为输入。通常,如果你从系统日志或任何标准应用程序生成的输出中收集日期,格式无需过多关注。
将日期字符串转换为纪元时间:
$ date --date "Wed mar 15 08:09:16 EDT 2017" +%s 1489579718
--date选项定义了一个日期字符串作为输入。我们可以使用任何日期格式选项来打印输出。date命令可以用来根据日期字符串查找星期几:
$ date --date "Jan 20 2001" +%A Saturday
日期格式字符串在它是如何工作的...部分中列出。
- 使用以
+为前缀的格式字符串组合作为date命令的参数,可以将日期按你选择的格式打印。考虑以下示例:
$ date "+%d %B %Y" 20 May 2010
- 设置日期和时间:
# date -s "Formatted date string" # date -s "21 June 2009 11:01:22"
在连接到网络的系统上,您可能需要使用ntpdate来设置日期和时间:
/usr/sbin/ntpdate -s time-b.nist.gov
- 优化代码的规则是先进行测量。
date命令可以用来计算一组命令执行的时间:
#!/bin/bash #Filename: time_take.sh start=$(date +%s) commands; statements; end=$(date +%s) difference=$(( end - start)) echo Time taken to execute commands is $difference seconds.
date命令的最小分辨率为一秒。对命令进行计时的更好方法是time命令:
time commandOrScriptName。
它是如何工作的...
Unix 纪元定义为自 1970 年 1 月 1 日 00:00:00(不包括闰秒)以来的秒数,采用协调世界时(UTC)。纪元时间在需要计算两个日期或时间之间的差异时非常有用。将两个日期字符串转换为纪元时间并取其差值。此食谱计算两个日期之间的秒数:
secs1=`date -d "Jan 2 1970"
secs2=`date -d "Jan 3 1970"
echo "There are `expr $secs2 - $secs1` seconds between Jan 2 and Jan 3"
There are 86400 seconds between Jan 2 and Jan 3
显示自 1970 年 1 月 1 日午夜以来的秒数对人类来说不易阅读。date命令支持以人类可读的格式输出。
下表列出了date命令支持的格式选项。
| 日期组件 | 格式 |
|---|---|
| 星期几 | %a(例如,Sat)%A(例如,Saturday) |
| 月份 | %b(例如,Nov)%B(例如,November) |
| 日 | %d(例如,31) |
| 日期格式(mm/dd/yy) | %D(例如,10/18/10) |
| 年 | %y(例如,10)%Y(例如,2010) |
| 小时 | %I 或 %H(例如,08) |
| 分钟 | %M(例如,33) |
| 秒 | %S(例如,10) |
| 纳秒 | %N(例如,695208515) |
| Unix 时间戳(秒) | %s(例如,1290049486) |
还有更多...
生成时间间隔在编写循环执行的监控脚本时非常重要。以下示例展示了如何生成时间延迟。
在脚本中产生延迟
sleep命令将根据给定的秒数延迟脚本的执行时间。以下脚本使用tput和sleep从 0 计数到 40 秒:
#!/bin/bash
#Filename: sleep.sh
echo Count:
tput sc
# Loop for 40 seconds
for count in `seq 0 40`
do
tput rc
tput ed
echo -n $count
sleep 1
done
在前面的示例中,一个变量遍历由seq命令生成的数字列表。我们使用tput sc来存储光标位置。每次循环执行时,我们通过恢复光标位置(使用tput rc)并使用tputs ed清除到行尾,将新计数值写入终端。清除行后,脚本会输出新值。sleep命令会使脚本在每次循环迭代之间延迟 1 秒。
调试脚本
调试通常比编写代码所需时间更长。每种编程语言都应实现一个功能,在发生意外时产生追踪信息。通过读取调试信息,可以了解程序为何会出现意外的行为。Bash 提供了每个开发者都应了解的调试选项。本食谱展示了如何使用这些选项。
如何做...
我们可以使用 Bash 内建的调试工具,或者编写便于调试的脚本;方法如下:
- 添加
-x选项以启用 shell 脚本的调试跟踪。
$ bash -x script.sh
使用-x标志运行脚本会打印每行源代码及其当前状态。
你也可以使用sh -x script。
- 仅调试脚本的某些部分,使用
set -x和set +x。考虑以下示例:
#!/bin/bash
#Filename: debug.sh
for i in {1..6};
do
set -x
echo $i
set +x
done
echo "Script executed"
在前面的脚本中,只有echo $i的调试信息会被打印,因为调试仅限于该部分,使用了-x和+x来控制。
脚本使用{start..end}构造从开始到结束值进行迭代,而不是前面示例中使用的seq命令。这个构造比调用seq命令稍快。
- 上述调试方法由 Bash 内建提供。它们以固定格式输出调试信息。在许多情况下,我们需要以自定义格式输出调试信息。我们可以定义一个 _DEBUG 环境变量来启用和禁用调试,并按照自己的调试风格生成消息。
看一下以下示例代码:
#!/bin/bash
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@ || :
}
for i in {1..10}
do
DEBUG echo "I is $i"
done
使用调试模式“开启”运行前面的脚本:
$ _DEBUG=on ./script.sh
我们在每个需要打印调试信息的语句前加上 DEBUG 前缀。如果没有将 _DEBUG=on 传递给脚本,调试信息将不会被打印。在 Bash 中,命令 : 会告诉 shell 什么都不做。
它是如何工作的...
-x 标志会输出每一行脚本的执行情况。然而,我们可能只需要观察源代码的某些部分。Bash 使用 set builtin 来在脚本中启用和禁用调试打印:
-
set -x:这会在命令执行时显示参数和命令。 -
set +x:这会禁用调试功能。 -
set -v:这会在读取时显示输入。 -
set +v:这会禁用打印输入。
还有更多内容...
我们也可以使用其他方便的方式来调试脚本。我们可以通过更复杂的方式使用 shebang 来调试脚本。
Shebang 黑客技巧
Shebang 可以从 #!/bin/bash 改为 #!/bin/bash -xv,以启用调试功能而无需额外的标志(即 -xv 标志本身)。
在默认输出中,每行前都有 +,这可能使得跟踪执行流程变得困难。可以将 PS4 环境变量设置为 '$LINENO:',以显示实际的行号:
PS4='$LINENO: '
调试输出可能很长。当使用 -x 或设置 -x 时,调试输出会被发送到 stderr。可以使用以下命令将其重定向到文件:
sh -x testScript.sh 2> debugout.txt
Bash 4.0 及更高版本支持使用编号流来调试输出:
exec 6> /tmp/debugout.txt
BASH_XTRACEFD=6
函数与参数
函数与别名乍一看相似,但行为略有不同。主要区别在于,函数参数可以在函数体内的任何地方使用,而别名仅将参数附加到命令末尾。
如何做...
函数通过 function 命令定义,函数名称、圆括号以及用大括号括起来的函数体:
- 函数的定义如下:
function fname()
{
statements;
}
或者,它也可以按如下方式定义:
fname()
{
statements;
}
它甚至可以按如下方式定义(适用于简单函数):
fname() { statement; }
- 使用函数名称调用一个函数:
$ fname ; # executes function
- 传递给函数的参数是按位置访问的,
$1是第一个参数,$2是第二个,以此类推:
fname arg1 arg2 ; # passing args
以下是 fname 函数的定义。在 fname 函数中,我们包含了多种访问函数参数的方式。
fname()
{
echo $1, $2; #Accessing arg1 and arg2
echo "$@"; # Printing all arguments as list at once
echo "$*"; # Similar to $@, but arguments taken as single
entity
return 0; # Return value
}
传递给脚本的参数可以通过 $0 访问(脚本的名称):
-
-
$1是第一个参数 -
$2是第二个参数 -
$n是第 n 个参数。 -
"$@"扩展为"$1" "$2" "$3",依此类推。 -
"$*"扩展为"$1c$2c$3",其中c是 IFS 的第一个字符。 -
"$@"比$*使用得更频繁,因为前者将所有参数作为一个单独的字符串提供。
-
-
比较别名与函数
-
下面是一个别名,通过将
ls输出传递给grep来显示文件的子集。参数附加在命令的末尾,因此lsg txt会展开为ls | grep txt:
$> alias lsg='ls | grep'
$> lsg txt
file1.txt
file2.txt
file3.txt
- 如果我们想要扩展它来获取
/sbin/ifconfig中设备的 IP 地址,可能会尝试如下操作:
$> alias wontWork='/sbin/ifconfig | grep'
$> wontWork eth0
eth0 Link encap:Ethernet HWaddr 00:11::22::33::44:55
grep命令找到的是eth0字符串,而不是 IP 地址。如果我们使用函数而不是别名,我们可以将参数传递给ifconfig,而不是将其附加到grep上:
$> function getIP() { /sbin/ifconfig $1 | grep 'inet '; }
$> getIP eth0
inet addr:192.168.1.2 Bcast:192.168.255.255 Mask:255.255.0.0
还有更多...
让我们探索更多 Bash 函数的技巧。
递归函数
Bash 中的函数也支持递归(函数可以调用自身)。例如,F() { echo $1; F hello; sleep 1; }。
Fork bomb
递归函数是指调用自身的函数:递归函数必须有一个退出条件,否则它们会一直生成进程,直到系统耗尽资源并崩溃。
这个函数::(){ :|:& };: 会永远生成进程,最终导致拒绝服务攻击。
&字符被附加到函数调用后面,将子进程带入后台。这个危险的代码会无限生成进程,称为 fork bomb。
你可能会觉得很难理解前面的代码。请参考 Wikipedia 页面 h t t p 😕/e n . w i k i p e d i a . o r g /w i k i /F o r k _ b o m b 了解更多细节和对 fork bomb 的解释。
通过在/etc/security/limits.conf中定义nproc值来限制能够生成的最大进程数,从而防止此类攻击。
这一行将限制所有用户最多只能启动 100 个进程:
hard nproc 100
导出函数
函数可以像环境变量一样通过export命令导出。导出扩展了函数的作用域到子进程:
export -f fname $> function getIP() { /sbin/ifconfig $1 | grep 'inet '; } $> echo "getIP eth0" >test.sh $> sh test.sh
sh: getIP: No such file or directory $> export -f getIP $> sh test.sh
inet addr: 192.168.1.2 Bcast: 192.168.255.255 Mask:255.255.0.0
读取命令的返回值(状态)
一个命令的返回值存储在$?变量中。
cmd; echo $?;
返回值被称为退出状态。这个值可以用来判断一个命令是否成功执行。如果命令成功退出,退出状态将为零,否则为非零值。
以下脚本报告命令的成功/失败状态:
#!/bin/bash
#Filename: success_test.sh
# Evaluate the arguments on the command line - ie success_test.sh 'ls | grep txt'
eval $@
if [ $? -eq 0 ];
then
echo "$CMD executed successfully"
else
echo "$CMD terminated unsuccessfully"
fi
向命令传递参数
大多数应用程序接受不同格式的参数。假设-p和-v是可用的选项,-k N是另一个需要数字的选项。此外,命令需要一个文件名作为参数。这个应用程序可以通过多种方式执行:
-
$ command -p -v -k 1 file -
$ command -pv -k 1 file -
$ command -vpk 1 file -
$ command file -pvk 1
在脚本中,命令行参数可以通过它们在命令行中的位置进行访问。第一个参数是$1,第二个是$2,依此类推。
这个脚本会显示前三个命令行参数:
echo $1 $2 $3
通常情况下,逐个迭代命令参数。shift命令将每个参数向左移动一个位置,使脚本可以将每个参数作为$1来访问。以下代码显示所有命令行的值:
$ cat showArgs.sh
for i in `seq 1 $#`
do
echo $i is $1
shift
done
$ sh showArgs.sh a b c
1 is a
2 is b
3 is c
将一个命令的输出发送给另一个命令
Unix shell 的一个最好的特点是可以轻松地组合多个命令来生成报告。一个命令的输出可以作为另一个命令的输入,而另一个命令则将其输出传递给下一个命令,依此类推。这个命令序列的输出可以分配给一个变量。这个示例展示了如何组合多个命令以及如何读取输出。
准备好
输入通常通过stdin或参数传递给命令。输出发送到stdout或stderr。当我们组合多个命令时,通常通过stdin提供输入,并生成输出到stdout。
在这种情况下,命令被称为过滤器。我们使用管道(由管道操作符|表示)将每个过滤器连接起来,如下所示:
$ cmd1 | cmd2 | cmd3
在这里,我们组合了三个命令。cmd1的输出传递给cmd2,cmd2的输出传递给cmd3,最终输出(来自cmd3)将显示在屏幕上,或者被重定向到一个文件。
如何做到这一点...
管道可以与子壳方法一起使用,用于组合多个命令的输出。
- 让我们从组合两个命令开始:
$ ls | cat -n > out.txt
ls(当前目录的列表)的输出被传递给cat -n,后者在通过stdin接收到的输入前加上行号。输出被重定向到out.txt。
- 将一系列命令的输出分配给一个变量:
cmd_output=$(COMMANDS)
这被称为子壳方法。考虑以下这个例子:
cmd_output=$(ls | cat -n) echo $cmd_output
另一种方法,称为反引号(有些人也称之为反勾引号),也可以用来存储命令输出:
cmd_output=`COMMANDS`
考虑以下这个例子:
cmd_output=`ls | cat -n`
echo $cmd_output
反引号与单引号字符不同。它是键盘上~键上的字符。
还有更多...
有多种方法可以对命令进行分组。
使用子壳生成一个独立的进程
子壳是独立的进程。子壳使用( )运算符来定义:
-
pwd命令打印工作目录的路径 -
cd命令将当前目录更改为给定的目录路径:
$> pwd
/
$> (cd /bin; ls)
awk bash cat...
$> pwd
/
当命令在子壳中执行时,当前 shell 不会发生任何变化;更改仅限于子壳。例如,当使用cd命令在子壳中更改当前目录时,目录更改不会反映在主 shell 环境中。
使用子壳引用来保留空格和换行符
假设我们使用子壳或反引号方法将命令的输出分配给一个变量,我们必须使用双引号来保留空格和换行符(\n)。考虑这个例子:
$ cat text.txt 1 2 3 $ out=$(cat text.txt) $ echo $out 1 2 3 # Lost \n spacing in 1,2,3 $ out="$(cat text.txt)" $ echo $out 1 2 3
读取 n 个字符而无需按回车键
bash 命令 read 从键盘或标准输入中读取文本。我们可以使用 read 来交互式地获取用户输入,但 read 还能做更多事情。任何编程语言中的大多数输入库都会从键盘读取输入,并在按下回车时终止字符串。在某些情况下,无法按下回车键,而字符串的终止是基于接收到的字符数(可能是单个字符)。例如,在一个互动游戏中,当按下 + 时,球会向上移动。按下 + 然后再按 return 来确认 + 的按下是不高效的。
这个方案使用 read 命令来完成此任务,无需按 return 键。
如何实现...
你可以使用 read 命令的各种选项来获取不同的结果,如下所示:
- 以下语句将从输入中读取 n 个字符到
variable_name变量中:
read -n number_of_chars variable_name
请考虑这个示例:
$ read -n 2 var $ echo $var
- 以不回显的模式读取密码:
read -s var
- 使用以下命令通过
read显示一条消息:
read -p "Enter input:" var
- 在超时后读取输入:
read -t timeout var
请考虑以下示例:
$ read -t 2 var # Read the string that is typed within 2 seconds into
variable var.
- 使用分隔符字符来结束输入行:
read -d delim_char var
请考虑以下示例:
$ read -d ":" var hello:#var is set to hello
执行命令直到它成功
有时,命令只能在满足特定条件时成功。例如,只有在文件创建后才能下载该文件。在这种情况下,你可能希望重复执行命令直到它成功。
如何实现...
以以下方式定义一个函数:
repeat()
{
while true
do
$@ && return
done
}
或者,可以将此添加到 shell 的 rc 文件中以便于使用:
repeat() { while true; do $@ && return; done }
它是如何工作的...
这个重复函数有一个无限的 while 循环,尝试执行作为参数传递给函数的命令(通过 $@ 访问)。如果命令执行成功,则返回并退出循环。
还有更多...
我们看到了一种基本的执行命令直到成功的方法。让我们使其更加高效。
更快速的方法
在大多数现代系统中,true 被实现为 /bin 中的一个二进制文件。这意味着每次上述的 while 循环运行时,shell 都需要启动一个进程。为了避免这种情况,我们可以使用 shell 内建的 : 命令,它始终返回退出码 0:
repeat() { while :; do $@ && return; done }
尽管这种方法不如第一种方法可读,但它比第一种方法更快。
添加延迟
假设你正在使用 repeat() 从互联网上下载一个当前不可用,但稍后会可用的文件。示例如下:
repeat wget -c http://www.example.com/software-0.1.tar.gz
这个脚本将向 www.example.com 发送过多的流量,导致服务器出现问题(如果服务器将你的 IP 列入黑名单,可能也会对你造成麻烦)。为了解决这个问题,我们修改了函数并添加了延迟,具体如下:
repeat() { while :; do $@ && return; sleep 30; done }
这将使命令每 30 秒运行一次。
字段分隔符和迭代器
内部字段分隔符(IFS)是 shell 脚本中的一个重要概念。它对文本数据的处理非常有用。
IFS 是一个特殊目的的定界符。它是一个存储分隔字符的环境变量,是运行的 shell 环境使用的默认定界符字符串。
考虑需要遍历字符串中的单词或以逗号分隔的值(CSV)的情况。在第一种情况下,我们将使用IFS=" ",在第二种情况下,使用IFS=","。
准备工作
考虑 CSV 数据的情况:
data="name,gender,rollno,location"
To read each of the item in a variable, we can use IFS.
oldIFS=$IFS
IFS=, # IFS is now a ,
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
这会生成以下输出:
Item: name Item: gender Item: rollno Item: location
IFS 的默认值是空白字符(换行符、制表符或空格字符)。
当 IFS 设置为,时,shell 将逗号解释为分隔符字符,因此在迭代过程中,$item变量将获取由逗号分隔的子字符串作为其值。
如果 IFS 没有设置为,,则会将整个数据作为一个字符串打印出来。
如何操作...
让我们通过另一个使用 IFS 的例子来解析/etc/passwd文件。在/etc/passwd文件中,每行包含用:分隔的项。文件中的每一行对应于与用户相关的一个属性。
考虑输入:root:x:0:0:root:/root:/bin/bash。每行的最后一项指定了该用户的默认 shell。
使用 IFS 技巧打印用户及其默认 shell:
#!/bin/bash
#Desc: Illustration of IFS
line="root:x:0:0:root:/root:/bin/bash"
oldIFS=$IFS;
IFS=":"
count=0
for item in $line;
do
[ $count -eq 0 ] && user=$item;
[ $count -eq 6 ] && shell=$item;
let count++
done;
IFS=$oldIFS
echo $user's shell is $shell;
输出将如下所示:
root's shell is /bin/bash
循环在遍历一系列值时非常有用。Bash 提供了多种类型的循环。
- 面向列表的
for循环:
for var in list;
do
commands; # use $var
done
列表可以是一个字符串或一系列值。
我们可以使用echo命令生成序列:
echo {1..50} ;# Generate a list of numbers from 1 to 50.
echo {a..z} {A..Z} ;# List of lower and upper case letters.
我们可以将这些结合起来以连接数据。
在以下代码中,每次迭代中,变量 i 将包含 a 到 z 范围内的字符:
for i in {a..z}; do actions; done;
- 遍历一系列数字:
for((i=0;i<10;i++))
{
commands; # Use $i
}
- 循环直到满足条件:
while 循环在条件为真时继续运行,而 until 循环在条件为真之前执行:
while condition
do
commands;
done
对于无限循环,使用true作为条件:
- 使用
until循环:
Bash 中有一个特殊的循环叫做until。这个循环会一直执行直到给定条件变为真。请考虑这个例子:
x=0;
until [ $x -eq 9 ]; # [ $x -eq 9 ] is the condition
do
let x++; echo $x;
done
比较和测试
程序中的流程控制通过比较和测试语句来处理。Bash 提供了几种执行测试的选项。我们可以使用if、if else和逻辑运算符来执行测试,并使用比较运算符来比较数据项。还有一个叫做test的命令,它也用于执行测试。
如何操作...
这里是一些用于比较和执行测试的方法:
- 使用
if条件:
if condition;
then
commands;
fi
- 使用
else if和else:
if condition;
then
commands;
else if condition; then
commands;
else
commands;
fi
if 和 else 可以嵌套。if 条件可能很长;为了简化它们,我们可以使用逻辑运算符:
[ condition ] && action; # 如果条件为真,执行动作
[ condition ] || action; # 如果条件为假,执行动作
&&是逻辑与操作,||是逻辑或操作。这在编写 Bash 脚本时非常有用。
执行数学比较:通常,条件包含在方括号 [] 中。注意,[ 或 ] 和操作数之间有空格。如果没有空格,将会出现错误。
[$var -eq 0 ] or [ $var -eq 0]
对变量和值进行数学测试,如下所示:
[ $var -eq 0 ] # It returns true when $var equal to 0\.
[ $var -ne 0 ] # It returns true when $var is not equal to 0
其他重要的运算符包括以下内容:
-
-gt:大于 -
-lt:小于 -
-ge:大于或等于 -
-le:小于或等于
-a 运算符是逻辑与,-o 运算符是逻辑或。多个测试条件可以组合使用:
[ $var1 -ne 0 -a $var2 -gt 2 ] # using and -a
[ $var1 -ne 0 -o var2 -gt 2 ] # OR -o
与文件系统相关的测试如下:
使用不同的条件标志测试不同的文件系统属性。
-
[ -f $file_var ]:如果给定的变量保存的是一个常规文件路径或文件名,返回 true。 -
[ -x $var ]:如果给定的变量保存的是一个可执行的文件路径或文件名,返回 true。 -
[ -d $var ]:如果给定的变量保存的是目录路径或目录名称,返回 true。 -
[ -e $var ]:如果给定的变量保存的是一个存在的文件,返回 true。 -
[ -c $var ]:如果给定的变量保存的是一个字符设备文件的路径,返回 true。 -
[ -b $var ]:如果给定的变量保存的是一个块设备文件路径,返回 true。 -
[ -w $var ]:如果给定的变量保存的是一个可写的文件路径,返回 true。 -
[ -r $var ]:如果给定的变量保存的是一个可读的文件路径,返回 true。 -
[ -L $var ]:如果给定的变量保存的是路径,返回 true。符号链接
考虑以下示例:
fpath="/etc/passwd"
if [ -e $fpath ]; then
echo File exists;
else
echo Does not exist;
fi
字符串比较:在使用字符串比较时,最好使用双中括号,因为使用单中括号有时可能会导致错误。
注意,双中括号是 Bash 扩展。如果脚本将在 ash 或 dash 中运行(为了提高性能),则不能使用双中括号。
测试两个字符串是否相同:
-
[[ $str1 = $str2 ]]:当str1等于str2时返回 true,即str1和str2的文本内容相同。 -
[[ $str1 == $str2 ]]:这是另一种字符串比较的方法。相等性检查
测试两个字符串是否不相同:
[[ $str1 != $str2 ]]:当str1和str2不匹配时返回 true。
查找字母顺序上较大的字符串:
字符串通过比较字符的 ASCII 值按字母顺序进行比较。例如,"A" 是 0x41,而 "a" 是 0x61。因此,"A" 小于 "a",而 "AAa" 小于 "Aaa"。
-
[[ $str1 > $str2 ]]:当str1在字母顺序上大于str2时返回 true。 -
[[ $str1 < $str2 ]]:当str1在字母顺序上小于str2时返回 true。
在 = 前后需要空格;如果没有提供空格,它就不是比较,而是赋值语句。
测试空字符串:
-
[[ -z $str1 ]]:如果str1保存的是一个空字符串,返回 true。 -
[[ -n $str1 ]]:如果str1保存的是一个非空字符串,返回 true。
使用逻辑运算符 && 和 || 来组合多个条件更为简便,如以下代码所示:
if [[ -n $str1 ]] && [[ -z $str2 ]] ;
then
commands;
fi
考虑这个例子:
str1="Not empty "
str2=""
if [[ -n $str1 ]] && [[ -z $str2 ]];
then
echo str1 is nonempty and str2 is empty string.
fi
这将是输出:
str1 is nonempty and str2 is empty string.
test 命令可以用来进行条件检查。这减少了使用大括号的数量,并且可以使代码更具可读性。[] 中的相同测试条件也可以与 test 命令一起使用。
注意,test 是一个外部程序,必须被 fork,而 [ 是 Bash 中的内部函数,因此效率更高。test 程序与 Bourne shell、ash、dash 等兼容。
考虑这个例子:
if [ $var -eq 0 ]; then echo "True"; fi
can be written as
if test $var -eq 0 ; then echo "True"; fi
使用配置文件定制 bash
你在命令行中输入的大多数命令都可以放入一个特殊文件中,在登录或启动新的 bash 会话时进行评估。通过将函数定义、别名和环境变量设置放入这些文件之一来定制你的 shell 是很常见的做法。
常见的命令可以放入配置文件中,包括以下内容:
# Define my colors for ls
LS_COLORS='no=00:di=01;46:ln=00;36:pi=40;33:so=00;35:bd=40;33;01'
export LS_COLORS
# My primary prompt
PS1='Hello $USER'; export PS1
# Applications I install outside the normal distro paths
PATH=$PATH:/opt/MySpecialApplication/bin; export PATH
# Shorthand for commands I use frequently
function lc () {/bin/ls -C $* ; }
我应该使用哪个定制文件?
Linux 和 Unix 有几个文件可能包含自定义脚本。这些配置文件分为三类——登录时加载的文件、交互式 shell 调用时评估的文件,以及每次 shell 被调用以处理脚本文件时评估的文件。
如何操作...
这些文件会在用户登录到 shell 时被评估:
/etc/profile, $HOME/.profile, $HOME/.bash_login, $HOME/.bash_profile /
注意,如果通过图形登录管理器登录,则可能不会加载 /etc/profile、$HOME/.profile 和 $HOME/.bash_profile。这是因为图形窗口管理器不会启动 shell。当你打开一个终端窗口时,会创建一个 shell,但这不是登录 shell。
如果存在 .bash_profile 或 .bash_login 文件,则 .profile 文件不会被读取。
这些文件将被交互式 shell 读取,如 X11 终端会话,或使用 ssh 执行类似命令 ssh 192.168.1.1 ls /tmp。
/etc/bash.bashrc $HOME/.bashrc
如下所示运行 shell 脚本:
$> cat myscript.sh
#!/bin/bash
echo "Running"
除非你已定义了 BASH_ENV 环境变量,否则这些文件都不会被加载:
$> export BASH_ENV=~/.bashrc
$> ./myscript.sh
使用 ssh 执行单个命令,如下所示:
ssh 192.168.1.100 ls /tmp
这将启动一个 bash shell,评估 /etc/bash.bashrc 和 $HOME/.bashrc,但不会评估 /etc/profile 或 .profile。
调用 ssh 登录会话,如下所示:
ssh 192.168.1.100
这将创建一个新的登录 bash shell,系统将评估以下内容:
/etc/profile
/etc/bash.bashrc
$HOME/.profile or .bashrc_profile
危险:其他外壳,如传统的 Bourne shell、ash、dash 和 ksh,也会读取此文件。线性数组(列表)和关联数组,并非所有外壳都支持。在 /etc/profile 或 $HOME/.profile 中避免使用它们。
使用这些文件来定义所有用户都希望拥有的非导出项,如别名。考虑这个例子:
alias l "ls -l"
/etc/bash.bashrc /etc/bashrc
使用这些文件来存储个人设置。它们对于设置必须由其他 bash 实例继承的路径很有用。它们可能包含如下行:
CLASSPATH=$CLASSPATH:$HOME/MyJavaProject; export CLASSPATH
$HOME/.bash_login $HOME/.bash_profile $HOME/.profile
如果 .bash_login 或 .bash_profile 存在,.profile 文件将不会被读取。.profile 文件可能会被其他 shell 读取。
使用这些文件来保存每次创建新 Shell 时需要定义的个人值。如果你希望它们在 X11 终端会话中可用,可以在这里定义别名和函数:
$HOME/.bashrc, /etc/bash.bashrc
导出的变量和函数会传播到下级 Shell,但别名不会。你必须将 BASH_ENV 定义为 .bashrc 或 .profile,在这些文件中定义别名,才能在 Shell 脚本中使用它们。
当用户登出会话时,此文件会被执行:
$HOME/.bash_logout
例如,如果用户远程登录,他们在登出时应清除屏幕。
$> cat ~/.bash_logout
# Clear the screen after a remote login/logout.
clear
第二章:拥有一条好命令
在本章中,我们将介绍以下技巧:
-
使用
cat连接文件 -
记录和回放终端会话
-
查找文件和文件列表
-
使用
xargs进行操作 -
使用
tr进行翻译 -
校验和和验证
-
加密工具和哈希
-
排序唯一行和重复行
-
临时文件命名和随机数
-
分割文件和数据
-
基于扩展名切割文件名
-
批量重命名和移动文件
-
拼写检查和字典操作
-
自动化交互式输入
-
通过运行并行进程加速命令执行
-
检查目录、文件及其子目录
介绍
类 Unix 系统拥有最好的命令行工具。每个命令执行简单的功能,以便使我们的工作更加轻松。这些简单的功能可以与其他命令组合在一起,解决复杂的问题。将简单命令组合起来是一门艺术;通过练习和积累经验,你会越来越擅长。 本章介绍了一些最有趣和最有用的命令,包括grep、awk、sed和find。
使用cat进行连接
cat命令显示或连接文件的内容,但cat命令能做更多的事情。例如,cat可以将标准输入数据与文件中的数据结合起来。将stdin数据与文件数据结合的一种方式是将stdin重定向到文件中,然后将两个文件合并。cat命令可以通过一次调用完成此操作。接下来的技巧展示了cat的基本和高级用法。
如何做到...
cat命令是一个简单且常用的命令,它代表conCATenate(连接)。
用于读取内容的cat命令的一般语法如下:
$ cat file1 file2 file3 ...
该命令将命令行参数中指定的文件的数据连接起来,并将这些数据发送到stdout。
- 要打印单个文件的内容,请执行以下命令:
$ cat file.txt
This is a line inside file.txt
This is the second line inside file.txt
- 要打印多个文件的内容,请执行以下命令:
$ cat one.txt two.txt
This line is from one.txt
This line is from two.txt
cat命令不仅可以从文件中读取并连接数据,还可以从标准输入读取数据。
管道操作符将数据重定向到cat命令的标准输入,如下所示:
OUTPUT_FROM_SOME COMMANDS | cat
cat命令也可以将文件内容与终端输入结合起来。
将stdin和另一个文件中的数据结合起来,像这样:
$ echo 'Text through stdin' | cat - file.txt
在此示例中,-充当stdin文本的文件名。
还有更多...
cat命令有许多其他选项来查看文件。你可以通过在终端会话中输入man cat来查看完整的列表。
去除多余的空行
有些文本文件中包含两个或多个连续的空行。如果你需要去除多余的空行,可以使用以下语法:
$ cat -s file
考虑以下示例:
$ cat multi_blanks.txt
line 1
line 2
line 3
line 4
$ cat -s multi_blanks.txt # Squeeze adjacent blank lines
line 1
line 2
line 3
line 4
我们可以使用tr删除所有空行,正如本章中的使用 tr 进行翻译技巧所讨论的那样。
将标签显示为 ^I
很难区分制表符和重复的空格字符。像 Python 这样的语言可能会对制表符和空格进行不同的处理。制表符和空格的混合在编辑器中可能看起来相似,但对解释器来说却是不同的缩进。当在文本编辑器中查看文件时,很难识别制表符和空格之间的区别。cat 还可以识别制表符。这有助于你调试缩进错误。
cat 命令的 -T 选项将制表符显示为 ^I:
$ cat file.py
def function():
var = 5
next = 6
third = 7
$ cat -T file.py
def function():
^Ivar = 5
^I^Inext = 6
^Ithird = 7^I
行号
cat 命令的 -n 标志会在每行前添加行号。请看这个示例:
$ cat lines.txt
line
line
line
$ cat -n lines.txt
1 line
2 line
3 line
cat 命令不会更改文件。它会在根据选项修改输入后将输出发送到 stdout。不要尝试使用重定向来覆盖输入文件。Shell 会在打开输入文件之前创建新的输出文件。cat 命令不会允许你将同一个文件作为输入和重定向输出。试图用管道和重定向输出来“欺骗” cat 会清空输入文件。
$> echo "This will vanish" > myfile
$> cat -n myfile >myfile
cat: myfile: input file is output file
$> cat myfile | cat -n >myfile
$> ls -l myfile
-rw-rw-rw-. 1 user user 0 Aug 24 00:14 myfile ;# myfile has 0
bytes
-n 选项会为所有行生成行号,包括空白行。如果你想跳过空白行的编号,可以使用 -b 选项。
记录和回放终端会话
录制屏幕会话作为视频很有用,但对于调试终端会话或提供 Shell 教程来说,视频显得有些过于复杂。
Shell 提供了另一个选项。script 命令会记录你输入的按键和按键的时间,同时将你的输入和结果输出保存到一对文件中。scriptreplay 命令将重放这个会话。
准备好
script 和 scriptreplay 命令在大多数 GNU/Linux 发行版中都有提供。你可以通过录制终端会话来创建命令行技巧和技巧的教程。你还可以分享录制的文件,让别人回放并查看如何用命令行执行特定任务。你甚至可以调用其他解释器,并记录发送到该解释器的按键输入。你无法记录像 vi、emacs 或其他将字符映射到屏幕上特定位置的应用程序。
如何操作...
使用以下命令开始记录终端会话:
$ script -t 2> timing.log -a output.session
一个完整的示例如下:
$ script -t 2> timing.log -a output.session
# This is a demonstration of tclsh
$ tclsh
% puts [expr 2 + 2]
4
% exit
$ exit
注意,这个方法在不支持将 stderr 仅重定向到文件的 Shell 中(如 csh shell)无法使用。
script 命令接受一个文件名作为参数。这个文件将保存按键输入和命令结果。当使用 -t 选项时,script 命令将时间数据发送到 stdout。这些时间数据可以被重定向到一个文件(timing.log),记录每个按键输入和输出的时间信息。之前的示例使用了 2> 来将 stderr 重定向到 timing.log。
使用这两个文件,timing.log 和 output.session,我们可以按如下方式重放命令执行的序列:
$ scriptreplay timing.log output.session
# Plays the sequence of commands and output
如何工作...
我们经常录制桌面视频来准备教程。然而,视频需要大量存储空间,而终端脚本文件只是一个文本文件,通常只有几千字节大小。
你可以将timing.log和output.session文件共享给任何希望在终端中重放终端会话的人。
查找文件和文件列表
find命令是 Unix/Linux 命令行工具箱中的一个伟大工具。它在命令行和 Shell 脚本中都很有用。像cat和ls一样,find有许多功能,而大多数人并没有充分利用它。这个食谱介绍了几种常见的使用find来定位文件的方法。
准备工作
find命令采用以下策略:find通过文件层次结构进行递归查找,匹配满足指定条件的文件,并执行某些操作。默认操作是打印文件和文件夹的名称,可以通过-print选项指定。
如何实现...
要列出从给定目录递归查找的所有文件和文件夹,请使用以下语法:
$ find base_path
base_path可以是find开始递归查找的任何位置(例如,/home/slynux/)。
这是这个命令的一个示例:
$ find . -print
.history
Downloads
Downloads/tcl.fossil
Downloads/chapter2.doc
…
.指定当前目录,..指定父目录。这一约定在整个 Unix 文件系统中都得到了遵循。
-print选项将每个文件或文件夹的名称与\n(换行符)分开。-print0选项将每个名称与空字符'\0'分开。-print0的主要用途是将包含换行符或空格字符的文件名传递给xargs命令。xargs命令将在稍后详细讨论:
$> echo "test" > "file name"
$> find . -type f -print | xargs ls -l
ls: cannot access ./file: No such file or directory
ls: cannot access name: No such file or directory
$> find . -type f -print0 | xargs -0 ls -l
-rw-rw-rw-. 1 user group 5 Aug 24 15:00 ./file name
还有更多...
前面的示例演示了如何使用find列出文件系统层次结构中的所有文件和文件夹。find命令可以根据通配符或正则表达式规则、文件系统树中的深度、日期、文件类型等选择文件。
基于名称或正则表达式匹配的搜索
-name参数指定名称的选择模式。-name参数接受通配符样式和正则表达式。在下面的示例中,'*.txt'匹配所有以.txt结尾的文件或文件夹名称并将其打印出来。
请注意*.txt周围的单引号。没有引号或使用双引号(")时,Shell 会展开通配符。单引号防止 Shell 展开*.txt,而是将该字符串传递给find命令。
$ find /home/slynux -name '*.txt' -print
find命令有一个-iname(忽略大小写)选项,它类似于-name,但它匹配不区分大小写的文件名。
请考虑以下示例:
$ ls
example.txt EXAMPLE.txt file.txt
$ find . -iname "example*" -print
./example.txt
./EXAMPLE.txt
find命令支持与选择选项的逻辑操作。-a和-and选项执行逻辑与,而-o和-or选项执行逻辑或。
$ ls
new.txt some.jpg text.pdf stuff.png
$ find . \( -name '*.txt' -o -name '*.pdf' \) -print
./text.pdf
./new.txt
上一个命令将打印所有 .txt 和 .pdf 文件,因为 find 命令匹配了 .txt 和 .pdf 文件。\( 和 \) 用来将 -name "*.txt" -o -name "*.pdf" 作为一个单一单元来处理。
以下命令演示了使用 -and 运算符仅选择以 s 开头并且名称中有 e 的文件。
$ find . \( -name '*e*' -and -name 's*' \)
./some.jpg
-path 参数将匹配限制为同时匹配路径和名称的文件。例如,$ find /home/users -path '*/slynux/*' -name '*.txt' -print 将找到 /home/users/slynux/readme.txt,但不会找到 /home/users/slynux.txt。
-regex 参数类似于 -path,但 -regex 基于正则表达式来匹配文件路径。
正则表达式比通配符更复杂,支持更精确的模式匹配。一个典型的文本匹配示例是识别所有电子邮件地址。电子邮件地址通常是 name@host.root 的形式。它可以被概括为 [a-z0-9]+@[a-z0-9]+\.[a-z0-9]+。方括号内的字符表示一组字符。在这个例子中,a-z 和 0-9。+ 符号表示前面的字符类别可以出现一次或多次。点号是一个通配符字符(类似于通配符中的 ?),所以它必须用反斜杠转义以匹配电子邮件地址中的实际点。因此,这个正则表达式的意思是“一个字母或数字的序列,后跟一个 @,然后是一个字母或数字的序列,再后面是一个点,最后是一个字母或数字的序列”。有关更多详细信息,请参见 第四章 中的 使用正则表达式 章节,开车与短信。
这个命令匹配 .py 或 .sh 文件:
$ ls
new.PY next.jpg test.py script.sh $ find . -regex '.*\.(py\|sh\)$'
./test.py
script.sh
-iregex 选项忽略正则表达式匹配的大小写。
请考虑以下示例:
$ find . -iregex '.*\(\.py\|\.sh\)$'
./test.py
./new.PY
./script.sh
否定参数
find 命令也可以使用 ! 来排除匹配模式的内容:
$ find . ! -name "*.txt" -print
这个命令将匹配所有文件名不以 .txt 结尾的文件。以下示例显示了命令的结果:
$ ls
list.txt new.PY new.txt next.jpg test.py
$ find . ! -name "*.txt" -print
.
./next.jpg
./test.py
./new.PY
基于目录深度进行搜索
find 命令会遍历所有子目录,直到到达每个子目录树的底部。默认情况下,find 命令不会跟随符号链接。-L 选项会强制它跟随符号链接。如果一个链接引用了指向原始文件的链接,find 会陷入循环。
-maxdepth 和 -mindepth 参数限制了 find 命令的遍历深度。这会打破 find 命令的无限搜索。
/proc 文件系统包含关于你的系统和正在运行任务的信息。任务的文件夹层级非常深,并且包含指向自身的符号链接。每个正在运行的进程在 proc 中都有一个条目,条目名称为进程 ID。每个进程 ID 下有一个名为 cwd 的文件夹,它是指向该任务当前工作目录的链接。
以下示例展示了如何列出文件夹中所有运行的任务,文件名为 bundlemaker.def:
$ find -L /proc -maxdepth 3 -name 'bundlemaker.def' 2>/dev/null
-
-L选项告诉find命令跟踪符号链接 -
/proc是开始搜索的文件夹 -
-maxdepth 3选项将搜索限制在当前文件夹中,而不包括子文件夹 -
-name 'bundlemaker.def'选项是要搜索的文件 -
2>/dev/null将关于递归循环的错误信息重定向到空设备
-mindepth 选项类似于 -maxdepth,但它设置了 find 报告匹配项的最小深度。它可以用于查找并打印从基路径起至少有一定深度的文件。例如,要打印所有以 f 开头并且至少距离当前目录两级子目录的文件,可以使用以下命令:
$ find . -mindepth 2 -name "f*" -print
./dir1/dir2/file1
./dir3/dir4/f2
当前目录或 dir1 和 dir3 中以 f 开头的文件将不会显示。
-maxdepth 和 -mindepth 选项应放在 find 命令的前面。如果它们被指定为后续参数,可能会影响 find 的效率,因为它必须进行不必要的检查。例如,如果 -maxdepth 在 -type 参数之后指定,find 命令会首先找到具有指定 -type 的文件,然后过滤掉那些不匹配正确深度的文件。然而,如果深度在 -type 之前指定,find 会先收集具有指定深度的文件,再检查文件类型,这样是最有效的搜索方式。
基于文件类型进行搜索
类 Unix 操作系统将每个对象视为文件。文件有不同种类,如常规文件、目录、字符设备、块设备、符号链接、硬链接、套接字、FIFO 等。
find 命令通过 -type 选项过滤文件搜索。使用 -type,我们可以告诉 find 只匹配指定类型的文件。
仅列出包括后代在内的目录:
$ find . -type d -print
列出目录和文件是比较困难的,但 find 命令可以帮助完成。仅列出常规文件,如下所示:
$ find . -type f -print
仅列出符号链接,如下所示:
$ find . -type l -print
以下表格显示了 find 命令识别的类型和参数:
| 文件类型 | 类型参数 |
|---|---|
| 常规文件 | f |
| 符号链接 | l |
| 目录 | d |
| 字符特殊设备 | c |
| 块设备 | b |
| 套接字 | s |
| FIFO | p |
基于文件时间戳进行搜索
Unix/Linux 文件系统在每个文件上有三种时间戳。它们如下所示:
-
访问时间 (
-atime): 文件最后被访问时的时间戳 -
修改时间 (
-mtime): 文件最后修改时的时间戳 -
修改时间 (
-ctime): 文件元数据(如权限或所有权)最后修改时的时间戳
Unix 默认不存储文件创建时间;然而,一些文件系统(ufs2、ext4、zfs、btrfs、jfs)会保存创建时间。可以使用 stat 命令访问创建时间。
鉴于某些应用程序通过创建新文件然后删除原始文件来修改文件,因此创建日期可能不准确。
-atime、-mtime 和 -ctime 选项是 find 命令中可用的时间参数选项。它们可以使用整数值指定,以 天数 为单位。该数字可以带有 - 或 + 前缀。- 表示小于,而 + 表示大于。
请考虑以下示例:
- 打印在过去七天内被访问的文件:
$ find . -type f -atime -7 -print
- 打印出访问时间恰好是七天前的文件:
$ find . -type f -atime 7 -print
- 打印出访问时间超过七天的文件:
$ find . -type f -atime +7 -print
-mtime 参数将根据修改时间搜索文件;-ctime 根据更改时间搜索文件。
-atime、-mtime 和 -ctime 使用以天为单位的时间。find 命令还支持按分钟为单位的时间选项。具体如下:
-
-amin(访问时间) -
-mmin(修改时间) -
-cmin(更改时间)
要打印出所有访问时间超过七分钟的文件,使用以下命令:
$ find . -type f -amin +7 -print
-newer 选项指定一个参考文件,其修改时间将用于选择比参考文件更近期修改的文件。
查找所有比 file.txt 文件更近期修改的文件:
$ find . -type f -newer file.txt -print
find 命令的时间戳标志对于编写备份和维护脚本非常有用。
基于文件大小的搜索
根据文件的大小,可以执行搜索:
# Files having size greater than 2 kilobytes
$ find . -type f -size +2k
# Files having size less than 2 kilobytes
$ find . -type f -size -2k
# Files having size 2 kilobytes
$ find . -type f -size 2k
除了 k,我们还可以使用这些不同的大小单位:
-
b:512 字节块 -
c:字节 -
w:双字节词 -
k:千字节(1,024 字节) -
M:兆字节(1,024 千字节) -
G:千兆字节(1,024 兆字节)
基于文件权限和所有权匹配
可以根据文件权限匹配文件。我们可以列出具有指定文件权限的文件:
$ find . -type f -perm 644 -print
# Print files having permission 644
-perm 选项指定 find 只匹配其权限设置为特定值的文件。权限的更多细节可以参考第三章《文件进,文件出》中的 与文件权限、所有权和粘滞位 配方。
作为一个使用案例,我们可以考虑 Apache web 服务器的情况。Web 服务器中的 PHP 文件需要适当的权限才能执行。我们可以找到没有适当执行权限的 PHP 文件:
$ find . -type f -name "*.php" ! -perm 644 -print
PHP/custom.php
$ ls -l PHP/custom.php
-rw-rw-rw-. root root 513 Mar 13 2016 PHP/custom.php
我们还可以基于文件的所有权进行搜索。可以使用 -user USER 选项找到由特定用户拥有的文件。
USER 参数可以是用户名或 UID。
例如,要打印出所有由 slynux 用户拥有的文件,可以使用以下命令:
$ find . -type f -user slynux -print
对文件执行操作
find 命令可以对其识别的文件执行操作。你可以删除文件,或对文件执行任意的 Linux 命令。
基于文件匹配删除
find 命令的 -delete 标志会删除匹配的文件,而不是显示它们。删除当前目录中的 .swp 文件:
$ find . -type f -name "*.swp" -delete
执行命令
find 命令可以使用 -exec 选项与其他许多命令配合使用。
考虑之前的示例。我们使用 -perm 查找没有正确权限的文件。类似地,在需要将某个用户(例如 root)拥有的所有文件的所有权更改为另一个用户(例如 www-data,Web 服务器中默认的 Apache 用户)时,我们可以使用 -user 选项查找所有 root 拥有的文件,并使用 -exec 执行所有权更改操作。
如果您想更改文件或目录的所有权,必须以 root 用户身份运行 find 命令。
find 命令使用一对大括号 {} 来表示文件名。在下一个示例中,每当 find 识别出一个文件时,它会用文件名替换 {} 并更改文件的所有权。例如,如果 find 命令找到两个 root 所有的文件,它将把它们都更改为 slynux 所有:
# find . -type f -user root -exec chown slynux {} \;
请注意,命令以 \; 结尾。分号必须被转义,否则它会被命令行 shell 作为 find 命令的结束符,而不是 chown 命令的结束符。
为每个文件调用命令会带来很大的开销。如果命令接受多个参数(如 chown),可以使用加号(+)而不是分号来结束命令。加号会使 find 创建一个包含所有匹配文件的列表,并使用单个命令行一次性执行该操作。
另一个用法示例是将给定目录中的所有 C 程序文件连接起来并写入一个单一的文件,比如 all_c_files.txt。以下每个示例都会执行这个操作:
$ find . -type f -name '*.c' -exec cat {} \;>all_c_files.txt
$ find . -type f -name '*.c' -exec cat {} > all_c_files.txt \;
$ fine . -type f -name '*.c' -exec cat {} >all_c_files.txt +
为了将 find 的数据重定向到 all_c_files.txt 文件中,我们使用了 > 操作符,而不是 >>(追加)。这是因为 find 命令的整个输出是一个单一的数据流(stdin);当多个数据流需要追加到单个文件时,才需要使用 >>。
以下命令会将所有超过 10 天的 .txt 文件复制到 OLD 目录中:
$ find . -type f -mtime +10 -name "*.txt" -exec cp {} OLD \;
find 命令可以与许多其他命令配合使用。
我们不能在 -exec 参数中使用多个命令。它只接受一个单独的命令,但我们可以使用一个技巧:将多个命令写入一个 shell 脚本(例如 commands.sh),并将其与 -exec 一起使用,如下所示:
-exec ./commands.sh {} \;
-exec 参数可以与 printf 配合使用,以产生 joutput。请考虑这个示例:
$ find . -type f -name "*.txt" -exec printf "Text file: %s\n" {} \;
Config file: /etc/openvpn/easy-rsa/openssl-1.0.0.cnf
Config file: /etc/my.cnf
使用 find 命令时跳过指定的目录
跳过某些子目录可能会在执行 find 时提高性能。例如,在使用版本控制系统(如 Git)的开发源代码树中进行文件搜索时,文件系统中每个子目录都包含一个用于存储与版本控制相关的信息的目录。这些目录可能不包含有用的文件,应该从搜索中排除。
排除文件和目录的技术被称为修剪。以下示例展示了如何使用-prune选项排除与模式匹配的文件。
$ find devel/source_path -name '.git' -prune -o -type f -print
-name ".git" -prune是修剪部分,指定应该排除.git目录。-type f -print部分描述要执行的操作。
玩转 xargs
Unix 命令从标准输入(stdin)或作为命令行参数接受数据。前面的示例已经展示了如何通过管道将一个应用程序的标准输出传递给另一个应用程序的标准输入。
我们可以以其他方式调用接受命令行参数的应用程序。最简单的方法是使用反引号符号来运行命令,并将其输出作为命令行:
$ gcc `find '*.c'`
这个解决方案在许多情况下都能正常工作,但如果需要处理的文件很多,你会看到那个可怕的Argument list too long错误信息。xargs程序可以解决这个问题。
xargs命令从stdin读取一组参数,并使用这些参数在命令行中执行一个命令。xargs命令还可以将任何一行或多行文本输入转换为其他格式,比如多个行(指定列数)或单行,反之亦然。
准备工作
xargs命令应该是管道操作符后出现的第一个命令。它使用标准输入作为主要数据源,并通过将从stdin读取的值作为新命令的命令行参数来执行另一个命令。这个示例将在一组 C 语言文件中搜索主字符串:
ls *.c | xargs grep main
如何操作...
xargs命令通过重新格式化通过stdin接收到的数据来为目标命令提供参数。默认情况下,xargs将执行echo命令。在许多方面,xargs命令类似于find命令的-exec选项所执行的操作:
- 将多行输入转换为单行输出:
Xargs 的默认echo命令可以用于将多行输入转换为单行文本,像这样:
$ cat example.txt # Example file
1 2 3 4 5 6
7 8 9 10
11 12
$ cat example.txt | xargs
1 2 3 4 5 6 7 8 9 10 11 12
- 将单行转换为多行输出:
xargs的-n参数限制每次命令行调用时放入的元素数量。这个例子将输入分成每行N个项目:
$ cat example.txt | xargs -n 3
1 2 3
4 5 6
7 8 9
10 11 12
它是如何工作的...
xargs命令的工作方式是从stdin接收输入,将数据解析为单个元素,然后用这些元素作为最终命令行参数来调用程序。默认情况下,xargs将根据空格拆分输入,并执行/bin/echo。
当文件和文件夹名中包含空格(甚至换行符)时,基于空白字符拆分输入会成为问题。My Documents文件夹会被解析为两个元素My和Documents,而这两个元素并不存在。
大多数问题都有解决方案,这个也不例外。
我们可以定义用于分隔参数的分隔符。要为输入指定自定义分隔符,使用-d选项:
$ echo "split1Xsplit2Xsplit3Xsplit4" | xargs -d X
split1 split2 split3 split4
在前面的代码中,stdin包含由多个X字符组成的字符串。我们使用-d选项将X定义为输入分隔符。
使用-n与前面的命令一起使用,我们可以将输入拆分成每行两个单词,如下所示:
$ echo "splitXsplitXsplitXsplit" | xargs -d X -n 2
split split
split split
xargs命令与 find 命令的结合非常好。find 命令的输出可以通过管道传递给xargs,执行比-exec选项更复杂的操作。如果文件系统中有文件名带有空格,find 命令的-print0选项会使用0(NULL)来分隔元素,这与xargs -0选项配合使用,可以解析这些元素。以下示例在 Samba 挂载的文件系统中查找.docx文件,其中包含大写字母和空格的文件名很常见。它使用grep来报告带有图片的文件:
$ find /smbMount -iname '*.docx' -print0 | xargs -0 grep -L image
还有更多...
前面的示例展示了如何使用xargs来组织一组数据。接下来的示例展示了如何在命令行中格式化数据集。
通过读取 stdin 传递格式化的参数给命令
这是一个小的echo脚本,明确展示了xargs如何提供命令参数:
#!/bin/bash
#Filename: cecho.sh
echo $*'#'
当参数传递给cecho.sh脚本时,它会打印以#字符结束的参数。考虑以下示例:
$ ./cecho.sh arg1 arg2
arg1 arg2 #
这是一个常见问题:
- 我有一个包含元素列表的文件(每行一个),需要提供给一个命令(比如
cecho.sh)。我需要以几种方式应用这些参数。在第一种方法中,我需要每次调用一个参数,如下所示:
./cecho.sh arg1
./cecho.sh arg2
./cecho.sh arg3
- 接下来,我需要每次执行命令时提供一个或两个参数,如下所示:
./cecho.sh arg1 arg2
./cecho.sh arg3
- 最后,我需要一次性提供所有参数给命令:
./cecho.sh arg1 arg2 arg3
运行cecho.sh脚本并注意输出,然后再进行以下部分。xargs命令可以格式化每个要求的参数。参数列表存储在名为args.txt的文件中:
$ cat args.txt
arg1
arg2
arg3
对于第一种形式,我们多次执行命令,每次执行一个参数。xargs -n选项可以限制每次命令行参数为一个:
$ cat args.txt | xargs -n 1 ./cecho.sh
arg1 #
arg2 #
arg3 #
要限制参数数量为两个或更少,请执行以下操作:
$ cat args.txt | xargs -n 2 ./cecho.sh
arg1 arg2 #
arg3 #
最后,为了在一次执行中使用所有参数,不要使用任何-n选项:
$ cat args.txt | xargs ./cecho.sh
arg1 arg2 arg3 #
在前面的示例中,xargs添加的参数被放置在命令的末尾。然而,我们可能需要在命令末尾保持一个常量短语,并希望xargs将它的参数替换到中间位置,如下所示:
./cecho.sh -p arg1 -l
在前面的命令执行中,arg1是唯一的变量文本。其他部分应该保持不变。从args.txt中的参数应按如下方式应用:
./cecho.sh -p arg1 -l
./cecho.sh -p arg2 -l
./cecho.sh -p arg3 -l
xargs -I选项指定一个替换字符串,该字符串会被 xargs 从输入中解析出的参数替换。当与xargs一起使用-I时,它会将每个参数执行一次命令。这一示例解决了问题:
$ cat args.txt | xargs -I {} ./cecho.sh -p {} -l
-p arg1 -l #
-p arg2 -l #
-p arg3 -l #
-I {}指定了替换字符串。对于命令的每个参数,{}字符串会被通过stdin读取的参数替换。
与-I一起使用时,命令会在循环中执行。当有三个参数时,命令会执行三次,并且每次都会替换{}为相应的参数。
使用 xargs 与 find
xargs和find命令可以结合使用来执行任务。然而,需要小心地组合它们。考虑以下示例:
$ find . -type f -name "*.txt" -print | xargs rm -f
这非常危险。它可能会导致意外文件的删除。我们无法预测find命令输出的分隔符(无论是'\n'还是' ')。如果任何文件名包含空格字符(' '),xargs可能会将其误解为分隔符。例如,bashrc text.txt会被xargs误解为bashrc和text.txt。此时,前一个命令不会删除bashrc text.txt,但会删除bashrc。
使用find的-print0选项来生成以空字符('\0')分隔的输出;你可以将find的输出用作xargs的输入。
这个命令将会find并删除所有.txt文件,而不会删除其他文件:
$ find . -type f -name "*.txt" -print0 | xargs -0 rm -f
统计源代码目录中 C 代码的行数
在某些时候,大多数程序员需要统计他们 C 程序文件中的代码行数(LOC)。完成此任务的代码如下:
$ find source_code_dir_path -type f -name "*.c" -print0 | xargs -0 wc -l
如果你想获取更多关于源代码的统计信息,一个名为SLOCCount的工具非常有用。现代的 GNU/Linux 发行版通常会提供该工具的包,或者你可以从www.dwheeler.com/sloccount/下载。
使用while和子 Shell 技巧与 stdin
xargs命令将参数放在命令的末尾;因此,xargs不能为多个命令集提供参数。我们可以创建一个子 Shell 来处理复杂的情况。子 Shell 可以使用while循环读取参数,并以更复杂的方式执行命令,像这样:
$ cat files.txt | ( while read arg; do cat $arg; done )
# Equivalent to cat files.txt | xargs -I {} cat {}
在这里,通过用while循环替换cat $arg为任何数量的命令,我们可以使用相同的参数执行多个命令操作。我们可以将输出传递给其他命令,而不需要使用管道。子 Shell( )技巧可以在多种复杂环境中使用。将命令封装在子 Shell 操作符内,它会作为一个整体执行,内部包含多个命令,像这样:
$ cmd0 | ( cmd1;cmd2;cmd3) | cmd4
如果cmd1在子 Shell 中是cd /,工作目录的路径会发生变化。然而,这种变化仅限于子 Shell 内。cmd4命令不会看到目录的变化。
shell 接受-c选项来调用带有命令行脚本的子 Shell。此选项可以与xargs结合使用,以解决需要多个替换的问题。以下示例查找所有的C文件并输出每个文件的名称,前面加上一个换行符(-e选项启用反斜杠替换)。在文件名之后,列出该文件中所有出现main的次数:
find . -name '*.c' | xargs -I ^ sh -c "echo -ne '\n ^: '; grep main ^"
使用 tr 进行翻译
tr命令是 Unix 命令战士工具包中的一个多功能工具。它用于编写优雅的单行命令。它执行字符替换、删除选定的字符,并可以从标准输入中压缩重复字符。tr是translate(翻译)的缩写,因为它将一组字符转换为另一组字符。在这个例子中,我们将看看如何使用tr在字符集之间进行基本的翻译。
准备好
tr命令通过stdin(标准输入)接收输入,不能通过命令行参数接收输入。它的调用格式如下:
tr [options] set1 set2
从stdin输入的字符从set1中的第一个字符映射到set2中的第一个字符,依此类推,输出写入stdout(标准输出)。set1和set2是字符类或字符集合。如果字符集的长度不相等,则set2会通过重复最后一个字符来扩展到与set1相同的长度;如果set2的长度大于set1,则set1长度以外的所有字符都将被忽略。
如何操作...
要执行将输入中的大写字符转换为小写字符的操作,请使用以下命令:
$ echo "HELLO WHO IS THIS" | tr 'A-Z' 'a-z'
hello who is this
'A-Z'和'a-z'是字符集。我们可以根据需要通过附加字符或字符类来指定自定义字符集。
'ABD-}'、'aA.,'、'a-ce-x'、'a-c0-9'等是有效的字符集。我们可以轻松定义字符集。无需写连续的字符序列,我们可以使用'startchar-endchar'格式。它还可以与其他任何字符或字符类结合使用。如果startchar-endchar不是有效的连续字符序列,则它们被视为三个字符的集合(例如,startchar、-和endchar)。你还可以使用特殊字符,例如'\t'、'\n'或任何 ASCII 字符。
它是如何工作的...
使用带有字符集概念的tr,我们可以轻松地将字符从一个集合映射到另一个集合。让我们通过一个示例来看看如何使用tr加密和解密数字字符:
$ echo 12345 | tr '0-9' '9876543210'
87654 #Encrypted
$ echo 87654 | tr '9876543210' '0-9'
12345 #Decrypted
tr命令可以用来加密文本。ROT13是一种著名的加密算法。在 ROT13 方案中,字符通过 13 个位置进行偏移,因此相同的功能可以同时加密和解密文本:
$ echo "tr came, tr saw, tr conquered." | tr 'a-zA-Z' 'n-za-mN-ZA-M'
输出将如下所示:
ge pnzr, ge fnj, ge pbadhrerq.
通过将加密文本再次发送给相同的 ROT13 函数,我们将得到以下结果:
$ echo ge pnzr, ge fnj, ge pbadhrerq. | tr 'a-zA-Z' 'n-za-mN-ZA-M'
输出将如下所示:
tr came, tr saw, tr conquered.
tr可以将每个制表符字符转换为单个空格,如下所示:
$ tr '\t' ' ' < file.txt
还有更多...
我们看到了一些使用tr命令的基本翻译方法。让我们看看tr还能帮助我们实现什么。
使用tr删除字符
tr命令有一个-d选项,用于删除在stdin中出现的字符集合,按照指定的字符集进行删除,如下所示:
$ cat file.txt | tr -d '[set1]'
#Only set1 is used, not set2
考虑这个示例:
$ echo "Hello 123 world 456" | tr -d '0-9'
Hello world
# Removes the numbers from stdin and print
补充字符集
我们可以使用-c标志来补充set1字符集。以下命令中set2是可选的:
tr -c [set1] [set2]
如果仅有 set1,tr 将删除所有不在 set1 中的字符。如果 set2 也存在,tr 将把不在 set1 中的字符翻译为 set2 中的值。如果单独使用 -c 选项,你必须使用 set1 和 set2。如果同时使用 -c 和 -d 选项,则只使用 set1,其他字符将被删除。
以下示例删除输入文本中所有字符,除了互补集指定的字符:
$ echo hello 1 char 2 next 4 | tr -d -c '0-9 \n'
124
这个例子将所有不在 set1 中的字符替换为空格:
$ echo hello 1 char 2 next 4 | tr -c '0-9' ' '
1 2 4
使用 tr 压缩字符
tr 命令可以执行许多文本处理任务。例如,它可以删除字符串中某个字符的多个出现。其基本形式如下:
tr -s '[set of characters to be squeezed]'
如果你习惯在句号后加两个空格,你需要移除多余的空格,但不删除重复的字母:
$ echo "GNU is not UNIX. Recursive right ?" | tr -s ' '
GNU is not UNIX. Recursive right ?
tr 命令还可以用于去除多余的换行符:
$ cat multi_blanks.txt | tr -s '\n'
line 1
line 2
line 3
line 4
在前面的 tr 用法中,它去除了多余的 '\n' 字符。让我们用一种巧妙的方式来使用 tr 从文件中求出给定数字列表的和,如下所示:
$ cat sum.txt
1
2
3
4
5
$ cat sum.txt | echo $[ $(tr '\n' '+' ) 0 ]
15
这个技巧是如何工作的?
在此,tr 命令将 '\n' 替换为 '+' 字符,因此我们形成了字符串 1+2+3+..5+,但在字符串的末尾有一个额外的 + 运算符。为了消除 + 运算符的影响,附加了 0。
$[ operation ] 执行一个数值操作。因此,它形成了这个字符串:
echo $[ 1+2+3+4+5+0 ]
如果我们使用循环通过读取文件中的数字来执行加法,它会占用几行代码。而使用 tr 命令,单行代码即可完成此操作。
更棘手的是当我们有一个包含字母和数字的文件,并且我们希望求和这些数字时:
$ cat test.txt
first 1
second 2
third 3
我们可以使用 tr 删除字母,使用 -d 选项,然后将空格替换为 +:
$ cat test.txt | tr -d [a-z] | echo "total: $[$(tr ' ' '+')]"
total: 6
字符类
tr 命令可以使用不同的字符类作为集合。以下是支持的字符类:
-
alnum: 字母数字字符 -
alpha: 字母字符 -
cntrl: 控制字符(不可打印字符) -
digit: 数字字符 -
graph: 图形字符 -
lower: 小写字母字符 -
print: 可打印字符 -
punct: 标点符号字符 -
space: 空白字符 -
upper: 大写字符 -
xdigit: 十六进制字符
我们可以选择所需的类,如下所示:
tr [:class:] [:class:]
考虑以下示例:
tr '[:lower:]' '[:upper:]'
校验和与验证
校验和程序用于从文件中生成一个相对较小的唯一键。我们可以重新计算该键,以确认文件未发生更改。文件可能被故意修改(添加新用户会更改密码文件)、意外修改(从 CD-ROM 驱动器读取数据时出错)或恶意修改(病毒被插入)。校验和可以让我们验证文件是否包含我们预期的数据。
校验和被备份应用程序用于检查文件是否已被修改并需要备份。
大多数软件分发包也提供校验和文件。即使像 TCP 这样强大的协议也可能允许文件在传输过程中被修改。因此,我们需要通过某种测试来确定接收到的文件是否为原始文件。
通过将我们下载的文件的校验和与分发者计算的校验和进行比较,我们可以验证接收到的文件是否正确。如果从源位置计算的原始文件校验和与目标位置计算的校验和匹配,则文件已成功接收。
一些系统验证套件会维护关键文件的校验和。如果恶意软件修改了文件,我们可以通过校验和的变化来检测到这一点。
在这个教程中,我们将学习如何计算校验和来验证数据的完整性。
准备工作
Unix 和 Linux 支持多个校验和程序,但最强大和广泛使用的算法是 MD5 和 SHA-1。md5sum 和 sha1sum 程序通过将相应的算法应用于数据来生成校验和字符串。让我们来看一下如何从文件生成校验和并验证该文件的完整性。
如何操作...
要计算 md5sum,请使用以下命令:
$ md5sum filename
68b329da9893e34099c7d8ad5cb9c940 filename
md5sum 是一个 32 字符的十六进制字符串,如下所示。
我们可以将校验和输出重定向到文件,以便稍后使用,如下所示:
$ md5sum filename > file_sum.md5
它是如何工作的...
md5sum 校验和计算的语法如下:
$ md5sum file1 file2 file3 ..
当使用多个文件时,输出将包含每个文件的校验和,每个校验和报告占一行:
[checksum1] file1
[checksum1] file2
[checksum1] file3
文件的完整性可以通过生成的文件进行验证,操作如下:
$ md5sum -c file_sum.md5
# It will output a message whether checksum matches or not
如果我们需要检查所有使用 .md5 文件信息的文件,请使用以下命令:
$ md5sum -c *.md5
SHA-1 是另一个常用的校验和算法。它从输入中生成一个 40 字符的十六进制代码。sha1sum 命令计算一个 SHA-1 校验和。它的用法类似于 md5sum。只需在之前提到的所有命令中,将 md5sum 替换为 sha1sum。将输出文件名从 file_sum.md5 改为 file_sum.sha1。
校验和对于验证从互联网下载的文件的完整性非常有用。ISO 镜像容易受到错误位的影响。即使是几个错误的位,ISO 文件可能无法读取,或者更糟糕的是,它可能安装一些异常的应用程序。大多数文件仓库都提供一个 md5 或 sha1 文件,可以用来验证文件是否正确下载。

这是创建的 MD5 校验和:
3f50877c05121f7fd8544bef2d722824 *ubuntu-16.10-desktop-amd64.iso
e9e9a6c6b3c8c265788f4e726af25994 *ubuntu-16.10-desktop-i386.iso
7d6de832aee348bacc894f0a2ab1170d *ubuntu-16.10-server-amd64.iso
e532cfbc738876b353c7c9943d872606 *ubuntu-16.10-server-i386.iso
还有更多内容...
校验和在使用多个文件时也很有用。让我们来看一下如何对一组文件应用校验和并验证其准确性。
目录的校验和
校验和是针对文件计算的。计算目录的校验和需要递归地计算目录中所有文件的校验和。
md5deep 或 sha1deep 命令可以遍历文件树并计算所有文件的校验和。这些程序可能未安装在您的系统上。使用 apt-get 或 yum 安装 md5deep 包。此命令的示例如下:
$ md5deep -rl directory_path > directory.md5
-r 选项允许 md5deep 递归进入子目录。-l 选项启用显示相对路径,而不是默认的绝对路径。
# `-r` to enable recursive traversal
# `-l` to use relative path. By default it writes absolute file
path in output
find 和 md5sum 命令可以递归地计算校验和:
$ find directory_path -type f -print0 | xargs -0 md5sum >> directory.md5
要验证,请使用以下命令:
$ md5sum -c directory.md5
- md5 和 SHA-1 校验和 是单向哈希算法,不能反向还原成原始数据。这些算法也用于从给定数据生成唯一密钥:
$ md5sum file
8503063d5488c3080d4800ff50850dc9 file
$ sha1sum file
1ba02b66e2e557fede8f61b7df282cd0a27b816b file
这些哈希通常用于存储密码。只有密码的哈希值被存储。当需要验证用户身份时,密码会被读取并转换为哈希值,然后该哈希值与存储的哈希值进行比较。如果两者相同,则密码验证通过,允许访问。存储明文密码字符串是有风险的,存在安全隐患。
尽管 md5sum 和 SHA-1 常被使用,但它们已经不再被视为安全的算法。这是因为近年来计算能力的提升使得破解这些算法变得更加容易。建议您改用 bcrypt 或 sha512sum 等工具。有关更多信息,请阅读 codahale.com/how-to-safely-store-a-password/。
- 类似 Shadow 的哈希(加盐哈希)
下一段将展示如何为密码生成类似 Shadow 的加盐哈希。Linux 中用户密码的哈希存储在 /etc/shadow 文件中。/etc/shadow 中的一行典型样式如下:
test:$6$fG4eWdUi$ohTKOlEUzNk77.4S8MrYe07NTRV4M3LrJnZP9p.qc1bR5c.
EcOruzPXfEu1uloBFUa18ENRH7F70zhodas3cR.:14790:0:99999:7:::
$6$fG4eWdUi$ohTKOlEUzNk77.4S8MrYe07NTRV4M3LrJnZP9p.qc1bR5c.EcOruzPXfEu1uloBFUa18ENRH7F70zhodas3cR 是与其密码对应的哈希值。
在某些情况下,我们需要编写脚本来编辑密码或添加用户。在这种情况下,我们必须生成一个 shadow 密码字符串,并向 shadow 文件中写入类似前述的行。我们可以使用 openssl 生成一个 shadow 密码。
Shadow 密码通常是加盐密码。SALT 是用于混淆并增强加密强度的额外字符串。盐由随机位组成,作为密钥派生函数的输入之一,用于生成密码的加盐哈希。
有关盐的更多信息,请参考此维基百科页面:h t t p 😕/e n . w i k i p e d i a . o r g /w i k i /S a l t _ (c r y p t o g r a p h y )。
$ opensslpasswd -1 -salt SALT_STRING PASSWORD
$1$SALT_STRING$323VkWkSLHuhbt1zkSsUG.
将 SALT_STRING 替换为随机字符串,将 PASSWORD 替换为您想要使用的密码。
加密工具和哈希函数
加密技术用于保护数据免受未经授权的访问。与我们刚才讨论的校验和算法不同,加密程序可以在不丢失数据的情况下重构原始数据。现在有很多可用的算法,我们将讨论在 Linux/Unix 世界中最常用的那些。
如何执行...
让我们看看如何使用 crypt、gpg 和 base64 等工具:
crypt命令在 Linux 系统上并不常见。它是一个简单且相对不安全的加密工具,接受来自stdin的输入,要求输入passphrase(密码短语),并将加密后的输出发送到stdout:
$ crypt <input_file >output_file
Enter passphrase:
我们可以在命令行中提供密码短语:
$ crypt PASSPHRASE <input_file >encrypted_file
要解密文件,请使用此命令:
$ crypt PASSPHRASE -d <encrypted_file >output_file
gpg(GNU 隐私保护工具)是一个广泛使用的工具,用于保护文件,确保数据在到达预定目的地之前不会被读取。
gpg 签名也广泛用于电子邮件通信中,以“签署”电子邮件消息,证明发件人的真实性。
若要使用 gpg 加密文件,请使用以下命令:
$ gpg -c filename
此命令交互式地读取密码短语,并生成 filename.gpg 文件。若要解密一个 gpg 文件,请使用以下命令:
$ gpg filename.gpg
此命令读取密码短语并解密文件。
本书中不详细介绍 gpg。有关更多信息,请参考 en.wikipedia.org/wiki/GNU_Privacy_Guard。
- Base64 是一类类似的编码方案,通过将二进制数据转换为 radix-64 表示法,来以 ASCII 字符串格式表示二进制数据。这些程序常用于通过电子邮件传输二进制数据。
base64命令用于编码和解码 Base64 字符串。要将二进制文件编码为 Base64 格式,请使用以下命令:
$ base64 filename > outputfile
或者,使用此命令:
$ cat file | base64 > outputfile
它可以从 stdin 读取。
按以下方式解码 Base64 数据:
$ base64 -d file > outputfile
或者,使用此命令:
$ cat base64_file | base64 -d > outputfile
排序唯一行和重复行
排序文本文件是一个常见任务。sort 命令可以排序文本文件和 stdin。它可以与其他命令结合使用以生成所需的输出。uniq 常与 sort 一起使用,以提取唯一(或重复)行。以下示例展示了 sort 和 uniq 的一些用法。
准备开始
sort 和 uniq 命令接受文件名或来自 stdin(标准输入)的输入,并通过写入 stdout 输出结果。
如何执行...
- 我们可以对一组文件(例如
file1.txt和file2.txt)进行排序,如下所示:
$ sort file1.txt file2.txt > sorted.txt
或者,使用以下命令:
$ sort file1.txt file2.txt -o sorted.txt
- 对于数字排序,使用以下命令:
$ sort -n file.txt
- 要按逆序排序,使用以下命令:
$ sort -r file.txt
- 要按月份排序(顺序为 1 月、2 月、3 月,...),请使用以下命令:
$ sort -M months.txt
- 要合并两个已排序的文件,请使用以下命令:
$ sort -m sorted1 sorted2
- 要从已排序的文件中提取唯一行,请使用此命令:
$ sort file1.txt file2.txt | uniq
- 要检查文件是否已排序,请使用以下代码:
#!/bin/bash
#Desc: Sort
sort -C filename ;
if [ $? -eq 0 ]; then
echo Sorted;
else
echo Unsorted;
fi
将 filename 替换为您要检查的文件并运行脚本。
它是如何工作的...
如示例所示,sort 命令接受多个参数来定义如何排序数据。sort 命令在与 uniq 命令结合使用时非常有用,uniq 期待排序过的输入。
sort 和 uniq 命令有很多种应用场景。让我们来看看各种选项和用法技巧。
要检查文件是否已经排序,我们可以利用sort命令的返回退出码($?)。如果文件已排序,返回 0,否则返回非零值。
if sort -c fileToCheck ; then echo sorted ; else echo unsorted ; fi
还有更多...
这些是sort命令的一些基本用法。以下是使用它来完成复杂任务的部分:
根据键或列进行排序
如果输入数据格式如下,我们可以使用列与sort命令进行排序:
$ cat data.txt
1 mac 2000
2 winxp 4000
3 bsd 1000
4 linux 1000
我们可以通过多种方式进行排序;当前它按序列号(第一列)进行数字排序。我们还可以按第二列或第三列进行排序。
-k选项指定按字符排序。一个数字指定列。-r选项指定按逆序排序。请考虑以下示例:
# Sort reverse by column1
$ sort -nrk 1 data.txt
4 linux 1000
3 bsd 1000
2 winxp 4000
1 mac 2000
# -nr means numeric and reverse
# Sort by column 2
$ sort -k 2 data.txt
3 bsd 1000
4 linux 1000
1 mac 2000
2 winxp 4000
始终小心使用用于数字排序的-n选项。sort命令对字母排序和数字排序的处理方式不同。因此,为了指定数字排序,应提供-n选项。
当-k后跟一个整数时,它指定文本文件中的一列。列之间由空格分隔。如果需要将键指定为一组字符(例如,第 2 列的第 4-5 个字符),我们通过使用两个由句点分隔的整数定义字符位置范围,并使用逗号连接第一个和最后一个字符位置:
$ cat data.txt
1 alpha 300
2 beta 200
3 gamma 100
$ sort -bk 2.3,2.4 data.txt ;# Sort m, p, t
3 gamma 100
1 alpha 300
2 beta 200
突出的字符将用作数字键。要提取这些字符,请使用它们在行中的位置作为键格式(在上一个示例中,它们是2和3)。
要使用第一个字符作为键,请使用此命令:
$ sort -nk 1,1 data.txt
要使排序的输出与xargs兼容并使用\0终止符,请使用以下命令:
$ sort -z data.txt | xargs -0
# Use zero terminator to make safe use with xargs
有时,文本中可能包含不必要的多余字符,如空格。要按字典顺序排序并忽略标点符号和折叠,请使用以下命令:
$ sort -bd unsorted.txt
-b选项用于忽略文件中的前导空行,-d选项指定按字典顺序排序。
uniq
uniq命令用于查找给定输入(stdin或文件名命令行参数)中的唯一行,并报告或删除重复的行。
此命令仅适用于已排序的数据。因此,uniq通常与sort命令一起使用。
要生成唯一的行(输入中的所有行都将被打印,重复的行只打印一次),请使用以下命令:
$ cat sorted.txt
bash
foss
hack
hack
$ uniq sorted.txt
bash
foss
hack
或者,使用此命令:
$ sort unsorted.txt | uniq
仅显示唯一行(输入文件中没有重复或复制的行):
$ uniq -u sorted.txt
bash
foss
或者,使用以下命令:
$ sort unsorted.txt | uniq -u
要统计文件中每一行出现的次数,请使用以下命令:
$ sort unsorted.txt | uniq -c
1 bash
1 foss
2 hack
要查找文件中的重复行,请使用以下命令:
$ sort unsorted.txt | uniq -d
hack
要指定键,我们可以结合使用-s和-w参数:
-
-s:此选项指定跳过前N个字符的数量 -
-w:此选项指定要比较的最大字符数
以下示例描述了使用比较键作为uniq操作的索引:
$ cat data.txt
u:01:gnu
d:04:linux
u:01:bash
u:01:hack
为了仅测试粗体字符(跳过前两个字符并使用接下来的两个字符),我们使用-s 2跳过前两个字符,使用-w 2来使用接下来的两个字符:
$ sort data.txt | uniq -s 2 -w 2
d:04:linux
u:01:bash
当一个命令的输出作为输入传递给xargs命令时,最好为每个数据元素使用零字节终止符。将uniq的输出传递给xargs命令时也遵循这一规则。如果没有使用零字节终止符,默认的空格字符将用来拆分xargs命令中的参数。例如,stdin中的一行文本this is a line将被xargs命令作为四个独立的参数处理,而不是一整行。当使用零字节终止符\0作为分隔符时,包括空格在内的完整行会被当作一个参数来处理。
-z选项会生成零字节终止的输出:
$ uniq -z file.txt
该命令会删除所有文件,这些文件名是从files.txt中读取的:
$ uniq -z file.txt | xargs -0 rm
如果一个文件名出现多次,uniq命令只会将文件名输出一次到stdout,从而避免出现rm: cannot remove FILENAME: No such file or directory错误。
临时文件命名和随机数字
Shell 脚本常常需要存储临时数据。最合适的位置是/tmp(该目录会在系统重启时被清空)。有两种方法可以为临时数据生成标准的文件名。
如何做...
mktemp命令会创建一个唯一的临时文件或文件夹名称:
- 创建一个临时文件:
$ filename=`mktemp`
$ echo $filename
/tmp/tmp.8xvhkjF5fH
这会创建一个临时文件,将文件名存储在 filename 中,然后显示该文件名。
- 创建一个临时目录:
$ dirname=`mktemp -d`
$ echo $dirname
tmp.NI8xzW7VRX
这会创建一个临时目录,将目录名存储在 filename 中,然后显示该目录名。
- 要生成一个文件名而不创建文件或目录,请使用以下命令:
$ tmpfile=`mktemp -u`
$ echo $tmpfile
/tmp/tmp.RsGmilRpcT
在这里,文件名存储在$tmpfile中,但文件并不会被创建。
- 要根据模板创建临时文件名,请使用以下命令:
$mktemp test.XXX
test.2tc
它是如何工作的...
mktemp命令非常简单。它会生成一个具有唯一名称的文件,并返回其文件名(或在目录的情况下,返回目录名)。
提供自定义模板时,X将被随机的字母数字字符替换。还需要注意的是,模板中必须至少有三个X字符,mktemp才能正常工作。
拆分文件和数据
将一个大文件拆分成小块有时是必要的。很久以前,我们必须拆分文件以便通过软盘传输大数据集。今天,我们拆分文件是为了提高可读性,生成日志,或绕过电子邮件附件的大小限制。这些示例将演示如何将文件拆分成不同大小的块。
如何做...
split命令是为拆分文件而创建的。它接受文件名作为参数,并创建一组较小的文件,其中原始文件的第一部分被放入按字母顺序排列的新文件中,接下来的部分按字母顺序依次放入其它文件中,依此类推。
例如,可以通过指定拆分大小将一个 100 KB 的文件拆分为每个 10k 的较小文件。split 命令支持 M 表示 MB,G 表示 GB,c 表示字节,w 表示单词。
$ split -b 10k data.file
$ ls
data.file xaa xab xac xad xae xaf xag xah xai xaj
上述代码会将 data.file 拆分为每个 10k 的十个文件。新文件的命名为 xab、xac、xad 等。默认情况下,split 使用字母后缀。要使用数字后缀,请使用 -d 参数。还可以通过 -a 参数指定后缀长度:
$ split -b 10k data.file -d -a 4
$ ls
data.file x0009 x0019 x0029 x0039 x0049 x0059 x0069 x0079
还有更多...
split 命令有更多选项。让我们逐一了解它们。
为拆分文件指定文件名前缀
所有之前的拆分文件名都以 x 开头。如果我们拆分多个文件,想要给文件命名以便于区分,我们可以通过在最后一个参数提供前缀来使用自己的文件名前缀。
让我们使用 split_file 前缀来运行上面的命令:
$ split -b 10k data.file -d -a 4 split_file
$ ls
data.file split_file0002 split_file0005 split_file0008
strtok.c
split_file0000 split_file0003 split_file0006 split_file0009
split_file0001 split_file0004 split_file0007
要根据每个拆分的行数而非块大小来拆分文件,请使用以下命令:
-l no_of_lines:
# Split into files of 10 lines each.
$ split -l 10 data.file
csplit 工具根据上下文而非大小来拆分文件。它可以基于行数或正则表达式模式来拆分,特别适用于拆分日志文件。
看一下下面的示例日志:
$ cat server.log
SERVER-1
[connection] 192.168.0.1 success
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 success
SERVER-2
[connection] 192.168.0.1 failed
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 success
[connection] 192.168.0.4 failed
SERVER-3
[connection] 192.168.0.1 pending
[connection] 192.168.0.2 pending
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 failed
我们可能需要根据每个文件中 SERVER 内容拆分文件成 server1.log、server2.log 和 server3.log。可以按以下方式执行:
$ csplit server.log /SERVER/ -n 2 -s {*} -f server -b "%02d.log" $ rm server00.log
$ ls
server01.log server02.log server03.log server.log
命令的详细信息如下:
-
/SERVER/:这是用于匹配拆分基准的行。 -
/[REGEX]/:这是格式。它将从当前行(第一行)复制到包含SERVER的匹配行,但不包括匹配行本身。 -
{*}:这指定了根据匹配一直拆分直到文件结束。我们可以通过在大括号中放置一个数字来指定拆分的次数。 -
-s:这是使命令静默运行的标志,而不是打印其他消息。 -
-n:这指定了用于后缀的数字位数。01、02、03等。 -
-f:这指定了拆分文件的文件名前缀(在前面的示例中,server是前缀)。 -
-b:这指定了后缀格式。"%02d.log"类似于 C 语言中的printf参数格式,在这里,文件名 = 前缀 + 后缀,即"server" + "%02d.log"。
我们移除 server00.log,因为第一个拆分文件是一个空文件(匹配词是文件的第一行)。
根据扩展名切割文件名
许多 Shell 脚本执行涉及修改文件名的操作。它们可能需要重命名文件并保留扩展名,或者将文件从一种格式转换为另一种格式并更改扩展名,同时保留文件名、提取文件名的一部分等等。
Shell 提供了内置功能来操作文件名。
如何执行...
% 操作符将从 name.extension 中提取文件名。此示例从 sample.jpg 中提取 sample:
file_jpg="sample.jpg"
name=${file_jpg%.*}
echo File name is: $name
输出结果如下:
File name is: sample
# 操作符将提取文件扩展名:
从存储在 file_jpg 变量中的文件名中提取 .jpg:
extension=${file_jpg#*.}
echo Extension is: jpg
输出如下:
Extension is: jpg
它是如何工作的...
要从格式为 name.extension 的文件名中提取名称,我们使用 % 操作符。
${VAR%.*} 的解释如下:
-
从
$VAR中删除右侧%(在前面的示例中为.*)的通配符模式中的字符串匹配。按照从右到左的顺序评估会找到通配符匹配。 -
将文件名存储为
VAR=sample.jpg。因此,从右到左的通配符匹配.*为.jpg。因此,它会从$VAR字符串中移除并输出sample。
% 是一个非贪婪操作。它从右到左找到通配符的最小匹配。%% 操作符与 % 类似,但它是贪婪的。这意味着它会找到通配符的最大匹配字符串。考虑这个例子,我们有:
VAR=hack.fun.book.txt
使用 % 操作符进行从右到左的非贪婪匹配,并匹配 .txt:
$ echo ${VAR%.*}
输出将是:hack.fun.book。
使用 %% 操作符进行贪婪匹配,并匹配 .fun.book.txt:
$ echo ${VAR%%.*}
输出将是:hack。
# 操作符提取文件名的扩展名。它与 % 类似,但从左到右进行评估。
${VAR#*.} 的解释如下:
- 从
$VARIABLE中删除右侧#(在前面的示例中为*.)的通配符模式中的字符串匹配。按照从左到右的顺序评估应该会找到通配符匹配。
类似于 %% 的情况,操作符 ## 是与 # 相对应的贪婪匹配。
它通过从左到右的顺序进行贪婪匹配,并从指定的变量中移除匹配的字符串。我们来看看这个例子:
VAR=hack.fun.book.txt
# 操作符从左到右执行非贪婪匹配,并匹配 hack:
$ echo ${VAR#*.}
输出将是:fun.book.txt。
## 操作符从左到右进行贪婪匹配,并匹配 hack.fun.book:
$ echo ${VAR##*.}
输出将是:txt。
## 操作符比 # 操作符更优,用来提取文件名的扩展名,因为文件名中可能包含多个 . 字符。由于 ## 进行的是贪婪匹配,因此它总是仅提取扩展名。
这里是一个实际的示例,提取域名(如 URL=www.google.com)的不同部分:
$ echo ${URL%.*} # Remove rightmost .*
www.google
$ echo ${URL%%.*} # Remove right to leftmost .* (Greedy operator)
www
$ echo ${URL#*.} # Remove leftmost part before *.
google.com
$ echo ${URL##*.} # Remove left to rightmost part before *.
(Greedy operator) com
批量重命名和移动文件
我们经常需要移动文件,甚至可能重命名一组文件。系统维护通常需要将具有公共前缀或文件类型的文件移动到新文件夹中。从相机下载的图像可能需要重命名并排序。音乐、视频和电子邮件文件最终都需要重新组织。
许多操作有自定义应用程序,但我们可以编写自己的自定义脚本以 我们 的方式来执行这些操作。
让我们看看如何编写脚本来执行这些操作。
准备工作
rename 命令使用 Perl 正则表达式来更改文件名。通过结合使用 find、rename 和 mv 命令,我们可以执行很多操作。
如何执行...
以下脚本使用 find 查找 PNG 和 JPEG 文件,然后使用 ## 操作符和 mv 命令将它们重命名为 image-1.EXT、image-2.EXT 等等。这会改变文件的名称,但不会改变文件扩展名:
#!/bin/bash
#Filename: rename.sh
#Desc: Rename jpg and png files
count=1;
for img in `find . -iname '*.png' -o -iname '*.jpg' -type f -maxdepth 1`
do
new=image-$count.${img##*.}
echo "Renaming $img to $new"
mv "$img" "$new"
let count++
done
输出如下:
$ ./rename.sh
Renaming hack.jpg to image-1.jpg
Renaming new.jpg to image-2.jpg
Renaming next.png to image-3.png
上述脚本将当前目录中的所有 .jpg 和 .png 文件重命名为新文件名,格式为 image-1.jpg、image-2.jpg、image-3.png、image-4.png,依此类推。
它是如何工作的...
上一个脚本使用 for 循环遍历所有以 .jpg 或 .png 扩展名结尾的文件名。find 命令执行此搜索,使用 -o 选项指定多个 -iname 选项,以进行不区分大小写的匹配。-maxdepth 1 选项限制搜索范围为当前目录,而不包括子目录。
count 变量初始化为 1 用于跟踪图像编号。然后,脚本使用 mv 命令重命名文件。文件的新名称通过 ${img##*.} 构造,该部分解析当前正在处理的文件名的扩展名(有关 ${img##*.} 的解释,请参见本章的 基于扩展名切割文件名 章节)。
let count++ 用于在每次循环执行时递增文件编号。
这里是执行重命名操作的其他方式:
- 如此将
*.JPG重命名为*.jpg:
$ rename *.JPG *.jpg
- 使用以下命令将文件名中的空格替换为
"_"字符:
$ rename 's/ /_/g' *
# 's/ /_/g' 是文件名中的替换部分,* 是目标文件的通配符。可以是 *.txt 或任何其他通配符模式。
- 使用以下命令将文件名从大写转换为小写,反之亦然:
$ rename 'y/A-Z/a-z/' *
$ rename 'y/a-z/A-Z/' *
- 使用以下命令递归地将所有
.mp3文件移动到指定目录:
$ find path -type f -name "*.mp3" -exec mv {} target_dir \;
- 使用以下命令递归地通过将空格替换为
_字符来重命名所有文件:
$ find path -type f -exec rename 's/ /_/g' {} \;
拼写检查和字典操作
大多数 Linux 发行版都包含一个字典文件。然而,极少有人知道这一点,因此拼写错误层出不穷。aspell 命令行工具是一个拼写检查工具。让我们一起了解一些利用字典文件和拼写检查器的脚本。
如何操作...
/usr/share/dict/ 目录包含一个或多个字典文件,它们是包含单词列表的文本文件。我们可以使用这个列表来检查一个单词是否是字典中的单词:
$ ls /usr/share/dict/
american-english british-english
要检查给定的单词是否为字典单词,请使用以下脚本:
#!/bin/bash
#Filename: checkword.sh
word=$1
grep "^$1$" /usr/share/dict/british-english -q
if [ $? -eq 0 ]; then
echo $word is a dictionary word;
else
echo $word is not a dictionary word;
fi
用法如下:
$ ./checkword.sh ful
ful is not a dictionary word
$ ./checkword.sh fool
fool is a dictionary word
它是如何工作的...
在 grep 中,^ 是单词起始符号,$ 是单词结束符号。-q 选项抑制任何输出,使得 grep 命令保持安静。
或者,我们可以使用拼写检查工具 aspell 来检查一个单词是否在字典中:
#!/bin/bash
#Filename: aspellcheck.sh
word=$1
output=`echo \"$word\" | aspell list`
if [ -z $output ]; then
echo $word is a dictionary word;
else
echo $word is not a dictionary word;
fi
aspell list 命令在给定输入不是字典单词时返回输出文本,而在输入是字典单词时不输出任何内容。-z 命令检查 $output 是否为空字符串。
look命令将显示以给定字符串开头的行。你可以用它来查找日志文件中以给定日期开头的行,或者查找字典中以给定字符串开头的单词。默认情况下,look会搜索/usr/share/dict/words,你也可以提供一个文件进行搜索。
$ look word
或者,可以使用以下方法:
$ grep "^word" filepath
请考虑以下示例:
$ look android
android
android's
androids
使用此命令查找/var/log/syslog中具有特定日期的行:
$look 'Aug 30' /var/log/syslog
自动化交互式输入
我们查看了接受命令行参数的命令。Linux 还支持许多交互式应用程序,从passwd到ssh。
我们可以创建自己的交互式 Shell 脚本。对于普通用户来说,通过一系列提示与系统交互比记住命令行标志和正确的顺序要容易得多。例如,一个用于备份用户工作的脚本,但不备份和锁定文件,可能如下所示:
$ backupWork.sh
-
应该备份哪个文件夹?
notes -
应该备份哪种类型的文件?
.docx
自动化交互式应用程序可以节省你在需要重新运行相同应用程序时的时间,并减少在开发应用程序时的挫败感。
正在准备
自动化任务的第一步是运行它并记录你所做的操作。之前讨论过的脚本命令可能会有帮助。
如何做到...
检查交互式输入的序列。从前面的代码中,我们可以像这样构造序列的步骤:
notes[Return]docx[Return]
除了前面的步骤外,输入notes,按Return键,输入docx,最后按Return键,将其转换为如下所示的单一字符串:
"notes\ndocx\n"
按下 Return 键时会发送\n字符。通过附加返回字符(\n),我们得到了传递给stdin(标准输入)的字符串。
通过发送用户输入的字符等效字符串,我们可以实现自动化地将输入传递给交互式进程。
它是如何工作的...
让我们编写一个脚本,以便交互式地读取输入,作为一个自动化示例:
#!/bin/bash
# backup.sh
# Backup files with suffix. Do not backup temp files that start with ~
read -p " What folder should be backed up: " folder
read -p " What type of files should be backed up: " suffix
find $folder -name "*.$suffix" -a ! -name '~*' -exec cp {} \
$BACKUP/$LOGNAME/$folder
echo "Backed up files from $folder to $BACKUP/$LOGNAME/$folder"
让我们自动化地将输入发送到命令:
$ echo -e "notes\ndocx\n" | ./backup.sh
Backed up files from notes to /BackupDrive/MyName/notes
这种自动化交互式脚本的方式可以在开发和调试过程中节省大量输入时间。它还确保你每次都执行相同的测试,避免因为输入错误而追踪到一个虚假的 bug。
我们使用了echo -e来生成输入序列。-e选项指示echo解释转义序列。如果输入内容较大,我们可以使用输入文件和重定向操作符来提供输入:
$ echo -e "notes\ndocx\n" > input.data
$ cat input.data
notes
docx
你可以手动编写输入文件,而不使用echo命令,通过手动键入。考虑以下示例:
$ ./interactive.sh < input.data
这会将交互式输入数据从文件中重定向。
如果你是一个逆向工程师,你可能玩过缓冲区溢出漏洞。为了利用这些漏洞,我们需要重定向一个如\xeb\x1a\x5e\x31\xc0\x88\x46这样的 Shell 代码,它是以十六进制书写的。这些字符无法直接在键盘上输入,因为这些字符的键并不存在。因此,我们使用:
echo -e \xeb\x1a\x5e\x31\xc0\x88\x46"
这将把字节序列重定向到一个易受攻击的可执行文件。
这些回显和重定向技术能够自动化交互式输入程序。然而,这些技术非常脆弱,因为没有有效性检查,并且假定目标应用程序始终按相同顺序接收数据。如果程序要求输入的顺序发生变化,或者某些输入并非始终需要时,这些方法就会失败。
expect 程序能够执行复杂的交互,并适应目标应用程序的变化。这个程序被广泛用于控制硬件测试、验证软件构建、查询路由器统计信息等。
还有更多……
expect 应用程序是一个类似于 shell 的解释器,它基于 TCL 语言。我们将讨论 spawn、expect 和 send 命令来进行简单的自动化。借助 TCL 语言的强大功能,expect 能执行更复杂的任务。你可以在 www.tcl.tk 网站上了解更多关于 TCL 语言的信息。
使用 expect 进行自动化
expect 并不是所有 Linux 发行版的默认安装包。你可能需要通过包管理器(apt-get 或 yum)安装 expect 包。
expect 有三个主要命令:
| 命令 | 描述 |
|---|---|
spawn |
运行新的目标应用程序。 |
expect |
监视目标应用程序发送的模式。 |
send |
向目标应用程序发送字符串。 |
以下示例生成备份脚本,并查找模式 *folder* 和 *file* 来确定备份脚本是要求文件夹名还是文件名。然后它会发送适当的回复。如果备份脚本被重写为首先请求文件然后是文件夹,这个自动化脚本仍然有效。
#!/usr/bin/expect
#Filename: automate_expect.tcl
spawn ./backup .sh
expect {
"*folder*" {
send "notes\n"
exp_continue
}
"*type*" {
send "docx\n"
exp_continue
}
}
以以下方式运行:
$ ./automate_expect.tcl
spawn 命令的参数是目标应用程序及其需要自动化的参数。
expect 命令接受一组要查找的模式,并在模式匹配时执行相应的操作。操作内容被大括号包围。
send 命令是要发送的消息。这类似于 echo -n -e,即它不会自动包括换行符,并且能够识别反斜杠符号。
通过运行并行进程来加快命令执行速度
计算能力不断提升,不仅是因为处理器的时钟周期更高,还因为它们拥有多个核心。这意味着在一个硬件处理器中有多个逻辑处理器。这就像拥有几台计算机,而不仅仅是一台。
然而,多个核心是没有用的,除非软件能够充分利用它们。例如,一个进行巨大计算的程序可能仅在一个核心上运行,而其他核心则处于空闲状态。如果我们希望程序运行得更快,软件必须能够识别并利用多个核心。
在这个示例中,我们将看到如何让命令运行得更快。
如何操作……
让我们来看一个我们在前面配方中讨论的md5sum命令的例子。这个命令执行复杂的计算,因此非常占用 CPU。如果我们有多个文件需要生成校验和,可以使用类似这样的脚本运行多个md5sum实例:
#/bin/bash
#filename: generate_checksums.sh
PIDARRAY=()
for file in File1.iso File2.iso
do
md5sum $file &
PIDARRAY+=("$!")
done
wait ${PIDARRAY[@]}
当我们运行此命令时,得到以下输出:
$ ./generate_checksums.sh
330dcb53f253acdf76431cecca0fefe7 File1.iso
bd1694a6fe6df12c3b8141dcffaf06e6 File2.iso
输出将与运行以下命令的结果相同:
md5sum File1.iso File2.iso
然而,如果md5sum命令同时运行,并且你的处理器是多核的,你会更快地获得结果(你可以使用time命令验证这一点)。
它是如何工作的...
我们利用 Bash 操作符&,它指示 Shell 将命令发送到后台并继续执行脚本。然而,这意味着脚本将在循环完成后退出,而md5sum进程仍在后台运行。为防止这种情况发生,我们使用$!获取进程的 PID,$!在 Bash 中保存最后一个后台进程的 PID。我们将这些 PID 添加到数组中,然后使用wait命令等待这些进程完成。
还有更多内容...
Bash 中的&操作符适用于少量任务。如果你有一百个文件需要生成校验和,脚本会尝试启动一百个进程,可能会导致系统交换,进而使任务运行变慢。
GNU parallel 命令并非所有安装中都有,但它可以通过包管理器加载。parallel 命令优化了资源的使用,而不会使其中任何一个过载。
parallel 命令读取stdin中的文件列表,并使用类似于 find 命令的-exec参数的选项来处理这些文件。{}符号表示要处理的文件,{.}符号表示没有后缀的文件名。
以下命令使用Imagemagick 的convert命令对文件夹中所有图片进行重新调整大小并生成新的图片:
ls *jpg | parallel convert {} -geometry 50x50 {.}Small.jpg
检查目录、文件和其中的子目录
我们处理的最常见问题之一是查找丢失的文件并整理混乱的文件层次结构。本节将讨论检查文件系统部分并展示其内容的技巧。
准备工作
我们讨论的find命令和循环为我们提供了检查目录及其内容并报告详细信息的工具。
如何做...
接下来的配方展示了检查目录的两种方法。首先,我们将显示文件结构为树形图,然后我们将看到如何生成目录下文件和文件夹的汇总。
生成目录的树状视图。
有时,如果文件系统以图形方式呈现,可能更容易进行可视化。
下一个配方将我们讨论过的几个工具组合在一起。它使用 find 命令生成当前文件夹下所有文件和子文件夹的列表。
-exec选项会创建一个子 shell,使用 echo 将文件名传递给tr命令的stdin。这里有两个tr命令,第一个删除所有字母数字字符,以及任何短横线(-)、下划线(_)或句点(.)。这样,路径中的斜杠(/)会传递到第二个tr命令,后者将斜杠转换为空格。最后,basename命令从文件名中去掉前缀路径并显示文件名。
使用以下命令查看/var/log目录中的文件夹树:
$ cd /var/log
$ find . -exec sh -c 'echo -n {} | tr -d "[:alnum:]_.\-" | \
tr "/" " "; basename {}' \;
生成的输出如下:
mail
statistics
gdm
::0.log
::0.log.1
cups
error_log
access_log
... access_l
生成文件和子目录的摘要
我们可以通过组合find命令、echo和wc命令来生成子目录的列表,以及其中文件的数量,接下来的章节会更详细地讨论这些命令。
使用以下命令获取当前文件夹的文件摘要:
for d in `find . -type d`;
do
echo `find $d -type f | wc -l` files in $d;
done
如果在/var/log目录下运行该脚本,它将生成如下输出:
103 files in .
17 files in ./cups
0 files in ./hp
0 files in ./hp/tmp
第三章:文件输入,文件输出
在本章中,我们将涵盖以下配方:
-
生成任意大小的文件
-
对文本文件进行交集和差集(A-B)操作
-
查找并删除重复文件
-
处理文件权限、所有权和粘滞位
-
使文件不可变
-
批量生成空白文件
-
查找符号链接及其目标
-
枚举文件类型统计信息
-
使用回环文件
-
创建 ISO 文件和混合 ISO
-
查找文件之间的差异,并进行修补
-
使用 head 和 tail 打印最后或最前面的 10 行
-
仅列出目录 - 替代方法
-
使用
pushd和popd快速导航命令行 -
计算文件中的行数、单词数和字符数
-
打印目录树
-
操作视频和图像文件
介绍
Unix 提供了一种文件式接口来访问所有设备和系统功能。特殊文件提供了直接访问设备(如 USB 闪存和磁盘驱动器)的方式,还提供了访问系统功能(如内存使用情况、传感器和进程栈)的功能。例如,我们使用的命令终端与一个设备文件相关联。通过写入对应的设备文件,我们可以向终端写入数据。我们可以像操作文件一样访问目录、常规文件、块设备、字符特殊设备、符号链接、套接字、命名管道等。文件名、大小、文件类型、修改时间、访问时间、变更时间、inode、关联的链接以及文件所在的文件系统,都是文件可能具备的属性和特性。本章将介绍与文件相关的操作或属性的处理方法。
生成任意大小的文件
随机数据文件对于测试非常有用。你可以使用此类文件来测试应用程序效率,确认应用程序是否真正对输入数据中立,确认应用程序没有大小限制,创建回环文件系统(回环文件是可以包含文件系统本身的文件,并且这些文件可以像物理设备一样通过 mount 命令进行挂载),等等。Linux 提供了通用的工具来构建此类文件。
如何执行...
创建指定大小的大文件最简单的方法是使用 dd 命令。dd 命令将给定的输入克隆并将精确副本写入输出。输入可以是 stdin、设备文件、常规文件等。输出可以是 stdout、设备文件、常规文件等。以下是 dd 命令的示例:
$ dd if=/dev/zero of=junk.data bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.00767266 s, 137 MB/s
该命令创建一个名为 junk.data 的文件,文件大小为 1 MB,全是零。
让我们了解一下参数:
-
if定义input文件 -
of定义output文件 -
bs定义块中的字节数 -
count定义要复制的块数
使用 dd 命令时要小心,因为它以低级方式操作设备。错误的操作可能会清除磁盘或破坏数据。务必仔细检查你的 dd 命令语法,尤其是 = 参数的准确性。
在前面的示例中,我们通过将bs设置为 1 MB 并设置count为 1 创建了一个 1 MB 的文件。如果bs设置为2M,count设置为2,那么总文件大小将为 4 MB。
我们可以使用不同的单位来指定块大小(bs)。将以下任何字符附加到数字后面来指定大小:
| 单位大小 | 代码 |
|---|---|
| 字节 (1 B) | C |
| 字节 (2 B) | W |
| 块 (512 B) | B |
| 千字节 (1024 B) | K |
| 兆字节 (1024 KB) | M |
| 千兆字节 (1024 MB) | G |
我们可以使用bs生成任何大小的文件。除了 MB 外,我们还可以使用表格中提到的其他单位表示法。
/dev/zero是一个字符特殊设备,返回零字节(\0)。
如果没有指定输入参数(if),dd将从stdin读取输入。如果没有指定输出参数(of),dd将使用stdout。
dd命令可以通过将大量数据传输到/dev/null并检查命令输出(例如,1048576 bytes (1.0 MB) copied, 0.00767266 s, 137 MB/s,如前面的示例所示)来测量内存操作的速度。
文本文件的交集和集合差异 (A-B)
交集和集合差异操作在数学课程中的集合理论中很常见。对字符串进行类似的操作在某些场景中也很有用。
准备中
comm命令是一个用于比较两个已排序文件的工具。它显示文件 1、文件 2 中的唯一行以及两个文件中共有的行。它有选项可以抑制某一列,使得执行交集和差异操作变得更加容易。
-
交集:交集操作将打印指定文件之间共有的行。
-
差异:差异操作将打印指定文件中存在的行,这些行在所有这些文件中并不相同。
-
集合差异:集合差异操作将打印文件
A中不与所有指定文件的集合(例如,B和C)匹配的行。
如何执行...
请注意,comm需要两个已排序的文件作为输入。以下是我们的示例输入文件:
$ cat A.txt
apple
orange
gold
silver
steel
iron
$ cat B.txt
orange
gold
cookies
carrot
$ sort A.txt -o A.txt ; sort B.txt -o B.txt
- 首先,执行没有任何选项的
comm:
$ comm A.txt B.txt
apple
carrot
cookies
gold
iron
orange
silver
steel
输出的第一列包含仅出现在A.txt中的行。第二列包含仅出现在B.txt中的行。第三列包含来自A.txt和B.txt的共有行。每列都使用制表符(\t)字符进行分隔。
- 为了打印两个文件的交集,我们需要移除第一列和第二列,并打印第三列。
-1选项移除第一列,-2选项移除第二列,剩下第三列:
$ comm A.txt B.txt -1 -2
gold
orange
- 仅打印两个文件之间不同的行,方法是移除第 3 列:
$ comm A.txt B.txt -3
apple
carrot
cookies
iron
silver
steel
此输出使用两列并带有空白,显示文件 1 和文件 2 中的唯一行。我们可以通过将两列合并成一列,使其更加易读,如下所示:
apple
carrot
cookies
iron
silver
steel
- 通过使用
tr去除制表符字符,可以合并这些行(具体内容见 第二章,掌握命令行)
$ comm A.txt B.txt -3 | tr -d '\t'
apple
carrot
cookies
iron
silver
steel
- 通过去除不必要的列,我们可以生成
A.txt和B.txt的集合差异,如下所示:
A.txt的集合差异:
$ comm A.txt B.txt -2 -3
-2 -3 删除第二列和第三列
B.txt的集合差异:
$ comm A.txt B.txt -1 -3
-2 -3 删除第二列和第三列
它是如何工作的……
这些命令行选项可以减少输出:
-
-1:删除第一列 -
-2:删除第二列 -
-3:删除第三列
集合差异操作允许你比较两个文件,并打印出 A.txt 或 B.txt 中的所有行,排除 A.txt 和 B.txt 中的公共行。当 A.txt 和 B.txt 作为参数传递给 comm 命令时,输出将包含第一列为 A.txt 相对于 B.txt 的集合差异,第二列为 B.txt 相对于 A.txt 的集合差异。
comm 命令允许在命令行中使用 - 字符来从 stdin 读取一个文件。这为比较多个文件与给定输入提供了一种方式。
假设我们有一个 C.txt 文件,内容如下:
$> cat C.txt
pear
orange
silver
mithral
我们可以将 B.txt 和 C.txt 文件与 A.txt 比较,像这样:
$> sort B.txt C.txt | comm - A.txt
apple
carrot
cookies
gold
iron
mithral
orange
pear
silver
steel
查找和删除重复文件
如果你需要恢复备份,或者在断网模式下使用笔记本电脑,或者从手机下载图片,你最终会遇到重复文件:内容相同的文件。你可能会想删除重复的文件,并保留一份副本。我们可以通过使用 shell 工具来检查内容,从而识别重复文件。这个方法描述了如何找到重复文件并根据结果执行操作。
准备工作
我们通过比较文件内容来识别重复文件。校验和非常适合此任务。内容相同的文件会产生相同的校验和值。
如何操作……
按照以下步骤查找或删除重复文件:
- 生成一些测试文件:
$ echo "hello" > test ; cp test test_copy1 ; cp test test_copy2;
$ echo "next" > other;
# test_copy1 and test_copy2 are copy of test
- 删除重复文件的脚本代码使用了
awk,这是在所有 Linux/Unix 系统中都可以使用的解释器:
#!/bin/bash
#Filename: remove_duplicates.sh
#Description: Find and remove duplicate files and
# keep one sample of each file.
ls -lS --time-style=long-iso | awk 'BEGIN {
getline; getline;
name1=$8; size=$5
}
{
name2=$8;
if (size==$5)
{
"md5sum "name1 | getline; csum1=$1;
"md5sum "name2 | getline; csum2=$1;
if ( csum1==csum2 )
{
print name1; print name2
}
};
size=$5; name1=name2;
}' | sort -u > duplicate_files
cat duplicate_files | xargs -I {} md5sum {} | \
sort | uniq -w 32 | awk '{ print $2 }' | \
sort -u > unique_files
echo Removing..
comm duplicate_files unique_files -3 | tee /dev/stderr | \
xargs rm
echo Removed duplicates files successfully.
- 按照以下方式运行代码:
$ ./remove_duplicates.sh
它是如何工作的……
上面的代码会在一个目录中找到相同文件的副本,并删除除一份副本之外的所有文件。让我们来逐步了解这段代码是如何工作的。
ls -lS 按文件大小列出当前文件夹中文件的详细信息。--time-style=long-iso 选项告诉 ls 以 ISO 格式打印日期。awk 读取 ls -lS 的输出,并对输入文本的列和行进行比较,以查找重复文件。
代码背后的逻辑如下:
-
我们按文件大小列出文件,所以相同大小的文件将相邻。查找相同文件的第一步是找到大小相同的文件。接下来,我们计算文件的校验和。如果校验和匹配,则这些文件是重复文件,并且会删除其中一组副本。
-
awk的BEGIN{}块在主要处理之前执行。它读取 "total" 行并初始化变量。大部分处理发生在{}块中,当awk读取并处理其余的ls输出时。END{}块中的语句在所有输入读取完成后执行。ls -lS的输出如下:
total 16
-rw-r--r-- 1 slynux slynux 5 2010-06-29 11:50 other
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy1
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy2
- 第一行的输出告诉我们文件的总数,在这种情况下没有用处。我们使用
getline来读取第一行,然后丢弃它。我们需要比较每一行和随后的行的大小。在BEGIN块中,我们读取第一行并存储文件名和大小(分别是第八列和第五列)。当awk进入{}块时,剩余的行会逐行读取。此块比较当前行获取的大小和先前存储在size变量中的大小。如果它们相等,则意味着这两个文件通过大小是重复的,必须通过md5sum进一步检查。
我们在解决方案的过程中玩了一些小技巧。
可以通过以下方式在 awk 中读取外部命令的输出:
"cmd"| getline
一旦读取了行,整行内容就会存储在 $0 中,每一列的内容可通过 $1、$2、...、$n 获取。在这里,我们将文件的 md5sum 校验和读取到 csum1 和 csum2 变量中。name1 和 name2 变量存储连续的文件名。如果两个文件的校验和相同,则它们被确认是重复文件并被打印出来。
我们需要从每一组重复文件中找到一个文件,这样我们就可以删除所有其他的重复文件。我们计算重复文件的 md5sum 值,并通过查找唯一行来打印每组重复文件中的一个文件,通过使用 -w 32 来比较每行的 md5sum(md5sum 输出的前 32 个字符;通常 md5sum 的输出是一个 32 字符的哈希值,后跟文件名)。每组重复文件中的一个样本被写入 unique_files。
现在,我们需要删除 duplicate_files 中列出的文件,排除 unique_files 中列出的文件。comm 命令打印出在 duplicate_files 中但不在 unique_files 中的文件。
为此,我们使用集合差操作(参见交集、差集和集合差集的相关操作)。
comm 仅处理排序后的输入。因此,使用 sort -u 来过滤 duplicate_files 和 unique_files。
tee 命令用于将文件名传递给 rm 命令以及 print。tee 命令将其输入发送到 stdout 和文件。我们还可以通过重定向到 stderr 将文本打印到终端。/dev/stderr 是对应 stderr(标准错误)的设备。通过重定向到 stderr 设备文件,发送到 stdin 的文本将作为标准错误在终端中打印出来。
处理文件权限、所有权和粘滞位
文件权限和所有权是 Unix/Linux 文件系统的一个重要特征。这些特性在多用户环境中保护你的信息。不匹配的权限和所有权也可能导致共享文件变得困难。这些内容解释了如何有效地使用文件的权限和所有权。
每个文件都有多种权限类型。通常会操作三组权限(用户、组和其他)。
用户是文件的所有者,通常拥有所有的访问权限。组是由系统管理员定义的用户集合,可能被授权访问该文件。其他用户是指除了所有者或所有者组成员之外的任何用户。
ls命令的-l选项显示文件的多个方面,包括类型、权限、所有者和组:
-rw-r--r-- 1 slynux users 2497 2010-02-28 11:22 bot.py
drwxr-xr-x 2 slynux users 4096 2010-05-27 14:31 a.py
-rw-r--r-- 1 slynux users 539 2010-02-10 09:11 cl.pl
输出的第一列定义了文件类型,具体如下:
-
-:如果是常规文件,则使用此标识 -
d:如果是目录,则使用此标识 -
c:用于字符设备 -
b:用于块设备 -
l:如果是符号链接,则使用此标识 -
s:用于套接字 -
p:用于管道
接下来的九个字符分为三组,每组三个字母(--- --- ---)。前三个字符对应用户(所有者)的权限,第二组三个字符对应组的权限,第三组三个字符对应其他用户的权限。九个字符中的每个字符指定是否设置权限。如果权限已设置,相应位置会出现一个字符,否则会出现-字符,这意味着对应的权限未设置(不可用)。
三个常见的字母组合如下:
-
r 读取:当这个设置时,文件、设备或目录可以被读取。 -
w 写入:当这个设置时,文件、设备或目录可以被修改。在文件夹上,这定义了是否可以创建或删除文件。 -
x 执行:当这个设置时,文件可以被执行。在文件夹上,这定义了文件夹中的文件是否可以被访问。
让我们来看看这些三个字符集对用户、组和其他的含义:
-
用户(权限字符串:
rwx------):这些定义了用户的选项。通常,数据文件的用户权限是rw-,而脚本或可执行文件的用户权限是rwx。用户还有一个特殊的权限,称为setuid(S),它出现在执行(x)的位置。setuid权限使得可执行文件即使由其他用户运行,也能像其所有者一样有效地执行。设置了setuid权限的文件示例是-rwS------。 -
组(权限字符串:
---rwx---):第二组三字符指定组权限。与setuid不同,组有一个setgid(S)位。这使得项目能够以其有效组为所有者组来运行可执行文件。但执行命令的组可能与文件所有者的组不同。一个组权限的例子是----rwS---。 -
其他(权限字符串:
------rwx):其他权限出现在权限字符串的最后三个字符中。如果这些权限被设置,任何人都可以访问该文件或文件夹。通常,您会希望将这些位设置为---。
目录有一个特殊权限,称为粘滞位。当目录设置了粘滞位时,即使组和其他用户有写权限,只有创建该目录的用户可以删除该目录中的文件。粘滞位出现在其他用户权限集中的执行字符(x)位置。若执行权限未设置且粘滞位已设置,x位置会显示为字符t。如果粘滞位和执行权限都已设置,则x位置显示为T。考虑这个例子:
------rwt , ------rwT
一个典型的启用了粘滞位的目录是/tmp,在该目录下,任何人都可以创建文件,但只有文件所有者可以删除文件。
在每一行ls -l输出中,字符串slynux users表示用户和组。这里,slynux是文件所有者,且是users组的成员。
如何执行...
为了设置文件权限,我们使用chmod命令。
假设我们需要设置权限为rwx rw- r-。
使用 chmod 设置这些权限:
$ chmod u=rwx g=rw o=r filename
此处使用的选项如下:
-
u:此项指定用户的权限 -
g:此项指定组的权限 -
o:此项指定其他用户的权限
使用+可以向用户、组或其他用户添加权限,使用-可以删除权限。
给一个已经拥有权限rwx rw- r-的文件添加执行权限:
$ chmod o+x filename
此命令为其他用户添加了x权限。
给所有权限类别添加执行权限,即用户、组和其他用户:
$ chmod a+x filename
这里的a表示所有用户。
若要移除权限,请使用-。例如,$ chmod a-x 文件名。
权限可以用三位八进制数字表示,每一位分别对应用户、组和其他用户,顺序为用户、组、其他。
读取、写入和执行权限有各自独特的八进制值,如下所示:
-
r= 4 -
w= 2 -
x= 1
我们通过加和八进制值来计算所需的权限组合。考虑这个例子:
-
rw-= 4 + 2 = 6 -
r-x= 4 + 1 = 5
权限rwx rw- r--的数字表示方法如下:
-
rwx= 4 + 2 + 1 = 7 -
rw-= 4 + 2 = 6 -
r--= 4
因此,rwx rw- r--等于764,使用八进制值设置权限的命令是$ chmod 764 文件名。
还有更多内容...
让我们来看看还可以对文件和目录执行哪些任务。
更改所有权
chown 命令将更改文件和文件夹的所有权:
$ chown user.group filename
考虑这个例子:
$ chown slynux.users test.sh
这里,slynux 是用户,users 是组。
设置粘滞位
粘滞位可以应用于目录。当设置了粘滞位时,只有文件的所有者可以删除文件,即使其他人对文件夹有写权限。
粘滞位通过 +t 选项传递给 chmod 来设置:
$ chmod a+t directory_name
递归应用文件权限
有时,你可能需要递归地更改当前目录中所有文件和目录的权限。chmod 的 -R 选项支持递归更改:
$ chmod 777 . -R
-R 选项指定递归地更改权限。
我们使用 . 来指定路径为当前工作目录。这相当于 $ chmod 777 "$(pwd)" -R。
递归应用所有权
chown 命令也支持 -R 标志来递归地更改所有权:
$ chown user.group . -R
以其他用户身份运行可执行文件(setuid)
一些可执行文件需要以当前用户之外的用户身份执行。例如,http 服务器可能会在启动过程中由 root 启动,但该任务应该由 httpd 用户拥有。setuid 权限使得文件可以在其他用户运行程序时,以文件所有者身份执行。
首先,将所有权更改为需要执行操作的用户,然后以该用户身份登录。接着,运行以下命令:
$ chmod +s executable_file
# chown root.root executable_file
# chmod +s executable_file
$ ./executable_file
现在无论谁调用它,都以 root 用户身份执行。
setuid 仅对 Linux ELF 二进制文件有效。你无法将 shell 脚本设置为以另一个用户身份运行。这是一项安全功能。
使文件不可变
读取、写入、执行和 Setuid 字段在所有 Linux 文件系统中都是常见的。扩展文件系统(ext2、ext3 和 ext4)支持更多属性。
扩展属性之一使文件不可变。当文件被设置为不可变时,任何用户或超级用户都无法删除该文件,直到不可变属性被从文件中移除。你可以使用 df -T 命令来确定文件系统的类型,或者通过查看 /etc/mtab 文件来确定。该文件的第一列指定了分区设备路径(例如,/dev/sda5),第三列指定了文件系统类型(例如,ext3)。
使文件不可变是一种防止文件被修改的方法。一个例子是将 /etc/resolv.conf 文件设置为不可变。resolv.conf 文件存储了 DNS 服务器的列表,这些服务器将域名(如 packtpub.com)转换为 IP 地址。DNS 服务器通常是你 ISP 的 DNS 服务器。但是,如果你更喜欢使用第三方服务器,可以修改 /etc/resolv.conf 来指向该 DNS。下次连接到 ISP 时,/etc/resolv.conf 会被覆盖为 ISP 的 DNS 服务器。为了防止这种情况发生,可以将 /etc/resolv.conf 设置为不可变。
在本教程中,我们将学习如何使文件不可变,并在需要时将其恢复为可变。
准备工作
chattr 命令用于更改扩展属性。它可以使文件不可变,还可以修改属性以调整文件系统同步或压缩。
如何操作...
要使文件不可变,请按照以下步骤操作:
- 使用
chattr使文件不可变:
# chattr +i file
- 该文件现在是不可变的。尝试以下命令:
rm file
rm: cannot remove `file': Operation not permitted
- 为了使其可写,请移除不可变属性,如下所示:
chattr -i file
批量生成空白文件
脚本必须在实际系统中使用之前进行测试。我们可能需要生成数千个文件,以确认没有内存泄漏或进程未结束。这个示例演示了如何生成空白文件。
正在准备
touch 命令用于创建空白文件或修改现有文件的时间戳。
如何操作...
要批量生成空白文件,请按照以下步骤操作:
- 使用不存在的文件名调用 touch 命令会创建一个空文件:
$ touch filename
- 按照不同的命名模式生成批量文件:
for name in {1..100}.txt
do
touch $name
done
在前面的代码中,{1..100} 将扩展为字符串 1, 2, 3, 4, 5, 6, 7...100。我们可以使用各种简写模式,比如 test{1..200}.c、test{a..z}.txt 等,代替 {1..100}.txt。
如果文件已存在,touch 命令会将与文件相关的所有时间戳更改为当前时间。以下选项定义了要修改的时间戳子集:
-
touch -a:修改访问时间 -
touch -m:这会修改修改时间
我们可以指定时间和日期,而不是使用当前时间:
$ touch -d "Fri Jun 25 20:50:14 IST 1999" filename
与 -d 一起使用的日期字符串不必是这个确切格式。它可以接受许多简单的日期格式。我们可以省略时间,只提供日期,如 Jan 20、2010。
查找符号链接及其目标
符号链接在类 Unix 系统中很常见。使用它们的原因包括方便访问、维护同一库或程序的多个版本等。这个示例将讨论处理符号链接的基本技术。
符号链接是指向其他文件或文件夹的指针。它们的功能类似于 MacOS X 中的别名或 Windows 中的快捷方式。当符号链接被删除时,不会影响原始文件。
如何操作...
以下步骤将帮助你处理符号链接:
- 要创建符号链接,请运行以下命令:
$ ln -s target symbolic_link_name
请参考此示例:
$ ln -l -s /var/www/ ~/web
这会在当前用户的主目录中创建一个符号链接(称为 web),指向 /var/www/。
- 要验证链接是否创建,运行此命令:
$ ls -l ~/web
lrwxrwxrwx 1 slynux slynux 8 2010-06-25 21:34 web -> /var/www
web -> /var/www 指定 web 指向 /var/www。
- 要打印当前目录中的符号链接,请使用此命令:
$ ls -l | grep "^l"
- 要打印当前目录及子目录中的所有符号链接,请运行此命令:
$ find . -type l -print
- 要显示给定符号链接的目标路径,使用
readlink命令:
$ readlink web
/var/www
如何工作...
当使用 ls 和 grep 显示当前文件夹中的符号链接时,grep ^l 命令过滤 ls -l 输出,仅显示以 l 开头的行。^ 指定字符串的开始,紧随其后的 l 表示字符串必须以 l 开头,这是链接的标识符。
使用 find 时,我们使用参数 -type l,指示 find 查找符号链接文件。-print 选项将符号链接的列表打印到标准输出(stdout)。初始路径设置为当前目录。
枚举文件类型统计信息
Linux 支持多种文件类型。本示例描述了一个脚本,它遍历一个目录及其子目录中的所有文件,并打印一个报告,报告中包含文件类型的详细信息(不同文件类型的文件)以及每种文件类型的计数。这个示例是一个练习,旨在编写脚本以遍历大量文件并收集细节。
正在准备
在 Unix/Linux 系统中,文件类型并非由文件扩展名定义(如微软 Windows 所做)。Unix/Linux 系统使用 file 命令,通过检查文件的内容来确定文件类型。此示例收集了多个文件的文件类型统计信息。它将相同类型文件的计数存储在一个关联数组中。
关联数组在 bash 4 及更高版本中得到支持。
如何操作...
要枚举文件类型统计信息,请按照以下步骤操作:
- 要打印文件的类型,请使用以下命令:
$ file filename
$ file /etc/passwd
/etc/passwd: ASCII text
- 打印文件类型而不打印文件名:
$ file -b filename
ASCII text
- 文件统计脚本如下:
#!/bin/bash
# Filename: filestat.sh
if [ $# -ne 1 ];
then
echo "Usage is $0 basepath";
exit
fi
path=$1
declare -A statarray;
while read line;
do
ftype=`file -b "$line" | cut -d, -f1`
let statarray["$ftype"]++;
done < (find $path -type f -print)
echo ============ File types and counts =============
for ftype in "${!statarray[@]}";
do
echo $ftype : ${statarray["$ftype"]}
done
使用方法如下:
$ ./filestat.sh /home/slynux/temp
- 示例输出如下所示:
$ ./filetype.sh /home/slynux/programs
============ File types and counts =============
Vim swap file : 1
ELF 32-bit LSB executable : 6
ASCII text : 2
ASCII C program text : 10
如何实现...
这个脚本依赖于关联数组 statarray。该数组按文件类型索引:PDF、ASCII 等等。每个索引存储该类型文件的计数。它由 declare -A statarray 命令定义。
脚本包含两个循环:一个 while 循环处理来自 find 命令的输出,另一个是 for 循环,遍历 statarray 变量的索引并生成输出。
while 循环的语法如下所示:
while read line;
do something
done < filename
对于这个脚本,我们使用 find 命令的输出,而不是文件,作为 while 的输入。
(find $path -type f -print) 命令等同于文件名,但它将文件名替换为子进程输出。
请注意,第一个 < 用于输入重定向,第二个 < 用于将子进程的输出转换为文件名。同时,它们之间有一个空格,以防止 shell 将其解释为 << 操作符。
find 命令使用 -type f 选项来返回 $path 中定义的子目录下的文件列表。文件名由 read 命令逐行读取。当 read 命令接收到 EOF(文件结束符)时,它返回一个 失败 并且 while 命令退出。
在while循环中,使用 file 命令来确定文件的类型。-b选项用于显示文件类型而不显示文件名。
file 命令提供了比我们需要的更多的详细信息,如图像编码和分辨率(对于图像文件)。这些详细信息是以逗号分隔的,示例如下:
$ file a.out -b
ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not
stripped
我们需要从之前的详细信息中提取出ELF 32-bit LSB executable。因此,我们使用-d,选项指定,为分隔符,并使用-f1选项选择第一个字段。
<(find $path -type f -print)相当于一个文件名,但它通过子进程输出替代文件名。注意,第一个<用于输入重定向,第二个<用于将子进程输出转换为文件名。同时,这两个<之间有一个空格,以免 Shell 将其解释为<<操作符。
在 Bash 3.x 及更高版本中,我们有一个新的操作符<<<,它允许我们将字符串输出作为输入文件。使用这个操作符,我们可以写出循环的完成行,如下所示:
done <<< "`find $path -type f -print`"
${!statarray[@]} 返回数组索引的列表。
使用回环文件
Linux 文件系统通常存在于硬盘或内存棒等设备上。文件也可以作为文件系统挂载。这个文件中的文件系统可以用于测试、自定义文件系统,甚至作为加密磁盘存储机密信息。
如何操作...
要在文件中创建 1 GB 的 ext4 文件系统,请按照以下步骤操作:
- 使用
dd创建一个 1 GB 的文件:
$ dd if=/dev/zero of=loobackfile.img bs=1G count=1
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 37.3155 s, 28.8 MB/s
创建的文件大小超过 1 GB,因为硬盘是块设备,因此存储必须按块大小的整数倍分配。
- 使用
mkfs命令将 1 GB 的文件格式化为 ext4:
$ mkfs.ext4 loopbackfile.img
- 使用 file 命令检查文件类型:
$ file loobackfile.img
loobackfile.img: Linux rev 1.0 ext4 filesystem data,
UUID=c9d56c42-
f8e6-4cbd-aeab-369d5056660a (extents) (large files) (huge files)
- 使用
mkdir和 mount 创建挂载点,并挂载回环文件:
# mkdir /mnt/loopback
# mount -o loop loopbackfile.img /mnt/loopback
-o loop选项用于挂载回环文件系统。
这是一个简短的方法,将回环文件系统附加到操作系统选择的设备上,设备名称类似于/dev/loop1或/dev/loop2。
- 要指定特定的回环设备,请运行以下命令:
# losetup /dev/loop1 loopbackfile.img
# mount /dev/loop1 /mnt/loopback
- 要 umount(卸载),请使用以下语法:
# umount mount_point
请考虑以下示例:
# umount /mnt/loopback
- 我们还可以使用设备文件路径作为
umount命令的参数:
# umount /dev/loop1
请注意,mount 和 umount 命令应该以 root 用户身份执行,因为它们是特权命令。
它是如何工作的...
首先,我们必须创建一个文件来制作回环文件系统。为此,我们使用dd,它是一个通用的原始数据复制命令。它将if参数指定的文件中的数据复制到of参数指定的文件中。我们指示dd以 1 GB 的块大小复制数据,并复制一个这样的块,从而创建一个 1 GB 的文件。/dev/zero文件是一个特殊文件,从中读取数据时总是返回 0。
我们使用mkfts.ext4命令在文件中创建一个 ext4 文件系统。任何可以挂载的设备都需要一个文件系统。常见的文件系统包括 ext4、ext3 和 vfat。
mount 命令将环回文件附加到 挂载点(在此情况下为 /mnt/loopback)。挂载点使用户能够访问文件系统上存储的文件。挂载点必须在执行 mount 命令之前使用 mkdir 命令创建。我们传递 -o loop 选项给 mount,告诉它我们挂载的是一个环回文件,而不是一个设备。
当 mount 知道它正在操作一个环回文件时,它会在 /dev 中设置一个与环回文件对应的设备,然后挂载它。如果我们希望手动执行此操作,我们可以使用 losetup 命令来创建设备,然后使用 mount 命令挂载它。
还有更多内容...
让我们探索一些环回文件和挂载的其他可能性。
在环回镜像中创建分区
假设我们想要创建一个环回文件,进行分区,然后最终挂载一个子分区。在这种情况下,我们不能使用 mount -o loop。我们必须手动设置设备并挂载其中的分区。
对一个零文件进行分区:
# losetup /dev/loop1 loopback.img
# fdisk /dev/loop1
fdisk 是 Linux 系统上的标准分区工具。关于使用 fdisk 创建分区的简明教程可在 www.tldp.org/HOWTO/Partition/fdisk_partitioning.html 上找到(请确保在本教程中使用 /dev/loop1 而不是 /dev/hdb)。
在 loopback.img 中创建分区并挂载第一个分区:
# losetup -o 32256 /dev/loop2 loopback.img
在这里,/dev/loop2 代表第一个分区,-o 是偏移标志,32256 字节用于 DOS 分区方案。第一个分区从硬盘开始的第 32256 字节处开始。
我们可以通过指定所需的偏移量来设置第二个分区。挂载后,我们可以像在物理设备上进行所有常规操作一样进行操作。
更快速地挂载带有分区的环回磁盘镜像
我们可以手动将分区偏移量传递给 losetup 来挂载环回磁盘镜像中的分区。然而,使用 kpartx 有一种更快捷的方法来挂载该镜像中的所有分区。此工具通常未安装,因此您需要通过包管理器安装它:
# kpartx -v -a diskimage.img
add map loop0p1 (252:0): 0 114688 linear /dev/loop0 8192
add map loop0p2 (252:1): 0 15628288 linear /dev/loop0 122880
这会创建磁盘镜像中分区到 /dev/mapper 中设备的映射,之后您可以将其挂载。例如,要挂载第一个分区,请使用以下命令:
# mount /dev/mapper/loop0p1 /mnt/disk1
当你完成设备操作(并使用 umount 卸载所有挂载的分区)后,通过运行以下命令删除映射:
# kpartx -d diskimage.img
loop deleted : /dev/loop0
将 ISO 文件作为环回文件挂载
ISO 文件是光盘介质的归档。我们可以像挂载物理磁盘一样,使用环回挂载来挂载 ISO 文件。
我们甚至可以使用一个非空目录作为挂载路径。然后,挂载路径将包含来自设备的数据,而不是原始内容,直到设备被卸载为止。考虑以下示例:
# mkdir /mnt/iso
# mount -o loop linux.iso /mnt/iso
现在,从 /mnt/iso 执行操作。ISO 是一个只读文件系统。
使用 sync 立即刷新更改
在挂载设备上进行的更改不会立即写入物理设备。它们仅在内部内存缓冲区满时才会被写入。我们可以使用sync命令强制写入:
$ sync
创建 ISO 文件和混合 ISO
ISO 镜像是一种存储光盘(如 CD-ROM、DVD-ROM 等)精确映像的归档格式。ISO 文件通常用于存储将要刻录到光盘介质的内容。
本节将描述如何将光盘中的数据提取到一个可以作为回环设备挂载的 ISO 文件中,然后解释如何生成可以刻录到光盘介质上的自定义 ISO 文件系统。
我们需要区分可引导光盘和不可引导光盘。可引导光盘能够从自身启动,并运行操作系统或其他产品。可引导的 DVD 包括安装包和Live系统,如 Knoppix 和 Puppy。
不可引导的 ISO 无法做到这一点。升级包、源代码 DVD 等都是不可引导的。
请注意,从可引导的 CD-ROM 复制文件到另一个 CD-ROM 并不足以使新光盘可引导。为了保留 CD-ROM 的引导特性,必须将其作为磁盘映像使用 ISO 文件进行复制。
许多人使用闪存驱动器来替代光盘。当我们将可引导的 ISO 写入闪存驱动器时,除非使用专门为此目的设计的混合 ISO 镜像,否则它将无法引导。
这些教程将为你提供有关 ISO 镜像及其操作的深入理解。
准备工作
如前所述,Unix 将一切视为文件。每个设备都是一个文件。因此,如果我们想复制设备的精确映像,我们需要从中读取所有数据并写入文件。光盘读取器将在/dev文件夹中,名称可能为/dev/cdrom、/dev/dvd,或者可能是/dev/sd0。访问sd*时需要小心。多个磁盘类型设备被命名为sd#。例如,你的硬盘可能是sd0,CD-ROM 可能是sd1。
cat命令将读取任何数据,并且重定向将该数据写入文件。这是可行的,但我们还将看到更好的方法来完成这项操作。
如何操作...
为了从/dev/cdrom创建 ISO 镜像,请使用以下命令:
# cat /dev/cdrom > image.iso
虽然这也可以工作,但创建 ISO 镜像的首选方法是使用dd命令:
# dd if=/dev/cdrom of=image.iso
mkisofs命令创建一个 ISO 镜像文件。由mkisofs创建的输出文件可以通过cdrecord等工具刻录到 CD-ROM 或 DVD-ROM 上。mkisofs命令将从包含所有要复制到 ISO 文件中的文件的目录中创建 ISO 文件:
$ mkisofs -V "Label" -o image.iso source_dir/
mkisofs命令中的-o选项指定 ISO 文件的路径。source_dir是作为 ISO 文件内容的目录路径,-V选项指定用于 ISO 文件的标签。
还有更多内容...
让我们学习更多与 ISO 文件相关的命令和技术。
从闪存驱动器或硬盘引导的混合 ISO
启动 ISO 文件通常不能直接传输到 USB 存储设备上以创建可启动的 USB 闪存盘。然而,某些特殊类型的 ISO 文件称为混合 ISO,它们可以被刷写到设备上,生成一个可启动的设备。
我们可以使用isohybrid命令将标准 ISO 文件转换为混合 ISO。isohybrid命令是一个新的工具,大多数 Linux 发行版默认不包含它。你可以从syslinux 包下载,网址是www.syslinux.org。该命令也可能在你的 yum 或apt-get软件库中作为syslinux-utils提供。
该命令将使 ISO 文件变得可启动:
# isohybrid image.iso
现在可以将 ISO 文件写入 USB 存储设备。
要将 ISO 写入 USB 存储设备,使用以下命令:
# dd if=image.iso of=/dev/sdb1
使用适当的设备代替/dev/sdb1,或者你也可以使用cat,如下所示:
# cat image.iso >> /dev/sdb1
从命令行刻录 ISO 文件
cdrecord命令将 ISO 文件刻录到 CD-ROM 或 DVD-ROM 上。
要将映像刻录到 CD-ROM 上,运行以下命令:
# cdrecord -v dev=/dev/cdrom image.iso
有用的选项包括:
- 使用
-speed选项指定刻录速度:
-speed SPEED
考虑以下示例:
# cdrecord -v dev=/dev/cdrom image.iso -speed 8
这里,8表示指定的速度为 8 倍速。
- CD-ROM 可以使用多重会话进行刻录,从而允许我们多次向同一磁盘写入数据。可以使用
-multi选项进行多会话刻录:
# cdrecord -v dev=/dev/cdrom image.iso -multi
操控 CD-ROM 托盘
如果你在桌面电脑上,尝试以下命令并享受乐趣:
$ eject
该命令会弹出托盘。
$ eject -t
该命令将关闭托盘。
为了加分,编写一个循环,使托盘反复打开和关闭几次。显然,你永远不会在同事去喝咖啡的时候悄悄地把这个脚本加到他们的.bashrc文件里。
查找文件之间的差异并打补丁
当有多个版本的文件可用时,突出显示文件之间的差异比手动比较它们更为有用。这个例子演示了如何生成文件之间的差异。在与多个开发者合作时,需要将更改分发给其他人。将整个源代码发送给其他开发者既耗时又麻烦。发送一个差异文件更为方便,因为它只包含被更改、添加或删除的行,并且附带行号。这个差异文件被称为补丁文件。我们可以使用patch命令将补丁文件中指定的更改应用到原始源代码中。如果需要,也可以通过再次应用补丁来撤销这些更改。
如何操作...
diff工具报告两个文件之间的差异。
- 为了演示
diff的行为,创建以下文件:
文件 1:version1.txt
this is the original text
line2
line3
line4
happy hacking !
文件 2:version2.txt
this is the original text
line2
line4
happy hacking !
GNU is not UNIX
- 非统一的
diff输出(没有-u标志)是:
$ diff version1.txt version2.txt
3d2
<line3
6c5
> GNU is not UNIX
- 统一的
diff输出是:
$ diff -u version1.txt version2.txt
--- version1.txt 2010-06-27 10:26:54.384884455 +0530
+++ version2.txt 2010-06-27 10:27:28.782140889 +0530
@@ -1,5 +1,5 @@
this is the original text
line2
-line3
line4
happy hacking !
-
+GNU is not UNIX
-u选项会生成统一格式的输出。统一格式的差异输出更具可读性,也更容易解读。
在统一diff中,以+开头的行表示添加的行,以-开头的行表示删除的行。
- 可以通过将
diff输出重定向到文件来生成补丁文件:
$ diff -u version1.txt version2.txt > version.patch
patch 命令可以对两个文件应用更改。当应用到 version1.txt 时,我们得到 version2.txt 文件。当应用到 version2.txt 时,我们生成 version1.txt。
- 此命令应用补丁:
$ patch -p1 version1.txt < version.patch
patching file version1.txt
我们现在有了 version1.txt,它的内容与 version2.txt 相同。
- 要撤销更改,请使用以下命令:
$ patch -p1 version1.txt < version.patch
patching file version1.txt
Reversed (or previously applied) patch detected! Assume -R? [n] y
#Changes are reverted.
如图所示,给已应用补丁的文件再次打补丁会撤销更改。为了避免提示用户进行 y/n 选择,我们可以使用 -R 选项与 patch 命令一起使用。
还有更多...
让我们了解 diff 的其他功能。
生成目录间的差异
diff 命令可以递归地作用于目录。它将为目录中的所有子文件生成差异输出。使用以下命令:
$ diff -Naur directory1 directory2
此命令中每个选项的解释如下:
-
-N:用于将缺失的文件视为空文件 -
-a:用于将所有文件视为文本文件 -
-u:用于生成统一格式的输出 -
-r:用于递归遍历目录中的文件
使用 head 和 tail 打印文件的最后或前 10 行
当检查一个非常大的文件时,cat 命令(会显示所有行)并不适用。相反,我们希望查看文件的某个子集(例如,文件的前 10 行或最后 10 行)。我们可能需要打印前 n 行或最后 n 行,或者打印除去最后 n 行或除去前 n 行的内容,或者打印两个位置之间的行。
head 和 tail 命令可以做到这一点。
如何执行...
head 命令读取输入文件的开头部分。
- 打印前 10 行:
$ head file
- 从
stdin读取数据:
$ cat text | head
- 指定要打印的前几行数量:
$ head -n 4 file
此命令打印前四行。
- 打印所有行,但不包括最后
M行:
$ head -n -M file
请注意,它是负 M。
例如,要打印所有行,但不包括最后五行,可以使用以下命令:
$ seq 11 | head -n -5
1
2
3
4
5
6
此命令打印第 1 到第 5 行:
$ seq 100 | head -n 5
-
打印除去最后几行是
head的常见用途。在查看日志文件时,我们最常想查看最新的(即最后)几行。 -
要打印文件的最后 10 行,请使用此命令:
$ tail file
- 要从
stdin读取数据,请使用以下命令:
$ cat text | tail
- 打印最后五行:
$ tail -n 5 file
- 要打印所有行,但不包括前 M 行,请使用此命令:
$ tail -n +(M+1)
例如,要打印所有行,但不包括前五行,M + 1 = 6,则命令如下:
$ seq 100 | tail -n +6
这将打印第 6 行到第 100 行。
tail 的一个常见用途是监控增长中的文件中的新行,例如系统日志文件。由于新行会追加到文件的末尾,tail 可以用于在数据写入时显示它们。为了监控文件的增长,tail 有一个特殊选项 -f 或 --follow,它允许 tail 跟踪追加的行并在数据被添加时显示它们:
$ tail -f growing_file
你可能会希望将此用在日志文件上。监控文件增长的命令是:
# tail -f /var/log/messages
或者,可以使用以下命令:
$ dmesg | tail -f
dmesg 命令返回内核环形缓冲区的消息内容。我们可以使用它来调试 USB 设备、检查磁盘行为或监控网络连接。-f 参数的 tail 可以添加一个休眠间隔 -s,用于设置监控文件更新时的间隔。
tail 命令可以指示在给定的进程 ID 终止后停止运行。
假设进程 Foo 正在向我们正在监控的文件中附加数据。-f 参数的 tail 命令应当在进程 Foo 结束前一直执行。
$ PID=$(pidof Foo)
$ tail -f file --pid $PID
当进程 Foo 终止时,tail 也会终止。
让我们做一个例子。
-
创建一个新文件
file.txt,并在你喜欢的文本编辑器中打开该文件。 -
现在运行以下命令:
$ PID=$(pidof gedit)
$ tail -f file.txt --pid $PID
- 向文件添加新行并频繁保存文件。
当你向文件末尾添加新行时,tail 命令会将新行写入终端。当你关闭编辑会话时,tail 命令将终止。
仅列出目录 - 替代方法
仅通过脚本列出目录是相当困难的。此配方介绍了多种仅列出目录的方法。
准备工作
准备工作 有多种方法可以仅列出目录。dir 命令类似于 ls,但选项较少。我们还可以使用 ls 和 find 来列出目录。
如何实现...
当前路径中的目录可以通过以下方式显示:
- 使用
ls和-d参数来打印目录:
$ ls -d */
- 使用
ls -F和grep:
$ ls -F | grep "/$"
- 使用
ls -l和grep:
$ ls -l | grep "^d"
- 使用
find打印目录:
$ find . -type d -maxdepth 1 -print
工作原理...
当 -F 参数与 ls 一起使用时,所有条目都会附加一些文件字符,如 @、*、| 等等。对于目录,条目会附加 / 字符。我们使用 grep 来过滤出仅以 /$ 结束的条目。
ls -l 输出的每一行的第一个字符是文件类型字符。对于目录,文件类型字符为 d。因此,我们使用 grep 来过滤以 "d." 开头的行。^ 是行首指示符。
find 命令可以将参数类型设置为目录,并将 maxdepth 设置为 1,因为我们不希望它在子目录中进行搜索。
使用 pushd 和 popd 进行快速命令行导航
在文件系统中导航多个位置时,一种常见做法是通过复制和粘贴路径来 cd。如果涉及多个位置,这种做法效率较低。当我们需要在不同位置之间来回切换时,每次使用 cd 命令输入或粘贴路径会非常浪费时间。Bash 和其他 Shell 支持 pushd 和 popd 用于在目录之间循环切换。
准备工作
pushd 和 popd 用于在多个目录之间切换,而无需重新输入目录路径。pushd 和 popd 会创建一个路径栈——一个 后进先出(LIFO)的目录列表,记录我们访问过的目录。
如何操作...
pushd 和 popd 命令替代 cd 来更改工作目录。
- 要推入并切换到一个目录路径,使用此命令:
~ $ pushd /var/www
现在栈中包含 /var/www ~,当前目录已经切换到 /var/www。
- 现在,推入下一个目录路径:
/var/www $ pushd /usr/src
现在栈中包含 /usr/src /var/www ~,当前目录是 /usr/src。
你可以根据需要推入任意数量的目录路径。
- 查看栈的内容:
$ dirs
/usr/src /var/www ~ /usr/share /etc
0 1 2 3 4
- 现在,当你想切换到列表中的任何路径时,给每个路径编号,从
0到n,然后使用需要切换的路径编号。考虑以下示例:
$ pushd +3
现在它将旋转栈并切换到 /usr/share 目录。
pushd 总是将路径添加到栈中。要从栈中移除路径,使用 popd。
- 删除最后一个推入的路径并切换到下一个目录:
$ popd
假设栈是 /usr/src /var/www ~ /usr/share /etc,当前目录是 /usr/src。执行 popd 命令会将栈更改为 /var/www ~ /usr/share /etc,并将当前目录更改为 /var/www。
- 要从列表中移除特定路径,使用
popd +num。num是从左到右按0到n计数的。
还有更多...
让我们来看看基本的目录导航操作。
当使用超过三个目录路径时,pushd 和 popd 非常有用。然而,当你只使用两个位置时,有一种更简单的替代方法,即 cd -。
当前路径是 /var/www。
/var/www $ cd /usr/src
/usr/src $ # do something
现在,要切换回 /var/www,你不需要重新输入 /var/www,只需执行:
/usr/src $ cd -
要切换到 /usr/src:
/var/www $ cd -
计算文件中的行数、单词数和字符数
计算文本文件中的行数、单词数和字符数是非常常用的操作。本书的其他章节包含一些复杂的示例,这些计数用于生成所需的输出。计算 LOC(代码行数)是开发人员常见的应用场景。我们可能需要计算某些文件的子集,例如,所有源代码文件,但不包括目标文件。结合 wc 和其他命令可以实现这个目的。
wc 工具用于统计行数、单词数和字符数。它代表 word count(字数统计)。
如何操作...
wc 命令支持选项来计算行数、单词数和字符数:
- 计算行数:
$ wc -l file
- 要使用
stdin作为输入,使用此命令:
$ cat file | wc -l
- 计算单词数:
$ wc -w file
$ cat file | wc -w
- 计算字符数:
$ wc -c file
$ cat file | wc -c
要计算文本字符串中的字符数,使用此命令:
echo -n 1234 | wc -c
4
在这里,-n 会删除最后一个换行符。
- 要打印行数、单词数和字符数,执行没有任何选项的
wc:
$ wc file
1435 15763 112200
这些是行数、单词数和字符数。
- 使用
-L选项打印文件中最长行的长度:
$ wc file -L
205
打印目录树
通过树状层级图形表示目录和文件系统,可以更容易地进行可视化。监控脚本使用这种表示方式将文件系统以易读的格式展示出来。
准备工作
tree 命令打印文件和目录的图形化树状结构。tree 命令并没有预安装在 Linux 发行版中,你需要通过包管理器安装它。
如何做...
以下是一个示例 Unix 文件系统树,用于展示一个例子:
$ tree ~/unixfs
unixfs/
|-- bin
| |-- cat
| `-- ls
|-- etc
| `-- passwd
|-- home
| |-- pactpub
| | |-- automate.sh
| | `-- schedule
| `-- slynux
|-- opt
|-- tmp
`-- usr
8 directories, 5 files
tree 命令支持多个选项:
- 要仅显示与模式匹配的文件,请使用
-P选项:
$ tree path -P PATTERN # Pattern should be wildcard in single
quotes
看这个例子:
$ tree PATH -P '*.sh' # Replace PATH with a directory path
|-- home
| |-- packtpub
| | `-- automate.sh
- 要显示与模式不匹配的文件,请使用
-I选项:
$ tree path -I PATTERN
- 要显示文件和目录的大小,请使用
-h选项:
$ tree -h
还有更多内容...
tree 命令可以生成 HTML 输出,也可以输出到终端。
tree 的 HTML 输出
这个命令创建一个带有 tree 输出的 HTML 文件:
$ tree PATH -H http://localhost -o out.html
将 http://localhost 替换为你打算托管文件的 URL。将 PATH 替换为真实的基础目录路径。对于当前目录,使用 . 作为 PATH。
从目录列表生成的网页将如下所示:

处理视频和图像文件
Linux 和 Unix 支持许多用于处理图像和视频文件的应用程序和工具。大多数 Linux 发行版都包括 ImageMagick 套件及其用于图像处理的 convert 应用程序。功能全面的视频编辑应用程序如 kdenlive 和 openshot 是基于 ffmpeg 和 mencoder 命令行工具构建的。
convert 应用程序有数百个选项。我们只使用一个用于提取图像部分的选项。
ffmpeg 和 mencoder 选项和功能足够多,几乎可以写成一本书。我们只看几个简单的用法。
本节包含一些处理静态图像和视频的命令。
准备工作
大多数 Linux 发行版都包含了 ImageMagick 工具。如果你的系统没有包含,或者工具版本过旧,可以参考 ImageMagick 网站上的说明下载和安装最新版本:www.imagemagick.org。
与 ImageMagick 类似,许多 Linux 发行版已经包含了 ffmpeg 和 mencoder 工具集。最新版本可以在 ffmpeg 和 mencoder 的官网找到:www.ffmpeg.org 和 www.mplayerhq.hu。
构建和安装视频工具可能需要加载编解码器和其他带有复杂版本依赖的附加文件。如果你打算将 Linux 系统用于音视频编辑,最简单的方法是使用专为此设计的 Linux 发行版,比如 Ubuntu Studio。
这里有几个常见音视频转换的示例:
从电影文件(mp4)中提取音频
音乐视频很好看,但音乐的重点是听。提取视频中的音频部分很简单:
如何做到...
以下命令接受一个mp4视频文件(FILE.mp4),并将音频部分提取到一个新的文件(OUTPUTFILE.mp3)中,以mp3格式保存:
ffmpeg -i FILE.mp4 -acodec libmp3lame OUTPUTFILE.mp3
从一组静态图像制作视频
许多相机支持间隔拍摄。你可以利用这个功能进行自己的延时摄影或制作定格动画视频。在www.cwflynt.com上有相关示例。你可以使用 OpenShot 视频编辑包或者通过命令行工具 mencoder 将一组静态图像转换为视频。
如何做到...
这个脚本将接受一个图像列表,并从中创建一个 MPEG 视频文件:
$ cat stills2mpg.sh
echo $* | tr ' ' '\n' >files.txt mencoder mf://@files.txt -mf fps=24 -ovc lavc \ -lavcopts vcodec=msmpeg4v2 -noskip -o movie.mpg
要使用这个脚本,复制/粘贴命令到一个名为stills2mpg.sh的文件中,使其具有可执行权限,并按如下方式调用:
./stills2mpg.sh file1.jpg file2.jpg file3.jpg ...
或者,使用以下方式调用它:
./stills2mpg.sh *.jpg
它是如何工作的...
mencoder命令要求输入文件格式为每行一个图像文件。脚本的第一行通过 tr 命令回显命令行参数,将空格分隔符转换为换行符。这将把单行列表转换为每行一个文件的文件列表。
你可以通过重置FPS(每秒帧数)参数来改变视频的速度。例如,将 fps 值设置为1将制作一个每秒换图的幻灯片。
从静态相机拍摄制作推移视频
如果你决定制作自己的视频,可能会想在某个时刻录制一段景观的推移镜头。你可以用大多数相机录制视频图像,但如果你只有一张静态图像,仍然可以制作推移视频。
如何做到...
相机通常拍摄的图像比视频所能容纳的要大。你可以使用 convert 应用程序从大图像中提取部分,利用mencoder将它们拼接成一个视频文件,从而制作一个动态电影效果:
$> makePan.sh
# Invoke as:
# sh makePan.sh OriginalImage.jpg prefix width height xoffset yoffset # Clean out any old data
rm -f tmpFiles
# Create 200 still images, stepping through the original xoffset and yoffset
# pixels at a time
for o in `seq 1 200`
do
x=$[ $o+$5 ]
convert -extract $3x$4+$x+$6 $1 $2_$x.jpg
echo $2_$x.jpg >> tmpFiles
done
#Stitch together the image files into a mpg video file
mencoder mf://@tmpFiles -mf fps=30 -ovc lavc -lavcopts \
vcodec=msmpeg4v2 -noskip -o $2.mpg
它是如何工作的...
这个脚本比我们之前看到的脚本更复杂。它使用七个命令行参数来定义输入图像、输出文件的前缀、中间图像的宽度和高度以及原始图像的起始偏移量。
在for循环中,它创建一组图像文件,并将文件名存储在名为tmpFiles的文件中。最后,脚本使用mencoder将提取的图像文件合并成一个 MPEG 视频,可以导入到视频编辑器中,如 kdenlive 或 OpenShot。
第四章:开车与发短信
本章将介绍以下几种解决方案:
-
使用正则表达式
-
使用
grep在文件中搜索和挖掘文本 -
使用
cut按列切割文件 -
使用
sed执行文本替换 -
使用
awk进行高级文本处理 -
查找文件中单词的使用频率
-
压缩或解压 JavaScript
-
将多个文件合并为列
-
打印文件或行中的第 n^(th)个单词或列
-
打印行号或模式之间的文本
-
按反向顺序打印行
-
从文本中解析电子邮件地址和 URL
-
删除包含某个单词的文件中的一句话
-
在目录中的所有文件中用文本替换模式
-
文本切片和参数操作
介绍
Shell 脚本包括许多问题解决工具。它提供了丰富的文本处理工具集。这些工具包括sed、awk、grep和cut等实用程序,它们可以组合起来执行文本处理任务。
这些工具通过字符、行、单词、列或行来处理文件,能够以多种方式处理文本文件。
正则表达式是一种基本的模式匹配技术。大多数文本处理工具都支持正则表达式。通过正则表达式字符串,我们可以在文本文件中进行过滤、去除、替换和搜索。
本章包括一系列解决方案,帮助你了解如何处理各种文本处理问题。
使用正则表达式
正则表达式是基于模式的文本处理的核心。为了有效使用正则表达式,我们需要理解它们。
每个使用ls的人都熟悉 glob 样式的模式。glob 规则在很多情况下都很有用,但对于文本处理来说,它们的功能太有限了。正则表达式允许你比 glob 规则更详细地描述模式。
一个典型的正则表达式来匹配电子邮件地址可能是这样的:
[a-z0-9_]+@[a-z0-9]+\.[a-z]+.
如果这看起来有些奇怪,别担心;一旦你通过本教程理解了这些概念,实际上它非常简单。
如何操作...
正则表达式由文本片段和具有特殊含义的符号组成。利用这些,我们可以构建一个正则表达式来匹配任何文本。正则表达式是许多工具的基础。本节介绍了正则表达式,但没有介绍使用它们的 Linux/Unix 工具。稍后的教程将介绍这些工具。
正则表达式由一个或多个元素组成,这些元素结合成一个字符串。一个元素可以是位置标记、标识符或计数修饰符。位置标记将正则表达式固定到目标字符串的开头或结尾。标识符定义一个或多个字符。计数修饰符定义标识符出现的次数。
在我们查看一些示例正则表达式之前,先来看一下规则。
位置标记
位置标记将正则表达式固定到字符串中的某个位置。默认情况下,任何与正则表达式匹配的字符集都可以使用,而不论它们在字符串中的位置。
| 正则表达式 | 描述 | 示例 |
|---|---|---|
^ |
这指定正则表达式匹配的文本必须从字符串的开头开始 | ^tux 匹配以 tux 开头的行 |
| ` | 正则表达式 | 描述 |
| --- | --- | --- |
^ |
这指定正则表达式匹配的文本必须从字符串的开头开始 | ^tux 匹配以 tux 开头的行 |
| 这指定正则表达式匹配的文本必须以目标字符串的最后一个字符结尾 | tux$ 匹配以 tux 结尾的行 |
标识符
标识符是正则表达式的基础。这些定义了必须存在(或不存在)的字符,以匹配正则表达式。
| 正则表达式 | 描述 | 示例 |
|---|---|---|
A 字符 |
正则表达式必须匹配此字母 | A 将匹配字母 A |
. |
这匹配任意一个字符 | "Hack." 匹配 Hack1、Hacki,但不匹配 Hack12 或 Hackil;仅匹配一个额外字符 |
[] |
这匹配方括号内的任意一个字符。方括号内的字符可以是一个集合或范围 | coo[kl] 匹配 cook 或 cool;[0-9] 匹配任意一个数字 |
[^] |
这匹配除了方括号内的字符之外的任意一个字符。方括号内的字符可以是一个集合或范围。 | 9[⁰¹] 匹配 92 和 93,但不匹配 91 和 90;A[⁰-9] 匹配字母 A 后跟除数字外的任意字符 |
计数修饰符
一个标识符可以出现一次、永不出现或多次。计数修饰符定义了一个模式可以出现多少次。
| 正则表达式 | 描述 | 示例 |
|---|---|---|
? |
这意味着前面的项必须匹配一次或零次 | colou?r 匹配 color 或 colour,但不匹配 colouur |
+ |
这意味着前面的项必须匹配一次或多次 | Rollno-9+ 匹配 Rollno-99 和 Rollno-9,但不匹配 Rollno- |
* |
这意味着前面的项必须匹配零次或多次 | co*l 匹配 cl、col 和 coool |
{n} |
这意味着前面的项必须匹配 n 次 | [0-9]{3} 匹配任意三位数;[0-9]{3} 可以扩展为 [0-9][0-9][0-9] |
{n,} |
这指定了前面的项必须至少匹配 n 次 | [0-9]{2,} 匹配任意一个两位数或更长的数字 |
{n, m} |
这指定了前面的项应至少匹配 n 次,最多匹配 m 次 | [0-9]{2,5} 匹配任何一个两位数到五位数的数字 |
其他
下面是一些其他字符,用来微调正则表达式的解析方式。
() |
这将括号内的内容视为一个整体 | ma(tri)?x 匹配 max 或 matrix |
|---|---|---|
| |
这指定了交替;| 两边的任意一项应匹配 |
Oct (1st | 2nd) 匹配 Oct 1st 或 Oct 2nd |
\ |
这是转义字符,用于转义前面提到的任何特殊字符 | a\.b 匹配 a.b,但不匹配 ajb;它忽略了 . 的特殊含义,因为有了 \ |
关于可用的正则表达式组件的更多细节,你可以参考www.linuxforu.com/2011/04/sed-explained-part-1/。
还有更多...
让我们看一些正则表达式的例子:
这个正则表达式会匹配任何单个单词:
( +[a-zA-Z]+ +)
最初的+字符表示我们需要一个或多个空格。
[a-zA-Z]集合是所有大写和小写字母。后面的加号表示我们需要至少一个字母,并且可以有更多字母。
最后的+字符表示我们需要以一个或多个空格终止单词。
这将不会匹配句子中的最后一个单词。为了匹配句子中的最后一个单词或逗号前的单词,我们可以这样写表达式:
( +[a-zA-Z]+[?,\.]? +)
[?,\.]?短语表示我们可能有一个问号、逗号或句点,但最多只有一个。句点被反斜杠转义,因为裸句点是一个通配符,能够匹配任何字符。
匹配 IP 地址更容易了。我们知道我们会有四个三位数的数字,且这些数字由句点分隔。
[0-9]短语定义了一个数字。{1,3}短语定义了计数为至少一个数字,最多三个数字:
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
我们还可以使用[[:digit:]]构造来定义一个数字,从而定义 IP 地址:
[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}
我们知道,IP 地址由四个整数(每个整数从 0 到 255)组成,且由点分隔(例如,192.168.0.2)。
这个正则表达式将匹配正在处理文本中的 IP 地址。然而,它并没有检查地址的有效性。例如,形式为123.300.1.1的 IP 地址虽然无效,但也会被这个正则表达式匹配。
它是如何工作的...
正则表达式是通过一个复杂的状态机解析的,状态机会尝试找到一个正则表达式与目标文本字符串的最佳匹配。这个文本可以是管道的输出、一个文件,甚至是你在命令行上输入的字符串。如果有多种方式可以满足正则表达式,引擎通常会选择匹配字符最多的一组。
例如,给定字符串this is a test和正则表达式s.*s,匹配结果将是s is a tes,而不是s is。
关于可用的正则表达式组件的更多细节,你可以参考www.linuxforu.com/2011/04/sed-explained-part-1/。
还有更多...
前面的表格描述了正则表达式中使用字符的特殊含义。
特殊字符的处理
正则表达式使用一些字符,如$、^、.、*、+、{和},作为特殊字符。但是,如果我们想将这些字符作为普通文本字符使用呢?我们来看一个正则表达式的例子,a.txt。
这将匹配字符a,后面跟着任何字符(因为.字符),然后是txt字符串。然而,我们希望.匹配字面上的.而不是任意字符。为了实现这一点,我们在字符前加上反斜杠\(这种做法称为转义字符)。这表示正则表达式要匹配字面字符,而不是它的特殊含义。因此,最终的正则表达式变为a\.txt。
正则表达式的可视化
正则表达式可能难以理解。幸运的是,有一些工具可以帮助可视化正则表达式。页面www.regexper.com允许你输入正则表达式,并创建一个图形来帮助你理解它。以下是描述一个简单正则表达式的截图:

使用grep在文件中搜索和挖掘文本
如果你忘记了把钥匙放在哪里,你只需搜索它们。如果你忘记了哪个文件包含某些信息,grep命令会为你找到它。本篇将教你如何定位包含模式的文件。
如何操作...
grep命令是一个强大的 Unix 工具,用于搜索文本。它接受正则表达式并可以以各种格式生成报告。
- 在
stdin中搜索与模式匹配的行:
$ echo -e "this is a word\nnext line" | grep word
this is a word
- 在单个文件中搜索包含给定模式的行:
$ grep pattern filename
this is the line containing pattern
或者,这也会执行相同的搜索:
$ grep "pattern" filename
this is the line containing pattern
- 在多个文件中搜索与模式匹配的行:
$ grep "match_text" file1 file2 file3 ...
- 要突出显示匹配的模式,可以使用
-color选项。虽然选项的位置无关紧要,但约定是将选项放在前面。
$ grep -color=auto word filename
this is the line containing word
grep命令默认使用基本正则表达式。这些是前面描述的规则的子集。-E选项将使grep使用扩展正则表达式语法。egrep命令是grep的变体,默认使用扩展正则表达式:
$ grep -E "[a-z]+" filename
或者:
$ egrep "[a-z]+" filename
-o选项将仅报告匹配的字符,而不是整个行:
$ echo this is a line. | egrep -o "[a-z]+\."
line
-v选项将打印所有行,除了包含match_pattern的行:
$ grep -v match_pattern file
添加-v选项到grep将反转匹配结果。
-c选项将统计模式出现的行数:
$ grep -c "text" filename
10
应该注意的是,-c统计的是匹配行的数量,而不是匹配的次数。请看这个例子:
$ echo -e "1 2 3 4\nhello\n5 6" | egrep -c "[0-9]"
2
即使有六个匹配项,grep只报告2,因为只有两行匹配。单行中的多个匹配项只计算一次。
- 要计算文件中匹配项的数量,请使用这个技巧:
$ echo -e "1 2 3 4\nhello\n5 6" | egrep -o "[0-9]" | wc -l
6
-n选项将打印匹配字符串的行号:
$ cat sample1.txt
gnu is not unix
linux is fun
bash is art
$ cat sample2.txt
planetlinux
$ grep linux -n sample1.txt
2:linux is fun
或者
$ cat sample1.txt | grep linux -n
如果使用多个文件,-c选项将打印带有结果的文件名:
$ grep linux -n sample1.txt sample2.txt
sample1.txt:2:linux is fun
sample2.txt:2:planetlinux
-b选项将打印匹配发生的行的偏移量。添加-o选项将打印模式匹配的精确字符或字节偏移量:
$ echo gnu is not unix | grep -b -o "not"
7:not
字符位置从0开始编号,而不是从1开始。
-l选项列出哪些文件包含模式:
$ grep -l linux sample1.txt sample2.txt
sample1.txt
sample2.txt
-l参数的反向参数是-L。-L参数返回不匹配的文件列表。
还有更多...
grep命令是最通用的 Linux/Unix 命令之一。它还包括选项来搜索文件夹、选择要搜索的文件以及更多用于识别模式的选项。
递归搜索多个文件
要递归地搜索文件层级结构中的文件中的文本,请使用以下命令:
$ grep "text" . -R -n
在这个命令中,.指定当前目录。
当与grep一起使用时,-R和-r选项的含义相同。
考虑以下示例:
$ cd src_dir
$ grep "test_function()" . -R -n
./miscutils/test.c:16:test_function();
test_function()位于miscutils/test.c的第 16 行。-R选项在搜索网站或源代码树中的短语时特别有用。它等同于以下命令:
$ find . -type f | xargs grep "test_function()"
忽略模式中的大小写
-i参数在匹配模式时不考虑大小写:
$ echo hello world | grep -i "HELLO"
hello
使用多个模式进行grep匹配
-e参数指定多个用于匹配的模式:
$ grep -e "pattern1" -e "pattern2"
这将打印包含任意模式的行,并为每个匹配输出一行。考虑以下示例:
$ echo this is a line of text | grep -o -e "this" -e "line"
this
line
可以在一个文件中定义多个模式。-f选项将读取文件并使用按行分隔的模式:
$ grep -f pattern_filesource_filename
考虑以下示例:
$ cat pat_file
hello
cool
$ echo hello this is cool | grep -f pat_file
hello this is cool
在grep搜索中包括和排除文件
grep可以通过通配符模式包含或排除文件进行搜索。
要仅递归搜索.c和.cpp文件,请使用-include选项:
$ grep "main()" . -r --include *.{c,cpp}
请注意,some{string1,string2,string3}扩展为somestring1 somestring2 somestring3。
使用-exclude标志排除所有README文件的搜索:
$ grep "main()" . -r --exclude "README"
--exclude-dir选项将排除指定目录的搜索:
$ grep main . -r -exclude-dir CVS
要从文件中读取排除文件的列表,使用--exclude-from FILE。
使用带有零字节后缀的xargs与grep
xargs命令将一系列命令行参数提供给另一个命令。当文件名作为命令行参数使用时,请使用零字节终止符代替默认的空格终止符。文件名可以包含空格字符,这将被误解释为名称分隔符,导致文件名被拆分成两个文件名(例如,New file.txt可能会被解释为两个文件名New和file.txt)。使用零字节后缀选项可以解决此问题。我们使用xargs从诸如grep和find等命令接收stdin文本。这些命令可以生成带有零字节后缀的输出。当使用-0标志时,xargs命令会期望使用零字节终止符。
创建一些测试文件:
$ echo "test" > file1
$ echo "cool" > file2
$ echo "test" > file3
-l选项告诉grep仅输出匹配发生的文件名。-Z选项使grep为这些文件使用零字节终止符(\0)。这两个选项通常一起使用。xargs的-0参数使其读取输入并以零字节终止符分隔文件名:
$ grep "test" file* -lZ | xargs -0 rm
grep的静默输出
有时,我们并不关心匹配的字符串,而只关心是否有匹配。安静选项(-q)使 grep 静默运行,并且不会生成任何输出。相反,它运行命令并返回基于成功或失败的退出状态。返回状态为 0 表示成功,非零表示失败。
grep 命令可以在安静模式下使用,用于测试匹配文本是否出现在文件中:
#!/bin/bash
#Filename: silent_grep.sh
#Desc: Testing whether a file contain a text or not
if [ $# -ne 2 ]; then
echo "Usage: $0 match_text filename"
exit 1
fi
match_text=$1
filename=$2
grep -q "$match_text" $filename
if [ $? -eq 0 ]; then
echo "The text exists in the file"
else
echo "Text does not exist in the file"
fi
silent_grep.sh 脚本接受两个命令行参数,一个是匹配词(Student),另一个是文件名(student_data.txt):
$ ./silent_grep.sh Student student_data.txt
The text exists in the file
打印匹配文本前后行
基于上下文的打印是 grep 的一个优点。当 grep 找到与模式匹配的行时,它只会打印匹配的行。我们可能需要查看匹配行之前或之后的 n 行。-B 和 -A 选项分别显示匹配前后的行。
-A 选项打印匹配后的行:
$ seq 10 | grep 5 -A 3
5
6
7
8
-B 选项打印匹配前的行:
$ seq 10 | grep 5 -B 3
2
3
4
5
-A 和 -B 选项可以一起使用,或者使用 -C 选项打印匹配前后相同数量的行:
$ seq 10 | grep 5 -C 3
2
3
4
5
6
7
8
如果有多个匹配项,则每个部分由 -- 行分隔:
$ echo -e "a\nb\nc\na\nb\nc" | grep a -A 1
a
b
--
a
b
使用 cut 按列切割文件
cut 命令按列拆分文件,而不是按行拆分。这对于处理具有固定宽度字段的文件、逗号分隔值(CSV 文件)或像标准日志文件这样的空格分隔文件非常有用。
如何做到...
cut 命令提取字符位置或列之间的数据。你可以指定分隔符来分隔每一列。在 cut 的术语中,每一列被称为 字段。
-f选项定义要提取的字段:
cut -f FIELD_LIST filename
FIELD_LIST 是一个要显示的列的列表。该列表由用逗号分隔的列号组成。参考以下示例:
$ cut -f 2,3 filename
在这里,显示了第二列和第三列。
cut命令也可以从stdin读取输入。
Tab 是字段的默认分隔符。没有分隔符的行将被打印。-s 选项将禁用打印没有分隔符字符的行。以下命令演示了如何从制表符分隔的文件中提取列:
$ cat student_data.txt
No Name Mark Percent
1 Sarath 45 90
2 Alex 49 98
3 Anu 45 90
$ cut -f1 student_data.txt
No
1
2
3
- 要提取多个字段,请提供多个由逗号分隔的字段编号,使用以下选项:
$ cut -f2,4 student_data.txt
Name Percent
Sarath 90
Alex 98
Anu 90
--complement选项将显示除了-f定义的字段之外的所有字段。此命令显示除了3之外的所有字段:
$ cut -f3 --complement student_data.txt
No Name Percent
1 Sarath 90
2 Alex 98
3 Anu 90
-d选项将设置分隔符。以下命令展示了如何使用cut处理冒号分隔的列表:
$ cat delimited_data.txt
No;Name;Mark;Percent
1;Sarath;45;90
2;Alex;49;98
3;Anu;45;90
$ cut -f2 -d";" delimited_data.txt
Name
Sarath
Alex
Anu
还有更多内容
cut 命令有更多选项来定义显示的列。
将字符或字节的范围指定为字段
固定宽度列的报告将在列之间有不同数量的空格。你不能根据字段位置提取值,但可以根据字符位置提取。cut 命令可以基于字节或字符以及字段进行选择。
逐个字符位置提取是不合理的,因此cut命令接受这些符号以及逗号分隔的列表:
N- |
从第 N 次字节、字符或字段到行的末尾 |
|---|---|
N-M |
从第 N 次到第 M 次(包括)字节、字符或字段 |
-M |
从第一个到第 M 次(包括)字节、字符或字段 |
我们使用前面的符号来指定字节、字符或字段的范围,使用以下选项:
-
-b表示字节 -
-c表示字符 -
-f用于定义字段
考虑这个示例:
$ cat range_fields.txt
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxy
显示第二到第五个字符:
$ cut -c2-5 range_fields.txt
bcde
bcde
bcde
bcde
显示前两个字符:
$ cut -c -2 range_fields.txt
ab
ab
ab
ab
用-b替换-c以按字节计数。
-output-delimiter选项指定输出分隔符。这在显示多个数据集时特别有用:
$ cut range_fields.txt -c1-3,6-9 --output-delimiter ","
abc,fghi
abc,fghi
abc,fghi
abc,fghi
使用sed执行文本替换
sed代表流编辑器。它最常用于文本替换。本教程涵盖了许多常见的sed技巧。
如何实现...
sed命令可以将模式的出现替换为另一字符串。该模式可以是简单的字符串或正则表达式:
$ sed 's/pattern/replace_string/' file
或者,sed可以从stdin读取:
$ cat file | sed 's/pattern/replace_string/'
如果你使用vi编辑器,你会注意到,替换文本的命令与此处讨论的非常相似。默认情况下,sed仅打印替换后的文本,可以在管道中使用它。
$ cat /etc/passwd | cut -d : -f1,3 | sed 's/:/ - UID: /'
root - UID: 0
bin - UID: 1
...
-I选项将使sed用修改后的数据替换原始文件:
$ sed -i 's/text/replace/' file
- 上一个示例替换了每行中模式的第一次出现。
-g参数将使sed替换每次出现:
$ sed 's/pattern/replace_string/g' file
/#g选项将从第 N 次出现位置开始替换:
$ echo thisthisthisthis | sed 's/this/THIS/2g'
thisTHISTHISTHIS
$ echo thisthisthisthis | sed 's/this/THIS/3g'
thisthisTHISTHIS
$ echo thisthisthisthis | sed 's/this/THIS/4g'
thisthisthisTHIS
sed命令将s后面的字符视为命令分隔符。这样我们就可以更改包含/字符的字符串:
sed 's:text:replace:g'
sed 's|text|replace|g'
当分隔符字符出现在模式内部时,必须使用\前缀对其进行转义,如下所示:
sed 's|te\|xt|replace|g'
\|是出现在模式中的分隔符,被转义替代。
还有更多内容…
sed命令支持正则表达式作为要替换的模式,并提供更多选项来控制其行为。
删除空行
正则表达式支持使得删除空行变得容易。^$正则表达式定义了一个从开始到结束之间没有任何内容的行==空行。最后的/d命令告诉sed删除这些行,而不是进行替换。
$ sed '/^$/d' file
在文件中直接进行替换
当文件名传递给sed时,它通常会打印到stdout。-I选项将使sed直接在文件中修改内容:
$ sed 's/PATTERN/replacement/' -i filename
例如,在文件中将所有三位数字替换为另一个指定的数字,如下所示:
$ cat sed_data.txt
11 abc 111 this 9 file contains 111 11 88 numbers 0000
$ sed -i 's/\b[0-9]\{3\}\b/NUMBER/g' sed_data.txt
$ cat sed_data.txt
11 abc NUMBER this 9 file contains NUMBER 11 88 numbers 0000
上述单行命令只替换三位数的数字。\b[0-9]\{3\}\b是用来匹配三位数的正则表达式。[0-9]是从0到9的数字范围。{3}定义了数字的数量。反斜杠用于赋予{和}特殊意义,\b表示空白字符,表示单词边界。
一个有用的做法是首先尝试没有-i的sed命令,以确保你的正则表达式是正确的。等你对结果满意后,再加上-i选项以修改文件。或者,你也可以使用以下形式的sed:
sed -i .bak 's/abc/def/' file
在这种情况下,sed会在文件上执行替换,并且还会创建一个名为file.bak的文件,包含原始内容。
匹配字符串标记()
&符号是匹配到的字符串。这个值可以在替换字符串中使用:
$ echo this is an example | sed 's/\w\+/[&]/g'
[this] [is] [an] [example]
这里,\w\+正则表达式匹配每个单词。然后,我们用[&]替换它,&对应于匹配的单词。
子字符串匹配标记(\1)
&对应给定模式的匹配字符串。正则表达式中的括号部分可以用\#来匹配:
$ echo this is digit 7 in a number | sed 's/digit \([0-9]\)/\1/'
this is 7 in a number
上述命令将digit 7替换为7。匹配到的子字符串是7。\ (pattern\)匹配该子字符串。模式被包含在()中,并使用反斜杠进行转义。对于第一个子字符串匹配,相应的标记是\1,对于第二个是\2,以此类推。
$ echo seven EIGHT | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1/'
EIGHT seven
([a-z]\+\)匹配第一个单词,\([A-Z]\+\)匹配第二个单词;\1和\2用来引用它们。这种引用方式叫做反向引用。在替换部分,它们的顺序被更改为\2 \1,因此出现的是反向顺序。
组合多个表达式
多个sed命令可以通过管道符连接,模式由分号分隔,或者使用-e PATTERN选项:
sed 'expression' | sed 'expression'
上述命令等同于以下命令:
$ sed 'expression; expression'
或者:
$ sed -e 'expression' -e expression'
看这些例子:
$ echo abc | sed 's/a/A/' | sed 's/c/C/'
AbC
$ echo abc | sed 's/a/A/;s/c/C/'
AbC
$ echo abc | sed -e 's/a/A/' -e 's/c/C/'
AbC
引号
sed表达式通常用单引号引起来。也可以使用双引号,shell 会在调用 sed 之前展开双引号。使用双引号在我们想在sed表达式中使用变量字符串时非常有用。
看这个例子:
$ text=hello
$ echo hello world | sed "s/$text/HELLO/"
HELLO world
$text被评估为hello。
使用 awk 进行高级文本处理
awk命令处理数据流。它支持关联数组、递归函数、条件语句等。
准备工作
一个awk脚本的结构是:
awk ' BEGIN{ print "start" } pattern { commands } END{ print "end"}' file
awk命令也可以从stdin读取数据。
一个awk脚本包含最多三部分:BEGIN,END,以及带有模式匹配选项的常规语句块。这些部分是可选的,脚本中可以没有任何一部分。
awk 会逐行处理文件。在 <code>awk</code> 开始处理文件之前,BEGIN 后的命令会被先行评估。awk 会处理与 PATTERN 匹配的每一行,并执行紧随其后的命令。最后,在处理完所有文件内容后,<CODE>awk</CODE> 会执行 END 后的命令。
如何做...
让我们写一个简单的 awk 脚本,用单引号或双引号括起来:
awk 'BEGIN { statements } { statements } END { end statements }'
或者:
awk "BEGIN { statements } { statements } END { end statements }"
该命令将报告文件中的行数:
$ awk 'BEGIN { i=0 } { i++ } END{ print i}' filename
或者:
$ awk "BEGIN { i=0 } { i++ } END{ print i }" filename
它是如何工作的...
awk 命令按以下顺序处理参数:
-
首先,它执行
BEGIN { commands }代码块中的命令。 -
接下来,
awk会从文件或stdin中读取一行,并在可选模式匹配时执行commands代码块。它会重复这个过程直到文件末尾。 -
当输入流结束时,它会执行
END { commands }代码块。
BEGIN 代码块在 awk 开始读取输入流中的行之前执行。它是一个可选的代码块。诸如变量初始化和为输出表格打印标题等命令,通常出现在 BEGIN 代码块中。
END 代码块类似于 BEGIN 代码块。它会在 awk 完成读取输入流中的所有行后执行。通常在分析完所有行后,使用该代码块打印结果。
最重要的代码块包含常见命令和模式代码块。这个代码块也是可选的。如果未提供该代码块,{ print } 会被执行来打印每一行。这个代码块会在 awk 读取的每一行上执行。它类似于一个 while 循环,循环体内会执行语句。
当一行被读取时,awk 会检查该行是否与模式匹配。模式可以是正则表达式匹配、条件、行范围等。如果当前行与模式匹配,awk 会执行 { } 中的命令。
模式是可选的。如果不使用模式,则匹配所有行:
$ echo -e "line1\nline2" | awk 'BEGIN{ print "Start" } { print } \
END{ print "End" } '
Start
line1
line2
End
当 print 未带任何参数时,awk 会打印当前行。
print 命令可以接受多个参数。这些参数用逗号分隔,并会以空格为分隔符打印。双引号用于字符串连接操作。
看这个例子:
$ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
print var1,var2,var3 ; }'
上述命令将显示如下内容:
v1 v2 v3
echo 命令将一行内容写入标准输出。因此,awk 中 { } 代码块的语句只会执行一次。如果 awk 的输入包含多行,awk 中的命令将会执行多次。
字符串连接通过引用的字符串来完成:
$ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
print var1 "-" var2 "-" var3 ; }'
v1-v2-v3
{ } 就像是一个循环中的代码块,用来逐行遍历文件。
在 BEGIN 代码块中,通常会放置初始变量赋值语句,如 var=0;。END{} 代码块包含打印结果的命令。
还有更多内容...
awk 命令与诸如 grep、find 和 tr 等命令不同,因为它具有更多的单一功能选项来更改行为。awk 命令是一个解释和执行程序的程序,包括像 shell 一样的特殊变量。
特殊变量
一些可以与 awk 一起使用的特殊变量如下:
-
NR:这代表当前记录号,当awk使用行作为记录时,它对应于当前行号。 -
NF:这代表字段的数量,对应于正在处理的当前记录中的字段数量。默认字段分隔符是空格。 -
$0:这是一个变量,包含当前记录的文本。 -
$1:这是一个变量,保存第一个字段的文本。 -
$2:这是一个变量,保存第二个字段的文本。
考虑这个例子:
$ echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | \
awk '{
print "Line no:"NR",No of fields:"NF, "$0="$0,
"$1="$1,"$2="$2,"$3="$3
}'
Line no:1,No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
Line no:2,No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
Line no:3,No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7
我们可以打印一行的最后一个字段为 print $NF,倒数第二个为 $(NF-1),依此类推。
awk 还支持与 C 中相同语法的 printf() 函数。
以下命令打印每行的第二个和第三个字段:
$awk '{ print $3,$2 }' file
我们可以使用 NR 计算文件中的行数:
$ awk 'END{ print NR }' file
在这里,我们只使用 END 块。当 awk 到达文件末尾时,NR 会包含最后一行的行号。可以按如下方式汇总每行的 field 1 中的所有数字:
$ seq 5 | awk 'BEGIN{ sum=0; print "Summation:" }
{ print $1"+"; sum+=$1 } END { print "=="; print sum }'
Summation:
1+
2+
3+
4+
5+
==
15
将外部变量传递给 awk
使用 -v 参数,我们可以传递除 stdin 以外的外部值给 awk,如下所示:
$ VAR=10000
$ echo | awk -v VARIABLE=$VAR '{ print VARIABLE }'
10000
有一种灵活的替代方法可以从外部传递许多变量值到 awk。考虑以下示例:
$ var1="Variable1" ; var2="Variable2"
$ echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
Variable1 Variable2
当通过文件而非标准输入给出输入时,请使用以下命令:
$ awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename
在前面的方法中,变量被指定为键值对,用空格分隔,(v1=$var1 v2=$var2 ) 作为 awk 命令的参数,紧跟在 BEGIN、{ } 和 END 块之后。
明确使用 getline 读取一行
awk 程序默认读取整个文件。getline 函数将读取一行。这可用于在 BEGIN 块中从文件中读取标题信息,然后在主块中处理实际数据。
语法是 getline var。var 变量将包含该行的内容。如果调用 getline 时没有参数,我们可以使用 $0、$1 和 $2 访问行的内容。
考虑这个例子:
$ seq 5 | awk 'BEGIN { getline; print "Read ahead first line", $0 }
{ print $0 }'
Read ahead first line 1
2
3
4
5
用过滤模式过滤 awk 处理的行
我们可以指定处理行的条件:
$ awk 'NR < 5' # first four lines
$ awk 'NR==1,NR==4' #First four lines
$ # Lines containing the pattern linux (we can specify regex)
$ awk '/linux/'
$ # Lines not containing the pattern linux
$ awk '!/linux/'
设置字段的分隔符
默认情况下,字段的分隔符是空格。-F 选项定义了一个不同的字段分隔符。
$ awk -F: '{ print $NF }' /etc/passwd
或:
awk 'BEGIN { FS=":" } { print $NF }' /etc/passwd
我们可以通过在 BEGIN 块中设置 OFS="delimiter" 来设置输出字段分隔符。
从 awk 读取命令输出
awk 可以调用命令并读取输出。将命令字符串放在引号内,并使用竖线将输出传输给 getline:
"command" | getline output ;
以下代码从/etc/passwd中读取一行,并显示登录名和主目录。它在BEGIN块中重置字段分隔符为:,并在主块中调用grep。
$ awk 'BEGIN {FS=":"} { "grep root /etc/passwd" | getline; \
print $1,$6 }'
root /root
Awk 中的关联数组
Awk 支持包含数字或字符串的变量,还支持关联数组。关联数组是一种由字符串而非数字作为索引的数组。你可以通过方括号中的索引识别出关联数组:
arrayName[index]
数组可以通过等号进行赋值,就像简单的用户定义变量一样:
myarray[index]=value
在 Awk 中使用循环
Awk 支持一个类似于C语言的数值for循环语法:
for(i=0;i<10;i++) { print $i ; }
Awk 还支持一种列表风格的循环,它将显示数组的内容:
for(i in array) { print array[i]; }
以下示例展示了如何将数据收集到一个数组中并显示它。此脚本从/etc/password读取行,将其按:符号分割成字段,并创建一个名字数组,其中索引是登录 ID,值是用户的名字:
$ awk 'BEGIN {FS=":"} {nam[$1]=$5} END {for {i in nam} \
{print i,nam[i]}}' /etc/passwd
root root
ftp FTP User
userj Joe User
Awk 中的字符串处理函数
awk语言包含了许多内置的字符串处理函数:
-
length(string):此函数返回字符串的长度。 -
index(string, search_string):此函数返回在字符串中找到search_string的位置。 -
split(string, array, delimiter):此函数将字符串按分隔符字符拆分后,填充到一个数组中。 -
substr(string, start-position, end-position):此函数返回从起始位置到结束位置之间的子字符串。 -
sub(regex, replacement_str, string):此函数将替换字符串中第一次出现的符合正则表达式的匹配项,替换为replacment_str。 -
gsub(regex, replacment_str, string):此函数类似于sub(),但它会替换每一个符合正则表达式的匹配项。 -
match(regex, string):此函数返回正则表达式(regex)是否在字符串中找到匹配。如果找到了匹配项,则返回非零值,否则返回零。match()函数有两个与之关联的特殊变量,分别是RSTART和RLENGTH。RSTART变量包含正则表达式匹配开始的位置,RLENGTH变量包含匹配的字符串长度。
查找文件中单词的使用频率
计算机擅长计数。我们经常需要计算一些项目,比如发送垃圾邮件的站点数量,不同网页的下载量,或者文本中单词的使用频率。本例展示了如何计算文本中的单词使用情况。这些技术同样适用于日志文件、数据库输出等。
准备工作
我们可以使用awk的关联数组以不同的方式解决这个问题。单词是字母字符,以空格或句号为分隔符。首先,我们需要解析给定文件中的所有单词,然后找出每个单词的出现次数。可以使用正则表达式通过sed、awk或grep等工具解析单词。
如何做...
我们刚刚探讨了解决方案的逻辑和思路,现在让我们按照如下方式创建 shell 脚本:
#!/bin/bash
#Name: word_freq.sh
#Desc: Find out frequency of words in a file
if [ $# -ne 1 ];
then
echo "Usage: $0 filename";
exit -1
fi
filename=$1
egrep -o "\b[[:alpha:]]+\b" $filename | \
awk '{ count[$0]++ }
END {printf("%-14s%s\n","Word","Count") ;
for(ind in count)
{ printf("%-14s%d\n",ind,count[ind]);
}
}
脚本将生成以下输出:
$ ./word_freq.sh words.txt
Word Count
used 1
this 2
counting 1
它是如何工作的...
egrep命令将文本文件转换为单词流,每行一个单词。\b[[:alpha:]]+\b模式匹配每个单词,并移除空白字符和标点符号。-o选项将匹配的字符序列作为每行的一个单词打印出来。
awk命令统计每个单词的出现次数。它会对每一行执行{ }块中的语句,因此我们无需为此专门编写循环。计数通过count[$0]++命令递增,其中$0表示当前行,count是一个关联数组。处理完所有行后,END{}块将打印出单词及其计数。
本过程的主体可以使用我们之前讨论过的其他工具进行修改。我们可以使用tr命令将大写和小写单词合并为一个计数,并使用sort命令对输出进行排序,如下所示:
egrep -o "\b[[:alpha:]]+\b" $filename | tr [A=Z] [a-z] | \
awk '{ count[$0]++ }
END{ printf("%-14s%s\n","Word","Count") ;
for(ind in count)
{ printf("%-14s%d\n",ind,count[ind]);
}
}' | sort
另见
-
本章中的使用 awk 进行高级文本处理一节介绍了
awk命令。 -
本书中第一章的数组和关联数组一节,介绍了 Bash 中的数组。
压缩或解压 JavaScript
JavaScript 在网站中广泛使用。在开发 JavaScript 代码时,我们使用空白符、注释和制表符来提高代码的可读性和可维护性。这会增加文件大小,从而降低页面加载速度。因此,大多数专业网站使用压缩后的 JavaScript 来加速页面加载。此压缩(也称为最小化 JS)通过移除空白字符和换行符来实现。一旦 JavaScript 被压缩,可以通过替换足够的空白字符和换行符将其解压缩,使其可读。这个过程在 shell 中实现类似的功能。
准备工作
我们将编写一个 JavaScript 压缩工具以及一个解压缩工具。请考虑以下 JavaScript:
$ cat sample.js
function sign_out()
{
$("#loading").show();
$.get("log_in",{logout:"True"},
function(){
window.location="";
});
}
我们的脚本需要执行以下步骤来压缩 JavaScript:
-
移除换行符和制表符。
-
移除重复的空格。
-
替换类似
/* content */的注释。
要解压缩或使 JavaScript 更具可读性,我们可以执行以下任务:
-
将
;替换为;\n -
将
{替换为{\n,将}替换为\n}
如何做...
通过这些步骤,我们可以使用以下命令链:
$ cat sample.js | \
tr -d '\n\t' | tr -s ' ' \
| sed 's:/\*.*\*/::g' \
| sed 's/ \?\([{}();,:]\) \?/\1/g'
输出如下:
function sign_out(){$("#loading").show();$.get("log_in",
{logout:"True"},function(){window.location="";});}
以下解压脚本将使混淆的代码变得可读:
$ cat obfuscated.txt | sed 's/;/;\n/g; s/{/{\n\n/g; s/}/\n\n}/g'
或者:
$ cat obfuscated.txt | sed 's/;/;\n/g' | sed 's/{/{\n\n/g' | sed
's/}/\n\n}/g'
脚本有一个限制:它会去掉本应存在的额外空格。例如,如果你有以下一行: var a = "hello world"
这两个空格将被转换为一个空格。你可以使用我们讨论过的模式匹配工具来修复类似的问题。此外,在处理关键的 JavaScript 代码时,建议使用成熟的工具来完成这一操作。
它是如何工作的...
压缩命令执行以下任务:
- 移除
\n和\t字符:
tr -d '\n\t'
- 移除多余的空格:
tr -s ' ' or sed 's/[ ]\+/ /g'
- 移除注释:
sed 's:/\*.*\*/::g'
: 被用作 sed 的分隔符,避免了转义 / 的需要,因为我们需要使用/*和*/。
在 sed 中,* 被转义为 \*。
.* 匹配/*和*/之间的所有文本。
- 移除
{、}、(、)、;、:和,字符前后所有空格:
sed 's/ \?\([{}();,:]\) \?/\1/g'
上面的 sed 语句是这样工作的:
-
/ \?\([{}();,:]\) \?/在sed代码中是匹配部分,/\1 /g是替换部分。 -
\([{}();,:]\)用于匹配[ { }( ) ; , : ]集合中的任何一个字符(为可读性插入空格)。\(和\)是分组操作符,用于记住匹配并在替换部分进行反向引用。(和)被转义以赋予它们作为分组操作符的特殊含义。\?前后加在分组操作符旁,用来匹配可能出现在集合中的任意字符前后的空格字符。 -
在替换部分,匹配的字符串(即
:, 空格(可选),字符集中的字符,以及可选的空格)被替换为匹配的字符。它使用反向引用来引用通过分组操作符()记住的匹配字符。反向引用的字符通过\1符号引用分组匹配。
解压命令的工作方式如下:
-
s/;/;\n/g将;替换为;\n。 -
s/{/{\n\n/g将{替换为{\n\n。 -
s/}/\n\n}/g将}替换为\n\n}。
另见
-
本章中的使用 sed 进行文本替换食谱解释了
sed命令。 -
第二章中的使用 tr 进行翻译食谱解释了
tr命令。
将多个文件合并为列。
can 命令可用于按行合并两个文件,一个文件接一个文件。有时我们需要将两个或多个文件并排合并,将文件 1 的行与文件 2 的行连接在一起。
如何做...
paste命令执行按列拼接:
$ paste file1 file2 file3 ...
下面是一个例子:
$ cat file1.txt
1
2
3
4
5
$ cat file2.txt
slynux
gnu
bash
hack
$ paste file1.txt file2.txt
1 slynux
2 gnu
3 bash
4 hack
5
默认的分隔符是制表符。我们可以使用-d来指定分隔符:
$ paste file1.txt file2.txt -d ","
1,slynux
2,gnu
3,bash
4,hack
5,
另见
- 本章中的使用 cut 按列切割文件食谱解释了如何从文本文件中提取数据。
打印文件或行中的第 n 个单词或列。
我们常常需要从文件中提取几列有用的数据。例如,在一个按分数排序的学生列表中,我们想找到第四名得分者。本食谱展示了如何做到这一点。
如何做...
awk命令通常用于此任务。
- 要打印第五列,请使用以下命令:
$ awk '{ print $5 }' filename
- 我们可以打印多个列,并在列之间插入自定义字符串。
以下命令将打印当前目录中每个文件的权限和文件名:
$ ls -l | awk '{ print $1 " : " $8 }'
-rw-r--r-- : delimited_data.txt
-rw-r--r-- : obfuscated.txt
-rw-r--r-- : paste1.txt
-rw-r--r-- : paste2.txt
另请参见
-
本章中的使用 awk 进行高级文本处理食谱解释了
awk命令。 -
本章中的通过 cut 按列裁剪文件食谱解释了如何从文本文件中提取数据。
打印行号或模式之间的文本
我们可能需要打印文件的选定部分,可能是一个行号范围,或者是由开始和结束模式匹配的范围。
准备工作
Awk、grep或sed将根据条件选择要打印的行。使用grep打印包含某个模式的行是最简单的。awk是功能最强大的工具。
如何做到这一点...
要打印行号或模式之间的文本,请按照以下步骤操作:
- 打印文本的行号范围,
M到N:
$ awk 'NR==M, NR==N' filename
awk可以从stdin读取:
$ cat filename | awk 'NR==M, NR==N'
- 替换
M和N为数字:
$ seq 100 | awk 'NR==4,NR==6'
4
5
6
- 打印
start_pattern和end_pattern之间的文本行:
$ awk '/start_pattern/, /end _pattern/' filename
请参考此示例:
$ cat section.txt
line with pattern1
line with pattern2
line with pattern3
line end with pattern4
line with pattern5
$ awk '/pa.*3/, /end/' section.txt
line with pattern3
line end with pattern4
awk中使用的模式是正则表达式。
另请参见
- 本章中的使用 awk 进行高级文本处理食谱解释了
awk命令。
按逆序打印行
这个食谱可能看起来不太有用,但它可以用来模拟 Bash 中的堆栈数据结构。
准备工作
实现这一目标最简单的方法是使用tac命令(cat的反向命令)。此任务也可以通过awk来完成。
如何做到这一点...
我们首先来看如何使用 tac做到这一点。
tac的语法如下:
tac file1 file2 ...
tac命令也可以从stdin读取:
$ seq 5 | tac
5
4
3
2
1
tac的默认行分隔符是\n。-s 选项将重新定义此分隔符:
$ echo "1,2" | tac -s ,
2
1
- 这个
awk脚本将按逆序打印行:
seq 9 | \
awk '{ lifo[NR]=$0 } \
END { for(lno=NR;lno>-1;lno--) { print lifo[lno]; }
}'
在 shell 脚本中,\用于将单行命令序列拆分为多行。
它是如何工作的...
awk脚本将每一行存储到一个关联数组中,使用行号作为索引(NR返回行号)。读取完所有行后,awk会执行END块。NR变量由awk维护,它保存当前行号。当awk开始执行 END 块时,NR的值就是行数。通过在{ }块中使用lno=NR,从最后一行号迭代到0,以逆序打印行。
从文本中解析电子邮件地址和 URL
解析电子邮件地址和 URL 等元素是常见任务。正则表达式使得查找这些模式变得简单。
如何做到这一点...
匹配电子邮件地址的正则表达式模式如下:
[A-Za-z0-9._]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4}
请参考以下示例:
$ cat url_email.txt
this is a line of text contains,<email> #slynux@slynux.com.
</email> and email address, blog "http://www.google.com",
test@yahoo.com dfdfdfdddfdf;cool.hacks@gmail.com<br />
<a href="http://code.google.com"><h1>Heading</h1>
由于我们使用的是扩展正则表达式(例如+),所以我们应使用egrep:
$ egrep -o '[A-Za-z0-9._]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4}'
url_email.txt
slynux@slynux.com
test@yahoo.com
cool.hacks@gmail.com
egrep正则表达式模式用于 HTTP URL 如下:
http://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4}
请参考此示例:
$ egrep -o "http://[a-zA-Z0-9.]+\.[a-zA-Z]{2,3}" url_email.txt
http://www.google.com
http://code.google.com
它是如何工作的...
正则表达式易于逐部分设计。在电子邮件的正则表达式中,我们都知道电子邮件地址的形式是name@domain.some_2-4_letter_suffix。在正则表达式语言中编写这个模式应该是这样的:
[A-Za-z0-9.]+@[A-Za-z0-9.]+\.[a-zA-Z]{2,4}
[A-Za-z0-9.]+表示我们需要一个或多个字符在[]块中(+表示至少一个,可能更多)。这个字符串后面跟着一个@字符。接下来,我们将看到域名,它由字母或数字、一个点和 2-4 个字母组成。[A-Za-z0-9]+模式定义了一个字母数字字符串。\.模式表示必须出现一个字面上的点。[a-zA-Z]{2,4}模式定义了 2、3 或 4 个字母。
HTTP URL 类似于电子邮件,但我们不需要电子邮件正则表达式中的name@匹配部分:
http://[a-zA-Z0-9.]+\.[a-zA-Z]{2,3}
另见
-
本章中的使用 sed 进行文本替换章节解释了
sed命令 -
本章中的使用正则表达式章节解释了如何使用正则表达式
删除包含特定单词的文件中的一句话
删除包含特定单词的句子是一个简单的任务,可以通过正则表达式实现。本例展示了解决类似问题的技巧。
准备工作
sed是执行替换操作的最佳工具。本例使用sed将匹配的句子替换为空白。
如何操作...
让我们创建一个包含一些文本的文件来进行替换。考虑这个例子:
$ cat sentence.txt
Linux refers to the family of Unix-like computer operating systems
that use the Linux kernel. Linux can be installed on a wide variety
of computer hardware, ranging from mobile phones, tablet computers
and video game consoles, to mainframes and supercomputers. Linux is
predominantly known for its use in servers.
要删除包含mobile phones的句子,请使用以下sed表达式:
$ sed 's/ [^.]*mobile phones[^.]*\.//g' sentence.txt
Linux refers to the family of Unix-like computer operating systems
that use the Linux kernel. Linux is predominantly known for its use
in servers.
本例假设没有句子跨越多行,例如,一个句子应该始终在文本的同一行开始和结束。
它是如何工作的...
sed正则表达式's/ [^.]*mobile phones[^.]*\.//g'的格式是's/substitution_pattern/replacement_string/g。它将每个substitution_pattern的出现替换为替代字符串。
替换模式是句子的正则表达式。每个句子以空格开始,以.结束。正则表达式必须匹配格式为“空格 一些文本 匹配字符串 一些文本 点”的文本。一个句子可以包含除“点”之外的任何字符,“点”是分隔符。[^.]模式匹配任何非句点.字符。*模式定义了任意数量的这些字符。mobile phones文本匹配字符串放置在非句点字符的模式之间。每个匹配的句子被//(空)替换。
另见
-
本章中的使用 sed 进行文本替换章节解释了
sed命令 -
本章中的使用正则表达式章节解释了如何使用正则表达式
在目录中所有文件中用文本替换模式
我们经常需要在目录中的每个文件中将某些文本替换为新文本。例如,可能需要在网站的源代码目录中更改一个常见的 URI。
如何操作...
我们可以使用find来定位需要修改文本的文件。我们可以使用sed来执行实际的替换操作。
要将所有.cpp文件中的Copyright文本替换为Copyleft,请使用以下命令:
find . -name *.cpp -print0 | \
xargs -I{} -0 sed -i 's/Copyright/Copyleft/g' {}
它是如何工作的……
我们在当前目录(.)使用find查找具有.cpp后缀的文件。find命令使用-print0打印一个以空字符分隔的文件列表(当文件名中有空格时,请使用-print0)。我们将该列表通过管道传递给xargs,xargs将文件名传递给sed,以进行修改。
还有更多……
如果你还记得,find有一个-exec选项,可以用于对匹配搜索条件的每个文件运行命令。我们可以使用这个选项来实现相同的效果或将文本替换为新文本:
$ find . -name *.cpp -exec sed -i 's/Copyright/Copyleft/g' \{\} \;
或:
$ find . -name *.cpp -exec sed -i 's/Copyright/Copyleft/g' \{\} \+
这些命令执行相同的功能,但第一种形式会为每个文件调用一次sed,而第二种形式会将多个文件名合并并一起传递给sed。
文本切片和参数操作
本配方介绍了一些简单的文本替换技巧和在 Bash 中可用的参数扩展快捷方式。几种简单的技巧可以帮助避免编写多行代码。
如何做到这一点……
让我们进入任务。
替换变量中的一些文本:
$ var="This is a line of text"
$ echo ${var/line/REPLACED}
This is a REPLACED of text"
line词被替换为REPLACED。
我们可以通过指定起始位置和字符串长度来生成一个子字符串,使用以下语法:
${variable_name:start_position:length}
从第五个字符开始打印:
$ string=abcdefghijklmnopqrstuvwxyz
$ echo ${string:4}
efghijklmnopqrstuvwxyz
从第五个字符开始打印八个字符:
$ echo ${string:4:8}
efghijkl
字符串中的第一个字符位于位置0。我们可以从最后一个字母开始计数为-1。当-1位于括号内时,(-1)是最后一个字母的索引:
echo ${string:(-1)}
z
$ echo ${string:(-2):2}
yz
参见
- 本章中的使用 sed 进行文本替换配方解释了其他字符操作技巧
第五章:错综复杂的 Web?一点也不!
在本章中,我们将介绍以下方法:
-
从网页下载
-
将网页下载为纯文本
-
cURL 入门
-
从命令行访问未读的 Gmail 邮件
-
从网站解析数据
-
图片爬虫和下载器
-
Web 照片相册生成器
-
Twitter 命令行客户端
-
通过 Web 服务器访问单词定义
-
查找网站中的断开链接
-
跟踪网站的变化
-
向网页发布数据并读取响应
-
从互联网下载视频
-
使用 OTS 摘要文本
-
从命令行翻译文本
介绍
Web 已经成为技术的面貌,也是数据处理的中央访问点。Shell 脚本无法完成 PHP 等语言在 Web 上能够实现的所有功能,但对于许多任务,Shell 脚本是理想的选择。我们将探讨一些方法,下载和解析网站数据,向表单发送数据,自动化网站使用任务以及类似的活动。通过几行脚本,我们可以自动化许多通过浏览器交互执行的活动。HTTP 协议和命令行工具提供的功能使我们能够编写脚本来解决许多 Web 自动化需求。
从网页下载
下载文件或网页非常简单。有一些命令行下载工具可以完成这个任务。
准备就绪
wget 是一个灵活的文件下载命令行工具,可以通过许多选项进行配置。
如何操作...
可以使用 wget 下载网页或远程文件:
$ wget URL
例如:
$ wget knopper.net
--2016-11-02 21:41:23-- http://knopper.net/
Resolving knopper.net... 85.214.68.145
Connecting to knopper.net|85.214.68.145|:80...
connected.
HTTP request sent, awaiting response... 200 OK
Length: 6899 (6.7K) [text/html]
Saving to: "index.html.1"
100% [=============================]45.5K=0.1s
2016-11-02 21:41:23 (45.5 KB/s) - "index.html.1" saved
[6899/6899]
也可以指定多个下载 URL:
$ wget URL1 URL2 URL3 ..
它是如何工作的...
默认情况下,下载的文件名称与 URL 相同,下载信息和进度将写入 stdout。
-O 选项指定输出文件名。如果已存在该名称的文件,它将被下载的文件替换:
$ wget http://www.knopper.net -O knopper.html.
-o 选项指定一个 logfile,而不是将日志打印到 stdout:
$ wget ftp://ftp.example.com/somefile.img -O dloaded_file.img -o log
使用上述命令不会在屏幕上打印任何内容。日志或进度将写入日志,输出文件将是 dloaded_file.img。
由于互联网连接不稳定,下载可能会中断。-t 选项指定工具在放弃之前重试的次数:
$ wget -t 5 URL
使用 0 值强制 wget 无限次尝试:
$ wget -t 0 URL
还有更多...
wget 工具提供了选项来微调行为并解决问题。
限制下载速度
当带宽有限且许多应用程序共享带宽时,一个大文件可能会占用所有带宽,导致其他进程(可能是交互式用户)无法访问。wget 选项 -limit-rate 将指定下载任务的最大带宽,允许所有应用程序公平地访问互联网:
$ wget --limit-rate 20k http://example.com/file.iso
在此命令中,k(千字节)指定了速度限制。你也可以使用 m 表示兆字节。
-quota(或-Q)选项指定下载的最大大小。当超过配额时,wget会停止。这在下载多个文件到空间有限的系统时很有用:
$ wget -Q 100m http://example.com/file1 http://example.com/file2
重新开始下载并继续
如果wget在下载完成之前被中断,可以使用-c选项从中断处恢复下载:
$ wget -c URL
复制整个网站(镜像)
wget可以通过递归地收集 URL 链接并像爬虫一样下载它们,来下载完整的网站。要下载页面,请使用--mirror选项:
$ wget --mirror --convert-links exampledomain.com
或者,使用以下命令:
$ wget -r -N -l -k DEPTH URL
-l选项指定网页的深度,以级别表示。这意味着它只会遍历该级别的数量。它与-r(递归)一起使用。-N参数用于启用文件的时间戳功能。URL是需要发起下载的站点的基本 URL。-k或--convert-links选项指示wget将其他页面的链接转换为本地副本。
在镜像其他网站时要谨慎。除非你获得许可,否则仅为个人使用并避免频繁进行此操作。
使用 HTTP 或 FTP 身份验证访问页面
--user和--password参数提供需要身份验证的网站的用户名和密码。
$ wget --user username --password pass URL
也可以在不直接指定密码的情况下请求密码。为此,请使用--ask-password而不是--password参数。
将网页下载为纯文本
网页只是包含 HTML 标签、JavaScript 和 CSS 的文本。HTML 标签定义了网页的内容,我们可以解析这些内容以提取特定信息。Bash 脚本可以解析网页。HTML 文件可以在网页浏览器中查看以正确显示格式,或使用前一章中描述的工具进行处理。
解析文本文档比解析 HTML 数据更简单,因为我们不需要去除 HTML 标签。Lynx是一个命令行网页浏览器,可以将网页下载为纯文本。
准备工作
Lynx 并非所有发行版都预装,但可以通过包管理器安装。
# yum install lynx
或者,你可以执行以下命令:
apt-get install lynx
如何做...
-dump选项将网页下载为纯 ASCII 文本。下一个示例展示了如何将该 ASCII 版本的页面发送到文件中:
$ lynx URL -dump > webpage_as_text.txt
此命令将在References标题下单独列出所有超链接(<a href="link">),作为文本输出的页脚。这使我们可以使用正则表达式单独解析链接。
考虑以下示例:
$lynx -dump http://google.com > plain_text_page.txt
你可以使用cat命令查看text的纯文本版本:
$ cat plain_text_page.txt
Search [1]Images [2]Maps [3]Play [4]YouTube [5]News [6]Gmail
[7]Drive
[8]More »
[9]Web History | [10]Settings | [11]Sign in
[12]St. Patrick's Day 2017
_______________________________________________________
Google Search I'm Feeling Lucky [13]Advanced search
[14]Language tools
[15]Advertising Programs [16]Business Solutions [17]+Google
[18]About Google
© 2017 - [19]Privacy - [20]Terms
References
...
cURL 基础
cURL 使用 HTTP、HTTPS 或 FTP 协议在服务器与本地之间传输数据。它支持 POST、cookies、身份验证、从指定偏移位置下载部分文件、referer、用户代理字符串、额外的头部、限速、最大文件大小、进度条等功能。cURL 对于维护网站、检索数据以及检查服务器配置非常有用。
准备工作
与 wget 不同,cURL 并不是所有 Linux 发行版都自带的;你可能需要使用包管理器进行安装。
默认情况下,cURL 会将下载的文件输出到 stdout,并将进度信息输出到 stderr。若要禁用显示进度信息,请使用 --silent 选项。
如何操作...
curl 命令执行多种功能,包括下载、发送不同的 HTTP 请求和指定 HTTP 头部。
- 要将下载的文件输出到
stdout,请使用以下命令:
$ curl URL
-O选项指定将下载的数据发送到一个文件,该文件名是从 URL 解析出来的。注意,URL 必须是完整的页面 URL,而不仅仅是站点名称。
$ curl www.knopper.net/index.htm --silent -O
-o选项指定输出文件的名称。使用此选项,你可以仅指定站点名称以获取主页。
$curl www.knopper.net -o knoppix_index.html
% Total % Received % Xferd Avg Speed Time Time Time
Current
Dload Upload Total Spent Left Speed
100 6889 100 6889 0 0 10902 0 --:-- --:-- --:-- 26033
-silent选项可以防止curl命令显示进度信息:
$ curl URL --silent
-progress选项在下载时显示进度条:
$ curl http://knopper.net -o index.html --progress
################################## 100.0%
它是如何工作的...
cURL 下载网页或远程文件到本地系统。你可以使用 -O 和 -o 选项控制目标文件名,使用 -silent 和 -progress 选项控制冗余信息的显示。
还有更多...
在前面的章节中,你学习了如何下载文件。cURL 支持更多的选项,可以细化其行为。
继续和恢复下载
cURL 可以从指定的偏移位置恢复下载。如果你有每日数据限制并且需要下载大文件,这个功能非常有用。
$ curl URL/file -C offset
偏移量是一个字节数的整数值。
如果我们想恢复下载一个文件,cURL 不要求我们知道确切的字节偏移位置。如果你希望 cURL 自动计算正确的恢复点,可以使用 -C - 选项,如下所示:
$ curl -C - URL
cURL 会自动计算从指定文件的哪里重新开始下载。
使用 cURL 设置 referer 字符串
Referer 字段在 HTTP 头部中标识了引导当前网页的页面。当用户点击网页 A 上的链接跳转到网页 B 时,页面 B 的 referer 头部字符串将包含页面 A 的 URL。
一些动态页面在返回 HTML 数据之前会检查 referer 字符串。例如,一个网页可能会在用户从 Google 导航到某个网站时显示 Google 的徽标,而当用户直接输入 URL 时,则显示不同的页面。
一个 web 开发者可以编写一个条件语句,如果 referer 是 www.google.com,就返回一个 Google 页面;如果不是,则返回不同的页面。
你可以使用 --referer 与 curl 命令来指定 referer 字符串,如下所示:
$ curl --referer Referer_URL target_URL
考虑以下示例:
$ curl --referer http://google.com http://knopper.org
使用 cURL 处理 cookies
curl 可以指定并存储在 HTTP 操作过程中遇到的 cookies。
-cookie``COOKIE_IDENTIFER 选项指定要提供的 Cookies。Cookies 的格式为 name=value。多个 Cookies 应该用分号(;)分隔:
$ curl http://example.com --cookie "user=username;pass=hack"
-cookie-jar 选项指定用于存储 Cookies 的文件:
$ curl URL --cookie-jar cookie_file
使用 cURL 设置用户代理字符串
一些检查用户代理的网页,如果没有指定用户代理,可能无法正常工作。例如,某些旧网站要求使用 Internet Explorer(IE)。如果使用其他浏览器,它们会显示一个消息,提示该网站必须在 IE 中查看。这是因为该网站检查用户代理。你可以通过 curl 设置用户代理。
--user-agent 或 -A 选项设置用户代理:
$ curl URL --user-agent "Mozilla/5.0"
可以通过 cURL 传递额外的头部信息。使用 -H "Header" 传递额外的头部:
$ curl -H "Host: www.knopper.net" -H "Accept-language: en" URL
网上有许多不同的用户代理字符串,涵盖多个浏览器和爬虫。你可以在 www.useragentstring.com/pages/useragentstring.php 找到一些常见的列表。
在 cURL 中指定带宽限制
当带宽被多个用户共享时,我们可以使用 --limit-rate 选项来限制下载速率:
$ curl URL --limit-rate 20k
可以使用 k(千字节)或 m(兆字节)来指定速率。
指定最大下载大小
--max-filesize 选项指定最大文件大小:
$ curl URL --max-filesize bytes
如果文件大小超过限制,curl 命令将返回一个非零退出代码;如果下载成功,则返回零。
使用 cURL 进行身份验证
curl 命令的 -u 选项执行 HTTP 或 FTP 身份验证。
用户名和密码可以使用 -u username:password 进行指定:
$ curl -u user:pass http://test_auth.com
如果你希望系统提示输入密码,只提供用户名即可:
$ curl -u user http://test_auth.com
打印响应头部,排除数据部分
检查头部信息对于许多检查和统计已足够。例如,我们不需要下载整个页面来确认它是否可达。只读取 HTTP 响应即可。
另一个检查 HTTP 头部的使用场景是检查 Content-Length 字段以确定文件大小,或检查 Last-Modified 字段来查看文件是否比当前副本更新,然后再进行下载。
-I 或 -head 选项仅输出 HTTP 头部,不下载远程文件:
$ curl -I http://knopper.net
HTTP/1.1 200 OK
Date: Tue, 08 Nov 2016 17:15:21 GMT
Server: Apache
Last-Modified: Wed, 26 Oct 2016 23:29:56 GMT
ETag: "1d3c8-1af3-b10500"
Accept-Ranges: bytes
Content-Length: 6899
Content-Type: text/html; charset=ISO-8859-1
另见
- 本章的 发布到网页并读取响应 示例
从命令行访问未读 Gmail 邮件
Gmail 是 Google 提供的一款广泛使用的免费电子邮件服务:mail.google.com/。它允许你通过浏览器或经过身份验证的 RSS 提要来读取邮件。我们可以解析这些 RSS 提要,报告发件人名称和主题。这是一种快速扫描未读邮件而无需打开网页浏览器的方法。
如何做到...
让我们通过一个 shell 脚本解析 Gmail 的 RSS 提要,来显示未读邮件:
#!/bin/bash
#Desc: Fetch gmail tool
username='PUT_USERNAME_HERE'
password='PUT_PASSWORD_HERE'
SHOW_COUNT=5 # No of recent unread mails to be shown
echo
curl -u $username:$password --silent \
"https://mail.google.com/mail/feed/atom" | \
tr -d '\n' | sed 's:</entry>:\n:g' |\
sed -n 's/.*<title>\(.*\)<\/title.*<author><name>\([^<]*\)<\/name><email>
\([^<]*\).*/From: \2 [\3] \nSubject: \1\n/p' | \
head -n $(( $SHOW_COUNT * 3 ))
输出结果如下:
$ ./fetch_gmail.sh
From: SLYNUX [ slynux@slynux.com ]
Subject: Book release - 2
From: SLYNUX [ slynux@slynux.com ]
Subject: Book release - 1
.
... 5 entries
如果你使用带有两步验证的 Gmail 账户,你需要为此脚本生成一个新密钥并使用它。你的常规密码将无法使用。
它是如何工作的…
脚本使用 cURL 下载 RSS 订阅源。你可以通过登录 Gmail 帐户并查看mail.google.com/mail/feed/atom来查看传入数据的格式。
cURL 通过-u user:pass参数提供的用户认证读取 RSS 订阅源。当你仅使用-u user而不提供密码时,cURL 会交互式地询问密码。
-
tr -d '\n':这会删除换行符 -
sed 's:</entry>:\n:g':这将每个</entry>元素替换为换行符,因此每个电子邮件条目都由新行分隔,从而使邮件可以逐一解析。
需要作为一个单一表达式执行的下一个脚本块使用sed提取相关字段:
sed 's/.*<title>\(.*\)<\/title.*<author><name>\([^<]*\)<\/name><email>
\([^<]*\).*/Author: \2 [\3] \nSubject: \1\n/'
该脚本使用<title>\(.*\)<\/title正则表达式匹配标题,使用<author><name>\([^<]*\)<\/name>正则表达式匹配发送者名称,使用<email>\([^<]*\)匹配电子邮件。Sed 使用反向引用将作者、标题和邮件主题显示为易于阅读的格式:
Author: \2 [\3] \nSubject: \1\n
\1对应第一个子字符串匹配(标题),\2对应第二个子字符串匹配(姓名),依此类推。
SHOW_COUNT=5变量用于获取要在终端上打印的未读邮件条目数量。
head用于显示从第一行开始的SHOW_COUNT*3行。SHOW_COUNT乘以三,以便显示三行输出。
另见
-
本章中的cURL 入门配方解释了
curl命令 -
第四章中的使用 sed 进行文本替换配方解释了
sed命令
从网站解析数据
lynx、sed和awk命令可以用于从网站提取数据。你可能在第四章的使用 grep 搜索和提取文件中的文本配方中看到过一个女演员排名列表;它是通过解析www.johntorres.net/BoxOfficefemaleList.html网页生成的。
如何实现…
让我们来看看用于解析网站上女演员详细信息的命令:
$ lynx -dump -nolist \
http://www.johntorres.net/BoxOfficefemaleList.html
grep -o "Rank-.*" | \
sed -e 's/ *Rank-\([0-9]*\) *\(.*\)/\1\t\2/' | \
sort -nk 1 > actresslist.txt
输出如下:
# Only 3 entries shown. All others omitted due to space limits
1 Keira Knightley
2 Natalie Portman
3 Monica Bellucci
它是如何工作的…
Lynx 是一个命令行网页浏览器;它可以像在网页浏览器中看到的那样,转储网站的文本版本,而不是返回像wget或 cURL 那样的原始 HTML。这省去了去除 HTML 标签的步骤。-nolist选项显示没有编号的链接。使用sed对包含排名的行进行解析和格式化:
sed -e 's/ *Rank-\([0-9]*\) *\(.*\)/\1\t\2/'
这些行随后会根据排名进行排序。
另见
-
第四章中的使用 sed 进行文本替换配方解释了
sed命令 -
本章中的下载网页为纯文本配方解释了
lynx命令
图像爬虫和下载器
图片爬虫 会下载网页中出现的所有图片。我们可以通过脚本自动识别图片并下载,而不必手动从 HTML 页面中挑选图片。
如何实现...
这个 Bash 脚本将识别并下载网页中的所有图片:
#!/bin/bash
#Desc: Images downloader
#Filename: img_downloader.sh
if [ $# -ne 3 ];
then
echo "Usage: $0 URL -d DIRECTORY"
exit -1
fi
while [ $# -gt 0 ]
do
case $1 in
-d) shift; directory=$1; shift ;;
*) url=$1; shift;;
esac
done
mkdir -p $directory;
baseurl=$(echo $url | egrep -o "https?://[a-z.\-]+")
echo Downloading $url
curl -s $url | egrep -o "<img[^>]*src=[^>]*>" | \
sed 's/<img[^>]*src=\"\([^"]*\).*/\1/g' | \
sed "s,^/,$baseurl/," > /tmp/$$.list
cd $directory;
while read filename;
do
echo Downloading $filename
curl -s -O "$filename" --silent
done < /tmp/$$.list
示例用法如下:
$ url=https://commons.wikimedia.org/wiki/Main_Page
$ ./img_downloader.sh $url -d images
它是如何工作的...
图片下载脚本读取 HTML 页面,去除所有标签(除了 <img> 标签),解析 <img> 标签中的 src="img/URL" 并将其下载到指定目录。此脚本接受网页 URL 和目标目录作为命令行参数。
[ $# -ne 3 ] 语句检查脚本的参数总数是否为三,如果不是,则退出并返回使用示例。否则,这段代码会解析 URL 和目标目录:
while [ -n "$1" ]
do
case $1 in
-d) shift; directory=$1; shift ;;
*) url=${url:-$1}; shift;;
esac
done
while 循环会一直执行,直到所有参数处理完成。shift 命令会将参数向左移动,这样 $1 就会获取下一个参数的值,即 $2,以此类推。因此,我们可以通过 $1 本身评估所有参数。
case 语句检查第一个参数($1)。如果它匹配 -d,那么下一个参数必须是目录名,因此参数会被移位,目录名将被保存。如果该参数是其他任何字符串,则它是 URL。
这种解析参数的优势在于,我们可以在命令行中的任何位置放置 -d 参数:
$ ./img_downloader.sh -d DIR URL
或:
$ ./img_downloader.sh URL -d DIR
egrep -o "<img src=[^>]*>" 只会打印匹配的字符串,即包括其属性的 <img> 标签。[^>]* 这个短语匹配所有除关闭 > 之外的字符,即 <img src="img/image.jpg">。
sed's/<img src=\"\([^"]*\).*/\1/g' 从 src="img/url" 字符串中提取 url。
图像源路径有两种类型:绝对路径和相对路径。绝对路径 包含以 http:// 或 https:// 开头的完整 URL。相对 URL 以 / 或 image_name 本身开头。一个绝对 URL 的例子是 http://example.com/image.jpg。一个相对 URL 的例子是 /image.jpg。
对于相对 URL,起始的 / 应该被替换为基础 URL,从而将其转换为 http://example.com/image.jpg。该脚本通过以下命令提取初始 URL 来初始化 baseurl:
baseurl=$(echo $url | egrep -o "https?://[a-z.\-]+")
前述 sed 命令的输出被传递到另一个 sed 命令,替换掉开头的 / 为 baseurl,并将结果保存在以脚本的 PID 命名的文件中:(/tmp/$$.list)。
sed "s,^/,$baseurl/," > /tmp/$$.list
最终的 while 循环会遍历列表中的每一行,并使用 curl 下载图片。使用 --silent 参数来避免 curl 输出多余的进度信息。
另见
-
本章中的cURL 入门 食谱解释了
curl命令。 -
第四章中的使用 sed 执行文本替换食谱解释了
sed命令。 -
第四章中的 使用 grep 搜索和提取文件中的文本 食谱,文字和驾驶,解释了
grep命令
网络照片相册生成器
Web 开发人员常常创建全尺寸和缩略图照片相册。当点击缩略图时,会显示该图片的放大版。这需要调整大小并放置许多图像。这些操作可以通过简单的 Bash 脚本来自动化。该脚本创建缩略图,将其放置在正确的目录中,并自动生成<img>标签的代码片段。
准备工作
这个脚本使用for循环来遍历当前目录中的每个图像。它使用了常见的 Bash 工具,如cat和convert(来自 Image Magick 包)。这些工具将生成一个 HTML 相册,并将所有图像放入index.html中。
如何做...
这个 Bash 脚本将生成一个 HTML 相册页面:
#!/bin/bash
#Filename: generate_album.sh
#Description: Create a photo album using images in current directory
echo "Creating album.."
mkdir -p thumbs
cat <<EOF1 > index.html
<html>
<head>
<style>
body
{
width:470px;
margin:auto;
border: 1px dashed grey;
padding:10px;
}
img
{
margin:5px;
border: 1px solid black;
}
</style>
</head>
<body>
<center><h1> #Album title </h1></center>
<p>
EOF1
for img in *.jpg;
do
convert "$img" -resize "100x" "thumbs/$img"
echo "<a href=\"$img\" >" >>index.html
echo "<img src=\"thumbs/$img\" title=\"$img\" /></a>" >> index.html
done
cat <<EOF2 >> index.html
</p>
</body>
</html>
EOF2
echo Album generated to index.html
按如下方式运行脚本:
$ ./generate_album.sh
Creating album..
Album generated to index.html
它是如何工作的...
脚本的初始部分用于编写 HTML 页面头部部分。
以下脚本将所有内容重定向到 EOF1 之前的部分,并写入index.html:
cat <<EOF1 > index.html
contents...
EOF1
头部包括 HTML 和 CSS 样式。
for img in *.jpg *.JPG; 会遍历文件名并执行循环体的内容。
convert "$img" -resize "100x" "thumbs/$img"将创建宽度为 100px 的缩略图。
以下语句生成所需的<img>标签并将其附加到index.html中:
echo "<a href=\"$img\" >"
echo "<img src=\"thumbs/$img\" title=\"$img\" /></a>" >> index.html
最后,使用cat将页脚的 HTML 标签附加到index.html中,就像脚本的第一部分那样。
另请参见
- 本章中的 Web 照片相册生成器 食谱解释了
EOF和stdin重定向
Twitter 命令行客户端
Twitter 是最热门的微博平台,也是目前在线社交媒体的流行词汇。我们可以使用 Twitter API 从命令行读取我们时间线上的推文!
让我们看看怎么做。
准备工作
最近,Twitter 停止允许人们使用纯 HTTP 身份验证登录,因此我们必须使用 OAuth 来进行身份验证。本书不涉及 OAuth 的完整解释,因此我们将使用一个库,使得在 Bash 脚本中使用 OAuth 变得更加简单。请按照以下步骤进行操作:
-
从
github.com/livibetter/bash-oauth/archive/master.zip下载bash-oauth库,并将其解压到任何目录。 -
进入该目录,然后在子目录
bash-oauth-master内,以 root 用户身份运行make install-all。 -
访问
apps.twitter.com/并注册一个新应用。这将使得能够使用 OAuth。 -
注册新应用程序后,进入应用程序设置并将访问类型更改为“读取和写入”。
-
现在,进入应用程序的详情部分,记录下两个信息,Consumer Key 和 Consumer Secret,以便在我们将要编写的脚本中替换这些内容。
太好了,现在让我们编写一个使用这个功能的脚本。
如何做...
这个 Bash 脚本使用 OAuth 库来读取推文或发送自己的更新:
#!/bin/bash
#Filename: twitter.sh
#Description: Basic twitter client
oauth_consumer_key=YOUR_CONSUMER_KEY
oauth_consumer_scret=YOUR_CONSUMER_SECRET
config_file=~/.$oauth_consumer_key-$oauth_consumer_secret-rc
if [[ "$1" != "read" ]] && [[ "$1" != "tweet" ]];
then
echo -e "Usage: $0 tweet status_message\n OR\n $0 read\n"
exit -1;
fi
#source /usr/local/bin/TwitterOAuth.sh
source bash-oauth-master/TwitterOAuth.sh
TO_init
if [ ! -e $config_file ]; then
TO_access_token_helper
if (( $? == 0 )); then
echo oauth_token=${TO_ret[0]} > $config_file
echo oauth_token_secret=${TO_ret[1]} >> $config_file
fi
fi
source $config_file
if [[ "$1" = "read" ]];
then
TO_statuses_home_timeline '' 'YOUR_TWEET_NAME' '10'
echo $TO_ret | sed 's/,"/\n/g' | sed 's/":/~/' | \
awk -F~ '{} \
{if ($1 == "text") \
{txt=$2;} \
else if ($1 == "screen_name") \
printf("From: %s\n Tweet: %s\n\n", $2, txt);} \
{}' | tr '"' ' '
elif [[ "$1" = "tweet" ]];
then
shift
TO_statuses_update '' "$@"
echo 'Tweeted :)'
fi
按照以下方式运行脚本:
$./twitter.sh read
Please go to the following link to get the PIN:
https://api.twitter.com/oauth/authorize?
oauth_token=LONG_TOKEN_STRING
PIN: PIN_FROM_WEBSITE
Now you can create, edit and present Slides offline.
- by A Googler
$./twitter.sh tweet "I am reading Packt Shell Scripting Cookbook"
Tweeted :)
$./twitter.sh read | head -2
From: Clif Flynt
Tweet: I am reading Packt Shell Scripting Cookbook
它是如何工作的……
首先,我们使用 source 命令来引入 TwitterOAuth.sh 库,以便使用其功能访问 Twitter。TO_init 函数初始化该库。
每个应用程序在首次使用时都需要获取 OAuth 令牌和令牌密钥。如果没有这些信息,我们将使用 TO_access_token_helper 库函数来获取它们。获取到令牌后,我们将它们保存到一个 config 文件中,这样下次运行脚本时,只需简单地加载该文件即可。
TO_statuses_home_timeline 库函数从 Twitter 获取推文。这些数据以 JSON 格式作为一长串字符串返回,类似这样开始:
{"created_at":"Thu Nov 10 14:45:20 +0000
"016","id":7...9,"id_str":"7...9","text":"Dining...
每条推文都以 "created_at" 标签开始,并包含 text 和 screen_name 标签。脚本将提取文本和屏幕名称数据,并仅显示这些字段。
脚本将长字符串分配给 TO_ret 变量。
JSON 格式使用双引号括起键,并且键值可能会被引用或不被引用。键值对通过逗号分隔,键和值通过冒号(:)分隔。
第一个 sed 命令将每个 " 字符替换为换行符,将每个键值对分开。这些行被传送到另一个 sed 命令,替换每个 ": 的出现为波浪号 (~),从而生成类似这样的行:
screen_name~"Clif_Flynt"
最终的 awk 脚本读取每一行。-F~ 选项将每行按波浪号分成字段,因此 $1 是键,$2 是值。if 命令检查是否为 text 或 screen_name。文本通常出现在推文的第一部分,但如果先显示发送者会更容易阅读,因此脚本会先保存 text 的返回值,直到它看到 screen_name,然后打印出 $2 的当前值和保存的文本值。
TO_statuses_update 库函数生成一条推文。空的第一个参数将我们的消息定义为默认格式,消息是第二个参数的一部分。
另见
-
[第四章《发短信与开车》中的 使用 sed 进行文本替换 配方,解释了
sed命令。 -
在第四章《发短信与开车》中的 使用 grep 在文件中搜索和挖掘文本 配方,解释了
grep命令。
通过 web 服务器访问单词定义
网上有多个词典提供 API,可以通过脚本与其网站进行交互。本配方演示了如何使用一个流行的 API。
准备工作
我们将使用 curl、sed 和 grep 来定义这个实用程序。有许多词典网站,您可以注册并免费使用它们的 API 进行个人使用。在这个示例中,我们使用的是 Merriam-Webster 的字典 API。请按照以下步骤操作:
-
访问
www.dictionaryapi.com/register/index.htm,并为自己注册一个账户。选择《大学词典》和《学习者词典》: -
使用新创建的账户登录,进入“我的密钥”页面,获取密钥。记下学习者词典的密钥。
如何操作...
此脚本将显示一个单词的定义:
#!/bin/bash
#Filename: define.sh
#Desc: A script to fetch definitions from dictionaryapi.com
key=YOUR_API_KEY_HERE
if [ $# -ne 2 ];
then
echo -e "Usage: $0 WORD NUMBER"
exit -1;
fi
curl --silent \
http://www.dictionaryapi.com/api/v1/references/learners/xml/$1?key=$key | \
grep -o \<dt\>.*\</dt\> | \
sed 's$</*[a-z]*>$$g' | \
head -n $2 | nl
运行脚本的方法如下:
$ ./define.sh usb 1
1 :a system for connecting a computer to another device (such as
a printer, keyboard, or mouse) by using a special kind of cord a
USB cable/port USB is an abbreviation of "Universal Serial Bus."How
it works...
工作原理...
我们使用curl从字典 API 网页获取数据,指定我们的 API Key ($apikey)和我们想要查询的单词($1)。结果包含在<dt>标签中的定义,使用grep提取。sed命令用于去除标签。脚本从定义中选择所需的行数,并使用nl为每行添加行号。
另见
-
第四章中的使用 sed 进行文本替换示例解释了
sed命令。 -
第四章中的使用 grep 在文件中搜索和挖掘文本示例解释了
grep命令。
查找网站中的失效链接
网站必须进行失效链接测试。对于大型网站,手动完成这个任务是不可行的。幸运的是,这是一个可以轻松自动化的任务。我们可以使用 HTTP 操作工具来查找失效链接。
准备工作
我们可以使用lynx和curl来识别链接并找出失效的链接。Lynx 具有-traversal选项,可以递归访问网站上的页面,并构建所有超链接的列表。cURL 用于验证每个链接。
如何操作...
此脚本使用lynx和curl查找网页中的失效链接:
#!/bin/bash
#Filename: find_broken.sh
#Desc: Find broken links in a website
if [ $# -ne 1 ];
then
echo -e "$Usage: $0 URL\n"
exit 1;
fi
echo Broken links:
mkdir /tmp/$$.lynx
cd /tmp/$$.lynx
lynx -traversal $1 > /dev/null
count=0;
sort -u reject.dat > links.txt
while read link;
do
output=`curl -I $link -s \
| grep -e "HTTP/.*OK" -e "HTTP/.*200"`
if [[ -z $output ]];
then
output=`curl -I $link -s | grep -e "HTTP/.*301"`
if [[ -z $output ]];
then
echo "BROKEN: $link"
let count++
else
echo "MOVED: $link"
fi
fi
done < links.txt
[ $count -eq 0 ] && echo No broken links found.
工作原理...
lynx -traversal URL将在工作目录中生成多个文件。其中包括一个reject.dat文件,里面包含该网站的所有链接。sort -u用于构建一个去重的列表。然后,我们遍历每个链接,使用curl -I检查其头部响应。如果头部的第一行包含 HTTP/和OK或200,则表示该链接有效。如果该链接无效,则重新检查并测试是否有301-链接已移动的响应。如果该测试也失败,损坏的链接将会显示在屏幕上。
从名字来看,reject.dat可能会让人觉得它应该包含无法访问或损坏的 URL 列表。然而,事实并非如此,lynx只是将所有的 URL 都添加到这个文件中。
另外需要注意的是,lynx会生成一个名为traverse.errors的文件,其中包含所有浏览时出问题的 URL。然而,lynx只会添加返回HTTP 404 (未找到)的 URL,因此我们会忽略其他错误(例如,HTTP 403 Forbidden)。这就是为什么我们需要手动检查状态码的原因。
另见
-
本章中的将网页下载为纯文本示例解释了
lynx命令。 -
本章中的cURL 入门示例解释了
curl命令。
跟踪网站变化
追踪网站变化对于 Web 开发者和用户都有用。手动检查网站是不现实的,但可以定期运行变化追踪脚本。一旦发生变化,它会生成通知。
准备工作
在 Bash 脚本中追踪变化意味着在不同时间获取网站,并使用diff命令查看差异。我们可以使用curl和diff来完成这个任务。
如何操作...
这个 Bash 脚本结合了不同的命令,用于追踪网页变化:
#!/bin/bash
#Filename: change_track.sh
#Desc: Script to track changes to webpage
if [ $# -ne 1 ];
then
echo -e "$Usage: $0 URL\n"
exit 1;
fi
first_time=0
# Not first time
if [ ! -e "last.html" ];
then
first_time=1
# Set it is first time run
fi
curl --silent $1 -o recent.html
if [ $first_time -ne 1 ];
then
changes=$(diff -u last.html recent.html)
if [ -n "$changes" ];
then
echo -e "Changes:\n"
echo "$changes"
else
echo -e "\nWebsite has no changes"
fi
else
echo "[First run] Archiving.."
fi
cp recent.html last.html
让我们来看一下你控制的一个网站上track_changes.sh脚本的输出。首先我们看到的是网页没有变化时的输出,然后是做出更改后的输出。
请注意,你应该将MyWebSite.org替换为你自己网站的名称。
- 首先,运行以下命令:
$ ./track_changes.sh http://www.MyWebSite.org
[First run] Archiving..
- 其次,再次运行命令:
$ ./track_changes.sh http://www.MyWebSite.org
Website has no changes
- 第三,在更改网页后,运行以下命令:
$ ./track_changes.sh http://www.MyWebSite.org
Changes:
--- last.html 2010-08-01 07:29:15.000000000 +0200
+++ recent.html 2010-08-01 07:29:43.000000000 +0200
@@ -1,3 +1,4 @@
<html>
+added line :)
<p>data</p>
</html>
它是如何工作的...
脚本通过[ ! -e "last.html" ];检查脚本是否是第一次运行。如果last.html不存在,这意味着是第一次运行,必须下载网页并将其保存为last.html。
如果这不是第一次,它会下载新的副本(recent.html),并通过 diff 工具检查差异。任何更改都会以 diff 输出的形式显示。最后,recent.html会被复制到last.html。
请注意,更改你正在检查的网站将会在第一次检查时生成一个巨大的 diff 文件。如果你需要追踪多个页面,可以为你打算观察的每个网站创建一个文件夹。
另见
- 本章中的《cURL 入门》配方解释了
curl命令
发布到网页并读取响应
POST和GET是 HTTP 请求中的两种类型,用于向网站发送信息或从网站检索信息。在GET请求中,我们通过网页的 URL 本身发送参数(名称-值对)。POST命令将键值对放在消息体中,而不是 URL 中。POST通常用于提交较长的表单,或隐藏信息,以防止在表面上暴露。
准备工作
对于这个示例,我们将使用tclhttpd包中包含的guestbook网站。你可以从sourceforge.net/projects/tclhttpd下载 tclhttpd,并在本地系统上运行它来创建一个本地 Web 服务器。留言簿页面请求一个名字和 URL,并将其添加到留言簿中,显示谁在用户点击“将我添加到留言簿”按钮时访问了该网站。
这个过程可以通过一个curl(或wget)命令来自动化。
如何操作...
下载 tclhttpd 包并cd到bin文件夹。使用以下命令启动 tclhttpd 守护进程:
tclsh httpd.tcl
发布和读取通用网站 HTML 响应的格式如下:
$ curl URL -d "postvar=postdata2&postvar2=postdata2"
考虑以下示例:
$ curl http://127.0.0.1:8015/guestbook/newguest.html \
-d "name=Clif&url=www.noucorp.com&http=www.noucorp.com"
curl命令打印出类似这样的响应页面:
<HTML>
<Head>
<title>Guestbook Registration Confirmed</title>
</Head>
<Body BGCOLOR=white TEXT=black>
<a href="www.noucorp.com">www.noucorp.com</a>
<DL>
<DT>Name
<DD>Clif
<DT>URL
<DD>
</DL>
www.noucorp.com
</Body>
-d是用于发布数据的参数。-d的字符串参数类似于GET请求的语义。var=value对应该通过&分隔。
你可以使用wget通过--post-data "string"发布数据。考虑以下示例:
$ wget http://127.0.0.1:8015/guestbook/newguest.cgi \
--post-data "name=Clif&url=www.noucorp.com&http=www.noucorp.com" \
-O output.html
使用与 cURL 相同的格式来传递名称-值对。output.html中的文本与 cURL 命令返回的文本相同。
传递给发布参数(例如-d或--post-data)的字符串应始终用引号括起来。如果不使用引号,&会被 shell 解释为指示该操作为后台进程。
如果你查看网页源代码(从浏览器选择查看源代码选项),你会看到定义了一个 HTML 表单,类似于以下代码:
<form action="newguest.cgi" " method="post" >
<ul>
<li> Name: <input type="text" name="name" size="40" >
<li> Url: <input type="text" name="url" size="40" >
<input type="submit" >
</ul>
</form>
在这里,newguest.cgi是目标网址。当用户输入详细信息并点击提交按钮时,名称和网址输入会作为POST请求发送到newguest.cgi,并且响应页面会返回到浏览器。
另请参见
-
本章中的cURL 入门食谱解释了
curl命令。 -
本章中的从网页下载食谱解释了
wget命令。
从互联网下载视频
下载视频有很多原因。如果你使用的是按流量计费的服务,你可能想在非高峰时段下载视频,那时候费用较便宜。你可能想在带宽不足以支持流媒体播放的情况下观看视频,或者你只是想确保总能拥有那段可爱猫咪的视频给朋友们展示。
准备工作
一个用于下载视频的程序是youtube-dl。它并不包含在大多数发行版中,且软件库可能没有更新,所以最好访问youtube-dl的官方网站yt-dl.org。
你会在该页面上找到下载和安装youtube-dl的链接和信息。
如何操作...
使用youtube-dl很简单。打开浏览器,找到你喜欢的视频。然后复制/粘贴那个网址到youtube-dl命令行中:
youtube-dl https://www.youtube.com/watch?v=AJrsl3fHQ74
当youtube-dl正在下载文件时,它会在你的终端上生成一个状态行。
它是如何工作的...
youtube-dl程序通过向服务器发送GET消息来工作,就像浏览器那样。它伪装成浏览器,以便 YouTube 或其他视频提供商下载视频,就好像设备正在进行流媒体播放。
-list-formats(-F)选项会列出视频可用的格式,而-format(-f)选项会指定下载的格式。如果你想下载一个比你的网络连接可以稳定播放的更高分辨率的视频,这个选项很有用。
使用 OTS 总结文本
开放文本摘要器(OTS)是一个应用程序,可以去除文本中的冗余内容,创建一个简明的摘要。
准备工作
ots包不是大多数 Linux 标准发行版的一部分,但可以通过以下命令安装:
apt-get install libots-devel
如何操作...
OTS 应用程序使用起来非常简单。它可以从文件或 stdin 中读取文本,并将生成的摘要输出到 stdout。
ots LongFile.txt | less
或
cat LongFile.txt | ots | less
OTS 应用程序也可以与 curl 一起使用,来总结网站上的信息。例如,你可以使用 ots 来总结冗长的博客:
curl http://BlogSite.org | sed -r 's/<[^>]+>//g' | ots | less
它是如何工作的...
curl 命令从博客站点获取页面并将页面传递给 sed。sed 命令使用正则表达式将所有 HTML 标签(即以小于符号开头,以大于符号结尾的字符串)替换为空白。去除标签后的文本会传递给 ots,生成的摘要会通过 less 显示。
从命令行翻译文本
Google 提供了一个在线翻译服务,你可以通过浏览器访问。Andrei Neculau 创建了一个 awk 脚本,能够访问该服务并从命令行进行翻译。
准备工作
大多数 Linux 发行版没有包含命令行翻译工具,但你可以通过以下方式从 Git 安装它:
cd ~/bin
wget git.io/trans
chmod 755 ./trans
如何操作...
trans 应用程序默认会根据你的本地环境变量翻译成该语言。
$> trans "J'adore Linux"
J'adore Linux
I love Linux
Translations of J'adore Linux
French -> English
J'adore Linux
I love Linux
你可以通过在文本之前添加一个选项来控制翻译的语言。选项的格式如下:
from:to
要将英文翻译成法语,请使用以下命令:
$> trans en:fr "I love Linux"
J'aime Linux
它是如何工作的...
trans 程序大约有 5,000 行 awk 代码,它使用 curl 与 Google、Bing 和 Yandex 翻译服务进行通信。
第六章:仓库管理
在本章中,我们将介绍以下操作:
-
创建一个新的 git 仓库
-
克隆一个远程 git 仓库
-
使用 git 添加并提交更改
-
使用 git 创建和合并分支
-
分享你的工作
-
将分支推送到服务器
-
获取当前分支的最新源代码
-
检查 git 仓库的状态
-
查看 git 历史
-
查找 bugs
-
提交信息的道德规范
-
使用 fossil
-
创建一个新的 fossil 仓库
-
克隆一个远程 fossil 仓库
-
打开一个 fossil 项目
-
使用 Fossil 添加并提交更改
-
使用 fossil 的分支和分叉
-
使用 fossil 分享你的工作
-
更新本地 fossil 仓库
-
检查 fossil 仓库的状态
-
查看 fossil 历史
介绍
你花在开发应用程序上的时间越多,就越能理解那些能够追踪你版本历史的软件。版本控制系统可以让你为新的问题解决方法创建一个沙盒,维护多个发布代码的分支,并在知识产权争议发生时提供开发历史。Linux 和 Unix 支持多种源代码控制系统,从早期的、原始的 SCCS 和 RCS,到并发系统如 CVS 和 SVN,以及现代的分布式开发系统如 GIT 和 FOSSIL。
Git 和 Fossil 相对于早期的系统如 CVS 和 SVN 的一个重大优势是,开发者可以在不连接到网络的情况下使用它们。像 CVS 和 RCS 这样的旧系统在你在办公室时工作得很好,但在远程工作时你无法检查新代码或查看旧代码。
Git 和 Fossil 是两种不同的版本控制系统,它们既有相似之处,也有不同之处。两者都支持分布式开发模型的版本控制。Git 提供源代码控制,并且有多个附加应用程序提供更多信息,而 Fossil 是一个单一的可执行文件,提供版本控制、故障单、Wiki、网页和技术笔记。
Git 被用于 Linux 内核开发,并且已被许多开源开发者采用。Fossil 是为 SQLite 开发团队设计的,也广泛应用于开源和闭源社区。
Git 已包含在大多数 Linux 发行版中。如果你的系统中没有,您可以通过 yum(Redhat 或 SuSE)或 apt-get(Debian 或 Ubuntu)安装它。
$ sudo yum install git-all
$ sudo apt-get install git-all
Fossil 可从 www.fossil-scm.org 作为源代码或可执行文件下载。
使用 Git
git 系统使用 git 命令和多个子命令来执行单个操作。我们将讨论 git clone、git commit、git branch 等命令。
要使用 git,你需要一个代码仓库。你可以自己创建一个(用于你的项目),或者克隆一个远程仓库。
创建一个新的 git 仓库
如果你在自己的项目中工作,你将需要创建自己的仓库。你可以在本地系统上创建仓库,或者在远程站点上创建,例如 GitHub。
准备工作
所有 git 项目都需要一个主文件夹来存放其他项目文件。
$ mkdir MyProject
$ cd MyProject
如何操作...
git init 命令在你当前的工作目录中创建 .git 子文件夹,并初始化配置 git 的文件。
$ git init
它是如何工作的...
git init 命令为本地使用初始化一个 git 仓库。如果你希望允许远程用户访问这个仓库,你需要使用 update-server-info 命令来启用它:
$ git update-server-info
克隆一个远程 git 仓库
如果你打算访问他人的项目,无论是为了贡献新代码还是仅仅使用该项目,你都需要将代码克隆到你的系统中。
克隆仓库时你需要保持在线。将文件复制到你的系统后,你可以提交新代码、回退到旧版本等等。你不能将任何新代码变更推送到你克隆的站点,直到你再次上线。
如何操作...
git clone 命令将文件从远程站点复制到你的本地系统。远程站点可能是一个匿名仓库,例如 GitHub,或者是一个你需要使用帐户名和可能的密码进行登录的系统。
从一个已知的远程站点克隆,例如 GitHub:
$ git clone http://github.com/ProjectName
从需要登录/密码保护的站点克隆(可能是你自己的服务器):
$ git clone clif@172.16.183.130:gitTest
clif@172.16.183.130's password:
使用 git 添加和提交更改
使用分布式版本控制系统,如 git,你大部分工作都是在本地复制的仓库中进行的。你可以添加新代码、修改代码、测试、修改,最后提交完全测试的代码。这鼓励你在本地仓库中进行频繁的小提交,代码稳定时再做一次大的提交。
如何操作...
git add 命令将你工作区的更改添加到暂存区。它不会改变仓库,只是将这个更改标记为下次提交时包含的内容:
$ vim SomeFile.sh
$ git add SomeFile.sh
每次编辑会话后执行 git add 是一个好习惯,如果你希望确保不会在提交时遗漏任何更改。
你也可以使用 git add 命令将新文件添加到仓库中:
$ echo "my test file" >testfile.txt
$ git add testfile.txt
另外,你可以添加多个文件:
$ git add *.c
git commit 命令将更改提交到仓库:
$ vim OtherFile.sh
$ git add OtherFile.sh
$ git commit
git commit 命令将打开你在 EDITOR shell 变量中定义的编辑器,并像这样预填充:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Committer: Clif Flynt <clif@cflynt.com>
#
# On branch branch1
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: SomeFile.sh
# modified: OtherFile.sh
在输入注释后,你的更改将保存在本地仓库的副本中。
这不会将你的更改推送到主仓库(可能是 github),但其他开发者如果在你的系统上有账户,还是可以从你的仓库中 拉取 新的代码。
你可以使用 -a 和 -m 参数来简化 add/commit 操作:
-
-a:在提交前添加新的代码 -
-m:这是在不进入编辑器的情况下定义消息
git commit -am "Add and Commit all modified files."
使用 git 创建和合并分支
如果你在维护一个应用程序,你可能需要返回到早期的分支进行测试。例如,你正在修复的 bug 可能已经存在,但没有被报告过很长时间。你会想要找出这个 bug 是何时引入的,以追踪导致问题的代码。(请参阅本章节中的查找 bug食谱中的git bisect命令。)
当你添加新功能时,你应该创建一个新分支来标识你的更改。项目维护者可以在新代码经过测试和验证后,将新分支合并到主分支中。你可以使用 git 的checkout子命令来改变和创建新分支。
准备中...
使用git init或git clone来在你的系统中创建项目。
如何操作...
要切换到之前定义的分支:
$ git checkout OldBranchName
工作原理...
checkout 子命令会检查系统上的.git文件夹,并恢复与所需分支关联的快照。
注意,如果你当前工作区中有未提交的更改,你无法切换到现有的分支。
在当前工作区有未提交的更改时,你可以创建一个新分支。要创建新分支,可以使用git checkout的-b选项:
$ git checkout -b MyBranchName
Switched to a new branch 'MyBranchName'
这将定义你的当前工作分支为MyBranchName。它设置一个指针,将MyBranchName与之前的分支匹配。随着你添加和提交更改,指针会进一步偏离初始分支。
当你在新的分支中测试过代码后,可以将更改合并回你最初开始的分支。
还有更多...
你可以使用git branch命令查看分支:
$ git branch
* MyBranchName
master
当前分支会用星号(*)标记。
合并分支
在你编辑、添加、测试并提交之后,你会想要将你的更改合并回初始分支。
如何操作...
在你创建了新分支并添加并提交了更改后,切换回原始分支,使用git merge命令将新分支中的更改合并:
$ git checkout originalBranch
$ git checkout -b modsToOriginalBranch
# Edit, test
$ git commit -a -m "Comment on modifications to originalBranch"
$ git checkout originalBranch
$ git merge modsToOriginalBranch
工作原理...
第一个git checkout命令会检索起始分支的快照。第二个git checkout命令将你的当前工作代码标记为一个新的分支。
git commit命令(或命令集)将新分支的快照指针逐渐移离原始分支。第三个git checkout命令会将你的代码恢复到你进行编辑和提交之前的初始状态。
git merge命令会将初始分支的快照指针移动到你正在合并的分支的快照。
还有更多...
合并一个分支后,你可能不再需要它。-d选项会删除该分支:
$ git branch -d MyBranchName
分享你的工作
Git 允许你在没有连接互联网的情况下工作。最终,你会希望共享你的工作。
这是有两种方法来实现:创建补丁或将你新的代码推送到主代码库。
制作补丁...
补丁文件是已提交更改的描述。其他开发人员可以将你的补丁文件应用到他们的代码中,以使用你的新代码。
format-patch命令将收集你的更改并创建一个或多个补丁文件。补丁文件将以数字、描述和.patch命名。
如何执行...
format-patch命令需要一个标识符来告诉 Git 第一个补丁应该是什么。Git 会创建需要的补丁文件,直到它把代码从原始状态更改为目标状态。
有多种方法可以识别起始快照。一组补丁常见的用途是将你对特定分支所做的更改提交给包维护者。
例如,假设你为一个新功能从 master 分支创建了一个新分支。当你完成测试后,可以将一组补丁文件发送给项目维护者,以便他们验证你的工作并将新功能合并到项目中。
带有父分支名称的format-patch子命令将生成用于创建当前分支的补丁文件:
$ git checkout master
$ git checkout -b newFeature
# Edits, adds and commits.
$ git format-patch master
0001-Patch-add-new-feature-to-menu.patch
0002-Patch-support-new-feature-in-library.patch
另一个常见的标识符是 git 快照SHA1。每个 git 快照都通过一个 SHA1 字符串来标识。
你可以使用git log命令查看仓库中所有提交的日志:
$ git log
commit 82567395cb97876e50084fd29c93ccd3dfc9e558
Author: Clif Flynt <clif@example.com>
Date: Thu Dec 15 13:38:28 2016 -0500
Fixed reported bug #1
commit 721b3fee54e73fd9752e951d7c9163282dcd66b7
Author: Clif Flynt <clif@example.com>
Date: Thu Dec 15 13:36:12 2016 -0500
Created new feature
带有 SHA1 标识符的git format-patch命令如下所示:
$ git format-patch SHA1
你可以使用 SHA1 标识符的唯一前段或完整的长字符串:
$ git format-patch 721b
$ git format-patch 721b3fee54e73fd9752e951d7c9163282dcd66b7
你也可以通过其与当前位置的距离来识别快照,使用-#选项。
该命令将为 master 分支的最新更改创建补丁文件:
$ git format-patch -1 master
该命令将为bleedingEdge分支的最近两次更改创建补丁文件:
$ git format-patch -2 bleedingEdge
应用补丁
git apply命令将补丁应用到你的工作代码集。在执行此命令之前,你必须签出相应的快照。
你可以使用--check选项测试补丁是否有效。
如果你的环境适用于此补丁,将不会返回任何信息。如果你没有签出正确的分支,补丁-check命令将生成错误条件:
$ git apply --check 0001-Patch-new-feature.patch
error: patch failed: feature.txt:2
error: feature.txt: patch does not apply
当--check选项没有生成错误信息时,使用git apply命令应用补丁:
$ git apply 0001-Patch-new-feature.patch
将分支推送到服务器
最终,你会希望与所有人分享你的新代码,而不仅仅是将补丁发送给个别人员。
git push命令将分支推送到 master。
如何执行...
如果你有一个唯一的分支,它可以始终推送到 master 仓库:
$ git push origin MyBranchName
如果你修改了现有的分支,你可能会收到如下错误信息:
-
remote: error: 拒绝更新已签出的分支:refs/heads/master -
remote: error: 默认情况下,在非裸仓库中更新当前分支
在这种情况下,你需要将你的更改推送到远程站点的一个新分支:
$ git push origin master:NewBranchName
你还需要通知包维护者将此分支合并到 master 分支中:
# On remote
$ git merge NewBranchName
获取当前分支的最新源代码。如果项目有多个开发者,你需要定期与远程仓库同步,获取其他开发者推送的数据。
git fetch 和 git pull 命令会从远程站点下载数据到你的本地仓库。
更新你的仓库而不更改工作代码。
git fetch 和 git pull 命令会下载新代码,但不会修改你当前的工作代码集。
get fetch SITENAME
克隆你仓库的站点被命名为 origin:
$ get fetch origin
要从其他开发者的仓库获取数据,使用以下命令:
$ get fetch Username@Address:Project
更新你的仓库和工作代码。
git pull 命令执行获取操作并将更改合并到当前代码中。如果存在冲突需要解决,这个操作会失败:
$ git pull origin
$ git pull Username@Address:Project
检查 git 仓库的状态
在进行集中的开发和调试后,你很可能会忘记所有的更改。>git status 命令会提醒你。
如何操作……
git status 命令报告当前项目的状态。它会告诉你所在的分支,是否有未提交的更改,以及你是否与远程仓库不同步:
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working
directory)
#
#modified: newFeature.tcl
它是如何工作的……
上一个示例显示了在添加并提交更改后,git status 输出的内容,并且有一个文件已被修改但尚未提交。
这行表示有一个提交尚未被推送:
# Your branch is ahead of 'origin/master' by 1 commit.
这种格式的行报告了已经修改但尚未提交的文件:
#modified: newFeature.tcl
git config --global user.name "Your Name"
git config --global user.email you@example.com
如果用于此提交的身份信息错误,你可以使用以下命令来修复:
git commit --amend --author='Your Name <you@example.com>'
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 testfile.txt
查看 git 历史
在开始一个项目之前,你应该回顾一下已完成的工作。你可能需要回顾最近的工作,以便跟上其他开发者的进度。
git log 命令生成一份报告,帮助你跟踪项目的更改。
如何操作……
git log 命令生成一个包含 SHA1 ID、提交该快照的作者、提交日期和日志信息的报告:
$ git log
commit fa9ef725fe47a34ab8b4488a38db446c6d664f3e
Author: Clif Flynt <clif@noucorp.com>
Date: Fri Dec 16 20:58:40 2016 -0500
Fixed bug # 1234
查找 bug
即使是最好的测试小组也难免会让 bug 滑出去。当这种情况发生时,开发者需要弄清楚是什么问题以及如何修复它。
Git 提供了工具来帮助你。
没有人故意制造 bug,所以问题可能是由修复旧 bug 或添加新特性导致的。
如果你能够隔离出引发问题的代码,可以使用 git blame 命令找到是谁提交了这段代码,以及提交的 SHA 编码是什么。
如何操作……
git blame 命令返回一个包含提交哈希码、作者、日期和提交消息第一行的列表:
$ git blame testGit.sh
d5f62aa1 (Flynt 2016-12-07 09:41:52 -0500 1) Created testGit.sh
063d573b (Flynt 2016-12-07 09:47:19 -0500 2) Edited on master repo.
2ca12fbf (Flynt 2016-12-07 10:03:47 -0500 3) Edit created remotely
and merged.
还有更多……
如果你有一个测试表明问题所在,但不清楚是哪行代码出了问题,你可以使用 git bisect 命令来找到引入问题的提交。
如何操作……
git bisect 命令需要两个标识符,一个是最后已知的良好代码,另一个是已知的坏版本。bisect 命令会为您识别一个位于好版本和坏版本之间的修订版本,供您进行测试。
测试完代码后,您可以重置好版本或坏版本的指针。如果测试成功,重置好版本指针;如果测试失败,重置坏版本指针。
Git 会在新的好版本和坏版本之间检查一个新的快照:
# Pull the current (buggy) code into a git repository
$ git checkout buggyBranch
# Initialize git bisect.
$ git bisect start
# Mark the current commit as bad
$ git bisect bad
# Mark the last known good release tag
# Git pulls a commit at the midpoint for testing.
$ git bisect good v2.5
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[6832085b8d358285d9b033cbc6a521a0ffa12f54] New Feature
# Compile and test
# Mark as good or bad
# Git pulls next commit to test
$ git bisect good
Bisecting: 1 revision left to test after this (roughly 1 step)
[2ca12fbf1487cbcd0447cf9a924cc5c19f0debf9] Merged. Merge branch
'branch1'
它是如何工作的...
git bisect 命令可以在已知良好版本和已知坏版本之间识别代码的版本。现在,您可以构建并测试该版本。测试后,重新运行 git bisect 以声明该分支是好版本还是坏版本。分支声明后,git bisect 会识别一个新版本,该版本位于新的好坏标记之间的中间位置。
标记快照
Git 支持使用助记符字符串和附加消息标记特定快照。您可以使用标签使开发树更加清晰,提供如 已合并新内存管理 之类的信息,或者标记沿分支的特定快照。例如,您可以使用标签标记 release-1.0 和 release-1.1,这两者位于 release-1 分支上。
Git 支持轻量级标签(仅标记快照)和带注释的标签。
Git 标签是仅限本地的。git push 默认不会推送标签。要将标签发送到远程仓库,必须包含 -tags 选项:
$ git push origin --tags
git tag 命令有添加、删除和列出标签的选项。
如何操作...
git tag 命令没有参数时,会列出所有可见标签:
$ git tag
release-1.0
release-1.0beta
release-1.1
您可以通过添加标签名在当前检出的版本上创建标签:
$ git tag ReleaseCandidate-1
您可以通过在 git tag 命令后添加 SHA-1 标识符,向之前的提交添加标签:
$ git log --pretty=oneline
72f76f89601e25a2bf5bce59551be4475ae78972 Initial checkin
fecef725fe47a34ab8b4488a38db446c6d664f3e Added menu GUI
ad606b8306d22f1175439e08d927419c73f4eaa9 Added menu functions
773fa3a914615556d172163bbda74ef832651ed5 Initial action buttons
$ git tag menuComplete ad606b
-a 选项会将注释附加到标签:
$ git tag -a tagWithExplanation
# git opens your editor to create the annotation
您可以在命令行上使用 -m 选项来定义消息:
$ git tag -a tagWithShortMessage -m "A short description"
当您使用 git show 命令时,将显示该消息:
$ git show tagWithShortMessage
tag tagWithShortmessage
Tagger: Clif Flynt <clif@cflynt.com>
Date: Fri Dec 23 09:58:19 2016 -0500
A short description
...
-d 选项会删除标签:
$ git tag
tag1
tag2
tag3
$ git tag -d tag2
$ git tag
tag2
tag3F
提交信息伦理
提交信息是自由格式的文本。它可以是任何您认为有用的内容。然而,Git 社区中有一些注释规范。
如何操作...
-
每行使用 72 个字符或更少。使用空行分隔段落。
-
第一行应为 50 个字符或更少,并总结为什么做出此提交。它应该足够具体,让阅读此行的人了解发生了什么。
-
不要写
Fix bug或者甚至是Fix bugzilla bug #1234,而是写删除每年 4 月 1 日出现的愚蠢消息。
以下段落描述了对跟踪您工作的人很重要的细节。提及您的代码使用的任何全局状态变量、副作用等。如果您修复了某个问题,包含该问题报告或功能请求的 URL。
使用 fossil
Fossil 应用程序是另一种分布式版本控制系统。像 Git 一样,它会记录更改,无论开发者是否能够访问主仓库站点。与 Git 不同,Fossil 支持自动同步模式,如果远程仓库可用,它会自动将提交推送到远程仓库。如果在提交时远程站点不可用,Fossil 会保存更改,直到远程站点可用为止。
Fossil 与 Git 在多个方面有所不同。Fossil 仓库使用一个单一的 SQLite 数据库来实现,而 Git 则使用一组文件夹来实现。Fossil 应用程序包括多个其他工具,例如 Web 界面、故障单系统和 Wiki,而 Git 则使用附加应用程序来提供这些服务。
像 Git 一样,Fossil 的主要界面是 fossil 命令,带有子命令来执行特定操作,如创建新仓库、克隆现有仓库、添加、提交文件等。
Fossil 包含一个帮助功能。fossil help 命令将生成一个支持命令的列表,而 fossil help CMDNAME 将显示帮助页面:
$ fossil help
Usage: fossil help COMMAND
Common COMMANDs: (use "fossil help -a|-all" for a complete list)
add cat finfo mv revert timeline
...
准备工作
Fossil 可能尚未安装在你的系统上,并且并非所有仓库都由其维护。
Fossil 的官方网站是 h t t p 😕/w w w . f o s s i l - s c m . o r g。
如何操作...
从 www.fossil-scm.org 下载适用于你的平台的 Fossil 可执行文件,并将其移至你的 bin 文件夹。
创建一个新的 Fossil 仓库
Fossil 对于你的个人项目以及你加入的现有项目来说,设置和使用都非常简单。
fossil new 和 fossil init 命令是相同的。你可以根据自己的偏好选择使用其中之一。
如何操作...
fossil new 和 fossil init 命令会创建一个空的 Fossil 仓库:
$ fossil new myProject.fossil
project-id: 855b0e1457da519d811442d81290b93bdc0869e2
server-id: 6b7087bce49d9d906c7572faea47cb2d405d7f72
admin-user: clif (initial password is "f8083e")
$ fossil init myProject.fossil
project-id: 91832f127d77dd523e108a9fb0ada24a5deceedd
server-id: 8c717e7806a08ca2885ca0d62ebebec571fc6d86
admin-user: clif (initial password is "ee884a")
它是如何工作的...
fossil init 和 fossil new 命令是相同的。它们会创建一个空的仓库数据库,并命名为你请求的名称。.fossil 后缀不是必需的,但它是一个常见的约定。
还有更多...
让我们来看一些更多的操作方法:
Fossil 的 Web 界面
Fossil 的 Web 服务器提供本地或远程访问 Fossil 系统的许多功能,包括配置、故障单管理、Wiki、提交历史图表等。
fossil ui 命令启动一个 http 服务器,并尝试将你的本地浏览器连接到 Fossil 服务器。默认情况下,该界面将连接到 UI,你可以执行任何需要的任务。
如何操作...
$ fossil ui
Listening for HTTP requests on TCP port 8080
#> fossil ui -P 80
Listening for HTTP requests on TCP port 80
使仓库可供远程用户使用
fossil server 命令启动一个 fossil 服务器,允许远程用户克隆你的仓库。默认情况下,fossil 允许任何人克隆项目。禁用 Admin/Users/Nobody 和 Admin/Users/Anonymous 页面上的提交、签出、克隆和下载 zip 功能,以将访问权限限制为仅注册用户。
当运行 fossil server 时,支持通过 Web 界面进行配置,但与默认设置不同,必须使用创建仓库时提供的凭据登录。
fossil server 可以通过提供仓库的完整路径来启动:
$ fossil server /home/projects/projectOne.fossil
fossil server 可以在不定义仓库的情况下从包含 fossil 仓库的文件夹启动:
$ cd /home/projects
$ ls
projectOne.fossil
$ fossil server
Listening for HTTP requests on TCP port 8080
克隆远程 fossil 仓库
由于 fossil 仓库包含在一个单一的文件中,你可以通过复制该文件来简单地克隆它。你可以将一个 fossil 仓库作为电子邮件附件发送给其他开发者,放到网站上,或复制到 USB 闪存驱动器中。
fossil scrub 命令会从数据库中删除 Web 服务器可能需要的用户和密码信息。建议在分发仓库副本之前执行此步骤。
如何操作...
你可以使用 fossil clone 命令从运行 fossil 的服务器模式网站克隆化石库。fossil clone 命令会分发版本历史,但不会包含用户和密码信息:
$ fossil clone http://RemoteSite:port projectName.fossil
它是如何工作的...
fossil clone 命令将指定网站上的仓库复制到你提供的本地文件中(示例中为 projectName.fossil)。
打开一个 fossil 项目
fossil open 命令会提取仓库中的文件。通常最好在包含 fossil 仓库的文件夹下创建一个子文件夹,用于存放项目。
如何操作...
下载 fossil 仓库:
$ fossil clone http://example.com/ProjectName project.fossil
为你的工作目录创建一个新文件夹并切换到该目录:
$ mkdir newFeature
$ cd newFeature
在工作文件夹中打开仓库:
$ fossil open ../project.fossil
它是如何工作的...
fossil open 命令会提取所有已检查到 fossil 仓库中的文件夹、子文件夹和文件。
还有更多...
你可以使用 fossil open 提取仓库中特定版本的代码。这个示例展示了如何签出 1.0 版本来修复旧的 bug。为你的工作目录创建一个新文件夹,并按以下方式更改:
$ mkdir fix_1.0_Bug
$ cd fix_1.0_Bug
在工作文件夹中打开仓库:
$ fossil open ../project.fossil release_1.0
使用 fossil 添加和提交更改
创建仓库后,你可以添加和编辑文件。fossil add 命令将新文件添加到仓库,而 fossil commit 命令则将更改提交到仓库。这与 Git 不同,在 Git 中,add 命令标记要添加的更改,而 commit 命令则执行提交操作。
如何操作...
以下示例展示了如果没有定义 EDITOR 或 VISUAL shell 变量时,fossil 的行为。如果定义了 EDITOR 或 VISUAL,fossil 将使用该编辑器,而不是在命令行中提示你:
$ echo "example" >example.txt
$ fossil add example.txt
ADDED example.txt
$ fossil commit
# Enter a commit message for this check-in. Lines beginning with #
are ignored.
#
# user: clif
# tags: trunk
#
# ADDED example.txt
$ echo "Line 2" >>example.txt
$ fossil commit
# Enter a commit message for this check-in. Lines beginning with #
are ignored.
#
# user: clif
# tags: trunk
#
# EDITED example.txt
还有更多...
当你编辑文件时,只需要提交。默认情况下,提交会记住你对本地仓库的所有更改。如果启用了自动同步,提交也会推送到远程仓库:
$ vim example.txt
$ vim otherExample.txt
$ fossil commit
# Enter a commit message for this check-in. Lines beginning with #
are ignored.
#
# user: clif
# tags: trunk
#
# EDITED example.txt, otherExample.txt
使用分支和分叉与 fossil
在理想的情况下,开发树是一个直线,每个修订版紧接着上一个修订版。然而,在现实中,开发人员通常从一个稳定的代码库开始工作,并进行更改,然后将其合并回主线开发。
Fossil 系统区分主线代码的临时分歧(例如,你仓库中的一个 bug 修复)和永久分歧(例如 1.x 版本仅进行 bug 修复,而新特性则进入 2.x 版本)。
在 Fossil 中的约定是将有意的分歧称为分支,而将无意的分歧称为分叉。例如,你可能会为正在开发的新代码创建一个分支,而在你尝试提交对某个文件的修改时,如果另一个人已经提交了更改,那么除非你先更新并解决冲突,否则这将会导致分叉。
分支可以是临时的,也可以是永久的。临时分支可能是在你开发新特性时创建的分支。永久分支则是在你进行版本发布时,打算与主线代码分歧的分支。
临时和永久分支都通过标签和属性进行管理。
当你使用 fossil init 或 fossil new 创建一个 Fossil 仓库时,它会将标签 trunk 分配给树。
fossil branch 命令用于管理分支。它有子命令可以创建新分支、列出分支和关闭分支。
如何操作
-
使用分支的第一步是创建一个分支。
fossil branch new命令可以创建一个新分支。它可以基于你当前检出的项目创建分支,也可以在项目的早期状态下创建分支。 -
fossil branch new命令将从指定的检查点创建一个新分支:
$ fossil branch new NewBranchName Basis-Id
New branch: 9ae25e77317e509e420a51ffbc43c2b1ae4034da
-
Basis-Id是一个标识符,用于告诉 Fossil 从哪个代码快照创建分支。定义Basis-Id有多种方法,最常见的方法将在下一节中讨论。 -
请注意,你需要执行检出操作,将工作文件夹更新到新分支:
$ fossil checkout NewBranchName
它是如何工作的...
NewBranchName 是新分支的名称。一个常见的约定是按描述所做修改的方式命名分支。例如,localtime_fixes 或 bug_1234_fix 等分支名称较为常见。
Basis-Id 是一个字符串,标识分支分歧的节点。如果你从某个分支的头部开始分歧,这个 Basis-Id 可以是分支的名称。
以下命令展示了如何从主干的最新状态创建一个分支:
$ fossil branch new test_rework_parse_logic trunk
New branch: 9ae25e77317e509e420a51ffbc43c2b1ae4034da
$ fossil checkout test_rework_parse_logic
fossil commit 命令允许你在提交时使用 --branch 选项指定一个新的分支名称:
$ fossil checkout trunk
# Make Changes
$ fossil commit --branch test_rework_parse_logic
还有更多...
合并分叉和分支
分支和分叉都可以合并回其父分支。分叉被认为是临时的,应在修改得到批准后尽快合并。分支被认为是永久性的,但即使是这些也可以合并回主线代码。
fossil merge 命令将合并一个临时分叉到另一个分支中。
如何操作...
- 要创建一个临时分支并将其合并回现有分支,您必须首先检出您打算进行开发的分支:
$ fossil checkout trunk
- 现在您可以编辑并进行测试。当您对新代码满意时,将新代码提交到新的分支上。
--branch选项会在必要时创建一个新分支,并将您的当前分支设置为新的branch:
$ fossil commit --branch new_logic
- 在代码经过测试和验证后,您可以通过检出要合并的分支,然后调用 fossil merge 命令来安排合并,最后提交合并:
$ fossil checkout trunk
$ fossil merge new_logic
$ fossil commit
- 在这一点上,Fossil 和 Git 的行为略有不同。
git merge命令会更新仓库,而化石合并命令则不会修改仓库,直到合并被提交。
与化石共享您的工作
如果您在多个平台上进行开发,或者您在参与他人的项目,您需要将本地仓库与远程主仓库进行同步。Fossil 有几种方式来处理这种情况。
如何操作...
默认情况下,fossil 在autosync模式下运行。在这种模式下,您的提交会立即传播到远程仓库。
autosync设置可以通过 fossil 设置命令启用或禁用:
$ fossil setting autosync off
$ fossil setting autosync on
当autosync被禁用(fossil 在手动合并模式下运行)时,您必须使用 fossil push 命令将本地仓库的更改发送到远程仓库:
$ fossil push
它是如何工作的...
push命令会将本地仓库中的所有更改推送到远程仓库。它不会修改任何已检出的代码。
更新您的本地 fossil 仓库
将您的工作推送到远程仓库的反面是更新您的本地仓库。如果您在笔记本电脑上进行开发,而主仓库在公司服务器上,或者您正在与多人合作的项目中工作并需要保持与他们的新功能同步,您将需要执行此操作。
如何操作...
化石服务器不会自动将更新推送到远程仓库。fossil pull命令将会将更新拉取到您的仓库中。它会更新仓库,但不会更改您的工作代码:
$ fossil pull
fossil checkout命令将在仓库发生更改时更新您的工作代码:
$ fossil checkout
您可以将 pull 和 checkout 子命令与fossil update命令结合使用:
$ fossil update
UPDATE main.tcl
-------------------------------------------------------------------
------------
updated-to: 47c85d29075b25aa0d61f39d56f61f72ac2aae67 2016-12-20
17:35:49 UTC
tags: trunk
comment: Ticket 1234abc workaround (user: clif)
changes: 1 file modified.
"fossil undo" is available to undo changes to the working checkout.
检查化石仓库的状态
在开始任何新开发之前,您应当将本地仓库的状态与主仓库进行比较。您不想浪费时间编写与已接受代码冲突的代码。
如何操作...
fossil status命令将报告项目的当前状态,包括是否有未提交的编辑,以及您的工作代码是否处于最新状态:
$ fossil status
repository: /home/clif/myProject/../myProject.fossil
local-root: /home/clif/myProject/
config-db: /home/clif/.fossil
checkout: 47c85d29075b25aa0d61f39d56f61f72ac2aae67 2016-12-20
17:35:49 UTC
parent: f3c579cd47d383980770341e9c079a87d92b17db 2016-12-20
17:33:38 UTC
tags: trunk
comment: Ticket 1234abc workaround (user: clif)
EDITED main.tcl
如果自您上次检出以来,您正在工作的分支上有新的提交,状态将包括一行类似于以下内容:
child: abcdef123456789... YYYY-MM-DD HH:MM::SS UTC
这表示在你的代码之后有一个提交。在你提交到分支头之前,你需要执行 fossil update 将你的工作副本同步。这可能需要你手动解决冲突。
请注意,fossil 只能报告你本地仓库中的数据。如果提交已经完成,但尚未推送到服务器并拉取到本地仓库,它们将不会显示。在执行 fossil status 之前,你应该先执行 fossil sync,以确认你的仓库已包含所有最新信息。
查看 fossil 历史
fossil server 和 fossil ui 命令启动 fossil 的 Web 服务器,并允许你通过你最喜欢的浏览器查看提交历史并浏览代码。
时间轴标签提供了一个树状结构的视图,展示了分支、提交和合并。Web 界面支持查看与提交相关联的源代码,并在不同版本之间执行差异比较。
如何操作...
启动 fossil 的 UI 模式。它会尝试找到你的浏览器并打开主页。如果失败,你可以手动将浏览器指向 fossil:
$ fossil ui
Listening for HTTP requests on TCP port 8080
$ konqueror 127.0.0.1:8080

查找 bug
Fossil 提供了帮助定位引入 bug 的提交的工具:
| 工具 | 描述 |
|---|---|
fossil diff |
该命令显示文件两个修订版本之间的差异 |
fossil blame |
该命令生成一份报告,显示文件中每行代码的提交信息 |
fossil bisect |
该命令使用二分查找法在应用程序的良好和坏版本之间进行切换 |
如何操作...
fossil diff 命令有多个选项。在寻找引入问题的代码时,我们通常需要对两个版本的文件进行差异比较。fossil diff 的 -from 和 -to 选项可以执行此操作:
$ fossil diff -from ID-1 -to ID-2FILENAME
ID-1 和 ID-2 是在仓库中使用的标识符。它们可能是 SHA-1 哈希值、标签、日期等。FILENAME 是提交到 fossil 的文件。
例如,要查找 main.tcl 两个修订版本之间的差异,请使用以下命令:
$ fossil diff -from 47c85 -to 7a7e25 main.tcl
Index: main.tcl
==================================================================
--- main.tcl
+++ main.tcl
@@ -9,10 +9,5 @@
set max 10
set min 1
+ while {$x < $max} {
- for {set x $min} {$x < $max} {incr x} {
- process $x
- }
-
还有更多...
两个修订版本之间的差异很有用,但查看整个文件的注释版更有帮助,这样你就能看到每一行代码的添加时间。
fossil blame 命令生成一个带有注释的文件列表,显示每一行代码的添加时间:
$ fossil blame main.tcl
7806f43641 2016-12-18 clif: # main.tcl
06e155a6c2 2016-12-19 clif: # Clif Flynt
b2420ef6be 2016-12-19 clif: # Packt fossil Test Script
a387090833 2016-12-19 clif:
76074da03c 2016-12-20 clif: for {set i 0} {$i < 10} {incr
i} {
76074da03c 2016-12-20 clif: puts "Buy my book"
2204206a18 2016-12-20 clif: }
7a7e2580c4 2016-12-20 clif:
当你知道某个版本存在问题,但另一个版本没有时,你需要集中精力查找引入问题的版本。
fossil bisect 命令提供了对这一过程的支持。它允许你定义代码的良好和坏版本,并自动检出这两个版本之间的版本进行测试。然后,你可以将此版本标记为好或坏,fossil 将重复此过程。Fossil bisect 还会生成报告,显示已测试的版本数量和待测试的版本数量。
如何操作...
fossil bisect reset 命令初始化好与坏指针。fossil bisect good 和 fossil bisect bad 命令将版本标记为好或坏,并检出介于好坏版本之间的代码版本:
$ fossil bisect reset
$ fossil bisect good 63e1e1
$ fossil bisect bad 47c85d
UPDATE main.tcl
-----------------------------------------------------------------------
updated-to: f64ca30c29df0f985105409700992d54e 2016-12-20 17:05:44 UTC
tags: trunk
comment: Reworked flaky test. (user: clif)
changes: 1 file modified.
"fossil undo" is available to undo changes to the working checkout.
2 BAD 2016-12-20 17:35:49 47c85d29075b25aa
3 CURRENT 2016-12-20 17:05:44 f64ca30c29df0f98
1 GOOD 2016-12-19 23:03:22 63e1e1290f853d76
在测试完f64ca版本的代码后,你可以将其标记为好或坏,fossil bisect将检出下一个版本进行测试。
还有更多内容…
fossil bisect status 命令生成一个报告,列出可用版本并标记已测试的版本:
$ fossil bisect status
2016-12-20 17:35:49 47c85d2907 BAD
2016-12-20 17:33:38 f3c579cd47
2016-12-20 17:30:03 c33415c255 CURRENT NEXT
2016-12-20 17:12:04 7a7e2580c4
2016-12-20 17:10:35 24edea3616
2016-12-20 17:05:44 f64ca30c29 GOOD
标记快照
Fossil 图中的每个节点都可以附加一个或多个标签。标签可以标识发布、分支或你可能希望引用的特定里程碑。例如,你可能希望创建一个 release-1 分支,并标记 release-1.0、release-1.1 等。标签可以与 checkout 或 merge 一起使用,而不是使用 SHA1 标识符。
标签通过 fossil tag 命令实现。Fossil 支持多个子命令来添加、取消、查找和列出标签。
fossil tag add 命令创建一个新标签:
$ fossil tag add TagName Identifier
如何操作…
TagName是你为分支命名的任何名称。
标识符是要标记的节点的标识符。标识符可以是以下之一:
-
一个分支名称:标记此分支上最新的提交
-
一个 SHA1 标识符:用这个 SHA1 标识符标记提交
-
一个日期戳 (YYYY-MM-DD):标记此日期戳之前的提交
-
一个时间戳 (YYYY-MM-DD HH:MM:SS):标记此时间戳之前的提交
# Tag the current tip of the trunk as release_1.0
$ fossil add tag release_1.0 trunk
# Tag the last commit on December 15 as beta_release_1
$ fossil add tag beta_release_1 2016-12-16
还有更多内容…
标签可以作为标识符来创建一个分支或分支:
$ fossil add tag newTag trunk
$ fossil branch new newTagBranch newTag
$ fossil checkout newTagBranch
标签可以使用-branch选项与提交一起创建分支:
$ fossil add tag myNewTag 2016-12-21
$ fossil checkout myNewTag
# edit and change
$ fossil commit -branch myNewTag
第七章:备份计划
本章将介绍以下配方:
-
使用
tar进行归档 -
使用
cpio进行归档 -
使用
gzip压缩数据 -
使用
zip进行归档和压缩 -
使用
pbzip2更快速地进行归档 -
使用压缩创建文件系统
-
使用
rsync进行备份快照 -
差异归档
-
使用
fsarchiver创建整个磁盘映像
介绍
没有人在需要备份时关心备份,除非被迫,否则没人会主动进行备份。因此,备份需要自动化。随着磁盘驱动技术的进步,最简单的做法是添加一个新硬盘或使用云存储进行备份,而不是备份到磁带驱动器。即便是便宜的硬盘或云存储,备份数据仍然应该压缩,以减少存储需求和传输时间。在存储数据到云之前,数据应该先进行加密。数据通常在加密之前会被归档并压缩。许多标准的加密程序可以通过脚本自动化。本章的配方描述了如何创建和维护文件或文件夹归档、压缩格式以及加密技术。
使用tar进行归档
tar命令最初是为了归档文件而编写的。它最初设计用于将数据存储到磁带中,因此得名Tape ARchive(磁带归档)。tar允许你将多个文件和目录合并为一个文件,同时保留文件属性,如所有者和权限。tar命令创建的文件通常被称为tarball。这些配方描述了使用tar创建归档的方法。
准备工作
tar命令默认在所有类 Unix 操作系统中都有。它具有简单的语法,并以便携的文件格式创建归档。它支持许多选项来微调其行为。
如何操作...
tar命令用于创建、更新、检查和解包归档。
- 使用
tar创建归档文件:
$ tar -cf output.tar [SOURCES]
c选项创建一个新归档,f选项告诉tar使用哪个文件名作为归档文件。f选项后面必须跟一个文件名:
$ tar -cf archive.tar file1 file2 file3 folder1 ..
-t选项列出归档的内容:
$ tar -tf archive.tar
file1
file2
-v或-vv标志会在输出中包含更多信息。这些功能分别称为详细输出(v)和非常详细输出(vv)。-v约定通常用于通过打印到终端生成报告的命令。-v选项会显示更多细节,例如文件权限、所有者组和修改日期:
$ tar -tvf archive.tar
-rw-rw-r-- shaan/shaan 0 2013-04-08 21:34 file1
-rw-rw-r-- shaan/shaan 0 2013-04-08 21:34 file2
文件名必须紧跟在-f后面,并且它应该是参数组中的最后一个选项。例如,如果你想要详细输出,你应该像这样使用选项:
$ tar -cvf output.tar file1 file2 file3 folder1 ..
它是如何工作的...
tar命令接受文件名列表或通配符(如*.txt)来指定源文件。当完成时,tar会将源文件归档到指定的文件中。
我们不能将成百上千的文件或文件夹作为命令行参数传递。因此,如果要归档许多文件,最好使用追加选项(稍后会解释)。
还有更多...
让我们一起看看tar命令支持的其他功能。
向归档文件中追加文件
-r选项将新文件追加到现有归档的末尾:
$ tar -rvf original.tar new_file
下一个示例创建一个包含一个文本文件的归档:
$ echo hello >hello.txt
$ tar -cf archive.tar hello.txt
-t选项显示归档中的文件。-f选项定义归档的名称:
$ tar -tf archive.tar
hello.txt
-r选项用于追加文件:
$ tar -rf archive.tar world.txt
$ tar -tf archive.tar
hello.txt
world.txt
现在归档包含了这两个文件。
从归档中提取文件和文件夹
-x选项将提取归档文件的内容到当前目录:
$ tar -xf archive.tar
当使用-x时,tar命令将提取归档的内容到当前目录。-C选项指定一个不同的目录来接收提取的文件:
$ tar -xf archive.tar -C /path/to/extraction_directory
该命令将归档内容提取到指定目录中。它会提取归档的全部内容。我们也可以通过将文件作为命令参数指定,仅提取部分文件:
$ tar -xvf file.tar file1 file4
前面的命令只提取了file1和file4,并忽略了归档中的其他文件。
stdin 和 stdout 与 tar
在归档时,我们可以指定stdout作为输出文件,这样管道中的另一个命令就可以将其作为stdin读取并处理归档。
该技术将通过安全外壳(SSH)连接传输数据,例如:
$ tar cvf - files/ | ssh user@example.com "tar xv -C Documents/"
在前面的示例中,文件/目录被添加到一个 tar 归档中,输出到stdout(由-表示),并提取到远程系统的Documents文件夹中。
拼接两个归档
-A选项将合并多个 tar 归档。
给定两个 tar 包,file1.tar 和 file2.tar,以下命令将把file2.tar的内容合并到file1.tar中:
$ tar -Af file1.tar file2.tar
通过列出内容来验证:
$ tar -tvf file1.tar
使用时间戳检查更新归档中的文件
追加选项会将给定的任何文件追加到归档中。如果文件已经存在于归档中,tar 将追加该文件,归档将包含重复的文件。更新选项-u只会追加比归档中现有文件更新的文件。
$ tar -tf archive.tar
filea
fileb
filec
如果只有在filea自上次被添加到archive.tar以来有修改,才将filea追加到归档中,使用以下命令:
$ tar -uf archive.tar filea
如果归档外的filea与归档内的filea具有相同的时间戳,则不会发生任何事情。
使用touch命令修改文件时间戳,然后再次尝试tar命令:
$ tar -uvvf archive.tar filea
-rw-r--r-- slynux/slynux 0 2010-08-14 17:53 filea
由于文件的时间戳比归档内的时间戳更新,因此该文件被追加,如-t选项所示:
$ tar -tf archive.tar
-rw-r--r-- slynux/slynux 0 2010-08-14 17:52 filea
-rw-r--r-- slynux/slynux 0 2010-08-14 17:52 fileb
-rw-r--r-- slynux/slynux 0 2010-08-14 17:52 filec
-rw-r--r-- slynux/slynux 0 2010-08-14 17:53 filea
注意,新的filea已经被追加到tar归档中。提取此归档时,tar 将选择filea的最新版本。
比较归档中的文件和文件系统中的文件
-d标志比较归档内的文件和文件系统中的文件。此功能可用于确定是否需要创建新的归档。
$ tar -df archive.tar
afile: Mod time differs
afile: Size differs
从归档中删除文件
-delete选项从归档中删除文件:
$ tar -f archive.tar --delete file1 file2 ..
或者,
$ tar --delete --file archive.tar [FILE LIST]
下一个示例演示删除文件:
$ tar -tf archive.tar
filea
fileb
filec
$ tar --delete --file archive.tar filea
$ tar -tf archive.tar
fileb
filec
使用 tar 归档进行压缩
默认情况下,tar命令归档文件,它不会压缩文件。Tar 支持选项来压缩生成的归档。压缩可以显著减少文件的大小。Tarball 通常被压缩成以下格式之一:
-
gzip 格式:
file.tar.gz或file.tgz -
bzip2 格式:
file.tar.bz2 -
Lempel-Ziv-Markov 格式:
file.tar.lzma
不同的tar标志用于指定不同的压缩格式:
-
bunzip2的
-j -
gzip的
-z -
lzma的
--lzma
可以使用压缩格式,而无需像之前那样明确指定特殊选项。tar可以根据输出的扩展名来压缩或根据输入文件的扩展名来解压缩。-a或auto-compress选项会使 tar 自动根据文件扩展名选择压缩算法:
$ tar acvf archive.tar.gz filea fileb filec
filea
fileb
filec
$ tar tf archive.tar.gz
filea
fileb
filec
排除一组文件不进行归档
-exclude [PATTEN]选项将排除与通配符模式匹配的文件不被归档。
例如,要排除所有.txt文件不被归档,可以使用以下命令:
$ tar -cf arch.tar * --exclude "*.txt"
请注意,模式应当用引号括起来,以防止 shell 展开它。
也可以通过-X标志排除在列表文件中提供的文件列表,方法如下:
$ cat list
filea
fileb
$ tar -cf arch.tar * -X list
现在它将filea和fileb排除在归档之外。
排除版本控制目录
tarball 的一种用途是分发源代码。许多源代码使用版本控制系统进行维护,如 subversion、Git、mercurial 和 CVS(请参阅前一章节)。版本控制下的代码目录通常包含如.svn或.git这样的特殊目录。这些由版本控制应用程序管理,对开发者之外的人没有用处。因此,它们应该从分发给用户的源代码 tarball 中排除。
为了在归档时排除与版本控制相关的文件和目录,可以与tar一起使用--exclude-vcs选项。考虑这个示例:
$ tar --exclude-vcs -czvvf source_code.tar.gz eye_of_gnome_svn
打印总字节数
-totals选项将打印复制到归档中的总字节数。请注意,这是实际数据的字节数。如果你包含了压缩选项,文件大小将小于归档的字节数。
$ tar -cf arc.tar * --exclude "*.txt" --totals
Total bytes written: 20480 (20KiB, 12MiB/s)
另见
- 本章节中的使用 gzip 压缩数据配方解释了
gzip命令
使用 cpio 进行归档
cpio应用程序是另一种类似于tar的归档格式。它用于将文件和目录存储在一个具有权限和所有权等属性的归档中。cpio格式用于 RPM 包归档(例如 Fedora 等distros使用),包含内核镜像的 Linux 内核initramfs文件等。这个示例将给出cpio的简单示例。
如何操作...
cpio应用程序通过stdin接收输入文件名,并将归档写入stdout。我们必须将stdout重定向到文件,以保存cpio输出:
- 创建测试文件:
$ touch file1 file2 file3
- 归档测试文件:
$ ls file* | cpio -ov > archive.cpio
- 列出
cpio档案中的文件:
$ cpio -it < archive.cpio
- 从
cpio档案中提取文件:
$ cpio -id < archive.cpio
它是如何工作的...
对于归档命令,选项如下:
-
-o:指定输出 -
-v:用于打印归档文件列表
使用cpio,我们也可以使用文件作为绝对路径进行归档。/usr/``somedir是一个绝对路径,因为它包含从根目录(/)开始的完整路径。
相对路径不会以/开头,而是从当前目录开始路径。例如,test/file意味着有一个名为test的目录,file位于test目录中。
在提取时,cpio会提取到绝对路径本身。然而,在tar的情况下,它会去除绝对路径中的/,并将其转换为相对路径。
列出给定cpio档案中所有文件的命令选项如下:
-
-i用于指定输入 -
-t用于列出
在提取命令中,-o表示提取,cpio会覆盖文件而不提示。-d选项告诉cpio根据需要创建新目录。
使用 gzip 压缩数据
gzip应用程序是 GNU/Linux 平台上常见的压缩格式。gzip、gunzip和zcat程序都处理gzip压缩。这些工具仅压缩/解压单个文件或数据流,不能直接归档目录和多个文件。幸运的是,gzip可以与tar和cpio一起使用。
如何做...
gzip将压缩文件,gunzip将其解压回原始状态:
- 使用
gzip压缩文件:
$ gzip filename
$ ls
filename.gz
- 提取
gzip压缩的文件:
$ gunzip filename.gz
$ ls
filename
- 要列出压缩文件的属性,可以使用以下命令:
$ gzip -l test.txt.gz
compressed uncompressed ratio uncompressed_name
35 6 -33.3% test.txt
gzip命令可以从stdin读取文件,并将压缩文件写入stdout。
从stdin读取数据并将压缩数据输出到stdout:
$ cat file | gzip -c > file.gz
-c选项用于指定将输出发送到stdout。
gzip的-c选项与cpio配合得很好:
$ ls * | cpio -o | gzip -c > cpiooutput.gz
$ zcat cpiooutput.gz | cpio -it
- 我们可以使用
--fast或--best选项来指定gzip的压缩级别,分别提供低压缩比和高压缩比。
还有更多内容...
gzip命令通常与其他命令一起使用,并提供高级选项来指定压缩比。
使用 gzip 压缩 tar 包
一个 gzip 压缩的 tar 包是一个使用 gzip 压缩的 tar 档案。我们可以使用两种方法来创建这样的 tar 包:
- 第一种方法如下:
$ tar -czvvf archive.tar.gz [FILES]
另外,可以使用以下命令:
$ tar -cavvf archive.tar.gz [FILES]
-z选项指定gzip压缩,-a选项指定压缩格式应根据扩展名确定。
- 第二种方法如下:
首先,创建一个 tar 包:
$ tar -cvvf archive.tar [FILES]
然后,压缩 tar 包:
$ gzip archive.tar
如果有许多文件(几百个)需要归档为 tar 包并进行压缩,我们可以使用第二种方法并做一些修改。命令行定义许多文件的问题是它只能接受有限数量的文件作为参数。为了解决这个问题,我们通过在循环中逐个添加文件并使用附加选项(-r)来创建 tar 文件,方法如下:
FILE_LIST="file1 file2 file3 file4 file5"
for f in $FILE_LIST;
do
tar -rvf archive.tar $f
done
gzip archive.tar
以下命令将提取一个 gzipped tar 包:
$ tar -xavvf archive.tar.gz -C extract_directory
在前面的命令中,-a 选项用于检测压缩格式。
zcat - 无需解压即可读取 gzipped 文件
zcat 命令从 .gz 文件中将未压缩的数据输出到 stdout,而不会重新创建原始文件。.gz 文件保持不变。
$ ls
test.gz
$ zcat test.gz
A test file
# file test contains a line "A test file"
$ ls
test.gz
压缩比
我们可以指定压缩比,压缩比的范围为 1 到 9,其中:
-
1 是最低的,但最快。
-
9 是最佳的,但最慢。
你可以按如下方式指定该范围内的任何比率:
$ gzip -5 test.img
默认情况下,gzip 使用值 -6,在牺牲一些速度的情况下,偏向于更好的压缩。
使用 bzip2
bzip2 在功能和语法上与 gzip 类似。不同之处在于,bzip2 提供更好的压缩效果,但比 gzip 更慢。
要使用 bzip2 压缩文件,请按以下方式使用命令:
$ bzip2 filename
按如下方式提取 bzipped 文件:
$ bunzip2 filename.bz2
压缩和提取 tar.bz2 文件的方式与之前讨论的 tar.gz 文件类似:
$ tar -xjvf archive.tar.bz2
这里,-j 指定使用 bzip2 格式压缩归档。
使用 lzma
lzma 压缩比 gzip 和 bzip2 提供更好的压缩比。
要使用 lzma 压缩文件,请按以下方式使用命令:
$ lzma filename
要提取 lzma 文件,请使用以下命令:
$ unlzma filename.lzma
tar 包可以使用 -lzma 选项进行压缩:
$ tar -cvvf --lzma archive.tar.lzma [FILES]
另外,可以使用以下方式:
$ tar -cavvf archive.tar.lzma [FILES]
要将使用 lzma 压缩的 tar 包提取到指定目录,请使用以下命令:
$ tar -xvvf --lzma archive.tar.lzma -C extract_directory
在前面的命令中,-x 用于提取文件。--lzma 指定使用 lzma 来解压缩生成的文件。
或者,可以使用以下命令:
$ tar -xavvf archive.tar.lzma -C extract_directory
另见
- 本章中的 使用 tar 归档 说明了
tar命令。
使用 zip 进行归档和压缩
ZIP 是一种流行的压缩归档格式,适用于 Linux、Mac 和 Windows。虽然它在 Linux 上不像 gzip 或 bzip2 那么常用,但在向其他平台分发数据时非常有用。
如何操作...
- 以下语法用于创建 zip 归档:
$ zip archive_name.zip file1 file2 file3...
考虑以下示例:
$ zip file.zip file
此处,将生成 file.zip 文件。
-r标志将递归归档文件夹:
$ zip -r archive.zip folder1 folder2
unzip命令会从 ZIP 文件中提取文件和文件夹:
$ unzip file.zip
unzip 命令提取内容,但不会删除归档文件(不同于 unlzma 或 gunzip)。
-u标志用于更新归档中的文件,替换为更新的文件:
$ zip file.zip -u newfile
-d标志会删除压缩包中的一个或多个文件:
$ zip -d arc.zip file.txt
-l标志用于列出归档中的文件:
$ unzip -l archive.zip
它是如何工作的...
虽然与我们已经讨论过的大多数归档和压缩工具类似,zip与lzma、gzip或bzip2不同的是,它在归档后不会删除源文件。虽然zip与tar类似,但它同时执行归档和压缩,而tar本身不执行压缩。
使用 pbzip2 更快的归档
大多数现代计算机至少有两个 CPU 核心。这几乎相当于两个真实的 CPU 在为你工作。然而,拥有多核 CPU 并不意味着程序一定会运行得更快;重要的是该程序能够充分利用多个核心。
到目前为止讲解的压缩命令只使用一个 CPU。pbzip2、plzip、pigz和lrzip命令是多线程的,能够使用多个核心,从而减少压缩文件所需的整体时间。
这些大多数发行版都没有预装,但可以通过 apt-get 或 yum 添加到你的系统中。
准备工作
pbzip2通常不会随大多数发行版预安装,你需要使用包管理器来安装它:
sudo apt-get install pbzip2
如何操作...
pbzip2命令将压缩单个文件:
pbzip2 myfile.tar
pbzip2检测到系统上的核心数量,并将myfile.tar压缩为myfile.tar.bz2。
- 要压缩和归档多个文件或目录,我们将
pbzip2与tar结合使用,如下所示:
tar cf sav.tar.bz2 --use-compress-prog=pbzip2 dir
另外,可以使用以下方法:
tar -c directory_to_compress/ | pbzip2 -c > myfile.tar.bz2
- 解压一个
pbzip2压缩文件的方法如下:
-d标志将解压文件:
pbzip2 -d myfile.tar.bz2
可以使用管道解压和提取 tar 归档:
pbzip2 -dc myfile.tar.bz2 | tar x
它是如何工作的...
pbzip2应用程序使用与bzip2相同的压缩算法,但它使用pthreads,一种线程库,同时压缩数据的不同块。线程对用户是透明的,但提供了更快的压缩速度。
像gzip或bzip2一样,pbzip2不创建归档文件。它只对单个文件起作用。要压缩多个文件和目录,我们需要将其与tar或cpio结合使用。
还有更多...
还有一些我们可以与pbzip2一起使用的有用选项:
手动指定 CPU 数量
-p选项指定要使用的 CPU 核心数。如果自动检测失败或你需要为其他任务释放核心,这是非常有用的:
pbzip2 -p4 myfile.tar
这将告诉pbzip2使用 4 个 CPU。
指定压缩比
从-1到-9的选项指定最快和最佳的压缩比,1是最快的,9是最好的压缩比。
创建带有压缩的文件系统
squashfs程序创建了一个只读的、压缩性很强的文件系统。squashfs程序可以将 2 到 3GB 的数据压缩成一个 700MB 的文件。Linux LiveCD(或 LiveUSB)发行版就是使用 squashfs构建的。这些 CD 使用一个只读压缩文件系统,将根文件系统保存在一个压缩文件中。该压缩文件可以通过回环挂载加载完整的 Linux 环境。当需要文件时,它们会被解压并加载到内存中运行,然后内存会被释放。
squashfs程序在你需要一个压缩存档并且需要随机访问文件时非常有用。完全解压缩一个大型压缩存档需要很长时间。回环挂载的存档提供了快速的文件访问,因为只有请求的部分存档会被解压。
准备工作
挂载squashfs文件系统是所有现代 Linux 发行版都支持的。然而,创建squashfs文件需要squashfs-tools,你可以通过包管理器安装它:
$ sudo apt-get install squashfs-tools
另外,可以使用以下方式:
$ yum install squashfs-tools
如何操作...
- 使用
mksquashfs命令通过添加源目录和文件来创建squashfs文件:
$ mksquashfs SOURCES compressedfs.squashfs
源可以是通配符、文件或文件夹路径。
考虑以下示例:
$ sudo mksquashfs /etc test.squashfs
Parallel mksquashfs: Using 2 processors
Creating 4.0 filesystem on test.squashfs, block size 131072.
[=======================================] 1867/1867 100%
更多详细信息将在终端上显示。为了节省空间,输出将被简化。
- 要将
squashfs文件挂载到挂载点,使用回环挂载,方法如下:
# mkdir /mnt/squash
# mount -o loop compressedfs.squashfs /mnt/squash
你可以在/mnt/squashfs访问内容。
还有更多...
squashfs文件系统可以通过指定额外的参数进行定制。
在创建squashfs文件时排除文件
-e标志将排除文件和文件夹:
$ sudo mksquashfs /etc test.squashfs -e /etc/passwd /etc/shadow
-e选项将排除/etc/``passwd和/etc/``shadow文件来自squashfs文件系统。
-ef选项读取包含排除文件列表的文件:
$ cat excludelist
/etc/passwd
/etc/shadow
$ sudo mksquashfs /etc test.squashfs -ef excludelist
如果我们想在排除列表中支持通配符,请使用-wildcard作为参数。
使用 rsync 备份快照
数据备份是需要定期进行的。除了本地备份,我们可能还需要进行远程位置之间的数据备份。rsync命令能够在最小化传输时间的同时,将文件和目录从一个位置同步到另一个位置。rsync相比cp命令的优点是:rsync会比较修改日期,只复制较新的文件,rsync支持跨远程机器的数据传输,并且支持压缩和加密。
如何操作...
- 要将源目录复制到目标位置,请使用以下命令:
$ rsync -av source_path destination_path
考虑以下示例:
$ rsync -av /home/slynux/data
slynux@192.168.0.6:/home/backups/data
在上述命令中:
-
-a表示归档 -
-v(详细模式)在标准输出上打印详细信息或进度
上述命令将递归地将所有文件从源路径复制到目标路径。源路径和目标路径可以是远程的或本地的。
- 要将数据备份到远程服务器或主机,请使用以下命令:
$ rsync -av source_dir username@host:PATH
要在目标位置保持镜像,请定期运行相同的rsync命令。它只会将更改过的文件复制到目标位置。
- 要将数据从远程主机恢复到
localhost,请使用以下命令:
$ rsync -av username@host:PATH destination
rsync命令使用 SSH 连接到远程机器,因此你需要以user@host格式提供远程机器的地址,其中 user 是用户名,host 是附加在远程机器上的 IP 地址或主机名。PATH是远程机器上需要复制数据的路径。
确保远程机器上已安装并运行 OpenSSH 服务器。此外,为避免在远程机器上输入密码,请参考第八章中的无密码 SSH 自动登录示例,老男孩网络。
- 在传输过程中压缩数据可以显著优化传输速度。
rsync -z选项指定在传输过程中压缩数据:
$ rsync -avz source destination
- 要将一个目录同步到另一个目录,请使用以下命令:
$ rsync -av /home/test/ /home/backups
上述命令将源路径(/home/test)复制到一个名为 backups 的现有文件夹中。
- 要将一个完整的目录复制到另一个目录中,请使用以下命令:
$ rsync -av /home/test /home/backups
此命令将源路径(/home/test)复制到一个名为 backups 的目录中,创建该目录。
对于路径格式,如果源路径末尾有/,rsync将会把source_path指定的末尾目录的内容复制到目标位置。
如果源路径末尾没有/,rsync将会把整个目录复制到目标位置。
添加-r选项将强制rsync递归复制目录的所有内容。
它是如何工作的...
rsync命令与源路径和目标路径一起工作,路径可以是本地的也可以是远程的。两个路径都可以是远程路径。通常,远程连接使用 SSH 进行,以提供安全的双向通信。本地路径和远程路径如下所示:
-
/home/user/data(本地路径) -
user@192.168.0.6:/home/backups/data(远程路径)
/home/user/data指定了执行rsync命令的机器中的绝对路径。user@192.168.0.6:/home/backups/data指定了该路径是在 IP 地址为192.168.0.6的机器上,并以user用户登录,路径为/home/backups/data。
还有更多内容...
rsync命令支持多个命令行选项,用于精细调整其行为。
使用 rsync 进行归档时排除文件
-exclude和-exclude-from选项指定不应被传输的文件:
--exclude PATTERN
我们可以指定一个排除文件的通配符模式。请参考以下示例:
$ rsync -avz /home/code/app /mnt/disk/backup/code --exclude "*.o"
此命令会排除.o文件的备份。
或者,我们也可以通过提供一个列表文件来指定需要排除的文件。
使用--exclude-from FILEPATH。
在更新 rsync 备份时删除不存在的文件
默认情况下,rsync不会从目标位置删除那些在源位置不存在的文件。-delete选项会删除那些在源位置不存在的目标文件:
$ rsync -avz SOURCE DESTINATION --delete
安排定期备份
你可以创建一个cron作业,定期安排备份。
示例如下:
$ crontab -ev
添加以下行:
0 */10 * * * rsync -avz /home/code user@IP_ADDRESS:/home/backups
前面的crontab条目将rsync安排为每 10 小时执行一次。
*/10是crontab语法中的小时位置。/10表示每 10 小时执行一次备份。如果*/10写在分钟位置,则表示每 10 分钟执行一次。
查看 第十章 中的 使用 cron 调度 章节,管理调用,了解如何配置 crontab。
差异归档
到目前为止描述的备份解决方案是文件系统在当时状态的完整副本。当你立即发现问题并需要最新快照来恢复时,这个快照非常有用。如果你没有及时发现问题,直到新快照创建时,之前的好数据已被当前坏数据覆盖,那么它就无法工作。
文件系统的归档提供了文件变更的历史记录。当你需要恢复损坏文件的旧版本时,这非常有用。
rsync、tar 和 cpio 可以用来制作文件系统的每日快照。然而,每天备份整个文件系统是非常昂贵的。为每周的每一天创建一个单独的快照将需要原始文件系统七倍的空间。
差异备份仅保存自上次完整备份以来发生变化的数据。Unix 的 dump/restore 工具支持这种类型的归档备份。不幸的是,这些工具是围绕磁带驱动器设计的,并且不容易使用。
find 工具可以与 tar 或 cpio 配合使用,以复制这种功能。
如何操作...
使用 tar 创建初始完整备份:
tar -cvz /backup/full.tgz /home/user
使用 find 的 -newer 标志来确定自完整备份创建以来哪些文件发生了变化,并创建新的归档:
tar -czf day-`date +%j`.tgz `find /home/user -newer
/backup/full.tgz`
它是如何工作的...
find 命令生成一个自完整备份创建以来已被修改的所有文件的列表 (/backup/full.tgz)。
date 命令根据儒略日期生成文件名。因此,年度的第一个差异备份将是 day-1.tgz,1 月 2 日的备份将是 day-2.tgz,依此类推。
随着越来越多的文件从初始完整备份中发生变化,差异归档会每天变得更大。当差异归档变得太大时,进行新的完整备份。
使用 fsarchiver 创建整个磁盘镜像
fsarchiver 应用程序可以将磁盘分区的内容保存为压缩归档文件。与 tar 或 cpio 不同,fsarchiver 保留扩展文件属性,并且可以恢复到没有当前文件系统的磁盘。fsarchiver 应用程序能够识别并保留 Windows 文件属性以及 Linux 属性,因此适合用于迁移 Samba 挂载的分区。
准备工作
fsarchiver 应用程序在大多数发行版中默认未安装。你需要使用你的包管理器安装它。更多信息,请访问 www.fsarchiver.org/Installation。
如何操作...
- 创建一个
filesystem/partition的备份。
像这样使用 fsarchiver 的 savefs 选项:
fsarchiver savefs backup.fsa /dev/sda1
这里,backup.fsa 是最终的备份文件,/dev/sda1 是要备份的分区
- 同时备份多个分区。
如前所述,使用savefs选项并将分区作为最后的参数传递给fsarchiver:
fsarchiver savefs backup.fsa /dev/sda1 /dev/sda2
- 从备份归档中恢复一个分区。
如此使用fsarchiver的restfs选项:
fsarchiver restfs backup.fsa id=0,dest=/dev/sda1
id=0表示我们希望从归档中选择第一个分区,并将其恢复到指定的分区dest=/dev/sda1。
从备份归档中恢复多个分区。
如前所述,使用restfs选项,方法如下:
fsarchiver restfs backup.fsa id=0,dest=/dev/sda1
id=1,dest=/dev/sdb1
在这里,我们使用两组id,dest参数来告诉fsarchiver从备份中恢复前两个分区到两个物理分区。
它是如何工作的...
与 tar 类似,fsarchiver检查文件系统以创建文件列表,然后将这些文件保存到一个压缩归档文件中。不同于仅保存文件信息的 tar,fsarchiver也会执行文件系统的备份。这使得在新分区上恢复备份时更为方便,因为不需要重新创建文件系统。
如果你第一次看到/dev/sda1这样的分区标记,这需要一些解释。Linux 中的/dev目录保存着被称为设备文件的特殊文件,这些文件指向物理设备。sda1中的sd代表SATA磁盘,接下来的字母可以是 a、b、c 等,后面跟着分区号。

第八章:老男孩网络
本章将介绍以下配方:
-
设置网络
-
让我们开始 Ping!
-
跟踪 IP 路由
-
列出网络上所有可用的机器
-
使用 SSH 在远程主机上运行命令
-
在远程机器上运行图形命令
-
通过网络传输文件
-
连接到无线网络
-
无密码自动登录 SSH
-
使用 SSH 进行端口转发
-
将远程驱动器挂载到本地挂载点
-
网络流量和端口分析
-
测量网络带宽
-
创建任意套接字
-
构建桥接
-
共享互联网连接
-
使用
iptables构建基础防火墙 -
创建虚拟私人网络
介绍
网络是将计算机连接起来以便交换信息的行为。最广泛使用的网络协议栈是 TCP/IP,其中每个节点都被分配一个唯一的 IP 地址以进行标识。如果你已经熟悉网络,可以跳过这部分介绍。
TCP/IP 网络通过将数据包从一个节点传输到另一个节点来工作。每个数据包都包含目标的 IP 地址以及可以处理该数据的应用程序的端口号。
当一个节点接收到一个数据包时,它会检查该数据包是否是其目标。如果是,它会检查端口号,并调用相应的应用程序来处理数据。如果该节点不是目标节点,它会根据网络状况判断,并将数据包传递给更接近最终目标的节点。
Shell 脚本可用于配置网络中的节点、测试机器的可用性、自动化在远程主机上的命令执行等。本章提供了涉及网络的工具和命令的配方,并展示如何有效使用它们。
设置网络
在深入了解基于网络的操作之前,首先需要具备设置网络、相关术语以及分配 IP 地址、添加路由等命令的基本知识。本篇提供了 GNU/Linux 网络中使用的命令概览。
准备工作
网络接口通过有线或无线连接将计算机与网络物理连接。Linux 使用诸如eth0、eth1或enp0s25(指以太网接口)这样的名称来表示网络接口。其他接口,如usb0、wlan0和tun0,分别用于 USB 网络接口、无线局域网和隧道。
在本配方中,我们将使用以下命令:ifconfig、route、nslookup和host。
ifconfig命令用于配置和显示有关网络接口、子网掩码等的详细信息。它应该位于/sbin/ifconfig。
如何操作...
- 列出当前的网络接口配置:
$ ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:6078 errors:0 dropped:0 overruns:0 frame:0
TX packets:6078 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:634520 (634.5 KB) TX bytes:634520 (634.5 KB)
wlan0 Link encap:EthernetHWaddr 00:1c:bf:87:25:d2
inet addr:192.168.0.82 Bcast:192.168.3.255 Mask:255.255.252.0
inet6addr: fe80::21c:bfff:fe87:25d2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:420917 errors:0 dropped:0 overruns:0 frame:0
TX packets:86820 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:98027420 (98.0 MB) TX bytes:22602672 (22.6 MB)
ifconfig输出的最左侧列显示网络接口的名称,右侧列则展示与对应网络接口相关的详细信息。
- 设置网络接口的 IP 地址,请使用以下命令:
# ifconfig wlan0 192.168.0.80
你需要以 root 身份运行上述命令
192.168.0.80 被定义为无线设备 wlan0 的地址
要设置子网掩码以及 IP 地址,请使用以下命令:
# ifconfig wlan0 192.168.0.80 netmask 255.255.252.0
- 许多网络使用动态主机配置协议(DHCP)在计算机连接到网络时自动分配 IP 地址。当您的计算机连接到自动分配 IP 地址的网络时,
dhclient命令会分配 IP 地址。如果通过 DHCP 分配地址,请使用dhclient而不是手动选择可能与网络上另一台机器冲突的地址。许多 Linux 发行版在检测到网络电缆连接时会自动调用dhclient。
# dhclient eth0
还有更多内容...
ifconfig 命令可以与其他 Shell 工具结合使用,生成特定的报告。
打印网络接口列表
下面这个单行命令序列会显示系统上可用的网络接口:
$ ifconfig | cut -c-10 | tr -d ' ' | tr -s 'n'
lo
wlan0
ifconfig 输出的每行的前十个字符用于写入网络接口的名称。因此,我们使用 cut 提取每行的前十个字符。tr -d ' ' 删除每行中的所有空格字符。现在,使用 tr -s '\n' 压缩每行的 n 换行符,以生成接口名称列表。
显示 IP 地址
ifconfig 命令显示系统上每个活动网络接口的详细信息。但是,我们可以使用以下命令将其限制为特定接口:
$ ifconfig iface_name
考虑以下示例:
$ ifconfig wlan0
wlan0 Link encap:EthernetHWaddr 00:1c:bf:87:25:d2
inet addr:192.168.0.82 Bcast:192.168.3.255 Mask:255.255.252.0
inet6 addr: fe80::3a2c:4aff:6e6e:17a9/64 Scope:Link
UP BROADCAST RUNNINT MULTICAST MTU:1500 Metric:1
RX Packets...
要控制设备,我们需要 IP 地址、广播地址、硬件地址和子网掩码:
-
HWaddr 00:1c:bf:87:25:d2: 这是硬件地址(MAC 地址) -
inet addr:192.168.0.82: 这是 IP 地址 -
Bcast:192.168.3.255: 这是广播地址 -
Mask:255.255.252.0: 这是子网掩码
要从 ifconfig 输出中提取 IP 地址,请使用此命令:
$ ifconfig wlan0 | egrep -o "inetaddr:[^ ]*" | grep -o "[0-9.]*"
192.168.0.82
egrep -o "inetaddr:[^ ]*" 命令返回 inet addr:192.168.0.82。该模式以 inetaddr: 开头,并以任何非空格字符序列结束(由 [^ ]* 指定)。下一个命令 grep -o "[0-9.]*" 将其输入减少为仅数字和点号,并打印出一个 IP4 地址。
伪造硬件地址(MAC 地址)
当认证或过滤基于硬件地址时,我们可以使用硬件地址伪造。硬件地址在 ifconfig 输出中显示为 HWaddr 00:1c:bf:87:25:d2。
ifconfig 的 hw 子命令将定义设备类和 MAC 地址:
# ifconfig eth0 hw ether 00:1c:bf:87:25:d5
在上述命令中,00:1c:bf:87:25:d5 是要分配的新 MAC 地址。当我们需要通过 MAC 身份验证的服务提供商访问互联网时,这是非常有用的,这些服务提供商为单台计算机提供互联网访问。
注意:此定义仅在计算机重新启动之前有效。
名称服务器和 DNS(域名服务)
互联网的底层地址方案是点分十进制格式(如 83.166.169.231)。人类更喜欢使用单词而不是数字,因此互联网上的资源通过被称为 URL 或 域名 的单词串来标识。例如, www.packtpub.com 是一个域名,它对应着一个 IP 地址。该站点可以通过数字或字符串名称来标识。
将 IP 地址映射到符号名称的技术称为 域名服务 (DNS)。当我们输入 www.google.com 时,我们的计算机会使用 DNS 服务器将域名解析为相应的 IP 地址。在本地网络中,我们设置本地 DNS,通过符号名称为本地机器命名。
名称服务器在 /etc/resolv.conf 中定义:
$ cat /etc/resolv.conf
# Local nameserver
nameserver 192.168.1.1
# External nameserver
nameserver 8.8.8.8
我们可以通过编辑该文件手动添加名称服务器,也可以使用一行命令:
# sudo echo nameserver IP_ADDRESS >> /etc/resolv.conf
获取 IP 地址的最简单方法是使用 ping 命令访问域名。回复中包括了 IP 地址:
$ ping google.com
PING google.com (64.233.181.106) 56(84) bytes of data.
数字 64.233.181.106 是 google.com 服务器的 IP 地址。
一个域名可能映射到多个 IP 地址。在这种情况下,ping 显示列表中的一个地址。要获取与域名关联的所有地址,我们应使用 DNS 查找工具。
DNS 查找
几个 DNS 查找工具提供从命令行进行名称和 IP 地址解析。host 和 nslookup 是两个常见的实用工具。
host 命令列出所有与域名关联的 IP 地址:
$ host google.com
google.com has address 64.233.181.105
google.com has address 64.233.181.99
google.com has address 64.233.181.147
google.com has address 64.233.181.106
google.com has address 64.233.181.103
google.com has address 64.233.181.104
nslookup 命令将名称映射到 IP 地址,也可以将 IP 地址映射到名称:
$ nslookup google.com
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: google.com
Address: 64.233.181.105
Name: google.com
Address: 64.233.181.99
Name: google.com
Address: 64.233.181.147
Name: google.com
Address: 64.233.181.106
Name: google.com
Address: 64.233.181.103
Name: google.com
Address: 64.233.181.104
Server: 8.8.8.8
前述命令行片段中的最后一行对应用于解析的默认名称服务器。
通过在 /etc/hosts 文件中添加条目,可以为 IP 地址解析添加符号名称。
/etc/hosts 中的条目遵循此格式:
IP_ADDRESS name1 name2 ...
你可以这样更新 /etc/hosts:
# echo IP_ADDRESS symbolic_name>> /etc/hosts
请考虑以下示例:
# echo 192.168.0.9 backupserver>> /etc/hosts
添加此条目后,任何对 backupserver 的解析都将解析为 192.168.0.9。
如果 backupserver 有多个名称,你可以在同一行中包括它们:
# echo 192.168.0.9 backupserver backupserver.example.com >> /etc/hosts
显示路由表信息
拥有互联网络是常见的。例如,工作或学校的不同部门可能在不同的网络中。当一个网络中的设备想要与另一个网络中的设备通信时,它需要通过一个对两个网络都通用的设备发送数据包。这个设备称为 网关,它的功能是将数据包从一个网络路由到另一个网络。
操作系统维护一个称为 路由表 的表格,其中包含有关如何在网络上通过机器转发数据包的信息。route 命令显示路由表:
$ route
Kernel IP routing table
Destination Gateway GenmaskFlags Metric Ref UseIface
192.168.0.0 * 255.255.252.0 U 2 0 0wlan0
link-local * 255.255.0.0 U 1000 0 0wlan0
default p4.local 0.0.0.0 UG 0 0 0wlan0
或者,你也可以使用以下方法:
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref UseIface
192.168.0.0 0.0.0.0 255.255.252.0 U 2 0 0 wlan0
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 wlan0
0.0.0.0 192.168.0.4 0.0.0.0 UG 0 0 0 wlan0
使用 -n 参数指定显示数字地址。默认情况下,route 会将数字地址映射到名称。
当系统不知道到达目标的路径时,它会将数据包发送到默认网关。默认网关可能是通向互联网的链接或是一个部门间的路由器。
route add 命令可以添加默认网关:
# route add default gw IP_ADDRESS INTERFACE_NAME
请看这个例子:
# route add default gw 192.168.0.1 wlan0
另见
-
第一章《Shell 脚本》中的 使用变量和环境变量 章节,解释了
PATH变量。 -
第四章《开车与发短信》中的 使用 grep 搜索和挖掘文件内容 章节,解释了
grep命令。
让我们来 ping 吧!
ping 命令是一个基本的网络命令,支持所有主流操作系统。Ping 用于验证网络中主机之间的连接性,并识别可访问的计算机。
如何操作...
ping 命令使用 互联网控制消息协议(ICMP)数据包来检查网络中两台主机的连接性。当这些回显数据包被发送到目标时,如果连接成功,目标会做出回应。如果没有到达目标的路由,或者目标到请求者的路由不可知,ping 请求可能会失败。
对一个地址进行 ping 测试将检查主机是否可达:
$ ping ADDRESS
ADDRESS 可以是主机名、域名或 IP 地址本身。
默认情况下,ping 将持续发送数据包,回复信息会打印在终端上。通过按 Ctrl + C 停止 ping 测试过程。
考虑以下示例:
- 当主机可达时,输出将类似于以下内容:
$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=1.44 ms
^C
--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.440/1.440/1.440/0.000 ms
$ ping google.com
PING google.com (209.85.153.104) 56(84) bytes of data.
64 bytes from bom01s01-in-f104.1e100.net (209.85.153.104):
icmp_seq=1 ttl=53 time=123 ms
^C
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 123.388/123.388/123.388/0.000 ms
- 当主机不可达时,输出将类似于此:
$ ping 192.168.0.99
PING 192.168.0.99 (192.168.0.99) 56(84) bytes of data.
From 192.168.0.82 icmp_seq=1 Destination Host Unreachable
From 192.168.0.82 icmp_seq=2 Destination Host Unreachable
如果目标无法到达,ping 命令会返回 Destination Host Unreachable 错误消息。
网络管理员通常会配置设备,如路由器,不响应 ping。这样做是为了降低安全风险,因为攻击者(通过暴力攻击)可以利用 ping 查找机器的 IP 地址。
还有更多...
除了检查网络中两点之间的连接性外,ping 命令还会返回其他信息。往返时间和丢包报告可用于判断网络是否正常工作。
往返时间
ping 命令会显示每个发送并返回的数据包的 往返时间(RTT)。RTT 以毫秒为单位报告。在内部网络中,RTT 小于 1ms 很常见。在 ping 测试互联网站点时,RTT 通常为 10-400 ms,可能超过 1000 ms:
--- google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4000ms
rtt min/avg/max/mdev = 118.012/206.630/347.186/77.713 ms
这里,最小 RTT 为 118.012 ms,平均 RTT 为 206.630 ms,最大 RTT 为 347.186 ms。ping 输出中的 mdev(77.713 ms)参数代表均方差。
序列号
每个 ping 发送的数据包都会分配一个序号,从 1 开始,直到 ping 停止。如果网络接近饱和,数据包可能会因为碰撞和重试而乱序返回,或者完全丢失:
$> ping example.com
64 bytes from example.com (1.2.3.4): icmp_seq=1 ttl=37 time=127.2 ms
64 bytes from example.com (1.2.3.4): icmp_seq=3 ttl=37 time=150.2 ms
64 bytes from example.com (1.2.3.4): icmp_seq=2 ttl=30 time=1500.3 ms
在这个例子中,第二个包被丢弃,然后在超时后重试,导致其顺序被打乱,并且返回的往返时间更长。
生存时间
每个 ping 包都有一个预定义的跳数限制,超过这个值就会被丢弃。每个路由器都会将该值减去 1。这个值表示从你的系统到你正在 ping 的网站之间有多少个路由器。初始的生存时间(TTL)值会根据你的平台或 ping 版本有所不同。你可以通过 ping 回环连接来确定初始值:
$> ping 127.0.0.1
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.049 ms
$> ping www.google.com
64 bytes from 173.194.68.99: icmp_seq=1 ttl=45 time=49.4 ms
在这个例子中,我们 ping 回环地址以确定 TTL 值(没有跳数的情况下为 64)。然后我们 ping 一个远程站点,并将 TTL 值从我们的无跳值中减去,以确定两个站点之间有多少跳数。在这个例子中,64-45 等于 19 跳。
TTL 值在两个站点之间通常是恒定的,但当条件需要选择替代路径时可能会发生变化。
限制发送的包数
ping 命令发送回显包并等待回显回复,直到通过按 Ctrl + C 停止。-c 参数将限制发送的回显包的数量:
-c COUNT
请看这个例子:
$ ping 192.168.0.1 -c 2
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=4.02 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=1.03 ms
--- 192.168.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.039/2.533/4.028/1.495 ms
在前一个例子中,ping 命令发送两个回显包后停止。这在我们需要通过脚本从 IP 地址列表中 ping 多台机器并检查它们的状态时非常有用。
ping 命令的返回状态
ping 命令在成功时返回退出状态0,失败时返回非零值。成功表示目标主机可达,而失败表示目标主机不可达。
返回状态可以通过以下方式获取:
$ ping domain -c2
if [ $? -eq0 ];
then
echo Successful ;
else
echo Failure
fi
路由追踪
当一个应用通过互联网请求服务时,服务器可能位于遥远的地方,并通过多个网关或路由器进行连接。traceroute 命令显示数据包在到达目标之前经过的所有中间网关的地址。traceroute 信息帮助我们了解每个数据包到达目标所经过的跳数。中间网关的数量表示网络中两个节点之间的有效距离,这个距离可能与物理距离无关。每次跳跃都会增加传输时间。路由器需要时间来接收、解码并转发数据包。
如何操作...
traceroute 命令的格式如下:
traceroute destinationIP
destinationIP 可以是数字或字符串:
$ traceroute google.com
traceroute to google.com (74.125.77.104), 30 hops max, 60 byte packets
1 gw-c6509.lxb.as5577.net (195.26.4.1) 0.313 ms 0.371 ms 0.457 ms
2 40g.lxb-fra.as5577.net (83.243.12.2) 4.684 ms 4.754 ms 4.823 ms
3 de-cix10.net.google.com (80.81.192.108) 5.312 ms 5.348 ms 5.327 ms
4 209.85.255.170 (209.85.255.170) 5.816 ms 5.791 ms 209.85.255.172 (209.85.255.172) 5.678 ms
5 209.85.250.140 (209.85.250.140) 10.126 ms 9.867 ms 10.754 ms
6 64.233.175.246 (64.233.175.246) 12.940 ms 72.14.233.114 (72.14.233.114) 13.736 ms 13.803 ms
7 72.14.239.199 (72.14.239.199) 14.618 ms 209.85.255.166 (209.85.255.166) 12.755 ms 209.85.255.143 (209.85.255.143) 13.803 ms
8 209.85.255.98 (209.85.255.98) 22.625 ms 209.85.255.110 (209.85.255.110) 14.122 ms
*
9 ew-in-f104.1e100.net (74.125.77.104) 13.061 ms 13.256 ms 13.484 ms
现代 Linux 发行版还附带了 mtr 命令,它类似于 traceroute,但显示实时数据,并会不断刷新。它对于检查网络承载质量非常有用。
列出网络中所有可用的机器
当我们监控一个大型网络时,需要检查所有机器的可用性。机器不可用可能有两个原因:未开机,或由于网络问题。我们可以编写一个 Shell 脚本来判断并报告哪些机器在网络中可用。
准备工作
在这个例子中,我们展示了两种方法。第一种方法使用 ping,第二种方法使用 fping。fping 命令比 ping 命令更适合脚本,并且有更多的功能。它可能不是你 Linux 发行版的一部分,但可以通过包管理器安装。
如何操作...
下一个示例脚本将使用 ping 命令找到网络中可见的机器:
#!/bin/bash
#Filename: ping.sh
# Change base address 192.168.0 according to your network.
for ip in 192.168.0.{1..255} ;
do
ping $ip -c 2 &> /dev/null ;
if [ $? -eq 0 ];
then
echo $ip is alive
fi
done
输出类似于以下内容:
$ ./ping.sh
192.168.0.1 is alive
192.168.0.90 is alive
它是如何工作的...
这个脚本使用 ping 命令来查找网络上可用的机器。它使用 for 循环遍历由 192.168.0.{1..255} 表达式生成的 IP 地址列表。{start..end} 表示法会生成从开始到结束的值。在这个例子中,它会创建从 192.168.0.1 到 192.168.0.255 的 IP 地址。
ping $ip -c 2 &> /dev/null 运行一个 ping 命令到相应的 IP 地址。-c 选项让 ping 只发送两个数据包。&> /dev/null 将 stderr 和 stdout 重定向到 /dev/null,因此终端上不会打印任何内容。脚本使用 $? 来评估退出状态。如果成功,退出状态为 0,并打印响应我们 ping 的 IP 地址。
在这个脚本中,为每个地址依次执行一个独立的 ping 命令。当某个 IP 地址没有回复时,脚本会变得很慢,因为每次 ping 必须等待超时才能开始下一个 ping。
还有更多...
接下来的脚本展示了如何改进 ping 脚本以及如何使用 fping。
并行 ping 测试
之前的脚本按顺序测试每个地址。每次测试的延迟会累积并变得很大。并行运行 ping 命令可以加快速度。将循环体括在 {}& 中会使 ping 命令并行运行。( ) 用来将一组命令括起来作为子 shell 执行,& 将其发送到后台:
#!/bin/bash
#Filename: fast_ping.sh
# Change base address 192.168.0 according to your network.
for ip in 192.168.0.{1..255} ;
do
(
ping $ip -c2 &> /dev/null ;
if [ $? -eq0 ];
then
echo $ip is alive
fi
)&
done
wait
在 for 循环中,我们执行了多个后台进程并退出循环,终止脚本。wait 命令会阻止脚本在所有子进程退出之前终止。
输出将按照 ping 响应的顺序显示。如果某些机器或网络段较慢,响应的顺序将与发送的顺序不同。
使用 fping
第二种方法使用了一种不同的命令,叫做 fping。fping 命令向多个 IP 地址发送 ICMP 消息,然后等待查看哪些地址会回复。它比第一种脚本运行得快得多。
fping 提供的选项包括以下内容:
-
fping的-a选项指定显示可用机器的 IP 地址 -
fping的-u选项指定显示不可达的机器 -
-g选项指定从指定的 IP/mask 或起始和结束 IP 地址的斜线子网掩码表示法生成一系列 IP 地址:
$ fping -a 192.160.1/24 -g
另外,可以使用以下方法:
$ fping -a 192.160.1 192.168.0.255 -g
2>/dev/null用于将由于主机不可达而打印的错误信息转储到一个空设备。
也可以手动指定一组 IP 地址作为命令行参数或通过stdin作为列表。考虑以下示例:
$ fping -a 192.168.0.1 192.168.0.5 192.168.0.6
# Passes IP address as arguments
$ fping -a <ip.list
# Passes a list of IP addresses from a file
另见
-
第一章中操作文件描述符和重定向的配方,Shell 一些操作,解释了数据重定向
-
第一章中比较与测试的配方,Shell 一些操作,解释了数字比较
使用 SSH 在远程主机上运行命令
SSH 代表安全外壳。它通过加密隧道连接两台计算机。SSH 允许您访问远程计算机上的 Shell,在该 Shell 中可以交互地运行单个命令并接收结果,或者启动交互式会话。
准备工作
SSH 并不是所有 GNU/Linux 发行版中预装的。您可能需要使用包管理器安装 openssh-server 和 openssh-client 包。默认情况下,SSH 运行在端口号 22 上。
如何操作...
- 要连接到运行 SSH 服务器的远程主机,可以使用以下命令:
$ ssh username@remote_host
该命令中的选项如下:
-
username是远程主机上存在的用户 -
remote_host可以是域名或 IP 地址
考虑这个示例:
$ ssh mec@192.168.0.1
The authenticity of host '192.168.0.1 (192.168.0.1)' can't be
established.
RSA key fingerprint is
2b:b4:90:79:49:0a:f1:b3:8a:db:9f:73:2d:75:d6:f9.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.0.1' (RSA) to the list of
known hosts.
Password:
Last login: Fri Sep 3 05:15:21 2010 from 192.168.0.82
mec@proxy-1:~$
SSH 会要求输入密码,成功验证后,它会连接到远程计算机的登录 Shell。
SSH 会执行指纹验证,以确保我们确实连接到目标远程计算机。这是为了避免所谓的中间人攻击,即攻击者试图冒充另一台计算机。SSH 默认会在第一次连接到服务器时存储指纹,并验证后续连接时指纹是否没有变化。
默认情况下,SSH 服务器运行在端口22。然而,某些服务器会在不同的端口运行 SSH 服务。在这种情况下,使用 -p port_num 选项与 ssh 命令一起指定端口。
- 连接到在端口
422上运行的 SSH 服务器:
$ ssh user@locahost -p 422
在 Shell 脚本中使用 ssh 时,我们不需要交互式 Shell,我们只想在远程系统上执行命令并处理命令的输出。
每次都输入密码对自动化脚本来说不太实际,因此应该配置 SSH 密钥的无密码登录。无密码自动登录与 SSH 配方在本章中解释了设置这一功能的 SSH 命令。
- 要在远程主机上运行命令并显示其输出到本地 Shell,可以使用以下语法:
$ sshuser@host 'COMMANDS'
考虑这个示例:
$ ssh mec@192.168.0.1 'whoami'
mec
您可以通过用分号分隔命令来提交多个命令:
$ ssh user@host "command1 ; command2 ; command3"
考虑以下示例:
$ ssh mec@192.168.0.1 "echo user: $(whoami);echo OS: $(uname)"
Password: user: mec OS: Linux
在这个示例中,执行的命令如下:
echo user: $(whoami);
echo OS: $(uname)
我们可以通过使用 ( ) 子 Shell 操作符,在命令序列中传递一个更复杂的子 Shell。
- 下一个示例是一个基于 SSH 的 Shell 脚本,用于收集一组远程主机的正常运行时间。正常运行时间是自上次开机以来的时间长度,可以通过
uptime命令获取。
假设IP_LIST中的所有系统都使用公共用户test。
#!/bin/bash
#Filename: uptime.sh
#Description: Uptime monitor
IP_LIST="192.168.0.1 192.168.0.5 192.168.0.9"
USER="test"
for IP in $IP_LIST;
do
utime=$(ssh ${USER}@${IP} uptime |awk '{ print $3 }' )
echo $IP uptime: $utime
done
期望输出:
$ ./uptime.sh
192.168.0.1 uptime: 1:50,
192.168.0.5 uptime: 2:15,
192.168.0.9 uptime: 10:15,
还有更多内容...
ssh命令可以执行多个附加选项。
带压缩的 SSH
SSH 协议支持数据传输压缩。当带宽有限时,这个功能特别有用。使用ssh命令的-C选项来启用压缩:
$ ssh -C user@hostname COMMANDS
将数据重定向到远程主机 shell 命令的 stdin
SSH 允许你将本地系统任务的输出作为输入传递给远程系统:
$ echo 'text' | ssh user@remote_host 'echo'
text
或者,可以使用以下方法:
# Redirect data from file as:
$ ssh user@remote_host 'echo' < file
在远程主机上,echo会打印通过stdin接收到的数据,而这些数据又是从本地主机的stdin传递过来的。
该功能可以用于将 tar 归档从本地主机传输到远程主机。详细内容请参见第七章,备份计划:
$> tar -czf - LOCALFOLDER | ssh 'tar -xzvf-'
在远程机器上运行图形命令
如果你尝试在远程机器上运行一个需要图形窗口的命令,你会看到类似cannot open display的错误信息。这是因为ssh终端尝试(并未成功)连接到远程机器上的 X 服务器。
如何操作...
要在远程服务器上运行图形应用程序,你需要设置$DISPLAY变量,以强制该应用程序连接到本地机器的 X 服务器:
ssh user@host "export DISPLAY=:0 ; command1; command2"""
这将在远程主机上启动图形输出。
如果你想在本地机器上显示图形输出,可以使用 SSH 的 X11 转发选项:
ssh -X user@host "command1; command2"
这将会在远程机器上运行命令,但会将图形显示在本地机器上。
另见
- 本章中的无密码自动登录 SSH配方解释了如何配置自动登录,执行命令时无需输入密码。
通过网络传输文件
网络计算机的一个主要用途是资源共享。文件是常见的共享资源。不同的文件传输方法包括使用 USB 闪存、sneakernet,以及通过 NFS 和 Samba 等网络链接进行文件传输。这些配方介绍了如何使用常见协议 FTP、SFTP、RSYNC 和 SCP 来传输文件。
准备工作
执行网络文件传输的命令在 Linux 安装时大多是默认可用的。可以使用传统的ftp命令或更新的lftp命令通过 FTP 传输文件,或通过 SSH 连接使用scp或sftp命令传输文件。还可以使用rsync命令在不同系统间同步文件。
如何操作...
文件传输协议(FTP)是一个较旧的协议,许多公共网站使用它来共享文件。此服务通常运行在21端口。FTP 要求远程主机上安装并运行 FTP 服务器。我们可以使用传统的ftp命令或更新的lftp命令来访问启用 FTP 的服务器。以下命令同时支持ftp和lftp。FTP 被广泛用于许多公共网站的文件共享。
要连接到 FTP 服务器并进行文件传输,使用以下命令:
$ lftpusername@ftphost
它将提示输入密码,然后显示已登录的提示符:
lftp username@ftphost:~>
你可以在此提示符下输入命令,如下所示:
-
cd directory:这将在远程系统上更改目录 -
lcd:这将更改本地机器上的目录 -
mkdir:这将在远程机器上创建一个目录 -
ls:这将列出远程机器上当前目录中的文件 -
get FILENAME:这将把文件下载到本地机器的当前目录:
lftp username@ftphost:~> get filename
put filename:这将从远程机器的当前目录上传文件:
lftp username@ftphost:~> put filename
quit命令将终止一个lftp会话。
lftp提示符支持自动补全
还有更多内容...
让我们了解通过网络进行文件传输时使用的额外技术和命令。
自动化 FTP 传输
lftp和ftp命令打开一个与用户的交互式会话。我们可以通过 Shell 脚本自动化 FTP 文件传输:
#!/bin/bash
#Automated FTP transfer
HOST=example.com'
USER='foo'
PASSWD='password'
lftp -u ${USER}:${PASSWD} $HOST <<EOF
binary
cd /home/foo
put testfile.jpg
quit
EOF
上述脚本的结构如下:
<<EOF
DATA
EOF
这用于通过stdin将数据发送到lftp命令。文件描述符和重定向操作,第一章的食谱《Shell Something Out》解释了重定向到stdin的各种方法。
-u选项使用我们定义的USER和PASSWD登录远程站点。binary命令将文件模式设置为二进制。
SFTP(安全 FTP)
SFTP 是一种文件传输系统,运行在 SSH 连接之上,并模拟 FTP 接口。它需要在远程系统上运行 SSH 服务器,而不是 FTP 服务器。它提供一个带有sftp提示符的交互式会话。
SFTP 支持与ftp和lftp相同的命令。
要启动sftp会话,使用以下命令:
$ sftp user@domainname
与lftp类似,sftp会话可以通过输入quit命令终止。
有时,SSH 服务器不会在默认端口22上运行。如果它在其他端口上运行,我们可以在-oPort=PORTNO后指定端口。请参考以下示例:
$ sftp -oPort=422 user@slynux.org
-oPort应该是sftp命令的第一个参数。
rsync 命令
rsync命令广泛用于通过网络复制文件以及进行备份快照。本章中详细描述了使用 rsync 进行备份快照。
第七章的食谱,备份计划。
SCP(安全拷贝程序)
SCP 是一种安全的文件拷贝命令,类似于旧版、不安全的远程拷贝工具rcp。文件通过加密通道使用 SSH 进行传输:
$ scp filename user@remotehost:/home/path
这将提示输入密码。像ssh一样,传输可以通过自动登录 SSH 技术实现免密登录。本章中的免密自动登录 SSH食谱解释了 SSH 自动登录。一旦 SSH 登录自动化,就可以在没有交互式密码提示的情况下执行 scp 命令。
remotehost可以是 IP 地址或域名。scp命令的格式如下:
$ scp SOURCE DESTINATION
SOURCE或DESTINATION可以采用username@host:/path格式:
$ scp user@remotehost:/home/path/filename filename
上述命令将文件从远程主机复制到当前目录,并使用给定的文件名。
如果 SSH 运行在与22不同的端口上,请使用-oPort,并采用相同的语法,sftp。
使用 scp 递归复制
-r参数告诉scp在两台机器之间递归地复制目录:
$ scp -r /home/usernameuser@remotehost:/home/backups
# Copies the directory /home/usernameto the remote backup
-p参数将使scp在复制文件时保留权限和模式。
另见
- 第一章中玩转文件描述符和重定向的食谱,解释了使用 EOF 的标准输入。
连接到无线网络
以太网连接配置简单,因为它是通过有线电缆连接的,没有像认证之类的特殊要求。然而,无线局域网需要一个扩展服务集标识符(ESSID)网络标识符,可能还需要一个密码短语。
准备工作
要连接到有线网络,只需使用ifconfig工具分配 IP 地址和子网掩码。无线网络连接需要iwconfig和iwlist工具。
如何操作...
本脚本将连接到使用WEP(有线等效隐私)的无线局域网:
#!/bin/bash
#Filename: wlan_connect.sh
#Description: Connect to Wireless LAN
#Modify the parameters below according to your settings
######### PARAMETERS ###########
IFACE=wlan0
IP_ADDR=192.168.1.5
SUBNET_MASK=255.255.255.0
GW=192.168.1.1
HW_ADDR='00:1c:bf:87:25:d2'
#Comment above line if you don't want to spoof mac address
ESSID="homenet"
WEP_KEY=8b140b20e7
FREQ=2.462G
#################################
KEY_PART=""
if [[ -n $WEP_KEY ]];
then
KEY_PART="key $WEP_KEY"
fi
if [ $UID -ne 0 ];
then
echo "Run as root"
exit 1;
fi
# Shut down the interface before setting new config
/sbin/ifconfig $IFACE down
if [[ -n $HW_ADDR ]];
then
/sbin/ifconfig $IFACE hw ether $HW_ADDR
echo Spoofed MAC ADDRESS to $HW_ADDR
fi
/sbin/iwconfig $IFACE essid $ESSID $KEY_PART freq $FREQ
/sbin/ifconfig $IFACE $IP_ADDR netmask $SUBNET_MASK
route add default gw $GW $IFACE
echo Successfully configured $IFACE
它是如何工作的...
ifconfig、iwconfig和route命令必须以 root 身份运行。因此,在脚本中执行任何操作之前会检查是否为 root 用户。
无线局域网连接需要诸如essid、key和frequency等参数。essid是要连接的无线网络的名称。一些网络使用 WEP 密钥进行认证,通常是一个五个或十个字符的十六进制密码。网络分配的频率是iwconfig命令所需的,用来将无线网卡与正确的无线网络连接。
iwlist工具将扫描并列出可用的无线网络:
# iwlist scan
wlan0 Scan completed :
Cell 01 - Address: 00:12:17:7B:1C:65
Channel:11
Frequency:2.462 GHz (Channel 11)
Quality=33/70 Signal level=-77 dBm
Encryption key:on
ESSID:"model-2"
Frequency参数可以从扫描结果中提取,来自Frequency:2.462 GHz (Channel 11)这一行。
示例中使用了 WEP。注意,WEP 是不安全的。如果你在管理无线网络,建议使用Wi-Fi 保护接入 2(WPA2)的变体。
另见
- 第一章中比较和测试的食谱,解释了字符串比较。
使用 SSH 实现无密码自动登录
SSH 在自动化脚本中广泛使用,因为它使得能够远程执行命令并读取远程主机的输出。通常,SSH 使用用户名和密码进行身份验证,这些信息会在执行 SSH 命令时提示输入。在自动化脚本中提供密码是不切实际的,因此我们需要实现自动登录。SSH 具有允许会话自动登录的功能。本食谱介绍了如何为自动登录创建 SSH 密钥。
准备工作
SSH 使用一种称为非对称密钥的加密技术,包含两个密钥——公钥和私钥,用于自动认证。ssh-keygen 应用程序会创建一对认证密钥。为了实现自动认证,公钥必须放置在服务器上(通过将公钥追加到 ~/.ssh/authorized_keys 文件中),而私钥文件应存在于客户端机器的 ~/.ssh 目录中。可以通过修改 /etc/ssh/sshd_config 配置文件来修改 SSH 配置选项(例如,authorized_keys 文件的路径和名称)。
如何操作...
实现 SSH 自动认证有两个步骤,具体如下:
-
在本地机器上创建 SSH 密钥
-
将公钥传输到远程主机并将其添加到
~/.ssh/authorized_keys(这需要访问远程机器)
要创建 SSH 密钥,请运行 ssh-keygen 命令,并指定加密算法类型为 RSA:
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_rsa):
Created directory '/home/username/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/username/.ssh/id_rsa.
Your public key has been saved in /home/username/.ssh/id_rsa.pub.
The key fingerprint is:
f7:17:c6:4d:c9:ee:17:00:af:0f:b3:27:a6:9c:0a:05 username@slynux-laptop
The key'srandomart image is:
+--[ RSA 2048]----+
| . |
| o . .|
| E o o.|
| ...oo |
| .S .+ +o.|
| . . .=....|
| .+.o...|
| . . + o. .|
| ..+ |
+-----------------+
您需要输入一个密码短语以生成公私钥对。虽然可以在不输入密码短语的情况下生成密钥对,但这样做不安全。
如果您打算编写使用自动登录的脚本来访问多台机器,应该留空密码短语,以防脚本在运行时要求输入密码短语。
ssh-keygen 程序会创建两个文件。~/.ssh/id_rsa.pub 和 ~/.ssh/id_rsa:id_rsa.pub 是生成的公钥,id_rsa 是私钥。公钥必须添加到需要从当前主机进行自动登录的远程服务器的 ~/.ssh/authorized_keys 文件中。
此命令将追加一个密钥文件:
$ ssh USER@REMOTE_HOST \
"cat >> ~/.ssh/authorized_keys" < ~/.ssh/id_rsa.pub
Password:
在之前的命令中提供登录密码。
从现在开始,自动登录已设置完成,因此 SSH 在执行过程中不会提示输入密码。使用以下命令测试:
$ ssh USER@REMOTE_HOST uname
Linux
您将不会被提示输入密码。大多数 Linux 发行版包括 ssh-copy-id,它会将您的私钥追加到远程服务器的适当 authorized_keys 文件中。这比前面描述的 ssh 技术更简洁:
ssh-copy-id USER@REMOTE_HOST
使用 SSH 进行端口转发
端口转发是一种将 IP 连接从一台主机重定向到另一台主机的技术。例如,如果您将 Linux/Unix 系统用作防火墙,则可以将连接重定向到端口 1234,并指向像 192.168.1.10:22 这样的内部地址,从外部世界通过 SSH 隧道访问内部机器。
如何操作...
您可以将本地机器上的端口转发到另一台机器上,也可以将远程机器上的端口转发到另一台机器。在以下示例中,一旦转发完成,您将得到一个 shell 提示符。保持此 shell 打开以使用端口转发,并在任何时候退出以停止端口转发。
- 此命令将把您本地机器的端口
8000转发到 www.kernel.org 的端口80:
ssh -L 8000:www.kernel.org:80user@localhost
将用户替换为您本地机器上的用户名。
- 该命令将会把远程机器上的端口 8000 转发到www.kernel.org的端口
80:
ssh -L 8000:www.kernel.org:80user@REMOTE_MACHINE
这里,将REMOTE_MACHINE替换为远程机器的主机名或 IP 地址,将user替换为你具有 SSH 访问权限的用户名。
还有更多内容...
使用非交互模式或反向端口转发时,端口转发更为有用。
非交互式端口转发
如果你只想设置端口转发,而不是保持一个打开的 Shell 会话,可以使用以下格式的ssh命令:
ssh -fL8000:www.kernel.org:80user@localhost -N
-f选项指示ssh在执行命令之前先分叉到后台。-N告诉ssh没有命令要运行;我们只是想进行端口转发。
反向端口转发
反向端口转发是 SSH 最强大的功能之一。它在以下情况特别有用:你有一台机器无法从互联网公开访问,但你希望别人能够访问这台机器上的服务。在这种情况下,如果你可以 SSH 连接到一台可以从互联网公开访问的远程机器,你可以在该远程机器上设置一个反向端口转发,将其转发到正在运行该服务的本地机器。
ssh -R 8000:localhost:80 user@REMOTE_MACHINE
该命令将把远程机器上的端口8000转发到本地机器上的端口80。请记得将REMOTE_MACHINE替换为远程机器的主机名或 IP 地址。
使用此方法时,如果你在远程机器上浏览http://localhost:8000,将会连接到本地机器上运行在端口80的 Web 服务器。
将远程驱动器挂载到本地挂载点
拥有一个本地挂载点来访问远程主机文件系统可以方便地进行读写数据传输操作。SSH 是常见的传输协议。sshfs应用程序利用 SSH 协议,让你可以将远程文件系统挂载到本地挂载点。
准备工作
sshfs在 GNU/Linux 发行版中默认并不安装。使用包管理器安装sshfs。sshfs是 FUSE 文件系统包的扩展,允许用户将各种数据挂载为本地文件系统。FUSE 的变体在 Linux、Unix、Mac OS/X、Windows 等系统上得到支持。
欲了解更多有关 FUSE 的信息,请访问其官方网站:fuse.sourceforge.net/。
如何操作...
要将远程主机上的文件系统位置挂载到本地挂载点:
# sshfs -o allow_otheruser@remotehost:/home/path /mnt/mountpoint
Password:
在提示时输入密码。密码被接受后,远程主机/home/path上的数据可以通过本地挂载点/mnt/mountpoint进行访问。
要卸载,请使用以下命令:
# umount /mnt/mountpoint
另见
- 本章的使用 SSH 在远程主机上运行命令食谱解释了
ssh命令。
网络流量和端口分析
每个访问网络的应用程序都通过端口进行。列出打开的端口、使用端口的应用程序和运行该应用程序的用户是一种跟踪系统预期和意外使用情况的方法。这些信息可以用于分配资源以及检查 rootkit 或其他恶意软件。
准备工作
有多种命令可用于列出网络节点上运行的端口和服务。lsof 和 netstat 命令在大多数 GNU/Linux 发行版中都可用。
如何操作...
lsof (列出打开文件)命令将列出打开的文件。-i 选项将其限制为打开的网络连接:
$ lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE
NAME
firefox-b 2261 slynux 78u IPv4 63729 0t0 TCP
localhost:47797->localhost:42486 (ESTABLISHED)
firefox-b 2261 slynux 80u IPv4 68270 0t0 TCP
slynux-laptop.local:41204->192.168.0.2:3128 (CLOSE_WAIT)
firefox-b 2261 slynux 82u IPv4 68195 0t0 TCP
slynux-laptop.local:41197->192.168.0.2:3128 (ESTABLISHED)
ssh 3570 slynux 3u IPv6 30025 0t0 TCP
localhost:39263->localhost:ssh (ESTABLISHED)
ssh 3836 slynux 3u IPv4 43431 0t0 TCP
slynux-laptop.local:40414->boney.mt.org:422 (ESTABLISHED)
GoogleTal 4022 slynux 12u IPv4 55370 0t0 TCP
localhost:42486 (LISTEN)
GoogleTal 4022 slynux 13u IPv4 55379 0t0 TCP
localhost:42486->localhost:32955 (ESTABLISHED)
lsof 输出中的每一项都对应一个具有活动网络端口的服务。输出的最后一列包含类似以下内容的行:
laptop.local:41197->192.168.0.2:3128
在此输出中,laptop.local:41197 对应 localhost,而 192.168.0.2:3128 对应远程主机。41197 是当前机器上使用的端口,3128 是服务连接的远程主机端口。
要列出当前机器上打开的端口,请使用以下命令:
$ lsof -i | grep ":[0-9a-z]+->" -o | grep "[0-9a-z]+" -o | sort | uniq
它是如何工作的...
:[0-9a-z]+-> 正则表达式用于 grep 提取 lsof 输出中的主机端口部分 (:34395-> 或 :ssh->)。接下来的 grep 移除前导冒号和尾随箭头,留下端口号(该端口号是字母数字组合)。通过同一端口可能会发生多个连接,因此,可能会出现相同端口的多个条目。输出将被排序并通过 uniq 过滤,确保每个端口只显示一次。
还有更多...
还有更多实用工具报告打开的端口和与网络流量相关的信息。
使用 netstat 查看打开的端口和服务
netstat 还会返回网络服务统计信息。它有许多功能,超出了本配方的范围。
使用 netstat -tnp 列出打开的端口和服务:
$ netstat -tnp
Proto Recv-Q Send-Q Local Address Foreign Address
State PID/Program name
tcp 0 0 192.168.0.82:38163 192.168.0.2:3128
ESTABLISHED 2261/firefox-bin
tcp 0 0 192.168.0.82:38164 192.168.0.2:3128
TIME_WAIT -
tcp 0 0 192.168.0.82:40414 193.107.206.24:422
ESTABLISHED 3836/ssh
tcp 0 0 127.0.0.1:42486 127.0.0.1:32955
ESTABLISHED 4022/GoogleTalkPlug
tcp 0 0 192.168.0.82:38152 192.168.0.2:3128
ESTABLISHED 2261/firefox-bin
tcp6 0 0 ::1:22 ::1:39263
ESTABLISHED -
tcp6 0 0 ::1:39263 ::1:22
ESTABLISHED 3570/ssh
测量网络带宽
前面对 ping 和 traceroute 的讨论涉及测量网络延迟和节点间的跳数。
iperf 应用程序提供了更多网络性能指标。iperf 默认没有安装,但大多数发行版的包管理器都提供该应用程序。
如何操作...
iperf 应用程序必须在链路的两端(主机和客户端)安装。安装好 iperf 后,启动服务器端:
$ iperf -s
然后运行客户端以生成吞吐量统计信息:
$ iperf -c 192.168.1.36
------------------------------------------------------------
Client connecting to 192.168.1.36, TCP port 5001
TCP window size: 19.3 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.1.44 port 46526 connected with 192.168.1.36 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 113 MBytes 94.7 Mbits/sec
-m 选项指示 iperf 还要查找 最大传输大小 (MTU):
$ iperf -mc 192.168.1.36
------------------------------------------------------------
Client connecting to 192.168.1.36, TCP port 5001
TCP window size: 19.3 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.1.44 port 46558 connected with 192.168.1.36 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 113 MBytes 94.7 Mbits/sec
[ 3] MSS size 1448 bytes (MTU 1500 bytes, ethernet)
创建任意套接字
对于文件传输和安全外壳等操作,有像 ftp 和 ssh 这样的预构建工具。我们也可以编写自定义脚本作为网络服务。下一个配方演示了如何创建简单的网络套接字并用它们进行通信。
准备工作
netcat 或 nc 命令将创建网络套接字,通过 TCP/IP 网络传输数据。我们需要两个套接字:一个用于监听连接,另一个连接到监听器。
如何操作...
- 使用以下命令设置监听套接字:
nc -l 1234
这将创建一个在本地机器上监听1234端口的套接字。
- 使用以下命令连接到套接字:
nc HOST 1234
如果你在与监听套接字相同的机器上运行此操作,将HOST替换为 localhost;否则,将其替换为机器的 IP 地址或主机名。
- 在你执行步骤 2 的终端上输入一些内容并按Enter,消息将在你执行步骤 1 的终端上显示。
还有更多内容...
网络套接字不仅可以用于文本通信,还可以用于其他用途,接下来的章节将介绍这些应用。
快速通过网络复制文件
我们可以利用netcat和 shell 重定向来通过网络复制文件。以下命令将把文件发送到监听机器:
- 在监听机器上,运行以下命令:
nc -l 1234 >destination_filename
- 在发送端机器上,运行以下命令:
nc HOST 1234 <source_filename
创建一个广播服务器
你可以使用netcan来创建一个自定义的服务器。接下来的例子展示了一个每 10 秒钟发送时间的服务器。通过连接到端口并使用客户端nc会话(telnet)可以接收时间:
# A script to echo the date out a port
while [ 1 ]
do
sleep 10
date
done | nc -l 12345
echo exited
它是如何工作的...
使用nc复制文件之所以有效,是因为nc将一个套接字的输入回显到另一个套接字的输出。
广播服务器稍微复杂一些。while [ 1 ]循环将会永远运行。在循环中,脚本休眠 10 秒钟,然后调用date命令,并将输出传递给nc命令。
你可以使用nc来创建一个客户端,如下所示:
$ nc 127.0.0.1 12345
构建桥接
如果你有两个独立的网络,可能需要一种方式将数据从一个网络传递到另一个网络。通常通过使用路由器、集线器或交换机将两个子网连接起来。
Linux 系统可以用作网络桥接。
桥接是一种低级连接方式,它根据 MAC 地址传递数据包,而不是通过 IP 地址进行标识。因此,桥接占用的计算机资源较少,效率更高。
你可以使用桥接将虚拟机连接到私有的非路由网络,或者将公司内部的不同子网连接起来,例如,将制造子网与运输子网连接,以便共享生产信息。
准备工作
Linux 内核自 2.2 版本以来就支持网络桥接。目前定义桥接的工具是 iproute2(ip)命令,这在大多数发行版中是标准工具。
如何操作...
ip命令通过命令/子命令模型执行多个操作。要创建桥接,我们使用ip link命令。
在将以太网适配器添加到桥接时,应该确保该适配器没有配置 IP 地址。桥接将配置一个地址,而不是网卡本身。
在这个例子中,有两个网卡:eth0已经配置并连接到192.168.1.0子网,而eth1尚未配置,但将通过桥接连接到10.0.0.0子网:
# Create a new bridge named br0
ip link add br0 type bridge
# Add an Ethernet adapter to the bridge
ip link set dev eth1 master br0
# Configure the bridge's IP address
ifconfig br0 10.0.0.2
# Enable packet forwarding
echo 1 >/proc/sys/net/ipv4/ip_forward
这创建了一个桥接,使数据包能够从eth0发送到eth1并返回。在桥接能够发挥作用之前,我们需要将这个桥接添加到路由表中。
在10.0.0.0/24网络中的机器上,我们添加一个到192.168.1.0/16网络的路由:
route add -net 192.168.1.0/16 gw 10.0.0.2
192.168.1.0/16子网中的机器需要知道如何找到10.0.0.0/24子网。如果eth0网卡配置为 IP 地址192.168.1.2,则路由命令如下:
route add -net 10.0.0.0/24 gw 192.168.1.2
共享互联网连接
大多数防火墙/路由器都能够与家里或办公室的设备共享互联网连接。这叫做网络地址转换(NAT)。一个带有两个网络接口卡(NIC)的 Linux 计算机可以充当路由器,提供防火墙保护和连接共享。
防火墙和 NAT 支持由内核中内置的 iptables 支持提供。这个配方介绍了iptables,通过它将计算机的以太网连接通过无线接口共享到互联网,从而让其他无线设备通过主机的以太网网卡访问互联网。
准备工作
这个配方使用iptables定义网络地址转换(NAT),它允许网络设备与其他设备共享连接。你需要无线接口的名称,可以通过iwconfig命令获得。
如何实现……
-
连接到互联网。在这个配方中,我们假设主要的有线网络连接
eth0已连接到互联网。根据你的设置进行更改。 -
使用你的发行版的网络管理工具,创建一个新的临时无线连接,设置如下:
- IP 地址:10.99.66.55
- 子网掩码:255.255.0.0(16)
- 使用以下脚本共享互联网连接:
#!/bin/bash
#filename: netsharing.sh
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -A FORWARD -i $1 -o $2 \
-s 10.99.0.0/16 -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate \
ESTABLISHED,RELATED -j ACCEPT
iptables -A POSTROUTING -t nat -j MASQUERADE
- 运行脚本:
./netsharing.sh eth0 wlan0
这里eth0是连接到互联网的接口,而wlan0是将共享互联网连接给其他设备的无线接口。
-
将你的设备连接到刚刚创建的无线网络,设置如下:
-
IP 地址:10.99.66.56(依此类推)
-
子网掩码:255.255.0.0
-
为了方便起见,你可能希望在你的机器上安装 DHCP 和 DNS 服务器,这样就不需要手动配置设备的 IP 地址。一个方便的工具是dnsmasq,它同时执行 DHCP 和 DNS 操作。
它是如何工作的
有三个 IP 地址段是为非路由用途保留的。这意味着没有可见于互联网的网络接口可以使用这些地址。它们仅供本地内部网络中的机器使用。地址分别是10.x.x.x、192.168.x.x和172.16.x.x-> 172.32.x.x。在这个配方中,我们使用10.x.x.x地址空间的一部分作为我们的内部网络。
默认情况下,Linux 系统会接收或生成数据包,但不会回显它们。这由in/proc/sys/net/ipv4/ip_forward的值控制。
将1回显到该位置会告诉 Linux 内核转发它无法识别的任何数据包。这使得位于10.99.66.x子网中的无线设备能够使用10.99.66.55作为它们的网关。它们将把目标是互联网站点的数据包发送到10.99.66.55,然后通过eth0的网关将其转发到互联网,并路由到目标地址。
iptables命令是我们与 Linux 内核的 iptables 子系统交互的方式。这些命令添加规则,将所有内部网络的数据包转发到外部世界,并将预期的数据包从外部世界转发到我们的内部网络。
下一个示例将讨论更多使用 iptables 的方法。
使用 iptables 的基本防火墙
防火墙是用于过滤网络流量、阻止不需要的流量并允许所需流量通过的网络服务。Linux 的标准防火墙工具是iptables,它在最新版本中集成到内核中。
如何操作...
iptables在所有现代 Linux 发行版中默认存在。它易于为常见场景进行配置:
- 如果你不想联系某个特定站点(例如,一个已知的恶意软件站点),你可以阻止到该 IP 地址的流量:
#iptables -A OUTPUT -d 8.8.8.8 -j DROP
如果你在另一个终端中使用PING 8.8.8.8,然后运行iptables命令,你将看到以下内容:
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_req=1 ttl=56 time=221 ms
64 bytes from 8.8.8.8: icmp_req=2 ttl=56 time=221 ms
ping: sendmsg: Operation not permitted
ping: sendmsg: Operation not permitted
在这里,ping 在第三次尝试时失败,因为我们使用了iptables命令来丢弃所有到8.8.8.8的流量。
- 你也可以阻止到特定端口的流量:
#iptables -A OUTPUT -p tcp -dport 21 -j DROP
$ ftp ftp.kde.org
ftp: connect: Connection timed out
如果你在/var/log/secure或/var/log/messages文件中发现类似的消息,那你遇到了一个小问题:
Failed password for abel from 1.2.3.4 port 12345 ssh2
Failed password for baker from 1.2.3.4 port 12345 ssh2
这些消息意味着一个机器人正在探测你的系统以寻找弱密码。你可以通过一个 INPUT 规则来防止机器人访问你的网站,该规则会丢弃来自该网站的所有流量。
#iptables -I INPUT -s 1.2.3.4 -j DROP
如何工作...
iptables是用于配置 Linux 防火墙的命令。iptables中的第一个参数是-A,指示iptables将新规则附加到链中,或者是-I,它将新规则放在规则集的开始。下一个参数定义了链。链是规则的集合,在之前的示例中我们使用了OUTPUT链,它用于评估出站流量,而在最后的示例中我们使用了INPUT链,它用于评估入站流量。
-d参数指定了与发送的数据包匹配的目标地址,而-s则指定了数据包的源地址。最后,-j参数指示iptables跳转到特定的动作。在这些示例中,我们使用了 DROP 动作来丢弃数据包。其他动作包括ACCEPT和REJECT。
在第二个示例中,我们使用-p参数指定该规则仅匹配指定端口的 TCP 流量,这会阻止仅出站的FTP流量。
还有更多...
你可以使用-flush参数清除对iptables链所做的更改:
#iptables -flush
创建虚拟私人网络
虚拟专用网络(VPN)是一个跨公共网络运行的加密通道。加密确保你的信息是私密的。VPN 用于连接远程办公室、分布式制造站点和远程工作人员。
我们已经讨论过使用nc、scp或ssh来复制文件。通过 VPN 网络,你可以通过 NFS 挂载远程驱动器,并像访问本地网络资源一样访问远程网络上的资源。
Linux 有多个 VPN 系统的客户端,并且支持 OpenVPN 的客户端和服务器。
本节的配方将描述如何设置 OpenVPN 服务器和客户端。此配方用于配置一个单一服务器来服务多个客户端,采用集线器和辐射式模型。OpenVPN 支持更多拓扑结构,超出了本章的范围。
准备工作
OpenVPN 并不是大多数 Linux 发行版的一部分。你可以使用包管理器安装它:
apt-get install openvpn
另外,也可以使用以下命令:
yum install openvpn
请注意,你需要在服务器和每个客户端上执行此操作。
确认隧道设备(/dev/net/tun)存在。在服务器和客户端系统上测试此项。在现代 Linux 系统上,隧道应该已经存在:
ls /dev/net/tun
如何操作...
设置 OpenVPN 网络的第一步是为服务器和至少一个客户端创建证书。处理此问题的最简单方法是使用 OpenVPN 2.3 版本之前附带的easy-rsa包制作自签名证书。如果你使用的是 OpenVPN 的较新版本,可以通过包管理器获得easy-rsa。
这个包可能安装在/usr/share/easy-rsa中。
创建证书
首先,确保清理干净,之前的安装没有任何残留:
# cd /usr/share/easy-rsa
# . ./vars
# ./clean-all
注意:如果你运行./clean-all,我会对/usr/share/easy-rsa/keys进行rm -rf操作。
接下来,使用build-ca命令创建证书颁发机构(CA)密钥。此命令会提示你输入有关你站点的信息。你需要多次输入这些信息。在此配方中,将你的名字、电子邮件、站点名称等替换为相应的值。不同命令所需的信息略有不同,只有唯一的部分会在这些配方中重复出现:
# ./build-ca
Generating a 2048 bit RSA private key
......+++
.....................................................+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For somefieldsthere will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:MI
Locality Name (eg, city) [SanFrancisco]:WhitmoreLake
Organization Name (eg, company) [Fort-Funston]:Example
Organizational Unit Name (eg, section) [MyOrganizationalUnit]:Packt
Common Name (eg, your name or your server's hostname) [Fort-Funston CA]:vpnserver
Name [EasyRSA]:
Email Address [me@myhost.mydomain]:admin@example.com
Next, build the server certificate with the build-key command:
# ./build-key server
Generating a 2048 bit RSA private key
..................................+++
.....................+++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request....
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
为至少一个客户端创建证书。你需要为每个希望连接到此 OpenVPN 服务器的机器创建一个独立的客户端证书:
# ./build-key client1
Generating a 2048 bit RSA private key
.......................+++
.................................................+++
writing new private key to 'client1.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
...
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /usr/share/easy-rsa/openssl-1.0.0.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'US'
stateOrProvinceName :PRINTABLE:'MI'
localityName :PRINTABLE:'WhitmoreLake'
organizationName :PRINTABLE:'Example'
organizationalUnitName:PRINTABLE:'Packt'
commonName :PRINTABLE:'client1'
name :PRINTABLE:'EasyRSA'
emailAddress:IA5STRING:'admin@example.com'
Certificate is to be certified until Jan 8 15:24:13 2027 GMT (3650 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
最后,使用build-dh命令生成Diffie-Hellman密钥。这个过程需要几秒钟,并会生成几屏点和加号:
# ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
......................+............+........
这些步骤将会在密钥文件夹中创建多个文件。下一步是将它们复制到将要使用的文件夹中。
将服务器密钥复制到/etc/openvpn:
# cp keys/server* /etc/openvpn
# cp keys/ca.crt /etc/openvpn
# cp keys/dh2048.pem /etc/openvpn
将客户端密钥复制到客户端系统:
# scp keys/client1* client.example.com:/etc/openvpn
# scp keys/ca.crt client.example.com:/etc/openvpn
配置 OpenVPN 服务器
OpenVPN 包括几乎可以直接使用的示例配置文件。你只需要根据你的环境自定义几行。这些文件通常位于/usr/share/doc/openvpn/examples/sample-config-files:
# cd /usr/share/doc/openvpn/examples/sample-config-files
# cp server.conf.gz /etc/openvpn
# cd /etc/openvpn
# gunzip server.conf.gz
# vim server.conf
设置本地 IP 地址以供监听。这是连接到您希望允许 VPN 连接的网络上的网卡的 IP 地址:
local 192.168.1.125
Modify the paths to the certificates:
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key # This file should be kept secret
最后,检查diffie-hellman参数文件是否正确。OpenVPN 示例config文件可能指定了 1024 位长度的密钥,而easy-rsa创建的是 2048 位(更安全)的密钥。
#dh dh1024.pem
dh dh2048.pem
配置客户端上的 OpenVPN
每个客户端都有一套类似的配置。
将客户端配置文件复制到/etc/openvpn:
# cd /usr/share/doc/openvpn/examples/sample-config-files
# cpclient.conf /etc/openvpn
编辑client.conf文件:
# cd /etc/openvpn
# vim client.conf
更改证书路径,指向正确的文件夹:
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key # This file should be kept secret
设置服务器的远程站点:
#remote my-server-1 1194
remote server.example.com 1194
启动服务器
现在可以启动服务器。如果一切配置正确,您将看到几行输出。需要注意的关键行是Initialization Sequence Completed。如果没有该行,查看输出中是否有错误信息:
# openvpnserver.conf
Wed Jan 11 12:31:08 2017 OpenVPN 2.3.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Nov 12 2015
Wed Jan 11 12:31:08 2017 library versions: OpenSSL 1.0.1t 3 May 2016, LZO 2.08...
Wed Jan 11 12:31:08 2017 client1,10.8.0.4
Wed Jan 11 12:31:08 2017 Initialization Sequence Completed
使用ifconfig,您可以确认服务器正在运行。您应该看到隧道设备(tun)列出:
$ ifconfig
tun0 Link encap:UNSPECHWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:10.8.0.1 P-t-P:10.8.0.2 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
启动并测试客户端
一旦服务器运行,您可以启动客户端。与服务器一样,OpenVPN 客户端通过openvpn命令创建。同样,输出中的关键部分是Initialization Sequence Completed行:
# openvpn client.conf
Wed Jan 11 12:34:14 2017 OpenVPN 2.3.4 i586-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Nov 19 2015
Wed Jan 11 12:34:14 2017 library versions: OpenSSL 1.0.1t 3 May 2016, LZO 2.08...
Wed Jan 11 12:34:17 2017 /sbin/ipaddr add dev tun0 local 10.8.0.6 peer 10.8.0.5
Wed Jan 11 12:34:17 2017 /sbin/ip route add 10.8.0.1/32 via 10.8.0.5
Wed Jan 11 12:34:17 2017 Initialization Sequence Completed
使用ifconfig命令,您可以确认隧道已经初始化:
$ /sbin/ifconfig
tun0 Link encap:UNSPECHWaddr 00-00-00-00-00-00-00-00...00-00-00-00
inet addr:10.8.0.6 P-t-P:10.8.0.5 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:2 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100
RX bytes:168 (168.0 B) TX bytes:336 (336.0 B)
使用netstat命令确认新网络的路由是否正确:
$ netstat -rn
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irttIface
0.0.0.0 192.168.1.7 0.0.0.0 UG 0 0 0 eth0
10.8.0.1 10.8.0.5 255.255.255.255 UGH 0 0 0 tun0
10.8.0.5 0.0.0.0 255.255.255.255 UH 0 0 0 tun0
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
该输出显示了隧道设备已连接到10.8.0.x网络,网关是10.8.0.1。
最后,您可以使用ping命令测试连接性:
$ ping 10.8.0.1
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=1.44 ms
第九章:戴上监控帽
本章将涵盖以下食谱:
-
监控磁盘使用情况
-
计算命令的执行时间
-
收集已登录用户的信息、启动日志和启动失败信息
-
列出一个小时内 CPU 消耗最多的前十个进程
-
使用
watch监控命令输出 -
记录文件和目录的访问
-
使用 syslog 进行日志记录
-
使用
logrotate管理日志文件 -
监控用户登录以发现入侵者
-
监控远程磁盘使用健康状况
-
确定系统上的活跃用户时段
-
测量和优化电源使用
-
监控磁盘活动
-
检查磁盘和文件系统的错误
-
检查磁盘健康状况
-
获取磁盘统计信息
介绍
计算机系统是硬件和控制它的软件组件的集合。软件包括操作系统内核,它分配资源,以及许多执行各个任务的模块,从读取磁盘数据到提供网页服务等。
管理员需要监控这些模块和应用程序,以确认它们是否正常工作,并了解是否需要重新分配资源(例如将用户分区迁移到更大的磁盘、提供更快的网络等)。
Linux 提供了交互式程序来检查系统当前的性能,以及用于记录性能随时间变化的模块。
本章介绍了监控系统活动的命令,并讨论了日志记录技术。
监控磁盘使用情况
磁盘空间始终是有限的资源。我们监控磁盘使用情况,以便在磁盘空间不足时及时发现,然后搜索大文件或文件夹以删除、移动或压缩。本食谱展示了磁盘监控命令。
准备工作
du(磁盘使用)和 df(磁盘空闲)命令用于报告磁盘使用情况。这些工具报告哪些文件和文件夹正在占用磁盘空间,以及剩余多少可用空间。
如何操作...
要查找文件(或文件)所占用的磁盘空间,请使用以下命令:
$ du FILENAME1 FILENAME2 ..
请参考这个例子:
$ du file.txt
要获取目录内所有文件的磁盘使用情况,并在每一行中显示每个文件的单独磁盘使用情况,请使用以下命令:
$ du -a DIRECTORY
-a 选项递归地输出指定目录或目录中所有文件的结果。
运行 du DIRECTORY 将输出类似的结果,但它只会显示子目录所占用的大小。然而,这并不显示每个文件的磁盘使用情况。要打印每个文件的磁盘使用情况,必须使用 -a。
请参考这个例子:
$ du -a test
4 test/output.txt
4 test/process_log.sh
4 test/pcpu.sh
16 test
du 命令可以用于查看目录:
$ du test
16 test
还有更多...
du 命令包括定义如何报告数据的选项。
以 KB、MB 或块为单位显示磁盘使用情况
默认情况下,磁盘使用命令显示文件所使用的总字节数。更易读的格式以 KB、MB 或 GB 等单位表示。-h 选项以人类可读的格式显示结果:
du -h FILENAME
请参考这个例子:
$ du -h test/pcpu.sh
4.0K test/pcpu.sh
# Multiple file arguments are accepted
或者,像这样使用:
# du -h DIRECTORY
$ du -h hack/
16K hack/
显示磁盘使用情况的总和
-c选项将计算文件或目录使用的总大小,并显示每个文件的大小:
$ du -c FILENAME1 FILENAME2..
du -c process_log.sh pcpu.sh
4 process_log.sh
4 pcpu.sh
8 total
或者,可以像这样使用它:
$ du -c DIRECTORY
$ du -c test/
16 test/
16 total
或者:
$ du -c *.txt
# Wildcards
-c选项可以与-a和-h等选项一起使用,生成常规输出,并附加一行包含总大小的额外信息。
-s选项(汇总),将打印总计作为输出。-h标志可以与其一起使用,以以人类可读的格式打印:
$ du -sh /usr/bin
256M /usr/bin
以指定单位打印大小
-b、-k和-m选项将强制du以指定的单位打印磁盘使用情况。请注意,这些选项不能与-h选项一起使用:
- 以字节为单位打印大小(默认):
$ du -b FILE(s)
- 以千字节为单位打印大小:
$ du -k FILE(s)
- 以兆字节为单位打印大小:
$ du -m FILE(s)
- 以指定的给定
BLOCK大小打印大小:
$ du -B BLOCK_SIZE FILE(s)
这里,BLOCK_SIZE以字节为单位指定。
请注意,返回的文件大小并不直观明显。使用-b选项时,du报告文件的确切字节数。使用其他选项时,du报告文件使用的磁盘空间量。由于磁盘空间是以固定大小的块(通常是 4K)分配的,一个 400 字节的文件将占用一个块(4K)的空间:
$ du pcpu.sh
4 pcpu.sh
$ du -b pcpu.sh
439 pcpu.sh
$ du -k pcpu.sh
4 pcpu.sh
$ du -m pcpu.sh
1 pcpu.sh
$ du -B 4 pcpu.sh
1024 pcpu.sh
在磁盘使用计算中排除文件
--exclude和-exclude-from选项会导致du排除文件的磁盘使用计算。
-exclude选项可以与通配符或单个文件名一起使用:
$ du --exclude "WILDCARD" DIRECTORY
考虑以下示例:
# Excludes all .txt files from calculation
$ du --exclude "*.txt" *
# Exclude temp.txt from calculation
$ du --exclude "temp.txt" *
--exclude选项将排除一个文件或与模式匹配的文件。-exclude-from选项允许排除更多的文件或模式。每个文件名或模式必须单独占一行。
$ ls *.txt >EXCLUDE.txt
$ ls *.odt >>EXCLUDE.txt
# EXCLUDE.txt contains a list of all .txt and .odt files.
$ du --exclude-from EXCLUDE.txt DIRECTORY
-max-depth选项限制du将检查的子目录层级。深度为1时计算当前目录的磁盘使用情况,深度为2时计算当前目录和下一级子目录的使用情况:
$ du --max-depth 2 DIRECTORY
-x选项将du限制为单个文件系统。du的默认行为是跟随链接和挂载点。
du命令需要对所有文件的读取权限,并且对所有目录需要读取和执行权限。如果执行该命令的用户没有适当的权限,du命令将抛出错误。
从给定目录中找到十个最大的文件
将du和排序命令结合起来,找出应删除或移动的大文件:
$ du -ak SOURCE_DIR | sort -nrk 1 | head
-a选项使du显示SOURCE_DIR中所有文件和目录的大小。输出的第一列是大小。-k选项使其以千字节为单位显示。第二列包含文件或文件夹名称。
-n选项对sort执行数字排序。-1选项指定列1,-r选项则会反转排序顺序。head命令从输出中提取前十行:
$ du -ak /home/slynux | sort -nrk 1 | head -n 4
50220 /home/slynux
43296 /home/slynux/.mozilla
43284 /home/slynux/.mozilla/firefox
43276 /home/slynux/.mozilla/firefox/8c22khxc.default
这个单行命令的一个缺点是它包括了目录在结果中。我们可以改进这个命令,通过find命令只输出大文件:
$ find . -type f -exec du -k {} \; | sort -nrk 1 | head
find命令仅选择文件名供du处理,而不是让du遍历文件系统来选择要报告的项目。
请注意,du命令报告一个文件所需的字节数。这不一定与文件实际占用的磁盘空间相同。磁盘空间是按块分配的,所以一个 1 字节的文件会占用一个磁盘块,通常大小在 512 到 4096 字节之间。
下一部分描述如何使用df命令来确定实际可用的空间。
磁盘空闲信息
du命令提供使用情况的信息,而df提供有关可用磁盘空间的信息。使用-h选项与df一起使用,可以以人类可读的格式打印磁盘空间。考虑这个例子:
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 9.2G 2.2G 6.6G 25% /
none 497M 240K 497M 1% /dev
none 502M 168K 501M 1% /dev/shm
none 502M 88K 501M 1% /var/run
none 502M 0 502M 0% /var/lock
none 502M 0 502M 0% /lib/init/rw
none 9.2G 2.2G 6.6G 25%
/var/lib/ureadahead/debugfs
df命令可以与文件夹名称一起调用。在这种情况下,它将报告包含该目录的磁盘分区的空闲空间。如果你不知道哪个分区包含该目录,这将非常有用:
$ df -h /home/user
Filesystem Size Used Avail Use% Mounted on
/dev/md1 917G 739G 133G 85% /raid1
计算命令的执行时间
执行时间是分析应用程序效率或比较算法的标准。
如何操作...
time命令用于衡量应用程序的执行时间。
考虑以下例子:
$ time APPLICATION
time命令执行APPLICATION。当APPLICATION完成时,time命令将实际时间、系统时间和用户时间统计信息报告到stderr,并将APPLICATION的正常输出发送到stdout。
$ time ls
test.txt
next.txt
real 0m0.008s
user 0m0.001s
sys 0m0.003s
time命令的可执行二进制文件位于/usr/bin/time。如果你使用的是 bash,默认情况下会得到 shell 内置的time命令。内置的time命令选项有限。使用绝对路径(/usr/bin/time)可以访问扩展功能。
-o选项将时间统计信息写入文件:
$ /usr/bin/time -o output.txt COMMAND
文件名必须紧跟在-o标志后面。
-a标志可以与-o一起使用,将时间统计信息附加到文件中:
$ /usr/bin/time -a -o output.txt COMMAND
-f选项指定要报告的统计信息及输出格式。格式字符串包括一个或多个以%为前缀的参数。格式参数包括以下内容:
-
实际时间:
%e -
用户时间:
%U -
系统时间:
%S -
系统页面大小:
%Z
我们可以通过将这些参数与额外的文本结合,创建格式化的输出:
$ /usr/bin/time -f "FORMAT STRING" COMMAND
考虑这个例子:
$ /usr/bin/time -f "Time: %U" -a -o timing.log uname
Linux
%U参数指定用户时间。
time命令将目标应用程序的输出发送到stdout,并将time命令的输出发送到stderr。我们可以使用重定向操作符(>)重定向输出,并使用错误重定向操作符(2>)重定向时间信息输出。
考虑以下例子:
$ /usr/bin/time -f "Time: %U" uname> command_output.txt
2>time.log
$ cat time.log
Time: 0.00
$ cat command_output.txt
Linux
- 格式命令可以报告内存使用情况以及计时信息。
%M标志显示最大使用内存(以 KB 为单位),%Z参数使得时间命令报告系统页面大小:
$ /usr/bin/time -f "Max: %M K\nPage size: %Z bytes" \
ls>
/dev/null
Max: 996 K
Page size: 4096 bytes
在这个例子中,目标应用程序的输出并不重要,因此标准输出被重定向到/dev/null,而不是显示出来。
它是如何工作的...
time命令默认报告以下时间:
-
Real:这是墙钟时间——命令从开始到结束的时间。包括其他进程使用的时间片以及进程在被阻塞时所花费的时间(例如,等待 I/O 完成的时间)。
-
User:这是进程在用户模式代码(内核外部)中消耗的 CPU 时间量。它是执行进程所用的 CPU 时间。其他进程以及这些进程在被阻塞时所花费的时间不计入此项。
-
Sys:这是进程在内核中消耗的 CPU 时间量;指的是在内核中进行系统调用时消耗的 CPU 时间,而不是运行在用户空间中的库代码。像用户时间一样,这只是进程使用的 CPU 时间。请参考下表,简要描述内核模式(也称为主管模式)和系统调用机制。
time 命令可以报告进程的许多细节,包括退出状态、接收到的信号数量和进行的上下文切换次数。每个参数都可以在提供适当格式字符串给 -f 选项时显示。
下表展示了一些有趣的参数:
| Parameter | Description |
|---|---|
%C |
显示正在计时的命令的名称和命令行参数。 |
%D |
显示进程未共享数据区域的平均大小,单位是千字节。 |
%E |
显示进程使用的实际(墙钟)时间,格式为 [小时:]分钟:秒。 |
%x |
显示命令的退出状态。 |
%k |
显示发送到进程的信号数量。 |
%W |
显示进程被交换出主内存的次数。 |
%Z |
显示系统的页面大小,单位是字节。这是一个每个系统的常量,但在不同的系统之间会有所不同。 |
%P |
显示该作业获得的 CPU 百分比。它是用户时间 + 系统时间除以总运行时间的结果,并显示百分号。 |
%K |
显示进程的平均总内存使用量(数据 + 堆栈 + 文本),单位是千字节。 |
%w |
显示程序自愿进行上下文切换的次数,例如,在等待 I/O 操作完成时。 |
%c |
显示进程被强制上下文切换的次数(因为时间片用完)。 |
收集有关登录用户、启动日志和启动失败的信息
Linux 支持报告运行时系统方面的命令,包括登录用户、计算机开机时长和启动失败。这些数据用于分配资源和诊断问题。
准备工作
本文介绍了 who、w、users、uptime、last 和 lastb 命令。
如何操作...
who命令报告当前用户的信息:
$ who
slynux pts/0 2010-09-29 05:24 (slynuxs-macbook-pro.local)
slynux tty7 2010-09-29 07:08 (:0)
该输出列出了登录名、用户使用的 TTY、登录时间以及关于登录用户的远程主机名(或 X 显示信息)。
TTY(这个术语源自TeleTYpewriter)是与文本终端相关联的设备文件,它在用户新创建终端时会在/dev目录下生成(例如,/dev/pts/3)。可以通过执行tty命令找到当前终端的设备路径。
w命令提供更详细的信息:
$ w
07:09:05 up 1:45, 2 users, load average: 0.12, 0.06, 0.02
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
slynux pts/0 slynuxs 05:24 0.00s 0.65s 0.11s sshd: slynux
slynux tty7 :0 07:08 1:45m 3.28s 0.26s bash
第一行列出了当前时间、系统开机时间、当前登录的用户数,以及过去 1、5 和 15 分钟的系统负载平均值。接下来,关于每个登录会话的详细信息会显示出来,每一行都包含登录名、TTY 名称、远程主机、登录时间、空闲时间、用户自登录以来的总 CPU 时间、当前进程的 CPU 时间以及当前进程的命令行。
uptime命令输出中的负载平均值表示系统负载。更详细的说明可以参见第十章,系统管理调用。
users命令仅列出登录用户的名字:
$ users
slynux slynux slynux hacker
如果用户打开了多个会话,比如多次远程登录或打开多个终端窗口,那么每个会话都会有一个条目。在上述输出中,slynux用户打开了三个终端会话。最简单的方法是通过sort和uniq命令来过滤输出,以打印唯一的用户:
$ users | tr ' ' '\n' | sort | uniq
slynux
hacker
tr命令将每个空格字符' '替换为换行符'\n'。然后,sort和uniq的组合将列表减少为每个用户的唯一条目。
uptime命令报告系统已开机的时长:
$ uptime
21:44:33 up 6 days, 11:53, 8 users, load average: 0.09, 0.14,
0.09
紧随up一词后的时间表示系统已开机的时长。我们可以编写一个一行命令,仅提取系统的开机时间:
$ uptime | sed 's/.*up \(.*\),.*users.*/\1/'
这使用sed命令将输出行替换为仅包含“up”一词和“users”前面的逗号之间的字符串。
last命令提供自/var/log/wtmp文件创建以来,所有登录系统的用户列表。这个列表可能会回溯一年或更长时间:
$ last
aku1 pts/3 10.2.1.3 Tue May 16 08:23 - 16:14 (07:51)
cfly pts/0 cflynt.com Tue May 16 07:49 still logged in
dgpx pts/0 10.0.0.5 Tue May 16 06:19 - 06:27 (00:07)
stvl pts/0 10.2.1.4 Mon May 15 18:38 - 19:07 (00:29)
last命令报告谁登录了,分配给他们的tty,他们从哪里登录(IP 地址或本地终端),登录时间、注销时间以及会话时长。重启会被伪用户reboot标记为登录。
last命令允许你定义一个用户,仅获取该用户的信息:
$ last USER
- 用户(USER)可以是真实用户或伪用户
reboot:
$ last reboot
reboot system boot 2.6.32-21-generi Tue Sep 28 18:10 - 21:48
(03:37)
reboot system boot 2.6.32-21-generi Tue Sep 28 05:14 - 21:48
(16:33)
lastb命令会列出所有失败的登录尝试:
# lastb
test tty8 :0 Wed Dec 15 03:56 - 03:56
(00:00)
slynux tty8 :0 Wed Dec 15 03:55 - 03:55
(00:00)
lastb命令必须以 root 用户身份运行。
last和lastb命令报告/var/log/wtmp文件的内容。默认情况下,报告事件的月份、日期和时间。然而,该文件中可能有多年的数据,月份和日期可能会让人困惑。
-F标志会报告完整日期:
# lastb -F
hacker tty0 1.2.3.4 Sat Jan 7 11:50:53 2017 -
Sat Jan 7 11:50:53 2017 (00:00)
列出一个小时内 CPU 消耗最多的十个进程
CPU 是另一个可以被不当行为的进程耗尽的资源。Linux 支持命令来识别并控制占用 CPU 资源的进程。
准备工作
ps命令显示系统中运行的进程的详细信息。它报告诸如 CPU 使用率、运行命令、内存使用情况和进程状态等详细信息。ps命令可以在脚本中使用,以识别在一小时内消耗最多 CPU 资源的进程。有关ps命令的更多详细信息,请参阅第十章《系统管理命令》。
如何实现...
这个 shell 脚本监视并计算 CPU 使用率,持续一个小时:
#!/bin/bash
#Name: pcpu_usage.sh
#Description: Script to calculate cpu usage by processes for 1 hour
#Change the SECS to total seconds to monitor CPU usage.
#UNIT_TIME is the interval in seconds between each sampling
SECS=3600
UNIT_TIME=60
STEPS=$(( $SECS / $UNIT_TIME ))
echo Watching CPU usage... ;
# Collect data in temp file
for((i=0;i<STEPS;i++))
do
ps -eocomm,pcpu | egrep -v '(0.0)|(%CPU)' >> /tmp/cpu_usage.$$
sleep $UNIT_TIME
done
# Process collected data
echo
echo CPU eaters :
cat /tmp/cpu_usage.$$ | \
awk '
{ process[$1]+=$2; }
END{
for(i in process)
{
printf("%-20s %s\n",i, process[i]) ;
}
}' | sort -nrk 2 | head
#Remove the temporary log file
rm /tmp/cpu_usage.$$
输出类似如下:
$ ./pcpu_usage.sh
Watching CPU usage...
CPU eaters :
Xorg 20
firefox-bin 15
bash 3
evince 2
pulseaudio 1.0
pcpu.sh 0.3
wpa_supplicant 0
wnck-applet 0
watchdog/0 0
usb-storage 0
它是如何工作的...
CPU 使用数据是由第一个循环生成的,该循环运行一小时(3600 秒)。每分钟,ps -eocomm,pcpu命令会生成该时刻的系统活动报告。-e选项指定收集所有进程的数据,而不仅仅是当前会话的任务。-o选项指定输出格式。comm和pcpu字段分别指定报告命令名和 CPU 使用百分比。该ps命令为每个正在运行的进程生成一行,显示命令名称和当前的 CPU 使用百分比。这些行通过grep过滤,去除没有 CPU 使用的行(%CPU 为 0.0)和COMMAND %CPU表头。感兴趣的行将附加到临时文件中。
临时文件命名为/tmp/cpu_usage.$$。其中,$$是一个脚本变量,表示当前脚本的进程 ID(PID)。例如,如果脚本的 PID 为1345,则临时文件名为/tmp/cpu_usage.1345。
统计文件将在一小时后准备好,并包含 60 组条目,分别对应每分钟的系统状态。awk脚本将每个进程的总 CPU 使用率加总到一个名为 process 的关联数组中。该数组以进程名称作为数组索引。最后,awk按照总 CPU 使用率对结果进行数字反向排序,并使用 head 命令限制报告为前 10 个使用量条目。
另见
-
第四章《文本处理与驾驶》中的使用 awk 进行高级文本处理方法解释了
awk命令 -
第三章《文件输入,文件输出》中的使用 head 和 tail 打印最后或最前 10 行方法解释了
tail命令
使用watch监控命令输出
watch命令会定期执行命令并显示该命令的输出。你可以使用终端会话和在第十章《系统管理命令》中描述的screen命令,创建一个自定义仪表盘,通过watch监控系统。
如何实现...
watch命令定期监视终端上命令的输出。watch命令的语法如下:
$ watch COMMAND
考虑这个例子:
$ watch ls
或者,它可以这样使用:
$ watch 'df /home'
请考虑以下示例:
# list only directories
$ watch 'ls -l | grep "^d"'
该命令将在默认的两秒间隔内更新输出。
-n SECONDS 选项定义了更新输出的时间间隔:
# Monitor the output of ls -l every of 5 seconds
$ watch -n 5 'ls -l'
还有更多
watch 命令可以与任何生成输出的命令一起使用。有些命令会频繁更改其输出,而这些变化比整个输出更为重要。watch 命令会突出显示连续执行之间的差异。请注意,这种高亮显示仅持续到下一次更新。
在 watch 输出中突出显示差异
-d 选项用于突出显示被监视命令连续执行之间的差异:
$ watch -d 'COMMANDS'
# Highlight new network connections for 30 seconds
$ watch -n 30 -d 'ss | grep ESTAB'
记录对文件和目录的访问
有很多原因可能需要在文件被访问时得到通知。你可能想知道何时修改了文件,以便进行备份,或者你可能想知道 /bin 目录中的文件是否被黑客修改。
准备中
inotifywait 命令用于监视文件或目录,并在发生事件时报告。并非每个 Linux 发行版都默认包含它。你需要安装 inotify-tools 包。它需要 Linux 内核中的 inotify 支持。大多数新的 GNU/Linux 发行版将 inotify 支持编译进内核。
如何做...
inotify 命令可以监视一个目录:
#/bin/bash
#Filename: watchdir.sh
#Description: Watch directory access
path=$1
#Provide path of directory or file as argument to script
$ inotifywait -m -r -e create,move,delete $path -q
示例输出如下所示:
$ ./watchdir.sh .
./ CREATE new
./ MOVED_FROM new
./ MOVED_TO news
./ DELETE news
工作原理...
上述脚本会记录指定路径中的创建、移动和删除事件。-m 选项使 watch 命令保持活跃,并持续监控更改,而不是在事件发生后退出。-r 选项启用递归监视目录(符号链接会被忽略)。-e 选项指定要监视的事件列表,-q 选项减少冗长信息,仅打印所需的内容。此输出可以重定向到日志文件。
inotifywait 可以检查的事件包括以下内容:
| 事件 | 描述 |
|---|---|
access |
当文件发生读取操作时 |
modify |
当文件内容被修改时 |
attrib |
当文件元数据发生变化时 |
move |
当文件发生移动操作时 |
create |
当新文件被创建时 |
open |
当文件进行打开操作时 |
close |
当文件进行关闭操作时 |
delete |
当文件被删除时 |
使用 syslog 记录日志
与守护进程和系统进程相关的日志文件位于 /var/log 目录中。这些日志文件使用一种名为 syslog 的标准协议,由 syslogd 守护进程处理。每个标准应用程序都会使用 syslogd 记录信息。本食谱描述了如何使用 syslogd 从 shell 脚本中记录信息。
准备中
日志文件有助于你推断系统出现问题的原因。记录进度和操作信息是一个很好的实践,使用日志文件消息。logger 命令会将数据记录到日志文件中,并使用 syslogd 进行管理。
以下是一些标准的 Linux 日志文件。有些发行版使用不同的文件名:
| 日志文件 | 描述 |
|---|---|
/var/log/boot.log |
启动日志信息 |
/var/log/httpd |
Apache 网络服务器日志 |
/var/log/messages |
启动后内核信息 |
/var/log/auth.log``/var/log/secure |
用户认证日志 |
/var/log/dmesg |
系统启动消息 |
/var/log/mail.log``/var/log/maillog |
邮件服务器日志 |
/var/log/Xorg.0.log |
X 服务器日志 |
如何操作...
logger 命令允许脚本创建和管理日志消息:
- 将消息放入系统日志文件
/var/log/messages:
$ logger LOG_MESSAGE
请考虑以下示例:
$ logger This is a test log line
$ tail -n 1 /var/log/messages
Sep 29 07:47:44 slynux-laptop slynux: This is a test log line
/var/log/messages 日志文件是一个通用日志文件。当使用 logger 命令时,它默认记录到 /var/log/messages。
-t标志定义消息的标签:
$ logger -t TAG This is a message
$ tail -n 1 /var/log/messages
Sep 29 07:48:42 slynux-laptop TAG: This is a message
-p 选项控制 logger 和 /etc/rsyslog.d 中的配置文件,决定日志消息的保存位置。
要保存到自定义文件,请按照以下步骤操作:
-
在
/etc/rsyslog.d中创建一个新的配置文件 -
为优先级和日志文件添加模式
-
重启日志守护进程
请考虑以下示例:
# cat /etc/rsyslog.d/myConfig
local7.* /var/log/local7
# cd /etc/init.d
# ./syslogd restart
# logger -p local7.info A line to be placed in /var/log/local7
-f选项将从另一个文件中记录行:
$ logger -f /var/log/source.log
参见
- 第三章 文件输入与输出 中的 使用 head 和 tail 打印最后或前 10 行 配方解释了 head 和 tail 命令
使用 logrotate 管理日志文件
日志文件跟踪系统上的事件。它们对于调试问题和监控实时机器至关重要。随着时间的推移,日志文件会增长,记录更多事件。由于较旧的数据不如当前数据有用,当日志文件达到大小限制时,它们会被重命名,并删除最旧的文件。
准备工作
logrotate 命令可以限制日志文件的大小。系统日志工具会将信息附加到日志文件的末尾,而不会删除之前的数据。因此,日志文件会随着时间推移而增大。logrotate 命令会扫描配置文件中定义的日志文件。它将保留日志文件的最后 100 千字节(例如,指定 SIZE = 100 k),并将其余数据(较旧的日志数据)移动到一个新文件 logfile_name.1。当旧数据文件(logfile_name.1)超过 SIZE 时,logrotate 会将该文件重命名为 logfile_name.2 并开始一个新的 logfile_name.1。logrotate 命令还可以将旧日志压缩为 logfile_name.1.gz、logfile_name.2.gz 等。
如何操作...
系统的 logrotate 配置文件保存在 /etc/logrotate.d 中。大多数 Linux 发行版在该文件夹中都有多个文件。
我们可以为日志文件(例如 /var/log/program.log)创建自定义配置:
$ cat /etc/logrotate.d/program
/var/log/program.log {
missingok
notifempty
size 30k
compress
weekly
rotate 5
create 0600 root root
}
这是一个完整的配置。/var/log/program.log 字符串指定了日志文件路径。Logrotate 会将旧日志存档到同一目录中。
它是如何工作的...
logrotate 命令支持配置文件中的这些选项:
| 参数 | 描述 |
|---|---|
missingok |
如果日志文件丢失,则忽略并返回,不进行日志轮换。 |
notifempty |
仅在源日志文件不为空时才进行轮换。 |
size 30k |
这限制了要进行轮换的日志文件的大小。可以设置为 1M,即 1MB。 |
compress |
启用 gzip 压缩以压缩旧日志。 |
weekly |
这指定了轮换执行的间隔。可以是每周、每年或每天。 |
rotate 5 |
这是要保留的日志文件存档的旧副本数量。指定为 5 时,将保留program.log.1.gz、program.log.2.gz,依此类推,直到program.log.5.gz。 |
create 0600 root root |
这指定了日志文件存档的权限、用户和组。 |
表中的选项是可以指定的示例。更多选项可以在logrotate配置文件中定义。有关更多信息,请参考http://linux.die.net/man/8/logrotate的手册页。
监控用户登录以寻找入侵者
日志文件可以用于收集系统状态和系统攻击的详细信息。
假设我们有一个连接到互联网的系统,并且启用了 SSH。许多攻击者正在尝试登录系统。我们需要设计一个入侵检测系统,以识别那些登录失败的用户。这些失败的尝试可能是黑客使用字典攻击进行的。脚本应生成一个报告,包含以下详细信息:
-
登录失败的用户
-
尝试次数
-
攻击者的 IP 地址
-
IP 地址的主机映射
-
登录尝试发生的时间
正在准备
一个 Shell 脚本可以扫描日志文件并收集所需的信息。登录详细信息记录在/var/log/auth.log或/var/log/secure中。脚本扫描日志文件以查找失败的登录尝试并分析数据。它使用host命令根据 IP 地址映射主机。
如何操作...
入侵检测脚本类似于如下:
#!/bin/bash
#Filename: intruder_detect.sh
#Description: Intruder reporting tool with auth.log input
AUTHLOG=/var/log/auth.log
if [[ -n $1 ]];
then
AUTHLOG=$1
echo Using Log file : $AUTHLOG
fi
# Collect the failed login attempts
LOG=/tmp/failed.$$.log
grep "Failed pass" $AUTHLOG > $LOG
# extract the users who failed
users=$(cat $LOG | awk '{ print $(NF-5) }' | sort | uniq)
# extract the IP Addresses of failed attempts
ip_list="$(egrep -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" $LOG | sort | uniq)"
printf "%-10s|%-3s|%-16s|%-33s|%s\n" "User" "Attempts" "IP address" \
"Host" "Time range"
# Loop through IPs and Users who failed.
for ip in $ip_list;
do
for user in $users;
do
# Count attempts by this user from this IP
attempts=`grep $ip $LOG | grep " $user " | wc -l`
if [ $attempts -ne 0 ]
then
first_time=`grep $ip $LOG | grep " $user " | head -1 | cut -c-16`
time="$first_time"
if [ $attempts -gt 1 ]
then
last_time=`grep $ip $LOG | grep " $user " | tail -1 | cut -c-16`
time="$first_time -> $last_time"
fi
HOST=$(host $ip 8.8.8.8 | tail -1 | awk '{ print $NF }' )
printf "%-10s|%-3s|%-16s|%-33s|%-s\n" "$user" "$attempts" "$ip"\
"$HOST" "$time";
fi
done
done
rm $LOG
输出类似如下:
Using Log file : secure
User |Attempts|IP address|Host |Time range
pi |1 |10.251.90.93 |3(NXDOMAIN) |Jan 2 03:50:24
root |1 |10.56.180.82 |2(SERVFAIL) |Dec 26 04:31:29
root |6 |10.80.142.25 |example.com |Dec 19 07:46:49 -> Dec 19 07:47:38
它是如何工作的...
intruder_detect.sh脚本默认使用/var/log/auth.log作为输入日志。或者,我们可以通过命令行参数提供日志文件。失败的登录记录会被收集到临时文件中,以减少处理。
当登录尝试失败时,SSH 日志行类似如下:
sshd[21197]: Failed password for bob1 from 10.83.248.32 port 50035
脚本greps用于查找Failed passw字符串,并将这些行放入/tmp/failed.$$.log文件中。
下一步是提取未能登录的用户。awk命令从末尾提取第五个字段(用户名),然后将其传递给sort和uniq命令,创建用户列表。
接下来,使用正则表达式和egrep命令提取唯一的 IP 地址。
嵌套的 for 循环遍历 IP 地址和用户,提取每个 IP 地址和用户组合的行。如果该 IP/用户组合的尝试次数大于 0,则使用grep、head 和 cut 提取第一次出现的时间。如果尝试次数大于 1,则使用 tail 而不是 head 提取最后一次的时间。
然后使用格式化的printf命令报告此登录尝试。
最后,临时文件被删除。
监控远程磁盘使用健康状况
磁盘会被填满,有时也会损坏。即使是 RAID 存储系统,如果在其他驱动器故障之前不更换有问题的驱动器,也可能会失败。监控存储系统的健康状况是管理员工作的一部分。
当一个自动化脚本检查网络上的设备并生成一行报告时,工作变得更加轻松,报告内容包括日期、机器的 IP 地址、设备、设备容量、已用空间、剩余空间、使用百分比和警告状态。如果磁盘使用率低于 80%,驱动器状态将报告为SAFE。如果驱动器快满并且需要注意,则状态将报告为ALERT。
准备工作
脚本使用 SSH 登录远程系统,收集磁盘使用统计数据,并将其写入中央机器的日志文件中。此脚本可以调度在特定时间运行。
脚本需要在远程机器上拥有一个普通用户账户,以便disklog脚本可以登录收集数据。我们应该为普通用户配置 SSH 自动登录(第八章《老男孩网络》中介绍了无密码 SSH 自动登录的操作)。
如何操作...
以下是代码:
#!/bin/bash
#Filename: disklog.sh
#Description: Monitor disk usage health for remote systems
logfile="diskusage.log"
if [[ -n $1 ]]
then
logfile=$1
fi
# Use the environment variable or modify this to a hardcoded value
user=$USER
#provide the list of remote machine IP addresses
IP_LIST="127.0.0.1 0.0.0.0"
# Or collect them at runtime with nmap
# IP_LIST=`nmap -sn 192.168.1.2-255 | grep scan | grep cut -c22-`
if [ ! -e $logfile ]
then
printf "%-8s %-14s %-9s %-8s %-6s %-6s %-6s %s\n" \
"Date" "IP address" "Device" "Capacity" "Used" "Free" \
"Percent" "Status" > $logfile
fi
(
for ip in $IP_LIST;
do
ssh $user@$ip 'df -H' | grep ^/dev/ > /tmp/$$.df
while read line;
do
cur_date=$(date +%D)
printf "%-8s %-14s " $cur_date $ip
echo $line | \
awk '{ printf("%-9s %-8s %-6s %-6s %-8s",$1,$2,$3,$4,$5); }'
pusg=$(echo $line | egrep -o "[0-9]+%")
pusg=${pusg/\%/};
if [ $pusg -lt 80 ];
then
echo SAFE
else
echo ALERT
fi
done< /tmp/$$.df
done
) >> $logfile
cron工具将定期调度脚本运行。例如,要每天上午 10 点运行该脚本,可以在crontab中写入以下条目:
00 10 * * * /home/path/disklog.sh /home/user/diskusg.log
运行crontab -e命令并添加上述行。
你可以通过以下方式手动运行脚本:
$ ./disklog.sh
之前脚本的输出类似如下:
01/18/17 192.168.1.6 /dev/sda1 106G 53G 49G 52% SAFE
01/18/17 192.168.1.6 /dev/md1 958G 776G 159G 84% ALERT
它是如何工作的...
disklog.sh脚本接受日志文件路径作为命令行参数,或者使用默认日志文件。-e $logfile检查文件是否存在。如果日志文件不存在,则使用列标题进行初始化。远程机器的 IP 地址列表可以在IP_LIST中硬编码,以空格分隔,或者可以使用nmap命令扫描网络以查找可用节点。如果使用nmap调用,请根据您的网络调整 IP 地址范围。
一个 for 循环遍历每个 IP 地址。ssh 应用程序向每个节点发送 df -H 命令以获取磁盘使用情况信息。df 输出存储在临时文件中。一个 while 循环逐行读取该文件,并调用 awk 提取相关数据并输出。一个 egrep 命令提取百分比值并去除 %。如果该值小于 80,则该行标记为 SAFE,否则标记为 ALERT。整个输出字符串必须重定向到 log 文件。因此,for 循环被包含在子 shell () 中,标准输出被重定向到日志文件。
参见
- 第十章《调度与 cron》中的调度与 cron 介绍了
crontab命令。
确定系统上活跃用户的小时数
这个配方利用系统日志来查明每个用户在服务器上花费了多少小时,并根据总使用时长对他们进行排名。报告包括排名、用户、首次登录日期、最后登录日期、登录次数和总使用时长等详细信息。
准备工作
关于用户会话的原始数据以二进制格式存储在 /var/log/wtmp 文件中。last 命令返回有关登录会话的详细信息。每个用户的会话小时数之和即为该用户的总使用时长。
如何操作...
该脚本将确定活跃用户并生成报告:
#!/bin/bash
#Filename: active_users.sh
#Description: Reporting tool to find out active users
log=/var/log/wtmp
if [[ -n $1 ]];
then
log=$1
fi
printf "%-4s %-10s %-10s %-6s %-8s\n" "Rank" "User" "Start" \
"Logins" "Usage hours"
last -f $log | head -n -2 > /tmp/ulog.$$
cat /tmp/ulog.$$ | cut -d' ' -f1 | sort | uniq> /tmp/users.$$
(
while read user;
do
grep ^$user /tmp/ulog.$$ > /tmp/user.$$
minutes=0
while read t
do
s=$(echo $t | awk -F: '{ print ($1 * 60) + $2 }')
let minutes=minutes+s
done< <(cat /tmp/user.$$ | awk '{ print $NF }' | tr -d ')(')
firstlog=$(tail -n 1 /tmp/user.$$ | awk '{ print $5,$6 }')
nlogins=$(cat /tmp/user.$$ | wc -l)
hours=$(echo "$minutes / 60.0" | bc)
printf "%-10s %-10s %-6s %-8s\n" $user "$firstlog" $nlogins $hours
done< /tmp/users.$$
) | sort -nrk 4 | awk '{ printf("%-4s %s\n", NR, $0) }'
rm /tmp/users.$$ /tmp/user.$$ /tmp/ulog.$$
输出类似于以下内容:
$ ./active_users.sh
Rank User Start Logins Usage hours
1 easyibaa Dec 11 531 349
2 demoproj Dec 10 350 230
3 kjayaram Dec 9 213 55
4 cinenews Dec 11 85 139
5 thebenga Dec 10 54 35
6 gateway2 Dec 11 52 34
7 soft132 Dec 12 49 25
8 sarathla Nov 1 45 29
9 gtsminis Dec 11 41 26
10 agentcde Dec 13 39 32
它是如何工作的...
active_users.sh 脚本从 /var/log/wtmp 或命令行中定义的 wtmp 日志文件中读取数据。last -f 命令提取日志文件内容。日志文件中的第一列是用户名。cut 命令提取日志文件中的第一列。sort 和 uniq 命令将其减少为唯一用户的列表。
脚本的外部循环遍历所有用户。对于每个用户,使用 grep 提取对应用户的日志行。
每行的最后一列是该次登录会话的持续时间。这些值在内部的 while read t 循环中进行求和。
会话持续时间的格式为 (HOUR:SEC)。该值通过 awk 提取最后一个字段,然后通过 tr -d 去除括号。第二个 awk 命令将 HH::MM 字符串转换为分钟数,然后将分钟数相加。当循环完成时,使用 $minutes 除以 60 将总分钟数转换为小时。
用户的首次登录时间是用户数据临时文件中的最后一行。这通过 tail 和 awk 提取。登录会话的数量是该文件中的行数,通过 wc 计算。
用户按总使用时长排序,使用 sort 的 -nr 选项进行数字降序排序,-k4 用于指定排序的列(使用时长)。最后,排序后的输出传递给 awk,它会在每行前加上行号,表示每个用户的排名。
测量和优化功耗
电池容量是移动设备(如笔记本电脑和平板电脑)上的一个关键资源。Linux 提供了测量功耗的工具,其中一个命令是powertop。
准备就绪
powertop应用程序在许多 Linux 发行版中没有预安装,你需要使用你的包管理器安装它。
如何操作...
powertop应用程序测量每个模块的功耗,并支持交互式优化功耗:
如果没有选项,powertop将在终端上显示一个界面:
# powertop
powertop命令进行测量,并显示有关功耗、最耗电的进程等详细信息:
PowerTOP 2.3 Overview Idle stats Frequency stats Device stats Tunable
Summary: 1146.1 wakeups/sec, 0.0 GPU ops/secs, 0.0 VFS ops/sec and 73.0% C Usage Events/s Category Description
407.4 ms/s 258.7 Process /usr/lib/vmware/bin/vmware
64.8 ms/s 313.8 Process /usr/lib64/firefox/firefox
-html标签将使powertop在一段时间内进行测量,并生成一个默认文件名为PowerTOP.html的 HTML 报告,你可以使用任何网页浏览器打开:
# powertop --html
在交互模式下,你可以优化功耗。当powertop运行时,使用箭头键或 Tab 键切换到 Tunables 标签页;这将显示powertop可以调整的属性列表,以减少功耗。选择你想要的选项,按回车键在“Bad”和“Good”之间切换。
如果你想监控便携设备电池的电力消耗,需要拔掉充电器并使用电池,以便powertop进行测量。
监控磁盘活动
监控工具的常见命名约定是以'top'结尾(用于监控进程的命令)。用于监控磁盘 I/O 的工具叫做iotop。
准备就绪
iotop应用程序在大多数 Linux 发行版中没有预安装,你需要使用你的包管理器安装它。iotop应用程序需要 root 权限,所以你需要以sudo或 root 用户身份运行它。
如何操作...
iotop应用程序可以执行连续监控或在固定时间段内生成报告:
- 对于连续监控,请使用以下命令:
# iotop -o
-o选项告诉iotop只显示那些在运行时进行活跃 I/O 的进程,从而减少输出中的噪声。
-n选项告诉 iotop 运行N次后退出:
# iotop -b -n 2
-p选项用于监控特定进程:
# iotop -p PID
PID是你希望监控的进程。
在大多数现代发行版中,你可以使用pidof命令,而不是查找 PID 并将其提供给iotop,可以将前面的命令写成:# iotop -p pidof cp
检查磁盘和文件系统的错误
Linux 文件系统非常强大。尽管如此,文件系统可能会损坏并导致数据丢失。尽早发现问题,能减少数据丢失和损坏的风险。
准备就绪
检查文件系统的标准工具是fsck。该命令在所有现代发行版中都已安装。请注意,你需要以 root 用户身份或通过sudo运行fsck。
如何操作...
如果文件系统长时间未检查,或者有原因(如电力中断后不安全的重启)怀疑它已经损坏,Linux 将在启动时自动运行fsck。你也可以手动运行fsck。
- 要检查分区或文件系统中的错误,传递路径给
fsck:
# fsck /dev/sdb3
fsck from util-linux 2.20.1
e2fsck 1.42.5 (29-Jul-2012)
HDD2 has been mounted 26 times without being checked, check forced.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
HDD2: 75540/16138240 files (0.7% non-contiguous),
48756390/64529088 blocks
-A标志检查/etc/fstab中配置的所有文件系统:
# fsck -A
这将遍历/etc/fstab文件,检查每个文件系统。fstab文件定义了物理磁盘分区与挂载点之间的映射。它在启动时用于挂载文件系统。
-a选项指示fsck自动尝试修复错误,而不是交互式地询问我们是否修复它们。使用此选项时要小心:
# fsck -a /dev/sda2
-N选项模拟fsck将执行的操作:
# fsck -AN
fsck from util-linux 2.20.1
[/sbin/fsck.ext4 (1) -- /] fsck.ext4 /dev/sda8
[/sbin/fsck.ext4 (1) -- /home] fsck.ext4 /dev/sda7
[/sbin/fsck.ext3 (1) -- /media/Data] fsck.ext3 /dev/sda6
它是如何工作的...
fsck应用程序是文件系统特定fsck应用程序的前端。当我们运行fsck时,它会检测文件系统的类型并运行相应的fsck.fstype命令,其中fstype是文件系统的类型。例如,如果我们在ext4文件系统上运行fsck,它将调用fsck.ext4命令。
因此,fsck仅支持所有文件系统特定工具中共有的选项。要查找更详细的选项,请阅读特定应用程序的手册页,如fsck.ext4。
虽然非常罕见,但fsck可能会丢失数据或使已经严重损坏的文件系统更糟。如果你怀疑文件系统严重损坏,应该使用-N选项列出fsck将执行的操作,而不实际执行它们。如果fsck报告它可以修复的超过十个问题,或者这些问题包括损坏的目录结构,你可能想要以只读模式挂载驱动器,并在运行fsck之前尽量提取关键数据。
检查磁盘健康
现代磁盘驱动器运行多年没有问题,但当磁盘故障时,它是一次重大灾难。现代磁盘驱动器配备了自监控、分析和报告技术(SMART)设施,以监控磁盘健康状态,这样你可以在发生重大故障之前更换即将故障的驱动器。
准备工作
Linux 通过smartmontools软件包支持与磁盘的 SMART 工具交互。大多数发行版默认安装此软件包。如果它未安装,你可以通过包管理器安装:
apt-get install smartmontools
另外,可以使用以下命令:
yum install smartmontools
如何操作...
smartmontools的用户界面是smartctl应用程序。此应用程序启动磁盘驱动器的测试并报告 SMART 设备的状态。
由于smartctl应用程序访问原始磁盘设备,因此你必须具有根权限才能运行它。
-a选项报告设备的完整状态:
$ smartctl -a /dev/sda
输出将包括基本信息的标题、一组原始数据值和测试结果。标题包括关于被测试驱动器的详细信息以及此报告的日期戳:
smartctl 5.43 2012-06-30 r3573 [x86_64-linux-2.6.32-
642.11.1.el6.x86_64] (local build)
Copyright (C) 2002-12 by Bruce Allen,
http://smartmontools.sourceforge.net
=== START OF INFORMATION SECTION ===
Device Model: WDC WD10EZEX-00BN5A0
Serial Number: WD-WCC3F1HHJ4T8
LU WWN Device Id: 5 0014ee 20c75fb3b
Firmware Version: 01.01A01
User Capacity: 1,000,204,886,016 bytes [1.00 TB]
Sector Sizes: 512 bytes logical, 4096 bytes physical
Device is: Not in smartctl database [for details use: -P
showall]
ATA Version is: 8
ATA Standard is: ACS-2 (unknown minor revision code: 0x001f)
Local Time is: Mon Jan 23 11:26:57 2017 EST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
...
原始数据值包括错误计数、启动时间、开机小时数等。最后两列(WHEN_FAILED 和 RAW_VALUE)尤其值得关注。在以下示例中,设备已开机 9823 小时。它已经开关机了 11 次(服务器不常进行断电重启),当前温度为 30°C。当开机时间接近制造商的平均故障间隔时间(MTBF)时,就该考虑更换硬盘或将其移至不太关键的系统。如果重启之间的电源循环次数增加,可能表示电源故障或电缆有问题。如果温度过高,应考虑检查硬盘的外壳,可能是风扇故障或过滤器堵塞:
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED
WHEN_FAILED RAW_VALUE
9 Power_On_Hours 0x0032 087 087 000 Old_age Always
- 9823
12 Power_Cycle_Count 0x0032 100 100 000 Old_age Always
- 11
194 Temperature_Celsius 0x0022 113 109 000 Old_age Always
- 30
输出的最后一部分将是测试结果:
SMART Error Log Version: 1
No Errors Logged
SMART Self-test log structure revision number 1
Num Test_Description Status Remaining LifeTime(hours)
LBA_of_first_error
# 1 Extended offline Completed without error 00% 9825
-
-t 标志强制 SMART 设备运行自检。这些测试是非破坏性的,可以在硬盘运行时进行。SMART 设备可以运行长时间或短时间的测试。短时间测试需要几分钟,而长时间测试在大容量设备上可能需要一小时或更长时间:
$ smartctl -t [long][short] DEVICE
$ smartctl -t long /dev/sda
smartctl 5.43 2012-06-30 r3573 [x86_64-linux-2.6.32-642.11.1.el6.x86_64] (local build)
Copyright (C) 2002-12 by Bruce Allen, http://smartmontools.sourceforge.net
=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===
Sending command: "Execute SMART Extended self-test routine immediately in off-line mode".
Drive command "Execute SMART Extended self-test routine immediately in off-line mode" successful.
Testing has begun.
Please wait 124 minutes for test to complete.
Test will complete after Mon Jan 23 13:31:23 2017
Use smartctl -X to abort test.
在两个多小时后,此测试将完成,并可以通过 smartctl -a 命令查看结果。
工作原理
现代硬盘远不止一个旋转的金属盘。它们包括 CPU、ROM、内存和定制的信号处理芯片。smartctl 命令通过与硬盘 CPU 上运行的小型操作系统交互,来请求测试并报告结果。
获取磁盘统计信息
smartctl 命令提供了许多磁盘统计信息,并测试硬盘。hdparm 命令提供更多统计信息,并检查磁盘在系统中的表现,可能会受到控制器芯片、电缆等的影响。
准备就绪
hdparm 命令在大多数 Linux 发行版中都是标准命令。使用它需要 root 权限。
如何操作...
-I 选项将提供有关设备的基本信息:
$ hdparm -I DEVICE
$ hdparm -I /dev/sda
以下示例输出展示了报告的部分数据。型号和固件与 smartctl 报告的一致。配置包括可以在分区和创建文件系统之前调节的参数:
/dev/sda:
ATA device, with non-removable media
Model Number: WDC WD10EZEX-00BN5A0
Serial Number: WD-WCC3F1HHJ4T8
Firmware Revision: 01.01A01
Transport: Serial, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0
Standards:
Used: unknown (minor revision code 0x001f)
Supported: 9 8 7 6 5
Likely used: 9
Configuration:
Logical max current
cylinders 16383 16383
heads 16 16
sectors/track 63 63
--
CHS current addressable sectors: 16514064
LBA user addressable sectors: 268435455
LBA48 user addressable sectors: 1953525168
Logical Sector size: 512 bytes
Physical Sector size: 4096 bytes
device size with M = 1024*1024: 953869 MBytes
device size with M = 1000*1000: 1000204 MBytes (1000 GB)
cache/buffer size = unknown
Nominal Media Rotation Rate: 7200
...
Security:
Master password revision code = 65534
supported
not enabled
not locked
not frozen
not expired: security count
supported: enhanced erase
128min for SECURITY ERASE UNIT. 128min for ENHANCED SECURITY ERASE UNIT.
Logical Unit WWN Device Identifier: 50014ee20c75fb3b
NAA : 5
IEEE OUI : 0014ee
Unique ID : 20c75fb3b
Checksum: correct
工作原理
hdparm 命令是进入内核库和模块的用户界面。它支持修改参数和报告参数。修改这些参数时必须小心谨慎!
还有更多
hdparm 命令可以测试磁盘性能。-t 和 -T 选项分别对缓冲和缓存读取进行时间测试:
# hdparm -t /dev/sda
Timing buffered disk reads: 486 MB in 3.00 seconds = 161.86 MB/sec
# hdparm -T /dev/sda
Timing cached reads: 26492 MB in 1.99 seconds = 13309.38 MB/sec
第十章:管理调用
本章将涵盖以下主题:
-
收集有关进程的信息
-
各种命令的功能 - which、whereis、whatis 和 file
-
杀死进程,发送和响应信号
-
向用户终端发送消息
-
/proc文件系统 -
收集系统信息
-
使用
cron调度任务 -
数据库风格与用途
-
编写和读取 SQLite 数据库
-
从 Bash 编写和读取 MySQL 数据库
-
用户管理脚本
-
批量调整图像大小和格式转换
-
从终端截屏
-
从一个管理多个终端
介绍
从一个 GNU/Linux 系统管理多个终端:GNU/Linux 生态系统由网络、硬件、操作系统内核(负责资源分配)、接口模块、系统工具和用户程序组成。管理员需要监控整个系统,以确保一切顺利运行。Linux 管理工具从集成的图形用户界面应用程序到专为脚本设计的命令行工具不等。
收集有关进程的信息
在这种情况下,进程一词指的是程序的运行实例。计算机上同时运行着多个进程。每个进程都会分配一个唯一的标识号,称为进程 ID(PID)。同名的多个程序实例可以同时运行,但它们会拥有不同的 PID 和属性。进程属性包括拥有进程的用户、程序使用的内存量、程序使用的 CPU 时间等。此教程展示了如何收集有关进程的信息。
准备就绪
与进程管理相关的重要命令有 top、ps 和 pgrep。这些工具在所有 Linux 发行版中都有提供。
如何执行...
ps 命令报告活动进程的信息。它提供有关哪个用户拥有进程、进程何时启动、执行进程的命令路径、PID、附加的终端(TTY,即电传打字机)、进程使用的内存、进程使用的 CPU 时间等信息。考虑以下示例:
$ ps
PID TTY TIME CMD
1220 pts/0 00:00:00 bash
1242 pts/0 00:00:00 ps
默认情况下,ps 只会显示当前终端(TTY)启动的进程。第一列显示 PID,第二列表示终端(TTY),第三列表示自进程启动以来经过的时间,最后一列是 CMD(命令)。
ps 命令报告可以通过命令行参数进行修改。
-f (full) 选项显示更多列的信息:
$ ps -f
UID PID PPID C STIME TTY TIME CMD
slynux 1220 1219 0 18:18 pts/0 00:00:00 -bash
slynux 1587 1220 0 18:59 pts/0 00:00:00 ps -f
-e(所有)和 -ax(全部)选项提供了有关系统上所有正在运行进程的报告。
-x 参数(与 -a 一起使用)表示去除 ps 默认的 TTY 限制。通常,如果没有使用任何参数,ps 只会打印与当前终端连接的进程。
命令 ps -e、ps -ef、ps -ax 和 ps -axf 生成有关所有进程的报告,并提供比 ps 更多的信息:
$ ps -e | head -5
PID TTY TIME CMD
1 ? 00:00:00 init
2 ? 00:00:00 kthreadd
3 ? 00:00:00 migration/0
4 ? 00:00:00 ksoftirqd/0
-e 选项生成一个长报告。这个示例使用 head 过滤输出,只显示前五个条目。
-o PARAMETER1, PARAMETER2 选项指定要显示的数据。
-o 参数之间用逗号(,)分隔。逗号运算符与下一个参数之间没有空格。
-o 选项可以与 -e(每个)选项(-eo)结合使用,以列出系统中运行的每个进程。但是,当你使用类似于限制 ps 到指定用户的过滤器与 -o 一起使用时,-e 将不会被使用。-e 选项会覆盖过滤器并显示所有进程。
在这个例子中,comm 代表 COMMAND,pcpu 代表 CPU 使用百分比:
$ ps -eo comm,pcpu | head -5
COMMAND %CPU
init 0.0
kthreadd 0.0
migration/0 0.0
ksoftirqd/0 0.0
它是如何工作的...
支持以下 -o 选项的参数:
| 参数 | 描述 |
|---|---|
pcpu |
CPU 使用百分比 |
pid |
进程 ID |
ppid |
父进程 ID |
pmem |
内存使用百分比 |
comm |
可执行文件名 |
cmd |
简单命令 |
user |
启动进程的用户 |
nice |
优先级(亲和度) |
time |
累积 CPU 时间 |
etime |
进程启动以来的经过时间 |
tty |
关联的 TTY 设备 |
euid |
有效用户 |
stat |
进程状态 |
还有更多内容...
ps 命令、grep 和其他工具可以结合使用,以生成自定义报告。
显示进程的环境变量
一些进程依赖于它们的环境变量定义。了解环境变量及其值可以帮助你调试或定制进程。
ps 命令通常不会显示命令的环境信息。命令末尾的 e 输出修饰符会将这些信息添加到输出中:
$ ps e
这是一个环境信息的示例:
$ ps -eo pid,cmd e | tail -n 1
1238 -bash USER=slynux LOGNAME=slynux HOME=/home/slynux PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAIL=/var/mail/slynux SHELL=/bin/bash SSH_CLIENT=10.211.55.2 49277 22 SSH_CONNECTION=10.211.55.2 49277 10.211.55.4 22 SSH_TTY=/dev/pts/0
环境信息有助于通过 apt-get 包管理器追踪问题。如果你使用 HTTP 代理连接到互联网,可能需要通过 http_proxy=host:port 设置环境变量。如果未设置此变量,apt-get 命令将不会选择代理,从而返回错误。知道没有设置 http_proxy 会使问题变得显而易见。
当使用调度工具(如本章后面讨论的 cron)来运行应用程序时,预期的环境变量可能没有设置。这个 crontab 条目不会打开 GUI 窗口应用程序:
00 10 * * * /usr/bin/windowapp
它失败是因为 GUI 应用程序需要 DISPLAY 环境变量。要确定所需的环境变量,请手动运行 windowapp,然后运行 ps -C windowapp -eo cmd e。
确定所需的环境变量后,在 crontab 中的命令名称之前定义它们:
00 10 * * * DISPLAY=:0 /usr/bin/windowapp
或者
DISPLAY=0
00 10 * * * /usr/bin/windowapp
定义 DISPLAY=:0 是通过 ps 输出获取的。
创建进程树视图
ps命令可以报告进程 PID,但从子进程追踪到最终父进程很麻烦。将f添加到ps命令的末尾会创建一个进程树视图,显示任务之间的父子关系。下一个示例显示了从 bash shell 中运行的ssh会话,该会话是在xterm内启动的:
$ ps -u clif f | grep -A2 xterm | head -3
15281 ? S 0:00 xterm
15284 pts/20 Ss+ 0:00 \_ bash
15286 pts/20 S+ 0:18 \_ ssh 192.168.1.2
对 ps 输出进行排序
默认情况下,ps命令的输出是未排序的。-sort参数强制ps对输出进行排序。可以通过在参数前添加+(升序)或-(降序)前缀来指定升序或降序:
$ ps [OPTIONS] --sort -paramter1,+parameter2,parameter3..
例如,要列出前五个 CPU 占用率最高的进程,请使用以下命令:
$ ps -eo comm,pcpu --sort -pcpu | head -5
COMMAND %CPU
Xorg 0.1
hald-addon-stor 0.0
ata/0 0.0
scsi_eh_0 0.0
这会显示前五个进程,按 CPU 使用百分比降序排序。
grep命令可以过滤ps输出。要仅报告当前正在运行的 Bash 进程,请使用以下命令:
$ ps -eo comm,pid,pcpu,pmem | grep bash
bash 1255 0.0 0.3
bash 1680 5.5 0.3
使用 ps 过滤真实用户或 ID、有效用户或 ID
ps命令可以根据指定的实际和有效用户名或 ID 对进程进行分组。ps命令通过检查每个条目是否属于有效用户或真实用户,从参数列表中过滤输出。
-
使用
-u EUSER1、EUSER2等指定有效用户列表 -
使用
-U RUSER1、RUSER2等指定真实用户列表
这是一个示例:
# display user and percent cpu usage for processes with real user
# and effective user of root
$ ps -u root -U root -o user,pcpu
-o选项可以与-e一起使用,如-eo,但当应用过滤器时,-e不应使用,它会覆盖过滤器选项。
ps 的 TTY 过滤器
ps输出可以通过指定进程所在的 TTY 来选择。使用-t选项指定 TTY 列表:
$ ps -t TTY1, TTY2 ..
这是一个示例:
$ ps -t pts/0,pts/1
PID TTY TIME CMD
1238 pts/0 00:00:00 bash
1835 pts/1 00:00:00 bash
1864 pts/0 00:00:00 ps
进程线程信息
ps的-L选项将显示进程线程的信息。此选项会在线程 ID 旁边添加 LWP 列。将-f选项与-L(-Lf)一起使用会添加两列:NLWP(线程数)和 LWP(线程 ID):
$ ps -Lf
UID PID PPID LWP C NLWP STIME TTY TIME
CMD
user 1611 1 1612 0 2 Jan16 ? 00:00:00
/usr/lib/gvfs/gvfsd
该命令列出了五个进程及其最大线程数:
$ ps -eLf --sort -nlwp | head -5
UID PID PPID LWP C NLWP STIME TTY TIME
CMD
root 647 1 647 0 64 14:39 ? 00:00:00
/usr/sbin/console-kit-daemon --no-daemon
root 647 1 654 0 64 14:39 ? 00:00:00
/usr/sbin/console-kit-daemon --no-daemon
root 647 1 656 0 64 14:39 ? 00:00:00
/usr/sbin/console-kit-daemon --no-daemon
root 647 1 657 0 64 14:39 ? 00:00:00
/usr/sbin/console-kit-daemon --no-daemon
指定输出宽度和要显示的列
ps命令支持多种选项来选择要显示的字段并控制其显示方式。以下是一些常见选项:
-f |
这指定了一个完整的格式,包含父 PID 的启动时间、用户 ID 等。 |
|---|---|
-u userList |
选择由列表中的用户拥有的进程,默认选择当前用户。 |
-l |
长格式列出,显示用户 ID、父 PID、大小等信息。 |
什么是什么 – which、whereis、whatis 和 file
可能有多个文件具有相同的名称。知道正在调用哪个可执行文件以及文件是编译代码还是脚本是有用的信息。
如何操作...
which、whereis、file和whatis命令报告文件和目录的信息。
which:which 命令报告命令的位置:
$ which ls
/bin/ls
-
我们经常在不知道可执行文件存储目录的情况下使用命令。根据
PATH变量的定义方式,你可能会使用来自/bin、/usr/local/bin或/opt/PACKAGENAME/bin的命令。 -
当我们输入命令时,终端会在一组目录中查找该命令,并执行它找到的第一个可执行文件。要搜索的目录由
PATH环境变量指定:
$ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
- 我们可以添加要搜索的目录并导出新的
PATH。要将/opt/bin添加到PATH,请使用以下命令:
$ export PATH=$PATH:/opt/bin
# /opt/bin is added to PATH
- whereis:
whereis类似于 which 命令。它不仅返回命令的路径,还打印命令的 man 页位置(如果有的话)以及命令源代码的路径(如果有的话):
$ whereis ls
ls: /bin/ls /usr/share/man/man1/ls.1.gz
- whatis:
whatis命令输出作为参数传递的命令的单行描述。它从man页解析信息:
$ whatis ls
ls (1) - list directory contents
file 命令报告文件类型。其语法如下:
$ file FILENAME
- 报告的文件类型可能包含几个词或一个长描述:
$file /etc/passwd
/etc/passwd: ASCII text
$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux
2.6.15, stripped
apropos
有时候我们需要搜索与主题相关的命令。apropos 命令会在 man 页中搜索关键字。以下是执行此操作的代码:Apropos 主题
从给定的命令名中查找进程 ID
假设正在执行多个命令实例。在这种情况下,我们需要每个进程的 PID。ps 和 pgrep 命令都会返回此信息:
$ ps -C COMMAND_NAME
或者,以下内容将被返回:
$ ps -C COMMAND_NAME -o pid=
当 = 被附加到 pid 时,它会从 ps 的输出中移除 PID 标头。要从某一列中移除标头,只需在参数后附加 =。
该命令列出 Bash 进程的进程 ID:
$ ps -C bash -o pid=
1255
1680
pgrep 命令也会返回一个命令的进程 ID 列表:
$ pgrep bash
1255
1680
pgrep 只需要命令名的一部分作为输入参数来提取 Bash 命令;例如,pgrep ash 或 pgrep bas 也能工作。但 ps 命令要求你输入准确的命令。pgrep 支持这些输出过滤选项。
-d 选项指定一个与默认换行符不同的输出分隔符:
$ pgrep COMMAND -d DELIMITER_STRING
$ pgrep bash -d ":"
1255:1680
-u 选项过滤出用户列表:
$ pgrep -u root,slynux COMMAND
在这个命令中,root 和 slynux 是用户。
-c 选项返回匹配进程的数量:
$ pgrep -c COMMAND
确定系统的繁忙程度
系统要么空闲,要么超载。load average 值描述了运行系统的总负载。它描述了系统上可运行进程的平均数量,这些进程拥有除 CPU 时间片外的所有资源。
系统的负载平均值由 uptime 和 top 命令报告。它会报告三个值。第一个值表示 1 分钟的平均值,第二个表示 5 分钟的平均值,第三个表示 15 分钟的平均值。
它由 uptime 报告:
$ uptime
12:40:53 up 6:16, 2 users, load average: 0.00, 0.00, 0.00
top 命令
默认情况下,top 命令显示消耗最多 CPU 的进程列表以及基本的系统统计信息,包括进程列表中的任务数、CPU 核心数和内存使用情况。输出会每隔几秒钟更新一次。
此命令显示多个参数以及消耗最多 CPU 的进程:
$ top
top - 18:37:50 up 16 days, 4:41,7 users,load average 0.08 0.05 .11
Tasks: 395 total, 2 running, 393 sleeping, 0 stopped 0 zombie
另请参见...
- 本章中的 使用 cron 调度任务 说明了如何调度任务
杀死进程,并发送和响应信号
如果你需要降低系统负载,或者在重启之前,可能需要杀死一些进程(如果它们失控并开始消耗过多资源)。信号是一种进程间通信机制,可以中断正在运行的进程,并迫使其执行某些操作。这些操作包括强制进程以受控或立即的方式终止。
准备就绪
信号向运行中的程序发送中断。当进程收到信号时,它会通过执行信号处理程序来响应。编译后的应用程序通过 kill 系统调用生成信号。信号可以通过命令行(或 shell 脚本)中的 kill 命令生成。trap 命令可用于脚本中处理接收到的信号。
每个信号都有一个名称和一个整数值。SIGKILL (9) 信号会立即终止进程。按键事件 Ctrl + C 和 Ctrl + Z 会发送信号中止任务或将任务放入后台。
如何操作...
kill -l命令将列出可用的信号:
$ kill -l
SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
...
- 终止进程:
$ kill PROCESS_ID_LIST
kill 命令默认会发送 SIGTERM 信号。进程 ID 列表使用空格作为分隔符。
-s选项指定要发送给进程的信号:
$ kill -s SIGNAL PID
SIGNAL 参数可以是信号名称或信号编号。不同用途的信号有很多种,最常见的如下:
-
SIGHUP 1:控制进程或终端死亡时的挂起检测 -
SIGINT 2:这是在按下 Ctrl + C 时发出的信号 -
SIGKILL 9:这是用于强制终止进程的信号 -
SIGTERM 15:这是默认用于终止进程的信号 -
SIGTSTP 20:这是在按下Ctrl + Z 时发出的信号
- 我们经常使用强制杀死进程的方式。使用时需谨慎。这是一个立即的操作,它不会保存数据或执行正常的清理操作。应首先尝试使用
SIGTERM信号;SIGKILL应仅在极端情况下使用:
$ kill -s SIGKILL PROCESS_ID
或者,使用此方法执行清理操作:
$ kill -9 PROCESS_ID
还有更多...
Linux 支持其他命令来发送信号或终止进程。
kill 系列命令
kill 命令将进程 ID 作为参数。killall 命令通过进程名终止进程:
$ killall process_name
-s 选项指定要发送的信号。默认情况下,killall 发送 SIGTERM 信号:
$ killall -s SIGNAL process_name
-9 选项通过进程名强制杀死进程:
$ killall -9 process_name
以下是前面的示例:
$ killall -9 gedit
-u 所有者指定进程的用户:
$ killall -u USERNAME process_name
-I 选项使 killall run 以交互模式运行:
pkill 命令类似于 kill 命令,但默认情况下它接受的是进程名而不是进程 ID:
$ pkill process_name
$ pkill -s SIGNAL process_name
SIGNAL 是信号号。SIGNAL 名称不支持 pkill。pkill 命令提供了与 kill 命令相同的许多选项。有关更多细节,请查看 pkill 的手册页。
捕获并响应信号
良好的程序在接收到 SIGTERM 信号时会保存数据并正常关闭。trap 命令为脚本中的信号分配信号处理程序。一旦通过 trap 命令将函数分配给信号,当脚本接收到该信号时,这个函数会被执行。
语法如下:
trap 'signal_handler_function_name' SIGNAL LIST
SIGNAL LIST 是空格分隔的。它可以包含信号号和信号名称。
这个 shell 脚本响应 SIGINT 信号:
#/bin/bash
#Filename: sighandle.sh
#Description: Signal handler
function handler()
{
echo Hey, received signal : SIGINT
}
# $$ is a special variable that returns process ID of current
# process/script
echo My process ID is $$
#handler is the name of the signal handler function for SIGINT signal
trap 'handler' SIGINT
while true;
do
sleep 1
done
在终端中运行这个脚本。当脚本正在运行时,按下 Ctrl + C,它会通过执行与之关联的信号处理程序来显示消息。Ctrl + C 对应于 SIGINT 信号。
while 循环用于保持进程永远运行而不被终止。这样做是为了让脚本能够响应信号。保持进程无限存活的循环通常被称为 事件循环。
如果给定了脚本的进程 ID,kill 命令可以向其发送信号:
$ kill -s SIGINT PROCESS_ID
执行时,会打印出前述脚本的进程 ID;或者,你可以使用 ps 命令找到它。
如果没有为信号指定信号处理程序,脚本将调用操作系统分配的默认信号处理程序。通常,按 Ctrl + C 会终止程序,因为操作系统提供的默认处理程序会终止该进程。这里定义的自定义处理程序会覆盖默认处理程序。
我们可以使用 trap 命令为任何可用的信号(kill -l)定义信号处理程序。一个信号处理程序可以处理多个信号。
向用户终端发送消息
Linux 支持三种应用程序向其他用户的屏幕显示消息。write 命令向用户发送消息,talk 命令让两个用户进行对话,wall 命令向所有用户发送消息。
在执行可能导致系统中断的操作之前(例如,重启服务器),系统管理员应该向系统或网络中每个用户的终端发送一条消息。
准备工作
write 和 wall 命令是大多数 Linux 发行版的一部分。如果一个用户登录了多个会话,你可能需要指定要发送消息的终端。
你可以使用 who 命令来确定用户的终端:
$> who
user1 pts/0 2017-01-16 13:56 (:0.0)
user1 pts/1 2017-01-17 08:35 (:0.0)
第二列(pts/#)是用户的终端标识符。
write 和 wall 程序仅在单一系统上工作。talk 程序可以在网络中连接多个用户。
talk 程序并不常安装。在使用 talk 的任何机器上,talk 程序和 talk 服务器都必须安装并运行。在基于 Debian 的系统上安装 talk 应用程序为 talk 和 talkd,在基于 Red Hat 的系统上为 talk 和 talk-server。你可能需要编辑 /etc/xinet.d/talk 和 /etc/xinet.d/ntalk 来将 disable 字段设置为 no。完成后,重新启动 xinet:
# cd /etc/xinet.d
# vi ntalk
# cd /etc/init.d
#./xinetd restart
如何操作...
向单个用户发送消息
write 命令将向单个用户发送消息:
$ write USERNAME [device]
你可以从文件、回显或者交互式方式重定向消息。交互式写入通过 Ctrl-D 结束。
通过将伪终端标识符附加到命令中,可以将消息定向到特定会话:
$ echo "Log off now. I'm rebooting the system" | write user1 pts/3
与其他用户进行对话
talk 命令会在两个用户之间开启一个交互式对话。其语法为 $ talk user@host。
下一个命令将在用户 2 的工作站上发起与其的对话:
$ talk user2@workstation2.example.com
输入 talk 命令后,你的终端会被清空,并分成两个窗口。在其中一个窗口,你将看到如下文本:
[Waiting for your party to respond]
你尝试与之交谈的人将看到如下消息:
Message from Talk_Daemon@workstation1.example.com
talk: connection requested by user1@workstation.example.com
talk: respond with talk user1@workstation1.example.com
当他们调用 talk 时,他们的终端会话也会被清空并分裂。你在一个窗口输入的内容将在他们的屏幕上显示,而他们输入的内容则会出现在你的屏幕上:
I need to reboot the database server.
How much longer will your processing take?
---------------------------------------------
90% complete. Should be just a couple more minutes.
向所有用户发送消息
wall(WriteALL)命令会广播一条消息到所有用户和终端会话:
$ cat message | wall
或:
$ wall < message
Broadcast Message from slynux@slynux-laptop
(/dev/pts/1) at 12:54 ...
This is a message
消息头显示了消息的发送者:包括用户和主机。
write、talk 和 wall 命令仅在启用写消息选项时才能在用户之间传递消息。来自 root 的消息无论写消息选项如何都将显示。
消息选项通常是启用的。mesg 命令将启用或禁用接收消息:
# enable receiving messages
$ mesg y
# disable receiving messages
$ mesg n
/proc 文件系统
/proc 是一个内存中的伪文件系统,提供对许多 Linux 内核内部数据结构的用户空间访问。大多数伪文件是只读的,但有些文件,如 /proc/sys/net/ipv4/forward(在第八章,老男孩网络中有描述),可以用来微调系统行为。
如何操作...
/proc 目录包含多个文件和目录。你可以使用 cat、less 或 more 查看 /proc 及其子目录中的大部分文件。它们以纯文本形式显示。
系统上运行的每个进程都有一个 /proc 目录,目录名称为该进程的 PID。
假设 Bash 正在运行,PID 为 4295(通过 pgrep bash 查找);在这种情况下,/proc/4295 将存在。这个文件夹将包含关于该进程的信息。/proc/PID 下的文件包括:
-
environ:它包含与进程相关的环境变量。cat /proc/4295/environ将显示传递给进程4295的环境变量。 -
cwd:这是指向进程工作目录的symlink。 -
exe:这是指向进程可执行文件的symlink:
$ readlink /proc/4295/exe
/bin/bash
-
fd:这是一个包含进程使用的文件描述符条目的目录。值 0、1 和 2 分别表示 stdin、stdout 和 stderr。 -
io:此文件显示进程读取或写入的字符数。
收集系统信息
描述计算机系统需要多组数据。这些数据包括网络信息、主机名、内核版本、Linux 发行版名称、CPU 描述、内存分配、磁盘分区等。这些信息可以通过命令行获取。
如何操作...
hostname和uname命令打印当前系统的主机名:
$ hostname
或者,它们会打印以下内容:
$ uname -n
server.example.com
uname的-a选项打印有关 Linux 内核版本、硬件架构等的详细信息:
$ uname -a
server.example.com 2.6.32-642.11.1.e16.x86_64 #1 SMP Fri Nov 18
19:25:05 UTC 2016 x86_64 x86_64 GNU/Linux
-r选项将报告限制为内核版本:
$ uname -r
2.6.32-642.11.1.e16.x86_64
-m选项打印机器类型:
$ uname -m
x86_64
/proc/目录包含关于系统、模块和正在运行的进程的信息。/proc/cpuinfo包含 CPU 详细信息:
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 63
model name : Intel(R)Core(TM)i7-5820K CPU @ 3.30GHz
...
如果处理器有多个核心,这些行会重复 n 次。要提取单项信息,请使用sed。第五行包含处理器名称:
$ cat /proc/cpuinfo | sed -n 5p
Intel(R)CORE(TM)i7-5820K CPU @ 3.3 GHz
/proc/meminfo包含有关内存和当前 RAM 使用情况的信息:
$ cat /proc/meminfo
MemTotal: 32777552 kB
MemFree: 11895296 kB
Buffers: 634628 kB
...
meminfo 的第一行显示系统的总 RAM:
$ cat /proc/meminfo | head -1
MemTotal: 1026096 kB
/proc/partitions描述磁盘分区:
$ cat /proc/partitions
major minor #blocks name
8 0 976762584 sda
8 1 512000 sda1
8 2 976248832 sda2
...
fdisk 程序编辑磁盘的分区表并报告当前分区表。以 root 身份运行此命令:
$ sudo fdisk -l
lshw和dmidecode应用程序生成关于系统的长时间且完整的报告。报告包括主板、BIOS、CPU、内存插槽、接口插槽、磁盘等信息。这些命令必须以 root 身份运行。dmidecode通常是可用的,但你可能需要安装lshw:
$ sudo lshw
description: Computer
product: 440BX
vendor: Intel
...
$ sudo dmidecode
SMBIOS 2.8 present
115 structures occupying 4160 bytes.
Table at 0xDCEE1000.
BIOS Information
Vendor: American Megatrends Inc
...
使用 cron 调度
GNU/Linux 系统支持几种调度任务的工具。cron 工具是最广泛支持的。它允许你安排任务在后台以固定间隔执行。cron 工具使用一个表(crontab),列出要执行的脚本或命令及其执行时间。
Cron 用于安排系统日常任务,例如执行备份、将系统时钟与 ntpdate 同步、删除临时文件等。
普通用户可能会使用 cron 安排互联网下载任务,在晚上网络提供商允许的时间进行下载,并且带宽较高。
准备就绪
cron 调度工具随所有 GNU/Linux 发行版提供。它扫描 cron 表以确定是否有命令应当执行。每个用户都有自己的 cron 表,这是一个纯文本文件。crontab 命令用于操作 cron 表。
如何操作...
crontab 条目指定执行命令的时间和要执行的命令。cron 表中的每一行定义一个单独的命令。该命令可以是脚本或二进制应用程序。当 cron 运行任务时,它以创建条目的用户身份运行,但不会加载该用户的 .bashrc 文件。如果任务需要环境变量,则必须在 crontab 中定义它们。
每行 cron 表由六个以空格分隔的字段组成,顺序如下:
-
分钟(0 - 59) -
小时(0 - 23) -
天(1 - 31) -
月份(1 - 12) -
星期几(0 - 6) -
命令(在指定时间执行的脚本或命令)
前五个字段指定命令执行的时间。多个值之间用逗号分隔(没有空格)。星号表示任何时间或任何一天都能匹配。除号表示按每 /Y 间隔触发事件(例如,*/5 表示每五分钟触发一次)。
- 在每小时的第 2 分钟执行
test.sh脚本:
02 * * * * /home/slynux/test.sh
- 在每天的第 5、6、7 小时执行 test.sh:
00 5,6,7 * * /home/slynux/test.sh
- 每隔一小时在星期天执行
script.sh:
00 */2 * * 0 /home/slynux/script.sh
- 每天凌晨 2 点关机:
00 02 * * * /sbin/shutdown -h
crontab命令既可以交互式使用,也可以与预写的文件一起使用。
使用 -e 选项与 crontab 一起编辑 cron 表:
$ crontab -e
02 02 * * * /home/slynux/script.sh
输入 crontab -e 时,默认文本编辑器(通常是 vi)将被打开,用户可以输入 cron 作业并保存。cron 作业将在指定的时间间隔内进行调度并执行。
-
crontab命令可以从脚本中调用,以用新的内容替换当前的 crontab。具体操作如下:- 创建一个文本文件(例如,
task.cron),其中包含cron任务,然后运行crontab并将该文件名作为命令参数:
- 创建一个文本文件(例如,
$ crontab task.cron
- 或者,将
cron任务作为内联函数指定,而不创建单独的文件。例如,参考以下内容:
$ crontab<<EOF
02 * * * * /home/slynux/script.sh
EOF
cron 任务需要写在 crontab<<EOF 和 EOF 之间。
它是如何工作的...
星号(*)表示在给定时间段内每次都执行该命令。cron 任务中的 小时 字段如果是 *,则表示每小时都执行该命令。要在时间段的多个实例中执行命令,请在该时间字段中指定用逗号分隔的时间间隔。例如,要在第 5 和第 10 分钟执行命令,可以在 分钟 字段中输入 5,10。斜杠(除号)符号表示按时间的分割执行命令。例如,在 分钟 字段中输入 0-30/6 将会在每小时的前半小时,每隔 5 分钟执行一次命令。小时 字段中的字符串 */12 将表示每隔一小时执行一次命令。
Cron 作业以创建 crontab 的用户身份执行。如果需要执行需要更高权限的命令,例如关闭计算机,请以 root 用户身份运行 crontab 命令。
在 cron 作业中指定的命令需要写出命令的完整路径。这是因为 cron 不会加载你的.bashrc,因此 cron 作业执行时的环境与我们在终端中执行 bash 时的环境不同。因此,PATH环境变量可能没有设置。如果你的命令需要特定的环境变量,必须显式地设置它们。
还有更多...
crontab命令有更多选项。
指定环境变量
许多命令要求环境变量正确设置才能执行。cron 命令将 SHELL 变量设置为"/bin/sh",并且还会从/etc/passwd中的值设置LOGNAME和HOME。如果需要其他变量,可以在crontab中定义。这些可以为所有任务定义,或者为单个任务单独定义。
如果定义了MAILTO环境变量,cron会通过电子邮件将命令的输出发送给该用户。
crontab通过在用户的cron表中插入带有变量赋值语句的一行来定义环境变量。
以下的crontab定义了一个http_proxy环境变量,用于在互联网交互时使用代理服务器:
http_proxy=http://192.168.0.3:3128
MAILTO=user@example.com
00 * * * * /home/slynux/download.sh
该格式由vixie-cron支持,vixie-cron用于 Debian、Ubunto 和 CentOS 发行版。对于其他发行版,环境变量可以按命令逐个定义:
00 * * * * http_proxy=http:192.168.0.2:3128;
/home/sylinux/download.sh
在系统启动时/开机时运行命令
在系统启动时(或开机时)运行特定命令是一个常见需求。一些cron实现支持@reboot时间字段,用于在重启过程中运行任务。请注意,并非所有cron实现都支持此功能,且在某些系统上只有 root 才能使用此功能。现在,看看以下代码:
@reboot command
这将在运行时以你的用户身份执行该命令。
查看 cron 表
-l选项可以列出当前用户的 crontab:
$ crontab -l
02 05 * * * /home/user/disklog.sh
添加-u选项将指定一个用户的 crontab 进行查看。你必须以 root 身份登录才能使用-u选项:
# crontab -l -u slynux
09 10 * * * /home/slynux/test.sh
删除 cron 表
-r选项将删除当前用户的 cron 表:
$ crontab -r
-u选项指定要删除的 crontab。你必须是 root 用户才能删除其他用户的 crontab:
# crontab -u slynux -r
数据库样式及用途
Linux 支持多种数据库样式,从简单的文本文件(/etc/passwd)到低级 B-树数据库(Berkely DB 和 bdb),轻量级 SQL(sqlite)以及功能全面的关系型数据库服务器,如 Postgres、Oracle 和 MySQL。
选择数据库样式的一个经验法则是使用对你的应用最适合且最简单的系统。当字段已知且固定时,文本文件和grep足以作为一个小型数据库。
一些应用程序需要引用。例如,书籍和作者的数据库应创建两个表,一个用于书籍,一个用于作者,以避免为每本书重复作者信息。
如果表的读取频率远高于修改频率,那么 SQLite 是一个不错的选择。这个数据库引擎不需要服务器,便于移植并易于嵌入到其他应用程序中(如 Firefox)。
如果数据库经常被多个任务修改(例如,网店的库存系统),那么像 Postgres、Oracle 或 MySQL 这样的关系数据库管理系统(RDBMS)是合适的选择。
准备就绪
你可以使用标准 Shell 工具创建一个基于文本的数据库。SqlLite 通常默认安装;其可执行文件是 sqlite3。你需要安装 MySQL、Oracle 和 Postgres。下一节将介绍如何安装 MySQL。你可以从 www.oracle.com 下载 Oracle。Postgres 通常可以通过包管理器安装。
如何操作...
可以使用常见的 Shell 工具构建一个文本文件数据库。
要创建一个地址列表,创建一个每行一个地址的文件,并且字段由已知字符分隔。在这种情况下,字符是波浪号(~):
first last~Street~City, State~Country~Phone~
例如:
Joe User~123 Example Street~AnyTown, District~1-123-123-1234~
然后添加一个函数,找到匹配模式的行并将每一行翻译成更易于理解的格式:
function addr {
grep $1 $HOME/etc/addr.txt | sed 's/~/\n/g'
}
使用时,这将类似于以下内容:
$ addr Joe
Joe User
123 Example Street
AnyTown District
1-123-123-1234
还有更多...
SQLite、Postgres、Oracle 和 MySQL 提供了一种更强大的数据库模型,称为关系型数据库。关系型数据库存储表之间的关系,例如书籍与作者之间的关系。
与关系型数据库交互的常见方式是使用 SQL 语言。SQLite、Postgres、Oracle、MySQL 以及其他数据库引擎都支持这种语言。
SQL 是一种功能丰富的语言,你可以阅读专门介绍它的书籍。幸运的是,使用 SQL 有效地只需要几个命令。
创建表格
使用 CREATE TABLE 命令定义表:
CREATE TABLE tablename (field1 type1, field2 type2,...);
下一行命令会创建一本书籍和作者的表格:
CREATE TABLE book (title STRING, author STRING);
向 SQL 数据库插入一行数据
插入命令会将一行数据插入到数据库中。
INSERT INTO table (columns) VALUES (val1, val2,...);
以下命令会插入你当前正在阅读的书籍:
INSERT INTO book (title, author) VALUES ('Linux Shell Scripting
Cookbook', 'Clif Flynt');
从 SQL 数据库中选择行
select 命令会选择所有符合测试条件的行:
SELECT fields FROM table WHERE test;
这个命令会从书籍表中选择包含 "Shell" 这个词的书名:
SELECT title FROM book WHERE title like '%Shell%';
编写和读取 SQLite 数据库
SQLite 是一个轻量级的数据库引擎,广泛应用于 Android 应用、Firefox 以及美国海军的库存系统等多个领域。由于其广泛的应用,运行 SQLite 的程序数量超过了其他任何数据库。
SQLite 数据库是一个单一文件,多个数据库引擎可以访问它。数据库引擎是一个 C 库,可以与应用程序链接;它也可以作为库加载到脚本语言中,如 TCL、Python 或 Perl,或者作为独立程序运行。
独立应用程序 sqlite3 是在 Shell 脚本中最容易使用的工具。
准备就绪
sqlite3 可执行文件可能没有在你的系统中安装。如果没有,你可以通过包管理器安装 sqlite3 包。
对于 Debian 和 Ubuntu,请使用以下命令:
apt-get install sqlite3 libsqlite3-dev
对于 Red Hat、SuSE、Fedora 和 Centos,请使用以下命令:
yum install sqlite sqlite-devel
如何操作...
sqlite3命令是一个交互式数据库引擎,连接到 SQLite 数据库,并支持创建表、插入数据、查询表等操作。
sqlite3命令的语法如下:
sqlite3 databaseName
如果databaseName文件存在,sqlite3将打开它。如果文件不存在,sqlite3将创建一个空的数据库。在本教程中,我们将创建一个表,插入一行数据,并检索该条目:
# Create a books database
$ sqlite3 books.db
sqlite> CREATE TABLE books (title string, author string);
sqlite> INSERT INTO books (title, author) VALUES ('Linux Shell
Scripting Cookbook', 'Clif Flynt');
sqlite> SELECT * FROM books WHERE author LIKE '%Flynt%';
Linux Shell Scripting Cookbook|Clif Flynt
它的工作原理...
sqlite3应用程序创建一个名为books.db的空数据库,并显示sqlite>提示符以接受 SQL 命令。
CREATE TABLE命令创建一个具有两个字段的表:title(标题)和 author(作者)。
INSERT命令将一本书插入到数据库中。SQL 中的字符串用单引号括起来。
SELECT命令检索与测试匹配的行。百分号(%)是 SQL 中的通配符,类似于 shell 中的星号(*)。
还有更多...
一个 shell 脚本可以使用sqlite3访问数据库,并提供一个简单的用户界面。下一个脚本实现了前述的地址数据库,使用sqlite代替平面文本文件。它提供了三个命令:
-
init:这是创建数据库的操作 -
insert:这是添加新行的操作 -
query:这是选择与查询匹配的行
使用时,它看起来像这样:
$> dbaddr.sh init
$> dbaddr.sh insert 'Joe User' '123-1234' 'user@example.com'
$> dbaddr.sh query name Joe
Joe User
123-1234
user@example.com
以下脚本实现了这个数据库应用:
#!/bin/bash
# Create a command based on the first argument
case $1 in
init )
cmd="CREATE TABLE address \
(name string, phone string, email string);" ;;
query )
cmd="SELECT name, phone, email FROM address \
WHERE $2 LIKE '$3';";;
insert )
cmd="INSERT INTO address (name, phone, email) \
VALUES ( '$2', '$3', '$4' );";;
esac
# Send the command to sqlite3 and reformat the output
echo $cmd | sqlite3 $HOME/addr.db | sed 's/|/\n/g'
这个脚本使用 case 语句来选择 SQL 命令字符串。其他命令行参数被替换为这个字符串,然后该字符串被发送到sqlite3进行评估。$1、$2、$3和$4分别是脚本的第一个、第二个、第三个和第四个参数。
从 Bash 写入和读取 MySQL 数据库
MySQL 是一个广泛使用的数据库管理系统。2009 年,Oracle 收购了 SUN,并随之收购了 MySQL 数据库。MariaDB 是 MySQL 的一个独立分支,与 Oracle 无关。MariaDB 可以访问 MySQL 数据库,但 MySQL 引擎并不总是能访问 MariaDB 数据库。
MySQL 和 MariaDB 都提供许多语言的接口,包括 PHP、Python、C++、Tcl 等。它们都使用mysql命令提供交互式会话,以便访问数据库。这是 shell 脚本与 MySQL 数据库交互的最简单方式。这些示例适用于 MySQL 或 MariaDB。
一个 bash 脚本可以将文本或逗号分隔值(CSV)文件转换为 MySQL 表格和行。例如,我们可以通过运行查询从 shell 脚本中读取存储在留言簿程序数据库中的所有电子邮件地址。
接下来的脚本演示了如何将文件的内容插入到学生数据库表中,并生成报告,同时对每个学生进行部门内排名。
准备就绪
MySQL 和 MariaDB 并非总是包含在基础 Linux 发行版中。它们可以作为 mysql-server 和 mysql-client 或 mariadb-server 包进行安装。MariaDB 发行版使用 MySQL 作为命令,并且有时在请求 MySQL 包时会一起安装。
MySQL 支持使用用户名和密码进行身份验证。在安装过程中,系统会提示输入密码。
使用 mysql 命令在全新安装的系统上创建数据库。在通过 CREATE DATABASE 命令创建数据库后,可以通过 use 命令选择使用该数据库。一旦选择了数据库,就可以使用标准 SQL 命令创建表并插入数据:
$> mysql -user=root -password=PASSWORD
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 44
Server version: 10.0.29-MariaDB-0+deb8u1 (Debian)
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> CREATE DATABASE test1;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> use test1;
quit 命令或 Ctrl-D 将终止 mysql 的交互式会话。
如何操作...
本方案包含三个脚本:一个用于创建数据库和表,一个用于插入学生数据,另一个用于从表中读取并显示数据。
创建数据库和表的脚本:
#!/bin/bash
#Filename: create_db.sh
#Description: Create MySQL database and table
USER="user"
PASS="user"
mysql -u $USER -p$PASS <<EOF 2> /dev/null
CREATE DATABASE students;
EOF
[ $? -eq 0 ] && echo Created DB || echo DB already exist
mysql -u $USER -p$PASS students <<EOF 2> /dev/null
CREATE TABLE students(
id int,
name varchar(100),
mark int,
dept varchar(4)
);
EOF
[ $? -eq 0 ] && echo Created table students || \
echo Table students already exist
mysql -u $USER -p$PASS students <<EOF
DELETE FROM students;
EOF
该脚本将数据插入到表中:
#!/bin/bash
#Filename: write_to_db.sh
#Description: Read from CSV and write to MySQLdb
USER="user"
PASS="user"
if [ $# -ne 1 ];
then
echo $0 DATAFILE
echo
exit 2
fi
data=$1
while read line;
do
oldIFS=$IFS
IFS=,
values=($line)
values[1]="\"`echo ${values[1]} | tr ' ' '#' `\""
values[3]="\"`echo ${values[3]}`\""
query=`echo ${values[@]} | tr ' #' ', ' `
IFS=$oldIFS
mysql -u $USER -p$PASS students <<EOF
INSERT INTO students VALUES($query);
EOF
done< $data
echo Wrote data into DB
最后的脚本查询数据库并生成报告:
#!/bin/bash
#Filename: read_db.sh
#Description: Read from the database
USER="user"
PASS="user"
depts=`mysql -u $USER -p$PASS students <<EOF | tail -n +2
SELECT DISTINCT dept FROM students;
EOF`
for d in $depts;
do
echo Department : $d
result="`mysql -u $USER -p$PASS students <<EOF
SET @i:=0;
SELECT @i:=@i+1 as rank,name,mark FROM students WHERE dept="$d" ORDER BY mark DESC;
EOF`"
echo "$result"
echo
done
输入的 CSV 文件(studentdata.csv)的数据大致如下:
1,Navin M,98,CS
2,Kavya N,70,CS
3,Nawaz O,80,CS
4,Hari S,80,EC
5,Alex M,50,EC
6,Neenu J,70,EC
7,Bob A,30,EC
8,Anu M,90,AE
9,Sruthi,89,AE
10,Andrew,89,AE
按照以下顺序执行脚本:
$ ./create_db.sh
Created DB
Created table students
$ ./write_to_db.sh studentdat.csv
Wrote data into DB
$ ./read_db.sh
Department : CS
rank name mark
1 Navin M 98
2 Nawaz O 80
3 Kavya N 70
Department : EC
rank name mark
1 Hari S 80
2 Neenu J 70
3 Alex M 50
4 Bob A 30
Department : AE
rank name mark
1 Anu M 90
2 Sruthi 89
3 Andrew 89
如何操作...
第一个脚本 create_db.sh 创建一个名为 students 的数据库,并在其中创建一个名为 students 的表。mysql 命令用于执行 MySQL 操作。mysql 命令通过 -u 指定用户名,通过 -pPASSWORD 指定密码。USER 和 PASS 变量用于存储用户名和密码。
mysql 命令的另一个参数是数据库名称。如果指定了数据库名称作为 mysql 命令的参数,则会使用该数据库;否则,我们必须通过 use database_name 命令显式定义要使用的数据库。
mysql 命令通过标准输入(stdin)接收要执行的查询。通过 <<EOF 方法提供多行输入是一种方便的方式。在 <<EOF 和 EOF 之间出现的文本将作为标准输入传递给 mysql。
CREATE DATABASE 和 CREATE TABLE 命令将 stderr 重定向到 /dev/null,以防止显示错误信息。该脚本检查存储在 $? 中的 mysql 命令的退出状态,以确定是否发生故障;如果发生故障,通常是因为表或数据库已存在。如果数据库或表已存在,脚本会显示一条消息通知用户;否则,将创建数据库和表。
write_to_db.sh 脚本接受学生数据 CSV 文件的文件名。它在 while 循环中读取 CSV 文件的每一行。在每次迭代中,CSV 文件中的一行被读取并重新格式化为 SQL 命令。脚本将逗号分隔的行数据存储到一个数组中。数组赋值的形式如下:array=(val1 val2 val3)。在这里,空格字符是内部字段分隔符(IFS)。这些数据是逗号分隔的值。通过将 IFS 设置为逗号,我们可以轻松地将值赋给数组(IFS=,)。
逗号分隔行中的数据元素是 id、name、mark 和 department。id 和 mark 值是整数,而 name 和 dept 是必须引用的字符串。
姓名可能包含空格字符,这会与 IFS 冲突。脚本将名称中的空格替换为字符(#),并在生成查询后恢复。
为了引用字符串,数组中的值会重新分配,前后加上 \" 前缀和后缀。tr 命令将名称中的每个空格替换为 #。
最后,通过将空格字符替换为逗号并将 # 替换为空格,生成查询。然后,执行 SQL 的 INSERT 命令。
第三个脚本 read_db.sh 生成按排名顺序排列的各个部门的学生名单。第一个查询查找部门的不同名称。我们使用 while 循环遍历每个部门并运行查询,按最高分数显示学生的详细信息。SET @i=0 是一个 SQL 结构,用于设置 i=0。在每一行中,它会递增并显示为学生的排名。
用户管理脚本
GNU/Linux 是一个多用户操作系统,允许多个用户同时登录并执行活动。涉及用户管理的管理任务包括设置用户的默认 shell、将用户添加到组、禁用 shell 账户、添加新用户、删除用户、设置密码、设置用户账户的到期日期等。本食谱展示了一个用户管理工具来处理这些任务。
如何操作……
该脚本执行常见的用户管理任务:
#!/bin/bash
#Filename: user_adm.sh
#Description: A user administration tool
function usage()
{
echo Usage:
echo Add a new user
echo $0 -adduser username password
echo
echo Remove an existing user
echo $0 -deluser username
echo
echo Set the default shell for the user
echo $0 -shell username SHELL_PATH
echo
echo Suspend a user account
echo $0 -disable username
echo
echo Enable a suspended user account
echo $0 -enable username
echo
echo Set expiry date for user account
echo $0 -expiry DATE
echo
echo Change password for user account
echo $0 -passwd username
echo
echo Create a new user group
echo $0 -newgroup groupname
echo
echo Remove an existing user group
echo $0 -delgroup groupname
echo
echo Add a user to a group
echo $0 -addgroup username groupname
echo
echo Show details about a user
echo $0 -details username
echo
echo Show usage
echo $0 -usage
echo
exit
}
if [ $UID -ne 0 ];
then
echo Run $0 as root.
exit 2
fi
case $1 in
-adduser) [ $# -ne 3 ] && usage ; useradd $2 -p $3 -m ;;
-deluser) [ $# -ne 2 ] && usage ; deluser $2 --remove-all-files;;
-shell) [ $# -ne 3 ] && usage ; chsh $2 -s $3 ;;
-disable) [ $# -ne 2 ] && usage ; usermod -L $2 ;;
-enable) [ $# -ne 2 ] && usage ; usermod -U $2 ;;
-expiry) [ $# -ne 3 ] && usage ; chage $2 -E $3 ;;
-passwd) [ $# -ne 2 ] && usage ; passwd $2 ;;
-newgroup) [ $# -ne 2 ] && usage ; addgroup $2 ;;
-delgroup) [ $# -ne 2 ] && usage ; delgroup $2 ;;
-addgroup) [ $# -ne 3 ] && usage ; addgroup $2 $3 ;;
-details) [ $# -ne 2 ] && usage ; finger $2 ; chage -l $2 ;;
-usage) usage ;;
*) usage ;;
esac
一个示例输出类似于以下内容:
# ./user_adm.sh -details test
Login: test Name:
Directory: /home/test Shell: /bin/sh
Last login Tue Dec 21 00:07 (IST) on pts/1 from localhost
No mail.
No Plan.
Last password change : Dec 20, 2010
Password expires : never
Password inactive : never
Account expires : Oct 10, 2010
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7
它是如何工作的……
user_adm.sh 脚本执行几个常见的用户管理任务。usage() 文本解释了当用户提供错误参数或包含 -usage 参数时,如何使用该脚本。一个 case 语句解析命令参数并执行适当的命令。
user_adm.sh 脚本的有效命令选项有:-adduser、-deluser、-shell、-disable、-enable、-expiry、-passwd、-newgroup、-delgroup、-addgroup、-details 和 -usage。当匹配到 *) 的情况下,表示没有识别到选项,因此会调用 usage()。
以 root 用户身份运行此脚本。在检查参数之前,它会确认用户 ID(root 的用户 ID 为 0)。
当参数匹配时,[ $# -ne 3 ] && 测试用法检查参数数量。如果命令的参数数量与要求的数量不匹配,则会调用 usage() 函数,并退出脚本。
以下脚本支持这些选项:
-useradd:useradd命令用于创建新用户:
useradd USER -p PASSWORD -m
-
-m选项会创建主目录。 -
-deluser:deluser命令用于删除用户:
deluser USER --remove-all-files
-
--remove-all-files选项会删除与用户相关的所有文件,包括home目录。 -
-shell:chsh命令用于更改用户的默认 shell:
chsh USER -s SHELL
-
-disable和-enable:usermod命令用于操作与用户账户相关的多个属性。usermod -L USER锁定用户账户,usermod -U USER解锁用户账户。 -
-expiry:change命令用于操作用户账户的过期信息:
chage -E DATE
支持以下选项:
-
-m MIN_DAYS:此选项设置密码更改之间的最小天数为MIN_DAYS。 -
-
-M MAX_DAYS:此选项设置密码的最大有效天数。 -
-W WARN_DAYS:此选项设置在密码更改前提供警告的天数。
-
-
-passwd:passwd命令用于更改用户密码:
passwd USER
该命令将提示输入新密码:
-newgroup和-addgroup:addgroup命令用于向系统添加新用户组:
addgroup GROUP
如果包含用户名,它将把该用户添加到一个组中:
addgroup USER GROUP
-delgroup
delgroup 命令用于删除用户组:
delgroup GROUP
-details:finger USER命令显示用户信息,包括主目录、最后登录时间、默认 shell 等。chage -l命令显示用户账户过期信息。
批量图像缩放和格式转换
我们所有人都会从手机和相机中下载照片。在通过电子邮件发送图像或发布到网上之前,我们可能需要调整其大小,或许还需要更改格式。我们可以使用脚本批量修改这些图像文件。本食谱描述了图像管理的相关操作。
准备就绪
ImageMagick 套件中的 convert 命令包含用于操作图像的工具。它支持多种图像格式和转换选项。大多数 GNU/Linux 发行版默认不包含 ImageMagick,你需要手动安装该软件包。有关更多信息,请在浏览器中访问 www.imagemagick.org。
如何操作...
convert 程序将文件从一种图像格式转换为另一种格式:
$ convert INPUT_FILE OUTPUT_FILE
这是一个示例:
$ convert file1.jpg file1.png
我们可以通过指定缩放百分比或输出图像的宽度和高度来调整图像大小。要通过指定 WIDTH 或 HEIGHT 来调整图像大小,请使用此命令:
$ convert imageOrig.png -resize WIDTHxHEIGHT imageResized.png
这是一个示例:
$ convert photo.png -resize 1024x768 wallpaper.png
如果缺少 WIDTH 或 HEIGHT,那么缺少的部分将自动计算以保持图像的长宽比:
$ convert image.png -resize WIDTHx image.png
这是一个示例:
$ convert image.png -resize 1024x image.png
要通过指定百分比缩放因子来调整图像大小,请使用此命令:
$ convert image.png -resize "50%" image.png
这个脚本将对目录中的所有图像执行一系列操作:
#!/bin/bash
#Filename: image_help.sh
#Description: A script for image management
if [ $# -ne 4 -a $# -ne 6 -a $# -ne 8 ];
then
echo Incorrect number of arguments
exit 2
fi
while [ $# -ne 0 ];
do
case $1 in
-source) shift; source_dir=$1 ; shift ;;
-scale) shift; scale=$1 ; shift ;;
-percent) shift; percent=$1 ; shift ;;
-dest) shift ; dest_dir=$1 ; shift ;;
-ext) shift ; ext=$1 ; shift ;;
*) echo Wrong parameters; exit 2 ;;
esac;
done
for img in `echo $source_dir/*` ;
do
source_file=$img
if [[ -n $ext ]];
then
dest_file=${img%.*}.$ext
else
dest_file=$img
fi
if [[ -n $dest_dir ]];
then
dest_file=${dest_file##*/}
dest_file="$dest_dir/$dest_file"
fi
if [[ -n $scale ]];
then
PARAM="-resize $scale"
elif [[ -n $percent ]]; then
PARAM="-resize $percent%"
fi
echo Processing file : $source_file
convert $source_file $PARAM $dest_file
done
以下示例将sample_dir目录中的图像缩放至20%:
$ ./image_help.sh -source sample_dir -percent 20%
Processing file :sample/IMG_4455.JPG
Processing file :sample/IMG_4456.JPG
Processing file :sample/IMG_4457.JPG
Processing file :sample/IMG_4458.JPG
要将图像的宽度调整为1024,请使用以下命令:
$ ./image_help.sh -source sample_dir -scale 1024x
要将文件缩放并转换到指定的目标目录,请使用以下命令:
# newdir is the new destination directory
$ ./image_help.sh -source sample -scale 50% -ext png -dest newdir
它是如何工作的...
上述image_help.sh脚本接受以下参数:
-
-source:指定图像的源目录。 -
-dest:指定转换后的图像文件的目标目录。如果未指定-dest,目标目录将与源目录相同。 -
-ext:指定转换后的目标文件格式。 -
-percent:指定缩放的百分比。 -
-scale:指定缩放后的宽度和高度。 -
-percent和-scale两个参数可能都不出现。 -
脚本首先检查命令参数的数量。四个、六个或八个参数是有效的。
命令行通过while循环和case语句进行解析,值被分配到适当的变量中。$#是一个特殊变量,包含参数的数量。shift命令将命令参数向左移动一个位置。这样,每次移位时,我们可以通过$1访问下一个命令参数,而不必使用$1、$2、$3等。
case语句类似于 C 语言中的 switch 语句。当匹配到某个 case 时,执行相应的语句。每个匹配的语句以;;结束。所有参数被解析到变量percent、scale、source_dir、ext和dest_dir后,for循环将遍历源目录中的每个文件并进行转换。
在for循环中执行了几个测试,以精细调整转换。
如果定义了变量ext(即命令参数中给出了-ext),目标文件的扩展名将从source_file.extension更改为source_file.$ext。
如果提供了-dest参数,目标文件路径将通过将源路径中的目录替换为目标目录来修改。
如果指定了-scale 或-percent 选项,则会将调整大小参数(-resize widthx或-resize perc%)添加到命令中。
参数评估后,执行convert命令并使用适当的参数。
另请参见
- 第二章《掌握命令》中的基于扩展名切割文件名食谱解释了如何提取文件名的某一部分。
从终端截图
随着图形用户界面(GUI)应用程序的增多,截图变得越来越重要,既可以记录操作过程,又可以报告意外结果。Linux 支持多种抓取截图的工具。
准备工作
本节将介绍xwd应用程序以及之前食谱中使用的 ImageMagick 工具。xwd 应用程序通常与基本的 GUI 一起安装。您可以使用包管理器安装 ImageMagick。
如何操作...
xwd 程序从窗口中提取视觉信息,将其转换为 X Window Dump 格式,并将数据输出到 stdout。该输出可以重定向到文件,文件可以转换为 GIF、PNG 或 JPEG 格式,如前面的示例所示。
当调用 xwd 时,它会将光标更改为十字准线。将该准线移动到一个 X Window 并点击它,窗口即被抓取:
$ xwd >step1.xwd
ImageMagick 的 import 命令支持更多截屏选项:
要截取整个屏幕的截图,使用以下命令:
$ import -window root screenshot.png
你可以手动选择一个区域并使用以下命令截取该区域的截图:
$ import screenshot.png
要截取特定窗口的截图,使用以下命令:
$ import -window window_id screenshot.png
xwininfo 命令会返回一个窗口 ID。运行该命令并点击你想要的窗口。然后,将这个 window_id 值传递给 import 命令的 -window 选项。
从一个终端管理多个终端
SSH 会话、Konsoles 和 xterms 是适用于长期运行应用的重型解决方案,但它们执行检查的频率较低(例如监视日志文件或磁盘使用情况)。
GNU screen 工具在终端会话中创建多个虚拟屏幕。你在虚拟屏幕中启动的任务,在屏幕隐藏时仍会继续运行。
准备就绪
为了实现这一点,我们将使用一个名为 GNU screen 的工具。如果你的发行版默认没有安装 screen,可以通过包管理器安装:
apt-get install screen
如何操作...
-
一旦屏幕工具创建了一个新窗口,所有的键盘输入都会传递给该窗口中运行的任务,除了 Control-A (Ctrl-A),它标志着一个屏幕命令的开始。
-
创建屏幕窗口:要创建一个新的屏幕,运行 shell 中的 screen 命令。你将看到包含屏幕信息的欢迎消息。按空格键或回车键返回到 shell 提示符。要创建一个新的虚拟终端,按 Ctrl + A 然后按 C(区分大小写),或者再次输入 screen。
-
查看打开窗口的列表:在运行 screen 时,按 Ctrl+A 然后按引号 (
") 会列出你的终端会话。 -
在窗口间切换:按 Ctrl + A 和 Ctrl + N 可以显示下一个窗口,按 Ctrl + A 和 Ctrl + P 可以显示上一个窗口。
-
附加和分离屏幕:screen 命令支持保存和加载屏幕会话,在 screen 术语中称为分离和附加。要从当前屏幕会话分离,按 Ctrl + A 然后按 Ctrl + D。要在启动屏幕时附加到现有屏幕,使用:
screen -r -d
- 这告诉屏幕附加到最后一个屏幕会话。如果你有多个分离的会话,屏幕会输出一个列表;然后使用:
screen -r -d PID
这里,PID 是你想要附加的屏幕会话的 PID。
第十一章:跟踪线索
本章将涉及以下主题:
-
使用
tcpdump跟踪数据包 -
使用
ngrep查找数据包 -
使用
ip跟踪网络路由 -
使用
strace跟踪系统调用 -
使用
ltrace跟踪动态库函数
介绍
没有跟踪,什么也不会发生。在 Linux 系统中,我们可以通过第九章中讨论的日志文件来跟踪事件,佩戴显示器帽。top命令显示哪些程序使用了最多的 CPU 时间,watch、df和du让我们监控磁盘使用情况。
本章将描述获取更多关于网络数据包、CPU 使用情况、磁盘使用情况和动态库调用的信息的方法。
使用 tcpdump 跟踪数据包
仅知道哪些应用程序正在使用某个端口,可能不足以追踪问题。有时你还需要检查正在传输的数据。
准备工作
你需要是 root 用户才能运行tcpdump。tcpdump应用程序可能在你的系统中没有默认安装,因此需要通过包管理器安装:
$ sudo apt-get install tcpdump
$ sudo yum install libpcap tcpdump
如何做到……
tcpdump应用程序是 Wireshark 和其他网络嗅探程序的前端。图形界面支持我们将很快描述的许多选项。
该应用程序的默认行为是显示在主以太网链路上看到的每个数据包。数据包报告的格式如下:
TIMESTAMP SRC_IP:PORT > DEST_IP:PORT: NAME1 VALUE1, NAME2 VALUE2,...
名称-值对包括:
-
Flags:与此数据包相关的标志如下: -
-
S代表SYN(开始连接) -
F代表FIN(结束连接) -
P代表PUSH(推送数据) -
R代表RST(重置连接) -
句号
.表示没有标志
-
-
seq:这指的是数据包的序列号。它将在 ACK 中回显,以识别被确认的数据包。 -
ack:这指的是确认,表示已收到数据包。其值为来自先前数据包的序列号。 -
win:这表示目的地缓冲区的大小。 -
options:这指的是为此数据包定义的 TCP 选项。它以逗号分隔的键值对形式报告。
以下输出显示了来自 Windows 计算机到 SAMBA 服务器的请求,夹杂着 DNS 请求。不同源和应用程序的数据包交错,使得追踪特定应用程序或主机上的流量变得困难。然而,tcpdump命令有一些标志,可以让我们的生活变得更轻松:
$ tcpdump
22:00:25.269277 IP 192.168.1.40.49182 > 192.168.1.2.microsoft-ds: Flags [P.], seq 3265172834:3265172954, ack 850195805, win 257, length 120SMB PACKET: SMBtrans2 (REQUEST)
22:00:25.269417 IP 192.168.1.44.33150 > 192.168.1.7.domain: 13394+ PTR? 2.1.168.192.in-addr.arpa. (42)
22:00:25.269917 IP 192.168.1.2.microsoft-ds > 192.168.1.40.49182: Flags [.], ack 120, win 1298, length 0
22:00:25.269927 IP 192.168.1.2.microsoft-ds > 192.168.1.40.49182: Flags [P.], seq 1:105, ack 120, win 1298, length 104SMB PACKET: SMBtrans2 (REPLY)
-w标志将tcpdump的输出发送到文件,而不是终端。输出格式为二进制形式,可以通过-r标志读取。嗅探数据包必须以 root 权限进行,但从先前保存的文件显示结果可以作为普通用户进行。
默认情况下,tcpdump运行并收集数据,直到通过 Ctrl-C 或SIGTERM结束。-c标志限制数据包的数量:
# tcpdump -w /tmp/tcpdump.raw -c 50
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
50 packets captured
50 packets received by filter
0 packets dropped by kernel
通常,我们希望检查单个主机上的活动,可能是某个特定的应用程序。
tcpdump 命令行的最后几个值构成了一个表达式,帮助我们过滤数据包。这个表达式是由带有修饰符和布尔运算符的键值对组成的。接下来的示例演示了如何使用过滤器。
只显示 HTTP 数据包
port 键只显示发送到或从指定端口的数据包:
$ tcpdump -r /tmp/tcpdump.raw port http
reading from file /tmp/tcpdump.raw, link-type EN10MB (Ethernet)
10:36:50.586005 IP 192.168.1.44.59154 > ord38s04-in-f3.1e100.net.http: Flags [.], ack 3779320903, win 431, options [nop,nop,TS val 2061350532 ecr 3014589802], length 0
10:36:50.586007 IP ord38s04-in-f3.1e100.net.http > 192.168.1.44.59152: Flags [.], ack 1, win 350, options [nop,nop,TS val 3010640112 ecr 2061270277], length 0
只显示由该主机生成的 HTTP 数据包
如果你尝试追踪网络上的网页使用情况,可能只需要查看你网站上生成的数据包。src 修饰符仅指定这些数据包,以及给定的值,来自源文件。dst 修饰符则指定目的地:
$ tcpdump -r /tmp/tcpdump.raw src port http
reading from file /tmp/tcpdump.raw, link-type EN10MB (Ethernet)
10:36:50.586007 IP ord38s04-in-f3.1e100.net.http > 192.168.1.44.59152: Flags [.], ack 1, win 350, options [nop,nop,TS val 3010640112 ecr 2061270277], length 0
10:36:50.586035 IP ord38s04-in-f3.1e100.net.http > 192.168.1.44.59150: Flags [.], ack 1, win 350, options [nop,nop,TS val 3010385005 ecr 2061270277], length 0
查看数据包的有效载荷以及头部
如果你需要追踪某个淹没网络的主机,你只需要头部信息。如果你正在调试一个 web 或数据库应用程序,你可能需要查看数据包的内容以及头部信息。
-X 标志将把数据包数据包含在输出中。
host 关键字可以与端口信息结合,限制报告仅显示往返于指定主机的数据。
这两个测试用 and 连接,执行布尔 and 操作,并且仅报告那些来自或去往 noucorp.com 和/或 HTTP 服务器的数据包。示例输出显示了一个 GET 请求的开始以及服务器的回复:
$ tcpdump -X -r /tmp/tcpdump.raw host noucorp.com and port http
reading from file /tmp/tcpdump.raw, link-type EN10MB (Ethernet)
11:12:04.708905 IP 192.168.1.44.35652 > noucorp.com.http: Flags [P.], seq 2939551893:2939552200, ack 1031497919, win 501, options [nop,nop,TS val 2063464654 ecr 28236429], length 307
0x0000: 4500 0167 1e54 4000 4006 70a5 c0a8 012c E..g.T@.@.p....,
0x0010: 98a0 5023 8b44 0050 af36 0095 3d7b 68bf ..P#.D.P.6..={h.
0x0020: 8018 01f5 abf1 0000 0101 080a 7afd f8ce ............z...
0x0030: 01ae da8d 4745 5420 2f20 4854 5450 2f31 ....GET./.HTTP/1
0x0040: 2e31 0d0a 486f 7374 3a20 6e6f 7563 6f72 .1..Host:.noucor
0x0050: 702e 636f 6d0d 0a55 7365 722d 4167 656e p.com..User-Agen
0x0060: 743a 204d 6f7a 696c 6c61 2f35 2e30 2028 t:.Mozilla/5.0.(
0x0070: 5831 313b 204c 696e 7578 2078 3836 5f36 X11;.Linux.x86_6
0x0080: 343b 2072 763a 3435 2e30 2920 4765 636b 4;.rv:45.0).Geck
0x0090: 6f2f 3230 3130 3031 3031 2046 6972 6566 o/20100101.Firef
0x00a0: 6f78 2f34 352e 300d 0a41 6363 6570 743a ox/45.0..Accept:
...
11:12:04.731343 IP noucorp.com.http > 192.168.1.44.35652: Flags [.], seq 1:1449, ack 307, win 79, options [nop,nop,TS val 28241838 ecr 2063464654], length 1448
0x0000: 4500 05dc 0491 4000 4006 85f3 98a0 5023 E.....@.@.....P#
0x0010: c0a8 012c 0050 8b44 3d7b 68bf af36 01c8 ...,.P.D={h..6..
0x0020: 8010 004f a7b4 0000 0101 080a 01ae efae ...O............
0x0030: 7afd f8ce 4854 5450 2f31 2e31 2032 3030 z...HTTP/1.1.200
0x0040: 2044 6174 6120 666f 6c6c 6f77 730d 0a44 .Data.follows..D
0x0050: 6174 653a 2054 6875 2c20 3039 2046 6562 ate:.Thu,.09.Feb
0x0060: 2032 3031 3720 3136 3a31 323a 3034 2047 .2017.16:12:04.G
0x0070: 4d54 0d0a 5365 7276 6572 3a20 5463 6c2d MT..Server:.Tcl-
0x0080: 5765 6273 6572 7665 722f 332e 352e 3220 Webserver/3.5.2.
工作原理...
tcpdump 应用程序设置了一个混杂标志,导致网络接口卡(NIC)将所有数据包传递给处理器。它这样做,而不是仅过滤那些与该主机相关的数据包。这个标志允许记录主机所在物理网络上的任何数据包,而不仅仅是传送到该主机的数据包。
这个应用程序用于追踪超负荷网络段、生成意外流量的主机、网络环路、故障的 NIC、格式错误的数据包等问题。
使用 -w 和 -r 选项,tcpdump 会以原始格式保存数据,允许你稍后以常规用户身份查看它。例如,如果凌晨 3 点网络数据包发生了大量碰撞,你可以设置一个 cron 任务,在凌晨 3 点运行 tcpdump,然后在正常工作时间查看数据。
使用 ngrep 查找数据包
ngrep 应用程序是 grep 和 tcpdump 的结合体。它监视网络端口并显示与模式匹配的数据包。你必须拥有 root 权限才能运行 ngrep。
准备工作
你可能没有安装 ngrep 包。不过,你可以通过大多数包管理器安装它:
# apt-get install ngrep
# yum install ngrep
如何操作...
ngrep 应用程序接受一个模式进行匹配(如同 grep),一个过滤字符串(如同 tcpdump),以及许多命令行标志来微调其行为。
以下示例监视 80 端口的流量,并报告任何包含字符串 Linux 的数据包:
$> ngrep -q -c 64 Linux port 80
interface: eth0 (192.168.1.0/255.255.255.0)
filter: ( port 80 ) and (ip or ip6)
match: Linux
T 192.168.1.44:36602 -> 152.160.80.35:80 [AP]
GET /Training/linux_detail/ HTTP/1.1..Host: noucorp.com..Us
er-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20
100101 Firefox/45.0..Accept: text/html,application/xhtml+xm
l,application/xml;q=0.9,*/*;q=0.8..Accept-Language: en-US,e
n;q=0.5..Accept-Encoding: gzip, deflate..Referer: http://no
ucorp.com/Training/..Connection: keep-alive..Cache-Control:
max-age=0....
-q 标志使 ngrep 只打印头部和有效载荷。
-c 标志定义了用于负载数据的列数。默认情况下,列数是四,这对于基于文本的数据包来说并不实用。
在标志之后是匹配字符串(Linux),然后是使用与 tcpdump 相同过滤语言的过滤器表达式。
它是如何工作的...
ngrep 应用程序还设置了混杂模式标志,允许它嗅探所有可见的数据包,无论这些数据包是否与主机相关。
上面的例子展示了所有的 HTTP 流量。如果主机系统连接在无线网络或通过集线器(而非交换机)进行有线连接,它会显示所有活跃用户造成的所有 Web 流量。
还有更多...
ngrep 中的 -x 选项显示十六进制转储和可打印形式。将其与 -X 结合使用,可以搜索二进制字符串(例如病毒签名或某些已知模式)。
这个例子监视来自 HTTPS 连接的二进制流:
# ngrep -xX '1703030034' port 443
interface: eth0 (192.168.1.0/255.255.255.0)
filter: ( port 443 ) and (ip or ip6)
match: 0x1703030034
#################################################
T 172.217.6.1:443 -> 192.168.1.44:40698 [AP]
17 03 03 00 34 00 00 00 00 00 00 00 07 dd b0 02 ....4...........
f5 38 07 e8 24 08 eb 92 3c c6 66 2f 07 94 8b 25 .8..$...<.f/...%
37 b3 1c 8d f4 f0 64 c3 99 9e b3 45 44 14 64 23 7.....d....ED.d#
80 85 1b a1 81 a3 d2 7a cd .......z.
哈希标记表示已扫描的数据包;它们不包括目标模式。ngrep 有许多其他选项;请阅读 man 页面查看完整列表。
使用 ip 路由追踪网络
ip 工具报告有关网络状态的信息。它可以告诉你有多少数据包正在发送和接收,发送的是哪种类型的数据包,数据包是如何路由的,等等。
准备工作
在 第八章 中描述的 netstat 工具,老式网络,是所有 Linux 发行版的标准工具;然而,它现在正被更高效的工具所取代,例如 ip。这些新工具包含在 iproute2 包中,已经安装在大多数现代发行版中。
如何做...
ip 工具有很多功能。本节将讨论一些在追踪网络行为时非常有用的功能。
使用 ip route 报告路由
当数据包无法到达目标(ping 或 traceroute 失败)时,一个有经验的用户首先检查的是电缆。接下来要检查的是路由表。如果系统缺少默认网关(0.0.0.0),它只能找到物理网络上的机器。如果你有多个网络在同一条线路上运行,你需要添加路由以允许连接到一个网络的机器向另一个网络发送数据包。
ip route 命令报告已知的路由:
$ ip route
10.8.0.2 dev tun0 proto kernel scope link src 10.8.0.1
192.168.87.0/24 dev vmnet1 proto kernel scope link src 192.168.87.1
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.44
default via 192.168.1.1 dev eth0 proto static
ip route 报告是以空格分隔的。在第一个元素之后,它由一组键值对组成。
上面的代码中的第一行描述了 10.8.0.2 地址作为一个使用内核协议的隧道设备,该地址仅在这个隧道设备上有效。第二行描述了 192.168.87.x 网络,用于与虚拟机通信。第三行是该系统的主网络,它连接到 /dev/eth0。最后一行定义了默认路由,通过 eth0 路由到 192.168.1.1。
ip route 报告的键包括以下内容:
-
via:指的是下一个跳跃的地址。 -
proto:这是路由的协议标识符。内核协议是由内核安装的路由,而静态路由由管理员定义。 -
scope:这是指地址有效的作用范围。链路作用域仅在此设备上有效。 -
dev:这是与该地址关联的设备。
追踪最近的 IP 连接和 ARP 表
ip neighbor 命令报告已知的 IP 地址、设备和硬件 MAC 地址之间的关系。它报告该关系是否已重新建立,或者是否已经过时:
$> ip neighbor
192.168.1.1 dev eth0 lladdr 2c:30:33:c9:af:3e STALE
192.168.1.4 dev eth0 lladdr 00:0a:e6:11:c7:dd STALE
172.16.183.138 dev vmnet8 lladdr 00:50:56:20:3d:6c STALE
192.168.1.2 dev eth0 lladdr 6c:f0:49:cd:45:ff REACHABLE
ip neighbor 命令的输出显示当前系统与默认网关之间,或者当前系统与 192.168.1.4 之间没有最近的活动。它还显示虚拟机与 192.168.1.2 之间没有最近活动,而该主机最近连接。
上述输出中的 REACHABLE 状态意味着 arp 表是最新的,并且主机认为它知道远程系统的 MAC 地址。此处的 STALE 值并不表示系统不可达,它仅仅表示 arp 表中的值已过期。当系统尝试使用这些路由时,它首先会发送 ARP 请求以验证与 IP 地址关联的 MAC 地址。
MAC 地址与 IP 地址之间的关系应仅在硬件更换或设备重新分配时发生变化。
如果网络上的设备显示间歇性连接,可能意味着两个设备被分配了相同的 IP 地址。也可能是两个 DHCP 服务器正在运行,或者有人手动分配了一个已经在使用的地址。
当两个设备具有相同的 IP 地址时,给定 IP 地址的 MAC 地址会间歇性变化,ip neighbor 命令可以帮助追踪错误配置的设备。
路由追踪
在 第八章《老男孩网络》中讨论的 traceroute 命令追踪从当前主机到目标的整个数据包路径。route get 命令报告当前机器的下一跳:
$ ip route get 172.16.183.138
172.16.183.138 dev vmnet8 src 172.16.183.1
cache mtu 1500 hoplimit 64
上述返回结果显示通往虚拟机的路由通过位于 172.16.183.1 的 vmnet8 接口。发送到此站点的数据包如果大于 1,500 字节,将被分割,并且在 64 跳后丢弃:
$ in route get 148.59.87.90
148.59.87.90 via 192.168.1.1 dev eth0 src 192.168.1.3
cache mtu 1500 hoplimit 64
要访问 Internet 上的一个地址,数据包需要通过默认网关离开本地网络,通往该网关的连接是主机的 eth0 设备,地址为 192.168.1.3。
它是如何工作的...
ip 命令在用户空间运行并与内核表接口。使用此命令,普通用户可以检查网络配置,而超级用户可以配置网络。
使用 strace 跟踪系统调用
一台 GNU/Linux 计算机可能同时运行数百个任务,但它只会拥有一个网络接口、一个磁盘驱动器、一个键盘,等等。Linux 内核分配这些有限的资源并控制任务如何访问它们。这可以防止两个任务在磁盘文件中不小心混淆数据,例如。
当你运行一个应用程序时,它会使用用户空间库(如printf和fopen)和系统空间库(如write和open)的组合。当你的程序调用printf(或者脚本调用echo命令)时,它会调用一个用户空间库的printf来格式化输出字符串;接着会调用系统空间的write函数。系统调用确保只有一个任务可以同时访问一个资源。
在一个完美的世界里,所有计算机程序都能无故障地运行。在一个几乎完美的世界里,你会有源代码,程序会使用调试支持进行编译,并且它会始终一致地出错。
在现实世界中,你有时不得不应对一些没有源代码、且间歇性失败的程序。除非你提供一些数据给开发者,否则他们无法帮你解决问题。
Linux 的strace命令报告应用程序所做的系统调用;即使我们没有源代码,它也能帮助我们理解程序在做什么。
准备工作
strace命令作为开发者包的一部分安装;也可以单独安装:
$ sudo apt-get install strace
$ sudo yum install strace
如何做到这一点……
理解strace的一种方式是编写一个简短的 C 程序,并使用strace查看它调用了哪些系统调用。
这个测试程序分配内存,使用内存,打印一条简短的消息,释放内存,并退出。
strace输出显示了这个程序调用的系统函数:
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main () {
char *tmp;
tmp=malloc(100);
strcat(tmp, "testing");
printf("TMP: %s\n", tmp);
free(tmp);
exit(0);
}
$ gcc test.c
$ strace ./a.out
execve("./a.out", ["./a.out"], [/* 51 vars */]) = 0
brk(0) = 0x9fc000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc85c7f5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=95195, ...}) = 0
mmap(NULL, 95195, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc85c7dd000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356\1\16;\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1928936, ...}) = 0
mmap(0x3b0e000000, 3750184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3b0e000000
mprotect(0x3b0e18a000, 2097152, PROT_NONE) = 0
mmap(0x3b0e38a000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3b0e38a000
mmap(0x3b0e390000, 14632, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3b0e390000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc85c7dc000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc85c7db000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc85c7da000
arch_prctl(ARCH_SET_FS, 0x7fc85c7db700) = 0
mprotect(0x3b0e38a000, 16384, PROT_READ) = 0
mprotect(0x3b0de1f000, 4096, PROT_READ) = 0
munmap(0x7fc85c7dd000, 95195) = 0
brk(0) = 0x9fc000
brk(0xa1d000) = 0xa1d000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 11), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc85c7f4000
write(1, "TMP: testing\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
它是如何工作的……
前几行是任何应用程序的标准启动命令。execve调用是初始化新可执行文件的系统调用。brk调用返回当前内存地址,mmap调用为动态库和其他加载的程序分配了 4,096 字节的内存。
尝试访问ld.so.preload失败,因为ld.so.preload是一个用于预加载库的钩子。大多数生产系统中并不需要它。
ld.so.cache文件是/etc/ld.so.conf.d的内存驻留副本,包含了加载动态库的路径。这些值保存在内存中,以减少启动程序时的开销。
接下来的几行包含mmap、mprotect、arch_prctl和munmap调用,继续加载库并将设备映射到内存中。
这两个brk调用是由程序的malloc调用触发的。它从堆中分配了 100 字节的内存。
strcat调用是一个用户空间函数,不会生成任何系统调用。
printf调用不会生成系统调用来格式化数据,但它会发起调用将格式化后的字符串发送到stdout。
fstat和mmap调用加载并初始化stdout设备。这些调用在生成输出到stdout的程序中只会发生一次。
write系统调用将字符串发送到stdout。
最后,exit_group调用退出程序,释放资源,并终止与可执行文件相关的所有线程。
请注意,没有与释放内存相关联的brk调用。malloc和free函数是用户空间函数,管理任务的内存。只有在程序的整体内存占用发生变化时,它们才会调用brk函数。当程序分配N字节时,它需要将这么多字节添加到可用内存中。当它释放这个块时,内存被标记为可用,但它仍然是这个程序内存池的一部分。接下来的malloc会使用来自可用内存池的内存空间,直到内存池耗尽。此时,另一个brk调用会将更多内存添加到程序的内存池中。
使用 ltrace 跟踪动态库函数
知道调用了哪些用户空间的库函数和知道调用了哪些系统函数一样有用。ltrace命令提供了类似于strace的功能;不过,它跟踪的是用户空间的库函数调用,而不是系统调用。
准备就绪
安装开发工具以使用ltrace命令。
如何操作...
要跟踪用户空间的动态库调用,执行strace命令,然后是你想要跟踪的命令:
$ ltrace myApplication
下一个示例是带有子例程的程序:
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int print (char *str) {
printf("%s\n", str);
}
main () {
char *tmp;
tmp=malloc(100);
strcat(tmp, "testing");
print(tmp);
free(tmp);
exit(0);
}
$ gcc test.c
$ ltrace ./a.out
(0, 0, 603904, -1, 0x1f25bc2) = 0x3b0de21160
__libc_start_main(0x4005fe, 1, 0x7ffd334a95f8, 0x400660, 0x400650 <unfinished ...>
malloc(100) = 0x137b010
strcat("", "testing") = "testing"
puts("testing") = 8
free(0x137b010) = <void>
exit(0 <unfinished ...>
+++ exited (status 0) +++
在ltrace输出中,我们看到了对动态链接的strcat的调用;但是我们没有看到静态链接的本地函数,即print。printf的调用被简化为对puts的调用。malloc和free的调用被显示出来,因为它们是用户空间函数调用。
它是如何工作的...
ltrace和strace工具使用ptrace函数来重写过程链接表(PLT),该表将动态库调用映射到被调用函数的实际内存地址。这意味着ltrace可以捕获任何动态链接的函数调用,但不能捕获静态链接的函数。
还有更多内容...
ltrace和strace命令非常有用,但如果能够同时跟踪用户空间和系统空间的函数调用,那就更好了。ltrace的-S选项可以做到这一点。下一个示例显示了从之前的可执行文件中获取的ltrace -S输出:
$> ltrace -S ./a.out
SYS_brk(NULL) = 0xa9f000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fcdce4ce000
SYS_access(0x3b0dc1d380, 4, 0x3b0dc00158, 0, 0) = -2
SYS_open("/etc/ld.so.cache", 0, 01) = 4
SYS_fstat(4, 0x7ffd70342bc0, 0x7ffd70342bc0, 0, 0xfefefefefefefeff) = 0
SYS_mmap(0, 95195, 1, 2, 4) = 0x7fcdce4b6000
SYS_close(4) = 0
SYS_open("/lib64/libc.so.6", 0, 00) = 4
SYS_read(4, "\177ELF\002\001\001\003", 832) = 832
SYS_fstat(4, 0x7ffd70342c20, 0x7ffd70342c20, 4, 0x7fcdce4ce640) = 0
SYS_mmap(0x3b0e000000, 0x393928, 5, 2050, 4) = 0x3b0e000000
SYS_mprotect(0x3b0e18a000, 0x200000, 0, 1, 4) = 0
SYS_mmap(0x3b0e38a000, 24576, 3, 2066, 4) = 0x3b0e38a000
SYS_mmap(0x3b0e390000, 14632, 3, 50, 0xffffffff) = 0x3b0e390000
SYS_close(4) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fcdce4b5000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fcdce4b4000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fcdce4b3000
SYS_arch_prctl(4098, 0x7fcdce4b4700, 0x7fcdce4b3000, 34, 0xffffffff) = 0
SYS_mprotect(0x3b0e38a000, 16384, 1, 0x3b0de20fd8, 0x1f25bc2) = 0
SYS_mprotect(0x3b0de1f000, 4096, 1, 0x4003e0, 0x1f25bc2) = 0
(0, 0, 987392, -1, 0x1f25bc2) = 0x3b0de21160
SYS_munmap(0x7fcdce4b6000, 95195) = 0
__libc_start_main(0x4005fe, 1, 0x7ffd703435c8, 0x400660, 0x400650 <unfinished ...>
malloc(100 <unfinished ...>
SYS_brk(NULL) = 0xa9f000
SYS_brk(0xac0000) = 0xac0000
<... malloc resumed> ) = 0xa9f010
strcat("", "testing") = "testing"
puts("testing" <unfinished ...>
SYS_fstat(1, 0x7ffd70343370, 0x7ffd70343370, 0x7ffd70343230, 0x3b0e38f040) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fcdce4cd000
SYS_write(1, "testing\n", 8) = 8
<... puts resumed> ) = 8
free(0xa9f010) = <void>
exit(0 <unfinished ...>
SYS_exit_group(0 <no return ...>
+++ exited (status 0) +++
这与strace示例显示的启动调用(如sbrk、mmap等)相同。
当一个用户空间的函数调用了系统空间的函数(如malloc和 puts 调用)时,显示会显示该用户空间函数被中断(malloc(100 <unfinished...>)),并在系统调用完成后恢复((<... malloc resumed>))。
请注意,malloc调用需要将控制权传递给sbrk,以便为应用程序分配更多内存。然而,free调用不会缩小应用程序的内存,它只是释放内存供未来该应用程序使用。
第十二章:调优 Linux 系统
本章将涵盖以下内容:
-
确定服务
-
使用
ss收集套接字数据 -
使用
dstat收集系统 I/O 使用情况 -
使用
pidstat识别资源消耗大户 -
使用
sysctl调整 Linux 内核 -
使用配置文件调优 Linux 系统
-
使用
nice命令改变调度器优先级
简介
没有任何系统能以我们希望的速度运行,任何计算机的性能都可以提升。
我们可以通过关闭未使用的服务、调整内核参数或添加新硬件来提高系统的性能。
调优系统的第一步是理解需求是什么,以及这些需求是否得到了满足。不同类型的应用有不同的关键需求。你需要问自己以下问题:
-
对于该系统,CPU 是否是关键资源?一个进行工程模拟的系统比其他资源更需要 CPU 周期。
-
对于该系统,网络带宽是否至关重要?一个文件服务器几乎不进行计算,但可能会使其网络容量饱和。
-
对于该系统,磁盘访问速度是否至关重要?文件服务器或数据库服务器对磁盘的需求要比计算引擎高。
-
对于该系统,RAM 是否是关键资源?所有系统都需要 RAM,但数据库服务器通常会构建大型内存表以执行查询,而文件服务器则通过更大的内存来提高磁盘缓存的效率。
-
你的系统是否被黑客攻击了?系统可能会突然变得无响应,因为它运行了意外的恶意软件。虽然这在 Linux 机器上不常见,但拥有众多用户的系统(例如大学或企业网络)容易受到暴力破解密码攻击。
接下来的问题是:我如何衡量系统的使用情况?了解一个系统的使用方式能帮助你提出问题,但可能不会直接给出答案。一个文件服务器会将常访问的文件缓存到内存中,因此内存不足的服务器可能会受到磁盘/内存的限制,而非网络限制。
Linux 有一些用于分析系统的工具。许多工具已经在第八章,老男孩网络,第九章,戴上监视器的帽子,和第十一章,追踪线索中讨论过了。本章将介绍更多的监控工具。
以下是需要检查的子系统和工具列表。书中已经讨论了这些工具中的许多(但并非全部)。
-
CPU:
top,dstat,perf,ps,mpstat,strace,ltrace -
网络:
netstat,ss,iotop,ip,iptraf,nicstat,ethtool,lsof -
磁盘:
ftrace,iostat,dstat,blktrace -
内存:top,
dstat,perf,vmstat,swapon
这些工具中的许多是标准 Linux 发行版的一部分。其他工具可以通过包管理器加载。
确定服务
一个 Linux 系统可以同时运行数百个任务。大部分任务是操作系统环境的一部分,但你可能会发现有些守护进程是你不需要的。
Linux 发行版支持三种用于启动守护进程和服务的工具之一。传统的SysV系统使用位于/etc/init.d的脚本。较新的systemd守护进程使用相同的/etc/init.d脚本,并且还使用systemctl调用。一些发行版使用 Upstart,它将配置脚本存储在/etc/init中。
SysV init系统正在逐步淘汰,取而代之的是systemd套件。upstart工具是由 Ubuntu 开发并使用的,但在 14.04 版本中被抛弃,改为使用systemd。本章将重点介绍systemd,因为它是大多数发行版使用的系统。
准备工作
第一步是确定你的系统使用的是 SysV init调用、systemd还是upstart。
Linux/Unix 系统必须有一个初始化进程作为PID 1运行。这个进程执行一个 fork 和 exec 来启动其他所有进程。ps命令可以告诉你哪个初始化进程正在运行:
$ ps -p 1 -o cmd
/lib/system/systemd
在上面的例子中,系统肯定正在运行systemd。然而,在一些发行版中,SysV init程序会被符号链接到实际的init进程,ps命令总是显示/sbin/init,无论实际上是使用的 SysV init、upstart还是systemd:
$ ps -p 1 -o cmd
/sbin/init
ps和grep命令提供了更多线索:
$ ps -eaf | grep upstart
或者,它们也可以这样使用:
ps -eaf | grep systemd
如果这些命令返回的任务包括upstart-udev-bridge或systemd/systemd,则系统分别正在运行upstart或systemd。如果没有匹配项,则系统可能正在运行 SysV init工具。
如何操作...
service命令在大多数发行版中得到支持。-status-all选项将报告/etc/init.d中定义的所有服务的当前状态。不同发行版之间的输出格式有所不同:
$> service -status-all
Debian:
[ + ] acpid
[ - ] alsa-utils
[ - ] anacron
[ + ] atd
[ + ] avahi-daemon
[ - ] bootlogs
[ - ] bootmisc.sh
...
CentOS:
abrt-ccpp hook is installed
abrtd (pid 4009) is running...
abrt-dump-oops is stopped
acpid (pid 3674) is running...
atd (pid 4056) is running...
auditd (pid 3029) is running...
...
grep命令将输出结果过滤为仅显示正在运行的任务:
Debian:
$ service -status-all | grep +
CentOS:
$ service -status-all | grep running
你应该禁用任何不必要的服务。这可以减轻系统负担并提高系统安全性。
需要检查的服务包括以下内容:
-
smbd、nmbd:这些是用于在 Linux 和 Windows 系统之间共享资源的 Samba 守护进程。 -
telnet:这是一个古老的、不安全的登录程序。除非有强烈的需求,否则使用 SSH。 -
ftp:这是一个古老的、不安全的文件传输协议。应使用 SSH 和 scp 代替。 -
rlogin:这是远程登录。SSH 更加安全。 -
rexec:这是远程执行。SSH 更加安全。 -
automount:如果你不使用 NFS 或 Samba,你可能不需要这个。 -
named:这个守护进程提供域名服务(DNS)。只有在系统定义本地名称和 IP 地址时才需要它。你不需要它来解析名称和访问网络。 -
lpd:行打印守护进程允许其他系统使用此系统的打印机。如果这不是打印服务器,则不需要此服务。 -
nfsd:这是 网络文件系统 守护进程。它允许远程计算机挂载此计算机的磁盘分区。如果这不是文件服务器,您可能不需要此服务。 -
portmap:这是 NFS 支持的一部分。如果系统未使用 NFS,则不需要此服务。 -
mysql:mysql 应用程序是一个数据库服务器。它可能被您的 Web 服务器使用。 -
httpd:这是 HTTP 守护进程。有时它作为 服务器系统 软件包的一部分被安装。
根据系统是基于 RedHat 还是 Debian,且是否运行 systemd、SysV 或 Upstart,禁用不必要服务的方式有多种。这些命令都必须以 root 权限运行。
基于 systemd 的计算机
systemctl 命令用于启用和禁用服务。语法如下:
systemctl enable SERVICENAME
另外,也可以按如下方式进行:
systemctl disable SERVICENAME
要禁用 FTP 服务器,请使用以下命令:
# systemctl disable ftp
基于 RedHat 的计算机
chkconfig 工具提供了一个前端,用于处理 /etc/rc#.d 中的 SysV 风格初始化脚本。-del 选项用于禁用服务,而 -add 选项用于启用服务。请注意,必须已有初始化文件才能添加服务。
语法如下:
# chkconfig -del SERVICENAME
# chkconfig -add SERVICENAME
要禁用 HTTPD 守护进程,请使用以下命令:
# chkconfig -del httpd
基于 Debian 的计算机
基于 Debian 的系统提供 update-rc.d 工具来控制 SysV 风格的初始化脚本。update-rc.d 命令支持 enable 和 disable 作为子命令:
要禁用 telnet 守护进程,请使用以下命令:
# update-rc.d disable telnetd
还有更多
这些技术将找到已通过 SysV 或 systemd 初始化脚本以 root 用户身份启动的服务。然而,服务可能是手动启动的,或者在启动脚本中,或通过 xinetd 启动的。
xinetd 守护进程的功能与 init 类似:它启动服务。与 init 不同,xinetd 守护进程只有在请求时才启动服务。对于像 SSH 这样的服务,它们不常需要,且一旦启动后运行时间较长,这可以减轻系统负担。像 httpd 这样执行小操作(提供网页)且频繁的服务,最有效的方式是启动一次并保持运行。
xinet 的配置文件是 /etc/xinetd.conf。各个服务文件通常存储在 /etc/xinetd.d。
各个服务文件大致如下:
# cat /etc/xinetd.d/talk
# description: The talk server accepts talk requests for chatting \
# with users on other systems.
service talk
{
flags = IPv4
disable = no
socket_type = dgram
wait = yes
user = nobody
group = tty
server = /usr/sbin/in.talkd
}
通过更改 disable 字段的值,可以启用或禁用服务。如果 disable 为 no,则服务已启用。如果 disable 为 yes,则服务已禁用。
编辑服务文件后,必须重新启动 xinetd:
# cd /etc/init.d
# ./inetd restart
使用 ss 收集套接字数据
由 init 和 xinetd 启动的守护进程可能不是系统中唯一运行的服务。守护进程可以通过 init 本地文件 (/etc/rc.d/rc.local) 中的命令、crontab 条目,甚至是有权限的用户启动。
ss命令返回套接字统计信息,包括使用套接字的服务和当前的套接字状态。
准备工作
ss工具包含在iproute2包中,该包在大多数现代发行版中已经预装。
如何执行...
ss命令显示的信息比netstat命令更多。这些示例将介绍它的一些功能。
显示 TCP 套接字的状态
每次 HTTP 访问、每个 SSH 会话等,都会为每个tcp套接字连接打开一个连接。-t选项报告 TCP 连接的状态:
$ ss -t
ESTAB 0 0 192.168.1.44:740 192.168.1.2:nfs
ESTAB 0 0 192.168.1.44:35484 192.168.1.4:ssh
CLOSE-WAIT 0 0 192.168.1.44:47135 23.217.139.9:http
这个例子显示了一个连接到 IP 地址192.168.1.2的 NFS 服务器,以及一个到192.168.1.4的 SSH 连接。
CLOSE-WAIT套接字状态意味着FIN信号已发送,但套接字尚未完全关闭。套接字可能永远保持在此状态(或直到你重启)。终止拥有该套接字的进程可能释放该套接字,但不能保证。
跟踪监听端口的应用程序
系统上的服务将以listen模式打开一个套接字,以接受来自远程站点的网络连接。SSHD 应用程序就是这样做的,用来监听 SSH 连接,HTTP 服务器也是如此,用来接受 HTTP 请求,依此类推。
如果你的系统被黑客攻击,可能会有一个新的应用程序正在监听来自其主人的指令。
ss命令的-l选项将列出以listen模式打开的套接字。-u选项指定报告 UDP 套接字。-t选项报告 TCP 套接字。
该命令显示了 Linux 工作站上监听的 UDP 套接字的一个子集:
$ ss -ul
State Recv-Q Send-Q Local Address:Port Peer Address:Port
UNCONN 0 0 *:sunrpc *:*
UNCONN 0 0 *:ipp *:*
UNCONN 0 0 *:ntp *:*
UNCONN 0 0 127.0.0.1:766 *:*
UNCONN 0 0 *:898 *:*
该输出显示系统将接受远程过程调用(sunrpc)。此端口由portmap程序使用。portmap程序控制对 RPC 服务的访问,并且被nfs客户端和服务器使用。
ipp和ntp端口分别用于互联网打印协议和网络时间协议。这两个工具都很有用,但在某些系统上可能不需要。
端口766和898没有列在/etc/services中。lsof命令的-I选项将显示打开端口的任务。你可能需要具有 root 权限才能查看:
# lsof -I :898
或者:
# lsof -n -I :898
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
rpcbind 3267 rpc 7u IPv4 16584 0t0 UDP *:898
rpcbind 3267 rpc 10u IPv6 16589 0t0 UDP *:898
该命令显示,监听端口898的任务是 RPC 系统的一部分,而不是黑客。
工作原理
ss命令使用系统调用从内部内核表中提取信息。系统上已知的服务和端口在/etc/services中定义。
使用 dstat 收集系统 I/O 使用情况
知道正在运行哪些服务可能并不能告诉你哪些服务正在拖慢系统。top命令(在第九章中讨论,戴上显示器的帽子)会告诉你 CPU 使用情况以及等待 I/O 的时间,但它可能无法告诉你足够的信息来追踪超负荷的任务。
跟踪 I/O 和上下文切换可以帮助追踪问题的根源。
dstat工具可以帮助你找到潜在的瓶颈。
准备工作
dstat 应用程序通常不会预装。需要通过包管理器安装。它需要 Python 2.2,现代 Linux 系统默认安装此版本:
# apt-get install dstat
# yum install dstat
操作方法...
dstat 应用程序定期显示磁盘、网络、内存使用情况和运行任务信息。默认输出提供了系统活动的概览。默认情况下,这份报告每秒会更新一次并显示在新的一行,方便与之前的值进行比较。
默认输出让你能够跟踪整体的系统活动。该应用程序还支持更多选项,以跟踪资源消耗最多的进程。
查看系统活动
如果不带任何参数调用 dstat,它会以每秒一次的间隔显示 CPU 活动、磁盘 I/O、网络 I/O、分页、硬件中断和上下文切换。
以下示例显示了默认的 dstat 输出:
$ dstat
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
1 2 97 0 0 0|5457B 55k| 0 0 | 0 0 |1702 3177
1 2 97 0 0 0| 0 0 | 15k 2580B| 0 0 |2166 4830
1 2 96 0 0 0| 0 36k|1970B 1015B| 0 0 |2122 4794
你可以忽略第一行。那些值是 dstat 从表格中提取的初始内容。接下来的行显示了在时间片期间的活动。
在此示例中,CPU 大部分时间处于空闲状态,磁盘活动很少。系统正在生成网络流量,但每秒只有少量的数据包。
这个系统没有进行分页。Linux 只有在主内存耗尽时,才会将内存分页到磁盘。分页可以让系统运行比没有分页时更多的应用程序,但磁盘访问比内存访问慢几千倍,所以如果需要分页,计算机会变得非常缓慢。
如果你的系统出现持续的分页活动,它需要更多的内存或更少的应用程序。
一个数据库应用程序可能会在评估需要构建大规模内存数组的查询时导致间歇性的分页。可以通过将查询改写为使用 IN 操作而不是 JOIN 来减少内存需求。(这是比本书中介绍的 SQL 更高级的用法。)
上下文切换(csw)发生在每次系统调用时(请参见 第十一章,追踪线索 中的 strace 和 ltrace 讨论),以及当时间片到期时,系统将另一个应用程序的访问权限转交给 CPU。每当执行 I/O 操作或程序进行自我调整时,都会发生系统调用。
如果系统每秒执行数万个上下文切换,这是潜在问题的症状。
工作原理
dstat 工具是一个 Python 脚本,收集并分析来自 /proc 文件系统的数据,详见 第十章,管理调用。
还有更多...
dstat 工具可以识别某一类别中资源消耗最多的进程:
-
-top-bio 磁盘使用情况:此项报告执行最多块 I/O 的进程。
-
-top-cpu CPU 使用情况:这是报告使用最多 CPU 资源的进程。
-
-top-io I/O 使用情况:此项报告执行最多 I/O(通常是网络 I/O)的进程。
-
-top-latency 系统负载:此项显示具有最高延迟的进程。
-
-top-mem 内存使用情况:此选项显示使用最多内存的进程
以下示例显示了每个类别的 CPU 和网络使用情况以及各个类别中的顶级用户:
$ dstat -c -top-cpu -n -top-io
----total-cpu-usage---- -most-expensive- -net/total- ----most-expensive----
usr sys idl wai hiq siq| cpu process | recv send| i/o process
1 2 97 0 0 0|vmware-vmx 1.0| 0 0 |bash 26k 2B
2 1 97 0 0 0|vmware-vmx 1.7| 18k 3346B|xterm 235B 1064B
2 2 97 0 0 0|vmware-vmx 1.9| 700B 1015B|firefox 82B 32k
在运行虚拟机的系统上,虚拟机使用最多的 CPU 时间,但占用的大部分 IO 资源不多。CPU 大部分时间处于空闲状态。
-c 和 -n 选项分别指定显示 CPU 使用情况和网络使用情况。
使用 pidstat 识别资源占用者
-top-io 和 -top-cpu 标志将标识一个主要的资源使用者,但如果有多个资源占用者实例,可能无法提供足够的信息来识别问题。
pidstat 程序将报告每个进程的统计数据,可以按需排序,以提供更多的洞察信息。
准备就绪
pidstat 应用程序可能不会默认安装。可以通过以下命令进行安装:
# apt-get install sysstat
如何操作...
pidstat 应用程序有多个选项用于生成不同的报告:
-
-d:此选项报告 IO 统计数据 -
-r:此选项报告页面错误和内存利用率 -
-u:此选项报告 CPU 利用率 -
-w:此选项报告任务切换情况
报告上下文切换活动:
$ pidstat -w | head -5
Linux 2.6.32-642.11.1.el6.x86_64 (rtdaserver.cflynt.com)
02/15/2017 _x86_64_ (12 CPU)
11:18:35 AM PID cswch/s nvcswch/s Command
11:18:35 AM 1 0.00 0.00 init
11:18:35 AM 2 0.00 0.00 kthreadd
pidstat 应用程序按 PID 编号对报告进行排序。数据可以通过排序工具进行重新组织。以下命令显示每秒产生最多上下文切换的五个应用程序(-w 输出中的字段 4):
$ pidstat -w | sort -nr -k 4 | head -5
11:13:55 AM 13054 351.49 9.12 vmware-vmx
11:13:55 AM 5763 37.57 1.10 vmware-vmx
11:13:55 AM 3157 27.79 0.00 kondemand/0
11:13:55 AM 3167 21.18 0.00 kondemand/10
11:13:55 AM 3158 21.17 0.00 kondemand/1
它是如何工作的
pidstat 应用程序查询内核以获取任务信息。排序和头部(head)工具会将数据缩减,找出占用资源最多的程序。
使用 sysctl 调整 Linux 内核
Linux 内核大约有 1,000 个可调参数。这些默认值适用于常见用法,意味着它们并不适合所有人。
入门
sysctl 命令在所有 Linux 系统上均可用。你必须是 root 用户才能修改内核参数。
sysctl 命令会立即更改参数值,但除非你在 /etc/sysctl.conf 中添加一行定义该参数,否则重启后值会恢复为原始值。
在修改 sysctl.conf 之前,最好手动更改值并进行测试。错误的值可能导致系统无法启动,从而无法应用 /etc/sysctl.conf。
如何操作...
sysctl 命令支持多个选项:
-
-a:此选项报告所有可用的参数 -
-p FILENAME:此选项从FILENAME读取值。默认从/etc/sysctl.conf -
PARAM:此选项报告PARAM的当前值 -
PARAM=NEWVAL:此选项设置PARAM的值
调整任务调度器
任务调度器针对桌面环境进行了优化,在这种环境中,快速响应用户比整体效率更为重要。增加任务驻留时间有助于提升服务器系统的性能。以下示例检查了 kernel.sched_migration_cost_ns 的值:
$ sysctl.kernel.shed_migration_cost_ns
kernel.sched_migration_cost_ns = 500000
kernel_sched_migration_cost_ns(在旧版本内核中为kernel.sched_migration_cost)控制任务在交换前会保持活跃多久。在任务或线程较多的系统上,这可能会导致过多的上下文切换开销。默认值500000纳秒对运行 Postgres 或 Apache 服务器的系统来说太小。建议将值更改为 5 毫秒:
# sysctl kernel.sched_migration_cost_ns=5000000
在某些系统(特别是 Postgres 服务器)上,取消设置sched_autogroup_enabled参数可以提高性能。
调整网络
对于执行大量网络操作的系统(如 NFS 客户端、NFS 服务器等),网络缓冲区的默认值可能太小。
检查最大读取缓冲区内存的值:
$ sysctl net.core.rmem_max
net.core.rmem_max = 124928
增加网络服务器的值:
# sysctl net.core.rmem_max=16777216
# sysctl net.core.wmem_max=16777216
# sysctl net.ipv4.tcp_rmem="4096 87380 16777216"
# sysctl net.ipv4.tcp_wmem="4096 65536 16777216"
# sysctl net.ipv4.tcp_max_syn_backlog=4096
它是如何工作的
sysctl命令让你可以直接访问内核参数。默认情况下,大多数发行版会为普通工作站优化这些参数。
如果你的系统内存较大,你可以通过增加分配给缓冲区的内存量来提高性能。如果内存较少,你可能需要缩减这些缓冲区的大小。如果系统是服务器,你可能希望让任务在内存中驻留更长时间,而不是单用户工作站那样频繁交换。
还有更多...
/proc文件系统在所有 Linux 发行版中都可用。它包括一个文件夹用于每个正在运行的任务,还包括所有主要内核子系统的文件夹。这些文件夹中的文件可以通过cat命令查看和更新。
sysctl 支持的参数通常也被/proc文件系统支持。
因此,net.core.rmem_max也可以通过/proc/sys/net/core/rmem_max来访问。
使用配置文件调整 Linux 系统
Linux 系统包含多个文件来定义如何挂载磁盘等。可以在这些文件中设置一些参数,而不是使用/proc或sysctl。
准备工作
在/etc目录下有多个文件控制着系统的配置。这些文件可以使用标准文本编辑器(如vi或emacs)进行编辑。更改可能需要重启系统才能生效。
如何操作...
/etc/fstab文件定义了磁盘的挂载方式以及支持的选项。
Linux 系统记录了文件的创建、修改和读取时间。知道文件是否已被读取并没有太大意义,而每次常见的工具(如cat)被访问时更新Accessed时间戳会导致额外开销。
noatime和relatime挂载选项将减少磁盘的频繁访问:
$ cat /dev/fstab
/dev/mapper/vg_example_root / ext4 defaults,noatime 1 1
/dev/mapper/gb_example_spool /var ext4 defaults,relatime 1 1
它是如何工作的
上面的示例挂载了/分区(其中包含/bin和/usr/bin),使用了常见的默认选项,并增加了noatime参数,以禁用每次访问文件时都更新磁盘。/var分区(包含邮件队列文件夹)设置了 realtime 选项,这将确保至少每天更新一次时间,而不是每次访问文件时都更新。
使用 nice 命令更改调度器优先级
每个 Linux 系统上的任务都有一个优先级。优先级的值范围从 -20 到 19。优先级值越低(-20),任务分配到的 CPU 时间就越多。默认优先级是 0。
并非所有任务都需要相同的优先级。交互式应用程序需要快速响应,否则会变得难以使用。通过 crontab 运行的后台任务只需要在计划再次运行之前完成。
nice 命令将修改任务的优先级。可以用它来调用一个修改过优先级的任务。提高任务的优先级值会为其他任务释放资源。
如何操作...
如果没有参数调用 nice 命令,将报告任务的当前优先级:
$ cat nicetest.sh
echo "my nice is `nice`"
$ sh nicetest.sh
my nice is 0
调用 nice 命令并跟随另一个命令名称,将以 10 的 niceness 执行第二个命令——它会将 10 加到任务的默认优先级上:
$ nice sh nicetest.sh
my nice is 10
在命令前调用 nice 命令并设置一个值,将以定义的 niceness 执行该命令:
$ nice -15 sh nicetest.sh
my nice is 15
只有超级用户才能通过分配负的 niceness 值为任务设置更高的优先级(更低的优先级数字):
# nice -adjustment=-15 nicetest.sh
my nice is -15
工作原理
nice 命令修改内核的调度表,以更高或更低的优先级运行任务。优先级值越低,调度器分配给此任务的时间就越多。
还有更多
renice 命令修改正在运行的任务的优先级。那些消耗大量资源但对时间要求不高的任务,可以使用这个命令将其变得更加友好。top 命令有助于找到最占用 CPU 的任务。
renice 命令通过新优先级值和程序 ID(PID)来调用:
$ renice 10 12345
12345: old priority 0, new priority 10
第十三章:容器、虚拟机和云
本章将涵盖以下主题:
-
使用 Linux 容器
-
使用 Docker
-
在 Linux 中使用虚拟机
-
云中的 Linux
引言
现代 Linux 应用程序可以部署在专用硬件、容器、虚拟机(VM)或云中。每种解决方案都有其优缺点,且每一种都可以通过脚本和图形用户界面(GUI)进行配置和维护。
如果你想部署多个相同应用程序的副本,其中每个实例都需要自己的数据副本,容器是理想的选择。例如,容器在数据库驱动的 Web 服务器中表现良好,每个服务器需要相同的 Web 基础设施,但拥有私有数据。
然而,容器的缺点是它依赖于宿主系统的内核。你可以在 Linux 宿主机上运行多个 Linux 发行版,但不能在容器中运行 Windows。
如果你需要一个与所有实例不相同的完整环境,使用虚拟机是最好的选择。通过虚拟机,你可以在单一宿主机上运行 Windows 和 Linux。这对于验证测试非常理想,当你不想在办公室里放置一堆计算机时,但又需要测试不同的发行版和操作系统。
虚拟机的缺点是它们非常庞大。每个虚拟机都实现了一个完整的计算机操作系统、设备驱动程序、所有应用程序和工具等。每个 Linux 虚拟机至少需要一个核心和 1 GB 的内存。Windows 虚拟机可能需要两个核心和 4 GB 的内存。如果你希望同时运行多个虚拟机,你需要足够的内存来支持每个虚拟机,否则宿主机会开始交换内存,性能会受到影响。
云就像是有很多计算机和大量带宽触手可及。你实际上可能是在云中运行虚拟机或容器,或者你可能拥有自己的专用系统。
云的最大优势是它可以扩展。如果你认为你的应用可能会突然火爆,或者你的使用是周期性的,能够快速扩展和收缩,而不需要购买或租赁新硬件或新的连接能力是必要的。例如,如果你的系统处理大学注册,它会在每年大约两周内超负荷运作,并且在其余时间几乎处于休眠状态。在那两周里,你可能需要一打硬件,但你并不希望它们在其余时间里空置。
云的缺点是它是你看不见的。所有的维护和配置必须远程完成。
使用 Linux 容器
Linux 容器(lxc)包提供了 Docker 和 LXD 容器部署系统使用的基本容器功能。
Linux 容器使用内核级支持的 控制组(cgroups)和 第十二章 中描述的 systemd 工具,调整 Linux 系统。cgroups 支持提供了控制一组程序可用资源的工具。这些工具告诉内核哪些资源可供容器中的进程使用。一个容器可能对设备、网络连接、内存等有有限访问权限。这种控制机制避免了容器之间的相互干扰,或可能对主机系统造成损害。
准备工作
默认发行版不提供容器支持。您需要单独安装容器支持。不同发行版的支持程度不一致。lxc 容器系统由 Canonical 开发,因此 Ubuntu 发行版提供完整的容器支持。相较于 Debian 8(Jessie),Debian 9(Stretch)在这方面有更好的支持。
Fedora 对 lxc 容器的支持有限。创建特权容器和桥接以太网连接非常简单,但从 Fedora 25 开始,所需的 cgmanager 服务已不可用,用于非特权容器的支持。
SuSE 支持有限的 lxc 使用。SuSE 的 libvirt-lxc 包类似但不完全等同于 lxc。本章未覆盖 SuSE 的 libvirt-lxc 包。创建一个没有以太网的特权容器在 SuSE 下非常简单,但它不支持非特权容器和桥接以太网。
以下是如何在主要发行版上安装 lxc 支持。
对于 Ubuntu,请使用以下代码:
# apt-get install lxc1
接下来是 Debian。Debian 发行版可能只包含 /etc/apt/sources.list 中的安全仓库。如果是这样,您需要将 deb http://ftp.us.debian.org/debian stretch main contrib 添加到 /etc/apt/sources.list 中,然后执行 apt-get update,再加载 lxc 包:
# apt-get install lxc
对于 OpenSuSE,请使用以下代码:
# zypper install lxc
RedHat, Fedora:
对于基于 Red Hat/Fedora 的系统,添加以下 Epel 仓库:
# yum install epel-release
完成此操作后,安装 lxc 支持之前,请先安装以下软件包:
# yum install perl libvirt debootstrap
libvirt 包提供网络支持,debootstrap 是运行基于 Debian 的容器所需的:
# yum install lxc lxc-templates tunctl bridge-utils
如何操作...
lxc 包为您的系统添加了多个命令。包括:
-
lxc-create:用于创建一个 lxc 容器 -
lxc-ls:用于列出可用的容器 -
lxc-start:用于启动一个容器 -
lxc-stop:用于停止一个容器 -
lxc-attach:用于连接到容器的 root shell -
lxc-console:用于连接到容器的登录会话
在基于 Red Hat 的系统上,您可能需要在测试时禁用 SELinux。在 OpenSuSE 系统上,您可能需要禁用 AppArmor。禁用 AppArmor 后,您需要通过 yast2 重启系统。
Linux 容器有两种基本类型:特权容器和无特权容器。特权容器由 root 用户创建,底层系统具有 root 权限。无特权容器由普通用户创建,仅具有用户权限。
特权容器更易于创建且被更广泛支持,因为它们不需要 uid 和 gid 映射、设备权限等。然而,如果用户或应用程序成功逃逸容器,他们将拥有宿主机上的完全权限。
创建特权容器是确认系统上所有必要软件包已安装的好方法。创建特权容器后,使用无特权容器来运行应用程序。
创建特权容器
开始使用 Linux 容器的最简单方法是下载一个预构建的发行版到一个特权容器中。lxc-create 命令创建一个基础容器结构,并可以用预定义的 Linux 发行版填充它。lxc-create 命令的语法如下:
lxc-create -n NAME -t TYPE
-n 选项为容器定义一个名称。此名称将在容器启动、停止或重新配置时用于标识该容器。
-t 选项定义用于创建此容器的模板。download 类型将你的系统连接到一个预构建容器的仓库,并提示你下载容器。
这是一个实验其他发行版或创建需要不同于主机 Linux 发行版的应用程序的简便方法:
$ sudo lxc-create -t download -n ContainerName
下载模板会从互联网上获取可用的预定义容器列表,并从网络档案中填充容器。创建命令提供可用容器的列表,并提示输入发行版、版本和架构。只有当你的硬件支持此架构时,你才能运行该容器。如果你的系统使用的是 Intel CPU,则无法运行 Arm 容器,但你可以在 64 位 Intel CPU 的系统上运行 32 位 i386 容器:
$ sudo lxc-create -t download -n ubuntuContainer
...
ubuntu zesty armhf default 20170225_03:49
ubuntu zesty i386 default 20170225_03:49
ubuntu zesty powerpc default 20170225_03:49
ubuntu zesty ppc64el default 20170225_03:49
ubuntu zesty s390x default 20170225_03:49
---
Distribution: ubuntu
Release: trusty
Architecture: i386
Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs
---
You just created an Ubuntu container (release=trusty, arch=i386, variant=default)
To enable sshd, run: apt-get install openssh-server
For security reason, container images ship without user accounts and without a root password.
Use lxc-attach or chroot directly into the rootfs to set a root password or create user accounts.
你可以通过选择一个与当前安装匹配的模板,来基于当前的发行版创建容器。模板定义在 /usr/share/lxc/templates 中:
# ls /usr/share/lxc/templates
lxc-busybox lxc-debian lxc-download ...
要为当前发行版创建容器,选择适当的模板并运行 lxc-create 命令。下载过程和安装需要几分钟时间。以下示例跳过了大多数安装和配置消息:
$ cat /etc/issue
Debian GNU/Linux 8
$ sudo lxc-create -t debian -n debianContainer
debootstrap is /usr/sbin/debootstrap
Checking cache download in /var/cache/lxc/debian/rootfs-jessie-i386 ...
Downloading debian minimal ...
I: Retrieving Release
I: Retrieving Release.gpg
I: Checking Release signature
I: Valid Release signature (key id 75DDC3C4A499F1A18CB5F3C8CBF8D6FD518E17E1)
...
I: Retrieving Packages
I: Validating Packages
I: Checking component main on http://http.debian.net/debian...
I: Retrieving acl 2.2.52-2
I: Validating acl 2.2.52-2
I: Retrieving libacl1 2.2.52-2
I: Validating libacl1 2.2.52-2
I: Configuring libc-bin...
I: Configuring systemd...
I: Base system installed successfully.
Current default time zone: 'America/New_York'
Local time is now: Sun Feb 26 11:38:38 EST 2017.
Universal Time is now: Sun Feb 26 16:38:38 UTC 2017.
Root password is 'W+IkcKkk', please change !
前述命令从你的软件包管理器定义的仓库中填充新容器。在使用容器之前,你必须启动它。
启动容器
lxc-start 命令启动一个容器。与其他 lxc 命令一样,你必须提供要启动的容器名称:
# lxc-start -n ubuntuContainer
启动顺序可能会挂起,您可能会看到类似以下的错误。这些错误是由于容器的启动顺序尝试执行图形操作,比如在客户端没有图形支持的情况下显示启动画面所导致的:
<4>init: plymouth-upstart-bridge main process (5) terminated with
status 1
...
您可以等待这些错误超时并忽略它们,或者禁用启动画面。禁用启动画面在不同的发行版和版本中有所不同。文件可能位于/etc/init,但这并不一定。
有两种方式可以在容器内工作:
-
lxc-attach:此命令直接附加到运行中的容器的 root 账户 -
lxc-console:这将打开一个登录会话的控制台,连接到正在运行的容器
容器的第一次使用是直接附加创建用户账户:
# lxc-attach -n containerName
root@containerName:/#
root@containerName:/# useradd -d /home/USERNAME -m USERNAME
root@containerName:/# passwd USERNAME
Enter new UNIX password:
Retype new UNIX password:
创建用户账户后,请使用lxc-console应用程序以普通用户或 root 身份登录:
$ lxc-console -n containerName
Connected to tty 1
Type <Ctrl+a q> to exit the console,
<Ctrl+a Ctrl+a> to enter Ctrl+a itself
Login:
停止容器
lxc-stop命令用于停止容器:
# lxc-stop -n containerName
列出已知的容器
lxc-ls命令列出当前用户可用的容器名称。此命令不会列出系统中的所有容器,只会列出当前用户拥有的容器:
$ lxc-ls
container1Name container2Name...
显示容器信息
lxc-info命令显示容器的信息:
$ lxc-info -n containerName
Name: testContainer
State: STOPPED
但此命令仅显示单个容器的信息。通过使用 Shell 循环(如在第一章《Shell Something Out》中所描述的),我们可以显示所有容器的信息:
$ for c in `lxc-ls`
do
lxc-info -n $c
echo
done
Name: name1
State: STOPPED
Name: name2
State: RUNNING
PID: 1234
IP 10.0.3.225
CPU use: 4.48 seconds
BlkIO use: 728.00 KiB
Memory use: 15.07 MiB
KMem use: 2.40 MiB
Link: vethMU5I00
TX bytes: 20.48 KiB
RX bytes: 30.01 KiB
Total bytes: 50.49 KiB
如果容器已停止,则无法获得状态信息。运行中的容器会记录其 CPU、内存、磁盘(块)、I/O 和网络使用情况。此工具允许您监视容器,查看哪些容器最活跃。
创建非特权容器
推荐使用非特权容器进行日常操作。如果容器配置不当或应用程序配置不当,可能会导致控制权限从容器中泄露出去。由于容器会调用主机内核中的系统调用,如果容器以 root 身份运行,那么系统调用也将以 root 身份运行。然而,非特权容器以普通用户权限运行,因此更为安全。
要创建非特权容器,主机必须支持 Linux 控制组和 uid 映射。这些支持包含在基础的 Ubuntu 发行版中,但其他发行版需要额外安装。cgmanager软件包并非所有发行版都提供。没有该软件包,无法启动非特权容器:
# apt-get install cgmanager uidmap systemd-services
启动cgmanager:
$ sudo service cgmanager start
Debian 系统可能需要启用克隆支持。如果在创建容器时收到chown错误,以下行可以解决此问题:
# echo 1 > /sys/fs/cgroup/cpuset/cgroup.clone_children
# echo 1 > /proc/sys/kernel/unprivileged_userns_clone
允许创建容器的账户的用户名必须包含在etc映射表中:
$ sudo usermod --add-subuids 100000-165536 $USER
$ sudo usermod --add-subgids 100000-165536 $USER
$ sudo chmod +x $HOME
这些命令将用户添加到用户 ID 和组 ID 映射表(/etc/subuid和/etc/subgid)中,并将 UID 从100000 -> 165536分配给用户。
接下来,设置容器的配置文件:
$ mkdir ~/.config/lxc
$ cp /etc/lxc/default.conf ~/.config/lxc
将以下行添加到 ~/.config/lxc/default.conf:
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
如果容器支持网络访问,请向 /etc/lxc/lxc-usernet 添加一行,以定义可以访问网络桥接器的用户:
USERNAME veth BRIDGENAME COUNT
这里,USERNAME 是拥有该容器的用户的名称。veth 是虚拟以太网设备的常用名称。BRIDGENAME 是 ifconfig 显示的名称,通常为 br0 或 lxcbro。COUNT 是允许的同时连接数:
$ cat /etc/lxc/lxc-usernet
clif veth lxcbr0 10
创建以太网桥接器
容器无法直接访问你的以太网适配器。它需要一个虚拟以太网与实际以太网之间的桥接。最近的 Ubuntu 发行版在安装 lxc 包时会自动创建以太网桥接器。而 Debian 和 Fedora 可能需要你手动创建桥接器。在 Fedora 上创建桥接器时,首先使用 libvirt 包创建虚拟桥接器:
# systemctl start libvirtd
然后,编辑 /etc/lxc/default.conf,将其引用 virbr0 而不是 lxcbr0:
lxc.network_link = virbr0
如果你已经创建了一个容器,也可以编辑该容器的配置文件。
要在 Debian 系统上创建桥接器,你必须编辑网络配置和容器配置文件。
编辑 /etc/lxc/default.conf。注释掉默认的空网络,并为 lxc 桥接器添加定义:
# lxc.network.type = empty
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flage = up`
接下来,创建网络桥接器:
# systemctl enable lxc-net
# systemctl start lxc-net
在执行这些步骤后创建的容器将启用网络功能。可以通过向容器的配置文件中添加 lxc.network 行来为现有容器添加网络支持。
它是如何工作的……
通过 lxc-create 命令创建的容器是一个目录树,包含容器的配置选项和根文件系统。特权容器构建在 /var/lib/lxc 下。非特权容器存储在 $HOME/.local/lxc 下:
$ ls /var/lib/lxc/CONTAINERNAME
config rootfs
你可以通过编辑容器顶层目录中的配置文件来检查或修改容器的配置:
# vim /var/lib/lxc/CONTAINERNAME/config
rootfs 文件夹包含容器的根文件系统。这是运行中的容器的根(/)文件夹:
# ls /var/lib/lxc/CONTAINERNAME/rootfs
bin boot cdrom dev etc home lib media mnt proc
root run sbin sys tmp usr var
你可以通过添加、删除或修改 rootfs 文件夹中的文件来填充容器。例如,要运行 Web 服务,容器可能通过包管理器安装了基本的 Web 服务,并通过将文件复制到 rootfs 来安装每个服务的实际数据。
使用 Docker
lxc 容器复杂,使用起来可能比较困难。这些问题促成了 Docker 包的出现。Docker 使用相同的底层 Linux 功能(如 namespaces 和 cgroups)来创建轻量级容器。
Docker 仅在 64 位系统上官方支持,因此对于旧系统来说,lxc 是更好的选择。
Docker 容器和 lxc 容器之间的主要区别是,Docker 容器通常只运行一个进程,而 lxc 容器运行多个进程。要部署一个数据库支持的 web 服务器,你至少需要两个 Docker 容器——一个用于 web 服务器,一个用于数据库服务器——但只需要一个 lxc 容器。
Docker 的理念使得从更小的构建模块构建系统变得简单,但它也可能使开发模块变得更加困难,因为许多 Linux 工具期望在一个完整的 Linux 系统中运行,并且需要有 crontab 条目来执行诸如清理、日志轮换等操作。
一旦 Docker 容器创建完成,它将在其他 Docker 服务器上按照预期运行。这使得在云集群或远程站点部署 Docker 容器变得非常简单。
准备就绪
Docker 并没有在大多数发行版中预安装。它是通过 Docker 的软件仓库进行分发的。使用这些仓库需要将新的仓库添加到你的软件包管理器中,并更新校验和。
Docker 为每个发行版和不同版本提供安装说明,相关信息可以在其主页 docs.docker.com 查找到。
如何操作...
当 Docker 首次安装时,它是没有运行的。你必须使用如下命令启动服务器:
# service docker start
Docker 命令有许多子命令提供不同的功能。这些命令将查找 Docker 容器并下载和运行它。以下是关于这些子命令的一些介绍:
-
# docker search:这会在 Docker 存档中搜索与关键字匹配的容器 -
# docker pull:这会将指定的容器拉取到你的系统中 -
# docker run:这会在容器中运行一个应用程序 -
# docker ps:这会列出正在运行的 Docker 容器 -
# docker attach:这会附加到一个正在运行的容器 -
# docker stop:这会停止一个容器 -
# docker rm:这会移除一个容器
默认的 Docker 安装要求 docker 命令必须以 root 用户身份运行,或者使用 sudo。
每个命令都有一个 man 页面。这个页面的名称由命令和子命令通过一个短横线连接组成。要查看 docker search 的 man 页面,使用 man docker-search。
下一条食谱展示了如何下载并运行一个 Docker 容器。
查找容器
docker search 命令返回一个符合搜索条件的 Docker 容器列表:
docker search TERM
这里 TERM 是一个字母数字字符串(不允许使用通配符)。搜索命令将返回最多 25 个包含该字符串的容器:
# docker search apache
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
eboraas/apache Apache (with SSL support) 70 [OK]
bitnami/apache Bitnami Apache Docker 25 [OK]
apache/nutch Apache Nutch 12 [OK]
apache/marmotta Apache Marmotta 4 [OK]
lephare/apache Apache container 3 [OK]
这里的 STARS 表示容器的评分。容器按评分从高到低排序。
下载容器
docker pull 命令从 Docker 注册表下载一个容器。默认情况下,它从 Docker 的公共注册表 registry-1.docker.io 拉取数据。下载的容器会被添加到你的系统中。容器通常存储在 /var/lib/docker 下:
# docker pull lephare/apache
latest: Pulling from lephare/apache
425e28bb756f: Pull complete
ce4a2c3907b1: Extracting [======================> ] 2.522 MB/2.522 MB
40e152766c6c: Downloading [==================> ] 2.333 MB/5.416 MB
db2f8d577dce: Download complete
Digest: sha256:e11a0f7e53b34584f6a714cc4dfa383cbd6aef1f542bacf69f5fccefa0108ff8
Status: Image is up to date for lephare/apache:latest
启动 Docker 容器
docker run 命令在容器中启动一个进程。通常,这个进程是一个 bash shell,允许你附加到容器并启动其他进程。该命令返回一个哈希值,用于定义此会话。
当 Docker 容器启动时,会自动为它创建一个网络连接。
运行命令的语法如下:
docker run [OPTIONS] CONTAINER COMMAND
docker run 命令支持许多选项,包括:
-
-t:分配一个伪 tty(默认值为 false) -
-i:保持交互式会话在未附加的状态下开启 -
-d:启动容器时使其分离(在后台运行) -
--name:为此实例指定名称
这个例子启动了之前拉取的容器中的 bash shell:
# docker run -t -i -d --name leph1 lephare/apache /bin/bash
1d862d7552bcaadf5311c96d439378617d85593843131ad499...
列出 Docker 会话
docker ps 命令列出当前正在运行的 Docker 会话:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
123456abc lephare/apache /bin/bash 10:05 up 80/tcp leph1
-a 选项将列出系统上所有的 Docker 容器,无论它们是否正在运行。
将显示器附加到运行中的 Docker 容器
docker attach 命令将你的显示器附加到正在运行的容器中的 tty 会话。你需要以 root 用户身份在此容器内运行。
要退出附加的会话,输入^P^Q。
这个例子在容器中创建一个 HTML 页面并启动 Apache web 服务器:
$ docker attach leph1
root@131aaaeeac79:/# cd /var/www
root@131aaaeeac79:/var/www# mkdir symfony
root@131aaaeeac79:/var/www# mkdir symfony/web
root@131aaaeeac79:/var/www# cd symfony/web
root@131aaaeeac79:/var/www/symfony/web# echo "<html><body><h1>It's Alive</h1></body></html>"
>index.html
root@131aaaeeac79:/# cd /etc/init.d
root@131aaaeeac79:/etc/init.d# ./apache2 start
[....] Starting web server: apache2/usr/sbin/apache2ctl: 87: ulimit: error setting limit (Operation
not permitted)
Setting ulimit failed. See README.Debian for more information.
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using
172.17.0.5\. Set the 'ServerName' directive globally to suppress this message
. ok
浏览到 172.17.0.5 将显示 It's Alive 页面。
停止 Docker 会话
docker stop 命令终止一个正在运行的 Docker 会话:
# docker stop leph1
删除 Docker 实例
docker rm 命令删除一个容器。删除容器之前,容器必须先停止。可以通过名称或标识符删除容器:
# docker rm leph1
或者,你可以使用以下命令:
# docker rm 131aaaeeac79
工作原理
Docker 容器使用与 lxc 容器相同的 namespace 和 cgroup 内核支持。最初,Docker 是 lxc 的一层,但它后来发展成了一个独立的系统。
服务器的主要配置文件存储在 /var/lib/docker 和 /etc/docker 中。
在 Linux 中使用虚拟机
在 Linux 中使用虚拟机有四种选择。三种开源选项是 KVM、XEN 和 VirtualBox。商业上,VMware 提供了一个可以托管在 Linux 上并运行虚拟机的虚拟引擎和执行程序。
VMware 支持虚拟机的时间比任何人都长。它们支持 Unix、Linux、Mac OS X 和 Windows 作为主机系统,并支持 Unix、Linux 和 Windows 作为客体系统。对于商业用途,VMware Player 或 VMware Workstation 是你最好的选择。
KVM 和 VirtualBox 是 Linux 中最流行的虚拟机引擎。KVM 提供更好的性能,但它需要支持虚拟化的 CPU(Intel VT-x)。大多数现代的 Intel 和 AMD CPU 都支持这些功能。VirtualBox 的优势在于它被移植到 Windows 和 Mac OS X,使得你可以轻松地将虚拟机迁移到其他平台。VirtualBox 不需要 VT-x 支持,这使得它适用于旧系统和现代系统。
准备就绪
虚拟盒子(VirtualBox)被大多数发行版支持,但可能不是这些发行版的默认软件包仓库的一部分。
要在 Debian 9 上安装 VirtualBox,您需要将 virtualbox.org 仓库添加到 apt-get 将接受的软件包来源中:
# vi /etc/apt/sources.list
## ADD:
deb http://download.virtualbox.org/virtualbox/debian stretch contrib
安装正确的密钥需要curl软件包。如果它还没有安装,请在添加密钥和更新仓库信息之前先安装它:
# apt-get install curl
# curl -O https://www.virtualbox.org/download/oracle_vbox_2016.asc
# apt-key add oracle_vbox_2016.asc
# apt-get update
一旦仓库更新完毕,您可以通过apt-get安装 VirtualBox:
# apt-get install virtualbox-5.1
OpenSuSE
# zypper install gcc make kernel-devel
Open yast2, select Software Management, search for virtualbox.
Select virtualbox, virtualbox-host-kmp-default, and virtualbox-qt.
如何操作...
安装 VirtualBox 后,它会在开始菜单中创建一个项目。它可能位于系统或应用程序/系统工具下。您可以从终端会话启动 GUI,命令是virtualbox或VirtualBox。
VirtualBox 的 GUI 使得创建和运行虚拟机变得容易。GUI 的左上角有一个名为“新建”的按钮;它用于创建一个新的空虚拟机。向导会提示您输入诸如内存和磁盘限制等信息。
一旦虚拟机创建完成,启动按钮将被激活。默认设置将虚拟机的 CD-ROM 连接到主机的 CD-ROM。您可以将安装盘放入 CD-ROM 中,然后点击启动以在新虚拟机上安装操作系统。
云中的 Linux
使用云服务器的主要原因有两个。服务提供商使用商业云服务,如亚马逊的 AWS,因为它可以在需求增加时轻松扩展资源,在需求减少时降低成本。云存储服务提供商,如 Google Docs,允许用户从任何设备访问他们的数据并与他人共享数据。
OwnCloud 软件包将您的 Linux 服务器转变为一个私有云存储系统。您可以将 OwnCloud 服务器作为私人企业文件共享系统,与朋友共享文件,或者作为手机或平板的远程备份。
OwnCloud 项目于 2016 年分叉。NextCloud 服务器和应用程序预计将使用与 OwnCloud 相同的协议,并且是可以互换的。
准备工作
运行 OwnCloud 软件包需要安装LAMP(Linux, Apache, MySQL, PHP)。这些软件包被所有 Linux 发行版所支持,尽管它们可能不是默认安装的。第十章中讨论了 MySQL 的管理和安装,管理调用。
大多数发行版不将 OwnCloud 服务器包含在其仓库中。相反,OwnCloud 项目维护仓库以支持这些发行版。在下载之前,您需要将 OwnCloud 附加到您的 RPM 或 apt 仓库中。
Ubuntu 16.10
以下步骤将在 Ubuntu 16.10 系统上安装 LAMP 堆栈。类似的命令适用于任何基于 Debian 的系统。不幸的是,软件包名称在不同版本之间有时会有所不同:
apt-get install apache2
apt-get install mysql-server php-mysql
OwnCloud 需要超出默认设置的安全性。mysql_secure_installation脚本将正确配置 MySQL:
/usr/bin/mysql_secure_installation
配置OwnCloud仓库:
curl \ https://download.owncloud.org/download/repositories/stable/ \ Ubuntu_16.10/Release.key/'| sudo tee \ /etc/apt/sources.list.d/owncloud.list
apt-get update
一旦仓库配置好,apt 将安装并启动服务器:
apt-get install owncloud
OpenSuSE Tumbleweed
使用 Yast2 安装 LAMP 堆栈。打开 yast2,选择软件管理,安装 apache2、mysql 和 owncloud-client。
接下来,选择 System 选项卡,并从该选项卡选择 Services Manager 选项卡。确认 mysql 和 apache2 服务已启用并处于活动状态。
这些步骤将安装 OwnCloud 客户端,允许你将工作区与 OwnCloud 服务器同步,并提供服务器的系统要求。
OwnCloud 需要比默认设置更高的安全性。mysql_secure_installation 脚本将正确配置 MySQL:
/usr/bin/mysql_secure_installation
以下命令将安装并启动 OwnCloud 服务器。前三个命令将配置 zypper 以包括 OwnCloud 仓库。一旦这些仓库被添加,OwnCloud 包将像安装其他软件包一样安装:
rpm --import https://download.owncloud.org/download/repositories/stable/openSUSE_Leap_42.2/repodata/repomd.xml.key
zypper addrepo http://download.owncloud.org/download/repositories/stable/openSUSE_Leap_42.2/ce:stable.repo
zypper refresh
zypper install owncloud
如何操作...
一旦安装了 OwnCloud,你可以配置一个管理员账户,并从那里添加用户账户。NextCloud Android 应用将与 OwnCloud 服务器以及 NextCloud 服务器进行通信。
配置 OwnCloud
一旦 owncloud 安装完成,你可以通过浏览器访问本地地址来进行配置:
$ konqueror http://127.0.0.1/owncloud
初始界面将提示你输入管理员用户名和密码。你可以以用户身份登录,以创建备份并在手机、平板电脑和电脑之间复制文件。
还有更多…
我们刚刚讨论的基本安装过程适合测试。如果支持 HTTPS,OwnCloud 和 NextCloud 将使用 HTTPS 会话。启用 HTTPS 支持需要一个 X.509 安全证书。
你可以从众多商业提供商中购买安全证书,或者为自己的使用自签证书,或者通过 Let's Encrypt(http://letsencrypt.org)创建一个免费的证书。
自签证书足以用于测试,但大多数浏览器和手机应用会标记此为不受信任的网站。Let's Encrypt 是互联网安全研究小组(ISRG)提供的服务。他们生成的证书是完全注册的,所有应用程序都可以接受它们。
获取证书的第一步是验证你的网站是否符合声明的身份。Let's Encrypt 证书使用名为自动证书管理环境(ACME)的系统进行验证。ACME 系统会在你的网页服务器上创建一个隐藏文件,并告知 证书颁发机构(CA)该文件的位置,CA 会确认该文件是否存在。这证明你可以访问网页服务器,并且 DNS 记录指向正确的硬件。
如果你使用的是常见的网页服务器,如 Nginx 或 Apache,设置证书的最简单方法是使用 EFF 创建的 certbot:
# wget https://dl.eff.org/certbot-auto
# chmod a+x certbot-auto
# ./certbot-auto
这个机器人将添加新的软件包并将你的新证书安装到正确的位置。
如果你使用的是不太常见的服务器或有非标准安装,getssl 包是更具可配置性的。getssl 包是一个 bash 脚本,它读取两个配置文件以自动创建证书。可以从此处下载并解压该包:https://github.com/srvrco/getssl。
解压 getssl.zip 会创建一个名为 getssl_master 的文件夹。
生成和安装证书需要三个步骤:
-
使用
getssl -c DOMAIN.com创建默认配置文件。 -
编辑配置文件。
-
创建证书。
首先,通过 cd 命令进入 getssl_master 文件夹并创建配置文件:
# cd getssl_master
# getssl -c DOMAIN.com
将 DOMAIN 替换为你的域名。
这一步会创建 $HOME/.getssl 和 $HOME/.getssl/DOMAIN.com 文件夹,并在这两个文件夹中创建名为 getssl.cfg 的文件。每个文件都必须进行编辑。
编辑 ~/.getssl/getssl.cfg 文件,并添加你的电子邮件地址:
ACCOUNT_EMAIL='myName@mySite.com'
其余字段中的默认值适用于大多数站点。
接下来,编辑 ~/.getssl/DOMAIN.com/getssl.cfg 文件。此文件中有多个字段需要修改。
主要的变化是设置 Acme Challenge Location (ACL) 字段。ACME 协议将尝试在 www.DOMAIN.com/.well-known/acme-challenge 中查找一个文件。ACL 值是该文件夹在系统中的物理位置。如果这些文件夹不存在,你必须创建 .well-known 和 .well-known/acme-challenge 文件夹并设置其所有权。
如果你的网页保存在 /var/web/DOMAIN 中,你可以按以下方式创建新的文件夹:
# mkdir /var/web/DOMAIN/.well-known
# mkdir /var/web/DOMAIN/.well-known/acme-challenge
# chown webUser.webGroup /var/web/DOMAIN/.well-known
# chown webUser.webGroup /var/web/DOMAIN/.well-known/acme-challenge
ACL 行应类似如下:
ACL="/var/web/DOMAIN/.well-known/acme-challenge"
USE_SINGLE_ACL="true"
你还必须定义证书将被放置的位置。这个位置必须与 web 服务器中的配置选项匹配。例如,如果证书保存在 /var/web/certs 中,定义应类似如下:
DOMAIN_CERT_LOCATION="/var/web/certs/DOMAIN.crt"
DOMAIN_KEY_LOCATION="/var/web/certs/DOMAIN.key"
CA_CERT_LOCATION="/var/web/certs/DOMAIN.com.bundle"
你必须设置 ACME 协议将使用的测试类型。这些设置已在配置文件的底部注释掉。通常使用默认值是最好的:
SERVER_TYPE="https"
CHECK_REMOTE="true"
完成这些编辑后,运行以下命令进行测试:
./getssl DOMAIN.com
这个命令类似于第一个,但不包括 -c(创建)选项。你可以重复运行这个命令,直到修正错误并对结果满意为止。
getssl 脚本的默认行为是生成一个无效的测试证书。这是因为 Let's Encrypt 限制了每个站点实际证书的生成数量,以防止滥用。
一旦配置文件正确,再次编辑它们并将服务器从 Staging 服务器更改为实际的 Let's Encrypt 服务器:
CA="https://acme-v01.api.letsencrypt.org"
然后,再次运行 getssl 脚本,并使用 -f 选项强制它重新构建并替换之前的文件:
./getssl -f DOMAIN.com
在新的文件被识别之前,你可能需要重启 web 服务器或重启系统。


浙公网安备 33010602011771号