充分利用 PHP 日志文件:实用指南
已发表: 2022-03-11可以正确地说,日志是自由 php 开发人员可以使用的最被低估和未充分利用的工具之一。 尽管他们可以提供丰富的信息,但日志是开发人员在尝试解决问题时最后查看的地方并不少见。
事实上,在许多情况下,PHP 日志文件应该是在出现问题时首先寻找线索的地方。 通常,它们包含的信息可以显着减少拔头发试图追踪一个粗糙的虫子所花费的时间。
但也许更重要的是,只要有一点创造力和远见,您的日志文件就可以用作使用信息和分析的宝贵来源。 创造性地使用日志文件可以帮助回答以下问题:哪些浏览器最常用于访问我的站点? 我的服务器的平均响应时间是多少? 对站点根目录的请求百分比是多少? 自我们部署最新更新以来,使用情况发生了怎样的变化? 还有很多很多。
本文提供了一些关于如何配置日志文件以及如何处理它们包含的信息的提示,以最大限度地发挥它们提供的优势。
尽管本文在技术上侧重于 PHP 开发人员的日志记录,但本文提供的大部分信息都与技术无关,并且也与其他语言和技术堆栈相关。
注意:本文假定您基本熟悉 Unix shell。 对于那些缺乏这方面知识的人,提供了一个附录,其中介绍了在 Unix 系统上访问和读取日志文件所需的一些命令。
我们的 PHP 日志文件示例项目
作为本文讨论目的的示例项目,我们将 Symfony Standard 作为一个工作项目,我们将在 Debian 7 Wheezy 上使用rsyslogd
、 nginx
和PHP-FPM
设置它。
composer create-project symfony/framework-standard-edition my "2.6.*"
这很快为我们提供了一个带有漂亮 UI 的工作测试项目。
配置日志文件的提示
以下是有关如何配置日志文件以帮助最大化其价值的一些提示。
错误日志配置
错误日志代表了最基本的日志形式; 即,在出现问题时捕获额外的信息和细节。 所以,在一个理想的世界里,你会希望没有错误并且你的错误日志是空的。 但是,当问题确实发生时(因为它们总是如此),您的错误日志应该是您在调试过程中所做的第一站。
错误日志通常很容易配置。
一方面,所有错误和崩溃消息都可以以完全相同的格式记录在错误日志中,否则它们将呈现给用户。 通过一些简单的配置,最终用户将永远不需要在您的站点上看到那些丑陋的错误痕迹,而 devops 仍然能够监控系统并查看这些错误消息的所有细节。 以下是在 PHP 中设置这种日志记录的方法:
log_errors = On error_reporting = E_ALL error_log = /path/to/my/error/log
另外两行很重要,要包含在实时站点的日志文件中,以防止向用户呈现血腥级别的错误详细信息,它们是:
display_errors = Off display_startup_errors = Off
系统日志 ( syslog
) 配置
在开源世界中, syslog
守护程序有许多普遍兼容的实现,包括:
-
syslogd
和sysklogd
最常见于 BSD 系列系统、CentOS、Mac OS X 等 syslog-ng
– 现代 Gentoo 和 SuSE 构建的默认值rsyslogd
– 广泛用于 Debian 和 Fedora 系列操作系统
(注意:在本文中,我们将使用rsyslogd
作为示例。)
基本的 syslog 配置通常足以在系统范围的日志文件中捕获日志消息(通常是/var/log/syslog
;也可能是/var/log/messages
或/var/log/system.log
取决于发行版你正在使用)。
系统日志提供了几个日志设施,其中八个( LOG_LOCAL0
到LOG_LOCAL7
)是为用户部署的项目保留的。 例如,这里是您如何设置LOG_LOCAL0
以根据日志记录级别(即错误、警告、信息、调试)写入 4 个单独的日志文件:
# /etc/rsyslog.d/my.conf local0.err /var/log/my/err.log local0.warning /var/log/my/warning.log local0.info -/var/log/my/info.log local0.debug -/var/log/my/debug.log
现在,每当您将日志消息写入LOG_LOCAL0
设施时,错误消息将转到/var/log/my/err.log
,警告消息将转到/var/log/my/warning.log
,依此类推。 但请注意,syslog 守护程序根据“此级别和更高级别”的规则过滤每个文件的消息。 因此,在上面的示例中,所有错误消息都将出现在所有四个配置文件中,警告消息将出现在除错误日志之外的所有文件中,信息消息将出现在 info 和 debug 日志中,并且调试消息只会转到debug.log
.
还有一个重要的注意事项; 上述配置文件示例中的 info 和 debug 级别文件之前的-
符号表示应该异步执行对这些文件的写入(因为这些操作是非阻塞的)。 对于信息和调试日志,这通常很好(甚至在大多数情况下推荐),但最好是同步写入错误日志(并且最可能是警告日志)。
为了关闭不太重要的日志级别(例如,在生产服务器上),您可以简单地将相关消息重定向到/dev/null
(即,无处):
local0.debug /dev/null # -/var/log/my/debug.log
一种有用的特定定制,尤其是支持我们将在本文后面讨论的某些 PHP 日志文件解析,是使用制表符作为日志消息中的分隔符。 这可以通过在/etc/rsyslog.d
中添加以下文件来轻松完成:
# /etc/rsyslog.d/fixtab.conf $EscapeControlCharactersOnReceive off
最后,不要忘记在进行任何配置更改后重新启动 syslog 守护程序以使它们生效:
service rsyslog restart
服务器日志配置
与您可以写入的应用程序日志和错误日志不同,服务器日志由相应的服务器守护进程(例如,Web 服务器、数据库服务器等)在每个请求上专门写入。 您对这些日志的唯一“控制”是服务器允许您配置其日志记录功能。 尽管在这些文件中可能有很多需要筛选的内容,但它们通常是清楚了解服务器“幕后”发生了什么的唯一方法。
让我们在带有 MySQL 存储后端的 nginx 环境中部署我们的 Symfony Standard 示例应用程序。 这是我们将使用的 nginx 主机配置:
server { server_name my.log-sandbox; root /var/www/my/web; location / { # try to serve file directly, fallback to app.php try_files $uri /app.php$is_args$args; } # DEV # This rule should only be placed on your development environment # In production, don't include this and don't deploy app_dev.php or config.php location ~ ^/(app_dev|config)\.php(/|$) { fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param HTTPS off; } # PROD location ~ ^/app\.php(/|$) { fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param HTTPS off; # Prevents URIs that include the front controller. This will 404: # http://domain.tld/app.php/some-path # Remove the internal directive to allow URIs like this internal; } error_log /var/log/nginx/my_error.log; access_log /var/log/nginx/my_access.log; }
关于上面的最后两个指令: access_log
表示一般请求日志,而error_log
表示错误,并且与应用程序错误日志一样,值得设置额外的监控以提醒问题,以便您可以快速做出反应。
注意:这是一个故意过度简化的 nginx 配置文件,仅用于示例目的。 它几乎不关注安全性和性能,不应该在任何“真实”环境中按原样使用。
这是我们在浏览器中输入http://my.log-sandbox/app_dev.php/
Enter
后在/var/log/nginx/my_access.log
中得到的内容。
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /app_dev.php/ HTTP/1.1" 200 6715 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/framework/css/body.css HTTP/1.1" 200 6657 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/framework/css/structure.css HTTP/1.1" 200 1191 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/css/demo.css HTTP/1.1" 200 2204 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-quick-tour.gif HTTP/1.1" 200 4770 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-demo.gif HTTP/1.1" 200 4053 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-configure.gif HTTP/1.1" 200 3530 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /favicon.ico HTTP/1.1" 200 6518 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36" 192.168.56.1 - - [26/Apr/2015:16:13:30 +0300] "GET /app_dev.php/_wdt/e50d73 HTTP/1.1" 200 13265 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36"
这表明,为了提供一个页面,浏览器实际上执行了 9 次 HTTP 调用。 然而,其中 7 个请求是对静态内容的请求,这些请求是简单而轻量级的。 但是,它们仍然占用网络资源,这可以通过使用各种精灵和缩小技术来优化。
虽然这些优化将在另一篇文章中讨论,但这里相关的是我们可以通过使用另一个location
指令分别记录对静态内容的请求:
location ~ \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js)$ { access_log /var/log/nginx/my_access-static.log; }
请记住,nginx location
执行简单的正则表达式匹配,因此您可以包含您希望在您的站点上调度的尽可能多的静态内容扩展。
解析此类日志与解析应用程序日志没有什么不同。
其他值得一提的日志
另外两个值得一提的 PHP 日志是调试日志和数据存储日志。
调试日志
关于 nginx 日志的另一个方便之处是调试日志。 我们可以通过将配置的error_log
行替换为以下内容来打开它(需要安装 nginx 调试模块):
error_log /var/log/nginx/my_error.log debug;
相同的设置适用于 Apache 或您使用的任何其他网络服务器。
顺便说一句,调试日志与错误日志无关,即使它们是在error_log
指令中配置的。
虽然调试日志确实可以很冗长(例如单个 nginx 请求,生成了 127KB 的日志数据!),但它仍然非常有用。 翻阅日志文件可能很麻烦和乏味,但它通常可以快速提供线索和信息,极大地帮助加速调试过程。
特别是调试日志可以真正帮助调试 nginx 配置,尤其是最复杂的部分,比如location
匹配和rewrite
链。
当然,调试日志不应该在生产环境中启用。 它们使用的空间量和存储的信息量也意味着服务器上的大量 I/O 负载,这会显着降低整个系统的性能。
数据存储日志
另一种类型的服务器日志(用于调试)是数据存储日志。 在 MySQL 中,您可以通过添加以下行来打开它们:
[mysqld] general_log = 1 general_log_file = /var/log/mysql/query.log
这些日志仅包含系统在按时间顺序为数据库请求提供服务时运行的查询列表,这有助于各种调试和跟踪需求。 但是,它们不应在生产系统上保持启用状态,因为它们会产生额外的不必要的 I/O 负载,从而影响性能。
写入您的日志文件
PHP 本身提供了用于打开、写入和关闭日志文件的函数(分别为openlog()
、 syslog()
和closelog()
)。
PHP 开发人员还有许多日志库,例如 Monolog(在 Symfony 和 Laravel 用户中很流行),以及各种特定于框架的实现,例如集成到 CakePHP 中的日志功能。 一般来说,像 Monolog 这样的库不仅包装syslog()
调用,还允许使用其他后端功能和工具。

这是一个如何写入日志的简单示例:
<?php openlog(uniqid(), LOG_ODELAY, LOG_LOCAL0); syslog(LOG_INFO, 'It works!');
我们在这里调用openlog
:
- 将 PHP 配置为在脚本的生命周期内为每个系统日志消息添加一个唯一标识符
- 将其设置为延迟打开 syslog 连接,直到发生第一个
syslog()
调用 - 将
LOG_LOCAL0
设置为默认日志记录工具
以下是运行上述代码后日志文件的内容:
# cat /var/log/my/info.log Mar 2 00:23:29 log-sandbox 54f39161a2e55: It works!
最大化 PHP 日志文件的价值
现在我们都熟悉了理论和基础知识,让我们看看通过对示例 Symfony 标准项目进行尽可能少的更改的日志可以得到多少。
首先,让我们创建脚本src/log-begin.php
(正确打开和配置我们的日志)和src/log-end.php
(记录有关成功完成的信息)。 请注意,为简单起见,我们只会将所有消息写入信息日志。
# src/log-begin.php <?php define('START_TIME', microtime(true)); openlog(uniqid(), LOG_ODELAY, LOG_LOCAL0); syslog(LOG_INFO, 'BEGIN'); syslog(LOG_INFO, "URI\t{$_SERVER['REQUEST_URI']}"); $browserHash = substr(md5($_SERVER['HTTP_USER_AGENT']), 0, 7); syslog(LOG_INFO, "CLIENT\t{$_SERVER['REMOTE_ADDR']}\t{$browserHash}"); <br /> # src/log-end.php <?php syslog(LOG_INFO, "DISPATCH TIME\t" . round(microtime(true) - START_TIME, 2)); syslog(LOG_INFO, 'END');
让我们在app.php
使用这些脚本:
<?php require_once(dirname(__DIR__) . '/src/log-begin.php'); syslog(LOG_INFO, "MODE\tPROD"); # original app.php contents require_once(dirname(__DIR__) . '/src/log-end.php');
对于开发环境,我们希望在app_dev.php
中也需要这些脚本。 这样做的代码与上面的代码相同,除了我们将MODE
设置为DEV
而不是PROD
。
我们还想跟踪正在调用哪些控制器,所以让我们在Acme\DemoBundle\EventListener\ControllerListener
中添加一行,就在ControllerListener::onKernelController()
方法的开头:
syslog(LOG_INFO, "CONTROLLER\t" . get_class($event->getController()[0]));
请注意,这些更改总共只增加了 15 行代码,但可以共同产生大量信息。
分析日志文件中的数据
首先,让我们看看服务每个页面加载需要多少 HTTP 请求。
根据我们配置日志记录的方式,这是一个请求的日志中的信息:
Mar 3 12:04:20 log-sandbox 54f58724b1ccc: BEGIN Mar 3 12:04:20 log-sandbox 54f58724b1ccc: URI /app_dev.php/ Mar 3 12:04:20 log-sandbox 54f58724b1ccc: CLIENT 192.168.56.1 1b101cd Mar 3 12:04:20 log-sandbox 54f58724b1ccc: MODE DEV Mar 3 12:04:23 log-sandbox 54f58724b1ccc: CONTROLLER Acme\DemoBundle\Controller\WelcomeController Mar 3 12:04:25 log-sandbox 54f58724b1ccc: DISPATCH TIME 4.51 Mar 3 12:04:25 log-sandbox 54f58724b1ccc: END Mar 3 12:04:25 log-sandbox 54f5872967dea: BEGIN Mar 3 12:04:25 log-sandbox 54f5872967dea: URI /app_dev.php/_wdt/59b8b6 Mar 3 12:04:25 log-sandbox 54f5872967dea: CLIENT 192.168.56.1 1b101cd Mar 3 12:04:25 log-sandbox 54f5872967dea: MODE DEV Mar 3 12:04:28 log-sandbox 54f5872967dea: CONTROLLER Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController Mar 3 12:04:29 log-sandbox 54f5872967dea: DISPATCH TIME 4.17 Mar 3 12:04:29 log-sandbox 54f5872967dea: END
所以现在我们知道每个页面加载实际上是由两个 HTTP 请求提供的。
其实这里有两点值得一提。 首先,每个页面加载的两个请求是为了在开发模式下使用 Symfony(我在整篇文章中都这样做了)。 您可以通过搜索/app-dev.php/
URL 块来识别开发模式调用。 其次,假设每个页面加载都由对 Symfony 应用程序的两个后续请求提供服务。 正如我们之前在 nginx 访问日志中看到的,实际上还有更多的 HTTP 调用,其中一些是针对静态内容的。
好的,现在让我们在演示站点上浏览一下(在日志文件中建立数据),看看我们还能从这些日志中学到什么。
自日志文件开始以来总共服务了多少个请求?
# grep -c BEGIN info.log 10
他们中的任何一个都失败了(脚本是否在没有到达结尾的情况下关闭了)?
# grep -c END info.log 10
我们看到BEGIN
和END
记录的数量匹配,所以这告诉我们所有的调用都成功了。 (如果 PHP 脚本没有成功完成,它就不会执行src/log-end.php
脚本。)
对站点根目录的请求百分比是多少?
# `grep -cE "\s/app_dev.php/$" info.log` 2
这告诉我们站点根目录有 2 个页面加载。 由于我们之前了解到 (a) 每次页面加载对应用程序有 2 个请求,并且 (b) 总共有 10 个 HTTP 请求,因此对站点根目录的请求百分比为 40%(即 2x2/10)。
哪个控制器类负责为站点根目录提供请求?
# grep -E "\s/$|\s/app_dev.php/$" info.log | head -n1 Mar 3 12:04:20 log-sandbox 54f58724b1ccc: URI /app_dev.php/ # grep 54f58724b1ccc info.log | grep CONTROLLER Mar 3 12:04:23 log-sandbox 54f58724b1ccc: CONTROLLER Acme\DemoBundle\Controller\WelcomeController
在这里,我们使用请求的唯一 ID 来检查与该单个请求相关的所有日志消息。 因此,我们能够确定负责向站点根目录提供请求的控制器类是Acme\DemoBundle\Controller\WelcomeController
。
哪些 IP 为
192.168.0.0/16
子网的客户端访问了该站点?
# grep CLIENT info.log | cut -d":" -f4 | cut -f2 | sort | uniq 192.168.56.1
正如在这个简单的测试案例中所预期的那样,只有我的主机访问了该站点。 这当然是一个非常简单的例子,但它展示的能力(能够分析您网站的流量来源)显然非常强大和重要。
我的网站有多少流量来自 FireFox?
将1b101cd
作为我的 Firefox User-Agent 的哈希值,我可以如下回答这个问题:
# grep -c 1b101cd info.log 8 # grep -c CLIENT info.log 10
答案:80%(即 8/10)
产生缓慢响应的请求的百分比是多少?
出于本示例的目的,我们将“慢”定义为需要 5 秒以上才能提供响应。 因此:
# grep "DISPATCH TIME" info.log | grep -cE "\s[0-9]{2,}\.|\s[5-9]\." 2
答案:20%(即 2/10)
有没有人提供过 GET 参数?
# grep URI info.log | grep \?
不,Symfony 标准只使用 URL slug,所以这也告诉我们没有人试图入侵该站点。
这些只是可以创造性地利用日志文件来产生有价值的使用信息甚至基本分析的一些相对基本的示例。
其他要记住的事情
确保安全
另一个提示是为了安全。 您可能认为记录请求是个好主意,在大多数情况下确实如此。 但是,在将任何可能敏感的用户信息存储到日志之前,请务必小心删除任何潜在的敏感用户信息。
对抗日志文件膨胀
由于日志文件是您始终向其附加信息的文本文件,因此它们会不断增长。 由于这是一个众所周知的问题,因此有一些相当标准的方法可以控制日志文件的增长。
最简单的方法是轮换日志。 轮换日志意味着:
- 定期用新的空文件替换日志以供进一步写入
- 为历史存储旧文件
- 删除“老化”到足以释放磁盘空间的文件
- 确保应用程序可以在发生这些文件更改时写入不间断的日志
最常见的解决方案是logrotate
,它预装在大多数 *nix 发行版中。 让我们看一个用于轮换日志的简单配置文件:
/var/log/my/debug.log /var/log/my/info.log /var/log/my/warning.log /var/log/my/error.log { rotate 7 daily missingok notifempty delaycompress compress sharedscripts postrotate invoke-rc.d rsyslog rotate > /dev/null endscript }
另一种更高级的方法是让rsyslogd
本身将消息写入文件,根据当前日期和时间动态创建。 这仍然需要一个自定义解决方案来删除旧文件,但让 devops 可以精确地管理每个日志文件的时间范围。 对于我们的示例:
$template DynaLocal0Err, "/var/log/my/error-%$NOW%-%$HOUR%.log" $template DynaLocal0Info, "/var/log/my/info-%$NOW%-%$HOUR%.log" $template DynaLocal0Warning, "/var/log/my/warning-%$NOW%-%$HOUR%.log" $template DynaLocal0Debug, "/var/log/my/debug-%$NOW%-%$HOUR%.log" local1.err -?DynaLocal0Err local1.info -?DynaLocal0Info local1.warning -?DynaLocal0Warning local1.debug -?DynaLocal0Debug
这样, rsyslog
将每小时创建一个单独的日志文件,并且不需要轮换它们并重新启动守护程序。 以下是如何删除超过 5 天的日志文件以完成此解决方案:
find /var/log/my/ -mtime +5 -print0 | xargs -0 rm
远程日志
随着项目的发展,从日志中解析信息变得越来越需要资源。 这不仅意味着创建额外的服务器负载; 这也意味着在解析日志时会在 CPU 和磁盘驱动器上产生峰值负载,这会降低用户的服务器响应时间(或者在最坏的情况下甚至会导致站点停机)。
为了解决这个问题,考虑设置一个集中的日志服务器。 为此,您只需要另一个打开 UDP 端口 514(默认)的框。 要让rsyslogd
监听连接,请将以下行添加到其配置文件中:
$UDPServerRun 514
有了这个,设置客户端就很简单了:
*.* @HOSTNAME:514
(其中HOSTNAME
是您的远程日志服务器的主机名)。
结论
虽然本文展示了日志文件可以提供比您以前想象的更有价值的信息的一些创造性方式,但重要的是要强调我们只是触及了可能的表面。 您可以记录的范围、范围和格式几乎是无限的。 这意味着——如果你想从日志中提取使用或分析数据——你只需要以一种易于随后解析和分析的方式记录它。 此外,通常可以使用标准 Linux 命令行工具(如grep
、 sed
或awk
)执行该分析。
事实上,PHP 日志文件是一个最强大的工具,可以带来巨大的好处。
资源
GitHub 上的代码:https://github.com/isanosyan/toptal-blog-logs-post-example
附录:在 Unix Shell 中读取和操作日志文件
这里简要介绍了一些更常见的 *nix 命令行工具,您需要熟悉这些工具来阅读和操作日志文件。
cat
可能是最简单的一种。 它将整个文件打印到输出流。 例如,以下命令会将logfile1
打印到控制台:cat logfile1
>
字符允许用户重定向输出,例如到另一个文件。 以写入模式打开目标流(这意味着擦除目标内容)。 下面是我们如何用logfile1
的内容替换tmpfile
的内容:cat logfile1 > tmpfile
>>
重定向输出并以附加模式打开目标流。 目标文件的当前内容将被保留,新行将添加到底部。 这会将logfile1
内容附加到tmpfile
:cat logfile1 >> tmpfile
grep
按某种模式过滤文件并仅打印匹配的行。 下面的命令将只打印包含Bingo
消息的logfile1
行:grep Bingo logfile1
cut
打印单列的内容(按从 1 开始的数字)。 默认情况下,搜索制表符作为列之间的分隔符。 例如,如果您的文件中充满了格式YYYY-MM-DD HH:MM:SS
的时间戳,这将只允许您打印年份:cut -d"-" -f1 logfile1
head
只显示文件的第一行tail
只显示文件的最后几行sort
对输出中的行进行排序uniq
过滤掉重复的行wc
计算字数(或与-l
标志一起使用时的行数)|
(即“管道”符号)将一个命令的输出作为输入提供给下一个命令。 管道对于组合命令非常方便。 例如,以下是我们如何找到 2014 年发生在一组时间戳中的月份的方法:grep -E "^2014" logfile1 | cut -d"-" -f2 | sort | uniq
在这里,我们首先将行与正则表达式“从 2014 年开始”匹配,然后减少月份。 最后,我们使用sort
和uniq
的组合只打印一次出现。