{"id":164,"date":"2009-04-23T21:05:34","date_gmt":"2009-04-23T20:05:34","guid":{"rendered":"http:\/\/pgregg.com\/wp\/?p=164"},"modified":"2009-04-23T21:05:34","modified_gmt":"2009-04-23T20:05:34","slug":"tail-file-in-php","status":"publish","type":"post","link":"https:\/\/blog.pgregg.com\/blog\/2009\/04\/tail-file-in-php\/","title":{"rendered":"tail -# file, in PHP"},"content":{"rendered":"<p>I read Kevin&#8217;s <a href=\"http:\/\/phpro.org\/examples\/Read-Line-From-File.html\">Read Line from File<\/a> article today and thought I would add some sample code I wrote a while back to address a similar task in PHP &#8211; How to perform the equivalent of &#8220;tail -# filename&#8221; in PHP.<\/p>\n<p>A typical task in some applications such as a shoutbox or a simple log file viewer is how to extract the last &#8216;n&#8217; lines from a (very) large file &#8211; efficiently.\u00a0 There are several approaches one can take to solving the problem (as always in programming there are many ways to skin the proverbial cat) &#8211; taking the easy way out and shelling out to run the tail command; or reading the file line by line keeping the last &#8216;n&#8217; lines in memory with a queue.<\/p>\n<p>However, here I will demonstrate a fairly tuned method of seeking to the end of the file and stepping back to read sufficient lines to the end of the file. If insufficient lines are returned, it incrementally looks back further in the file until it either can look no further, or sufficient lines are returned.<\/p>\n<p>Assumptions need to be made in order to tune the algorithm for the competing challenges of :<\/p>\n<ul>\n<li>Reading enough lines in<\/li>\n<li>as few reads as possible<\/li>\n<\/ul>\n<p>My approach to this is to provide an approximation to the average size of a line:<code> <span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$linelength<\/span><\/span><\/code><br \/>\nMultiply by &#8216;n&#8217;: <code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$linecount<\/span><\/span><\/code><br \/>\nand we have the byte <code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$offset <\/span><\/span><\/code>from the end of the file that we will seek to to begin reading.<\/p>\n<p>A check we need to do right here is that we haven&#8217;t offset to before the beginning of the file, and so we have to override <code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$offset <\/span><\/span><\/code>to match the file size.<\/p>\n<p>Also as I&#8217;m being over-cautious, I&#8217;m going to tell it to offset <code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$linecount + 1<\/span><\/span><\/code> &#8211; The main reason for this is by seeking to a specific byte location in the file, we would have to be very lucky to land on the first character of a new line &#8211; therefore we must perform a fgets() and throw away that result.<\/p>\n<p>Typically, I want it to be able to read &#8216;n&#8217; lines forward from the offset given, however if that proves insufficient, I&#8217;m going to grow the offset by 10% and try again. I also want to make it so that the algorithm is better able to tune itself if we grossly underestimate what <code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$linelength <\/span><\/span><\/code>should be.\u00a0 In order to do this, we&#8217;re going to track the string length of each line we do get back and adjust the offset accordingly.<\/p>\n<p>In our example, lets try reading the last 10 lines from Apache&#8217;s access_log<\/p>\n<p>So let&#8217;s look at the code so far, nothing interesting, we&#8217;re just prepping for the interesting stuff:<\/p>\n<blockquote><p><code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$linecount\u00a0\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">10<\/span><span style=\"color: #007700;\">;\u00a0\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0Number\u00a0of\u00a0lines\u00a0we\u00a0want\u00a0to\u00a0read<br \/>\n<\/span><span style=\"color: #0000bb;\">$linelength\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">160<\/span><span style=\"color: #007700;\">;\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0Apache's\u00a0logs\u00a0are\u00a0typically\u00a0~200+\u00a0chars<br \/>\n\/\/\u00a0I've\u00a0set\u00a0this\u00a0to\u00a0&lt;\u00a0200\u00a0to\u00a0show\u00a0the\u00a0dynamic\u00a0nature\u00a0of\u00a0the\u00a0algorithm<br \/>\n\/\/\u00a0offset\u00a0correction.<br \/>\n<\/span><span style=\"color: #0000bb;\">$file\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #dd0000;\">'\/usr\/local\/apache2\/logs\/access_log.demo'<\/span><span style=\"color: #007700;\">;<br \/>\n<\/span><span style=\"color: #0000bb;\">$fsize\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">filesize<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$file<\/span><span style=\"color: #007700;\">);<\/span><\/span><\/code><br \/>\n<code><\/code><br \/>\n<code><span style=\"color: #000000;\"><span style=\"color: #ff8000;\">\/\/\u00a0check\u00a0if\u00a0file\u00a0is\u00a0smaller\u00a0than\u00a0possible\u00a0max\u00a0lines<\/span><\/span><\/code><br \/>\n<code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">=\u00a0(<\/span><span style=\"color: #0000bb;\">$linecount<\/span><span style=\"color: #007700;\">+<\/span><span style=\"color: #0000bb;\">1<\/span><span style=\"color: #007700;\">)\u00a0*\u00a0<\/span><span style=\"color: #0000bb;\">$linelength<\/span><span style=\"color: #007700;\">;<\/span><\/span><\/code><br \/>\n<code><span style=\"color: #000000;\"><span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">&gt;\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">)\u00a0<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">;<\/span><\/span><\/code><br \/>\n<code><\/code><\/p><\/blockquote>\n<p><code><\/code>Next up we&#8217;re going to open the file and using our method of seeking to the end of the file, less our offset, here is the meat of our routine:<\/p>\n<blockquote><p><code><\/code><code><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$fp\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">fopen<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$file<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #dd0000;\">'r'<\/span><span style=\"color: #007700;\">);<br \/>\nif\u00a0(<\/span><span style=\"color: #0000bb;\">$fp\u00a0<\/span><span style=\"color: #007700;\">===\u00a0<\/span><span style=\"color: #0000bb;\">false<\/span><span style=\"color: #007700;\">)\u00a0exit;<\/span><\/span><\/code><\/p>\n<p><span style=\"color: #0000bb;\">$lines\u00a0<\/span><span style=\"color: #007700;\">=\u00a0array();\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0array\u00a0to\u00a0store\u00a0the\u00a0lines\u00a0we\u00a0read.<\/span><\/p>\n<p><span style=\"color: #0000bb;\">$readloop\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">true<\/span><span style=\"color: #007700;\">;<br \/>\nwhile(<\/span><span style=\"color: #0000bb;\">$readloop<\/span><span style=\"color: #007700;\">)\u00a0{<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0we\u00a0will\u00a0finish\u00a0reading\u00a0when\u00a0we\u00a0have\u00a0read\u00a0$linecount\u00a0lines,\u00a0or\u00a0the\u00a0file<br \/>\n\/\/\u00a0just\u00a0doesn&#8217;t\u00a0have\u00a0$linecount\u00a0lines<\/span><\/p>\n<p>\/\/\u00a0seek\u00a0to\u00a0$offset\u00a0bytes\u00a0from\u00a0the\u00a0end\u00a0of\u00a0the\u00a0file<br \/>\n<span style=\"color: #0000bb;\">fseek<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$fp<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">0\u00a0<\/span><span style=\"color: #007700;\">&#8211;\u00a0<\/span><span style=\"color: #0000bb;\">$offset<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">SEEK_END<\/span><span style=\"color: #007700;\">);<\/span><\/p>\n<p><code><span style=\"color: #000000;\"><span style=\"color: #007700;\">\u00a0 <\/span><span style=\"color: #ff8000;\">\/\/\u00a0discard\u00a0the\u00a0first\u00a0line\u00a0as\u00a0it\u00a0won't\u00a0be\u00a0a\u00a0complete\u00a0line<br \/>\n\/\/\u00a0unless\u00a0we're\u00a0right\u00a0at\u00a0the\u00a0start\u00a0of\u00a0the\u00a0file<br \/>\n<\/span><span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">!=\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">)\u00a0<\/span><span style=\"color: #0000bb;\">fgets<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$fp<\/span><span style=\"color: #007700;\">);<\/span><\/span><\/code><code><\/code><\/p>\n<p><span style=\"color: #ff8000;\">\/\/\u00a0tally\u00a0of\u00a0the\u00a0number\u00a0of\u00a0bytes\u00a0in\u00a0each\u00a0line\u00a0we\u00a0read<br \/>\n<\/span><span style=\"color: #0000bb;\">$linesize\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">0<\/span><span style=\"color: #007700;\">;<\/span><\/p>\n<p><span style=\"color: #ff8000;\">\/\/\u00a0read\u00a0from\u00a0here\u00a0till\u00a0the\u00a0end\u00a0of\u00a0the\u00a0file\u00a0and\u00a0remember\u00a0each\u00a0line<br \/>\n<\/span><span style=\"color: #007700;\">while(<\/span><span style=\"color: #0000bb;\">$line\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">fgets<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$fp<\/span><span style=\"color: #007700;\">))\u00a0{<br \/>\n<\/span><span style=\"color: #0000bb;\">array_push<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">$line<\/span><span style=\"color: #007700;\">);<br \/>\n<\/span><span style=\"color: #0000bb;\">$linesize\u00a0<\/span><span style=\"color: #007700;\">+=\u00a0<\/span><span style=\"color: #0000bb;\">strlen<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$line<\/span><span style=\"color: #007700;\">);\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0total\u00a0up\u00a0the\u00a0char\u00a0count<\/span><\/p>\n<p>\/\/\u00a0if\u00a0we&#8217;ve\u00a0been\u00a0able\u00a0to\u00a0get\u00a0more\u00a0lines\u00a0than\u00a0we\u00a0need<br \/>\n\/\/\u00a0lose\u00a0the\u00a0first\u00a0entry\u00a0in\u00a0the\u00a0queue<br \/>\n\/\/\u00a0Logically\u00a0we\u00a0should\u00a0decrement\u00a0$linesize\u00a0too,\u00a0but\u00a0if\u00a0we<br \/>\n\/\/\u00a0hit\u00a0the\u00a0magic\u00a0number\u00a0of\u00a0lines,\u00a0we\u00a0are\u00a0never\u00a0going\u00a0to\u00a0use\u00a0it<br \/>\n<span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">count<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">)\u00a0&gt;\u00a0<\/span><span style=\"color: #0000bb;\">$linecount<\/span><span style=\"color: #007700;\">)\u00a0<\/span><span style=\"color: #0000bb;\">array_shift<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">);<br \/>\n}<\/span><\/p>\n<p><span style=\"color: #ff8000;\">\/\/\u00a0We\u00a0have\u00a0now\u00a0read\u00a0all\u00a0the\u00a0lines\u00a0from\u00a0$offset\u00a0until\u00a0the\u00a0end\u00a0of\u00a0the\u00a0file<br \/>\n<\/span><span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">count<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">)\u00a0==\u00a0<\/span><span style=\"color: #0000bb;\">$linecount<\/span><span style=\"color: #007700;\">)\u00a0{<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0perfect\u00a0&#8211;\u00a0have\u00a0enough\u00a0lines,\u00a0can\u00a0exit\u00a0the\u00a0loop<br \/>\n<\/span><span style=\"color: #0000bb;\">$readloop\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">false<\/span><span style=\"color: #007700;\">;<br \/>\n}\u00a0elseif\u00a0(<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">&gt;=\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">)\u00a0{<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0file\u00a0is\u00a0too\u00a0small\u00a0&#8211;\u00a0nothing\u00a0more\u00a0we\u00a0can\u00a0do,\u00a0we\u00a0must\u00a0exit\u00a0the\u00a0loop<br \/>\n<\/span><span style=\"color: #0000bb;\">$readloop\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">false<\/span><span style=\"color: #007700;\">;<br \/>\n}\u00a0elseif\u00a0(<\/span><span style=\"color: #0000bb;\">count<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">)\u00a0&lt;\u00a0<\/span><span style=\"color: #0000bb;\">$linecount<\/span><span style=\"color: #007700;\">)\u00a0{<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0try\u00a0again\u00a0with\u00a0a\u00a0bigger\u00a0offset<br \/>\n<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">intval<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">*\u00a0<\/span><span style=\"color: #0000bb;\">1.1<\/span><span style=\"color: #007700;\">);\u00a0\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0increase\u00a0offset\u00a010%<br \/>\n\/\/\u00a0but\u00a0also\u00a0work\u00a0out\u00a0what\u00a0the\u00a0offset\u00a0could\u00a0be\u00a0if\u00a0based\u00a0on\u00a0the\u00a0lines\u00a0we\u00a0saw<br \/>\n<\/span><span style=\"color: #0000bb;\">$offset2\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">intval<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$linesize<\/span><span style=\"color: #007700;\">\/<\/span><span style=\"color: #0000bb;\">count<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">)\u00a0* <\/span>(<span style=\"color: #0000bb;\">$linecount+1)<\/span><span style=\"color: #007700;\">);<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0and\u00a0if\u00a0it\u00a0is\u00a0larger,\u00a0then\u00a0use\u00a0that\u00a0one\u00a0instead\u00a0(self-tuning)<br \/>\n<\/span><span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">$offset2\u00a0<\/span><span style=\"color: #007700;\">&gt;\u00a0<\/span><span style=\"color: #0000bb;\">$offset<\/span><span style=\"color: #007700;\">)\u00a0<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">$offset2<\/span><span style=\"color: #007700;\">;<br \/>\n<\/span><span style=\"color: #ff8000;\">\/\/\u00a0Also\u00a0remember\u00a0we\u00a0can&#8217;t\u00a0seek\u00a0back\u00a0past\u00a0the\u00a0start\u00a0of\u00a0the\u00a0file<br \/>\n<\/span><span style=\"color: #007700;\">if\u00a0(<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">&gt;\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">)\u00a0<\/span><span style=\"color: #0000bb;\">$offset\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">$fsize<\/span><span style=\"color: #007700;\">;<br \/>\necho\u00a0<\/span><span style=\"color: #dd0000;\">&#8216;Trying\u00a0with\u00a0a\u00a0bigger\u00a0offset:\u00a0&#8216;<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">$offset<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #dd0000;\">&#8220;n&#8221;<\/span><span style=\"color: #007700;\">;<br \/>\n<\/span><code><span style=\"color: #000000;\"><span style=\"color: #007700;\">\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #ff8000;\">\/\/\u00a0and\u00a0reset<br \/>\n<\/span><span style=\"color: #0000bb;\">$lines\u00a0<\/span><span style=\"color: #007700;\">=\u00a0array();<br \/>\n<\/span><\/span><\/code><code><span style=\"color: #000000;\"><span style=\"color: #007700;\">\u00a0 }<br \/>\n}<\/span><\/span><\/code><\/p>\n<p><span style=\"color: #ff8000;\">\/\/\u00a0Let&#8217;s\u00a0have\u00a0a\u00a0look\u00a0at\u00a0the\u00a0lines\u00a0we\u00a0read.<br \/>\n<\/span><span style=\"color: #0000bb;\">print_r<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lines<\/span><span style=\"color: #007700;\">);<\/span><br \/>\n<code><\/code><\/p><\/blockquote>\n<p>At first glance it might seem line overkill for the task, however stepping through the code you can see the expected while loop with fgets() to read each line. The only thing we are doing at this stage is shifting the first line of the $lines array if we happen to read too many lines, and also tallying up how many characters we managed to read for each line.<\/p>\n<p>If we exit the while\/fgets loop with the correct number of lines, then all is well, we can exit the main retry loop and we have the result in $lines.<\/p>\n<p>Where the code gets interesting is what we do if we don&#8217;t achieve the required number of lines.\u00a0 The simple fix is to step back by a further 10% by increasing the offset and trying again, but remember we also counted up the number of bytes we read for each line we did get, so we can very simply obtain an average real-file line size by dividing this with the number of lines in our $lines array. This enables us to override the previous offset value to something larger, if indeed we were wildly off in our estimates.<\/p>\n<p>By adjusting our offset value and letting the loop repeat, the routine will try again and repeat until it succeeds or fails gracefully by the file not having sufficient lines, in which case it&#8217;ll return what it could get.<\/p>\n<p>For the complete, working, source code please visit:<\/p>\n<blockquote><p><a href=\"http:\/\/pgregg.com\/projects\/php\/code\/tail-10.phps\">http:\/\/pgregg.com\/projects\/php\/code\/tail-10.phps<\/a><\/p><\/blockquote>\n<p>Sample execution:<\/p>\n<blockquote><p><a href=\"http:\/\/pgregg.com\/projects\/php\/code\/tail-10.php\">http:\/\/pgregg.com\/projects\/php\/code\/tail-10.php<\/a><\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>I read Kevin&#8217;s Read Line from File article today and thought I would add some sample code I wrote a while back to address a similar task in PHP &#8211; How to perform the equivalent of &#8220;tail -# filename&#8221; in PHP. A typical task in some applications such as a shoutbox or a simple log &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blog.pgregg.com\/blog\/2009\/04\/tail-file-in-php\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;tail -# file, in PHP&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10],"tags":[81,90],"class_list":["post-164","post","type-post","status-publish","format-standard","hentry","category-php","tag-php-2","tag-security"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pbQOUu-2E","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts\/164","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/comments?post=164"}],"version-history":[{"count":0,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts\/164\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/media?parent=164"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/categories?post=164"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/tags?post=164"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}