I'm working on a script that edits PHP files contents. So far, i'm able to check if the line is empty on the file and then write what I need into it. However I need to find a way to loop through the array until it finds the next empty line if the first query was not empty.
For example, I want to edit this PHP file - example.php - which contains the following:
<?php
I am not an empty line.
I am not an empty line.
I am not an empty line.
?>
My script:
// File variables
$file = 'path/example.php';
$content = '';
// Check if the file exists and is readable
if (file_exists($file) && is_readable($file)) {
$content = file_get_contents($file);
}
// put lines into an array
$lines = explode("\n", $content);
//Get the fourth line
$Getline = $lines[3];
// check if the line is emptpy
if (empty($Getline) && $Getline !== '0') {
// Write something in the file
}
else {
// Find the next empty line
}
So all I need is to loop through the array until it finds the next empty line. Although I'm not sure how do that.
Use PHP file() function instead of file_get_contents() function. It will read the file in array format itself.
Then you can parse this array using foreach() and can check blank value in it.
May this will help you.
<?php
foreach($lines as $line)
{
// check if the line is empty
if (empty($line) || $line == '')
{
//Line = empty and do stuff here.
}
}
?>
I'm trying to make a simple news hit counter with PHP and text file. i wrote a simple code to check and read the file:
Text File:
//Data in Source File
//Info: News-ID|Hits|Date
1|32|2013-9-25
2|241|2013-9-26
3|57|2013-9-27
PHP File:
//Get Source
$Source = ENGINE_DIR . '/data/top.txt';
$Read = file($Source);
//Add New Record
foreach($Read as $News){
//Match News ID
if($News[0] == "2"){
//Add New Record and Update the Text File
}
}
Problem is i can't change the news hits! For example, i need change hits from second line from 241 to 242 and write it again in to the txt file.
I searched in this site and Google and tried some ways but i couldn't fix that.
At the least, you're forgetting to write the increment back to the file. Also, you're going to want to parse each row into columns you can work with (delimited by a pipe |).
Untested code, but the idea is:
$Source = ENGINE_DIR . '/data/top.txt'; // you already have this line
$Read = file($Source); // and this one
foreach ( $Read as $LineNum => $News ) { // iterate through each line
$NewsParts = explode('|',$News); // expand the line into pieces to work with
if ( $NewsParts[0] == 2 ) { // if the first column is 2
$NewsParts[1]++; // increment the second column
$Read[$LineNum] = implode('|',$NewsParts); // glue the line back together, we're updating the Read array directly, rather than the copied variable $News
break; // we're done so exit the loop, saving cycles
}
}
$UpdatedContents = implode(PHP_EOL,$Read); // put the read lines back together (remember $Read as been updated) using "\n" or "\r\n" whichever is best for the OS you're running on
file_put_contents($Source,$UpdatedContents); // overwrite the file
You could read the file and do something like this:
//Get Source
$Source = ENGINE_DIR . '/data/top.txt';
$Read = file($Source);
$News = array();
foreach ($Read as $line) {
list($id, $views, $date) = explode('|', $line);
$News[$id] = array(
'id' => $id,
'views' => $views,
'date' => $date,
);
}
At this point you have the array $News which contains every news item and you can change them as you wish (example: $News[2]['views'] = 242;).
The only thing you're missing now is the writing back to the file part, which is also easy.
$fh = fopen(ENGINE_DIR . '/data/top.txt', 'w'); //'w' mode opens the file for write and truncates it
foreach ($News as $item) {
fwrite($fh, $item['id'] . '|' . $item['views'] . '|' . $item['date'] . "\n");
}
fclose($fh);
And that's it! :)
I want to do the following
I want to create .php file (executed via cronjobs) that will paste this code $files[] = 'example.php';
to other php file (paste.php) but it has to find the lastest $files[] line like regex $files[] = '(AnythingHere)'; and after this line to paste the new line. It can have random number of pages so I have no way of knowing.
<?php
if (!isset($php_file)) {
$files[] = 'page1.php';
$files[] = 'page2.php';
$files[] = 'page3.php';
$files[] = 'page4.php';
$file = $files[ rand(0,count($files)) ];
I hope you guys understand what I want; can anyone help me out with this one?
if you have ONLY $file[] = '...' in paste.php, you can simply append to the file:
$line = '$file[] = "pageX.php";' . PHP_EOL;
file_put_contents('paste.php', $line, FILE_APPEND);
of you want the last "page[]" enty.
$yourNewLine = '$file[] = "pageX.php";'; // this is an example. put your "line" prm here
$filename = 'paste.php';
$lines = file($filename);
$lines = array_reverse($lines)
$found = false;
$i = 0;
while ( ! $found )
{
if ( strpos($lines[$i], '$files[] = ' === 0) )
{
$found = true;
array_splice($lines, $i, 0, $yourNewLine.PHP_EOL);
}
$i++;
}
$lines = array_reverse($lines);
file_put_contents($filename, $lines);
Instead of doing it this way, how about instead setting your files array in a script and then include it at the top. This way you can reference the array directly and still only have to edit the file listing in only one place.
Quick and dirty first-fit solution:
Open the file
Read each line until you find one matching your regex for $files[] = ...
Read more lines until you find one that doesn't match the regex
Write each line read in 2 and 3 to the output file
Insert your new line into the output
Write the rest of the input to the output
This may not be the best way to approach the problem, drawbacks being that you have to read each line in and compare it with your regex until you find your insertion point. You'll also probably have a temporary file for output which you'll then rename to the original filename.
You'll have 2 while loops:
while (line does not match): read next line
and then
while (line does match): read next line
Someone who knows PHP better than I do might be able to come up with something a bit cleaner, but if you're just looking for something quick to get the job done, this ought to work.
Having this code:
$filesArray = array('page1.php','page2.php','page3.php','page4.php','page5.php',);
then getting the php file with $data = file("path/to/editable_file.php");
foreach($data as $line)
{
if(preg_replace("/\$filesArray\s=\sarray\([\w'.,]+()\);/", "'".$newfilename."',", $line, $match))
{
file_put_contents(implode("\r\n", $data));
break;
}
}
I have a file named $dir and a string named $line, I know that this string is a complete line of that file but I don't know its line number and I want to remove it from file, what should I do?
Is it possible to use awk?
$contents = file_get_contents($dir);
$contents = str_replace($line, '', $contents);
file_put_contents($dir, $contents);
Read the lines one by one, and write all but the matching line to another file. Then replace the original file.
this will just look over every line and if it not what you want to delete, it gets pushed to an array that will get written back to the file. see this
$DELETE = "the_line_you_want_to_delete";
$data = file("./foo.txt");
$out = array();
foreach($data as $line) {
if(trim($line) != $DELETE) {
$out[] = $line;
}
}
$fp = fopen("./foo.txt", "w+");
flock($fp, LOCK_EX);
foreach($out as $line) {
fwrite($fp, $line);
}
flock($fp, LOCK_UN);
fclose($fp);
It can be solved without the use of awk:
function remove_line($file, $remove) {
$lines = file($file, FILE_IGNORE_NEW_LINES);
foreach($lines as $key => $line) {
if($line === $remove) unset($lines[$key]);
}
$data = implode(PHP_EOL, $lines);
file_put_contents($file, $data);
}
Another approach is to read the file line by line until you find a match, then truncate the file to that point, and then append the rest of the lines.
This is also good if you're looking for a substring (ID) in a line and want to replace the old line with the a new line.
Code:
$contents = file_get_contents($dir);
$new_contents = "";
if (strpos($contents, $id) !== false) { // if file contains ID
$contents_array = explode(PHP_EOL, $contents);
foreach ($contents_array as &$record) { // for each line
if (strpos($record, $id) !== false) { // if we have found the correct line
continue; // we've found the line to delete - so don't add it to the new contents.
} else {
$new_contents .= $record . "\r"; // not the correct line, so we keep it
}
}
file_put_contents($dir, $new_contents); // save the records to the file
echo json_encode("Successfully updated record!");
}
else {
echo json_encode("failed - user ID ". $id ." doesn't exist!");
}
Example:
input: "123,student"
Old file:
ID,occupation
123,student
124,brick layer
Running the code will change file to:
New file:
ID,occupation
124,brick layer
All answeres here have in common, that they load the complete file into the memory. Here is an implementation of removing one (or more) line(s) without coyping the files content into a variable.
The idea is to iterate over the files lines. If a line should be removed, the lines length is added to the $byte_offset. The next line is then moved $byte_offset bytes "upwards". This is done with all following lines. If all lines are processed, the files last $byte_offset bytes are removed.
I guess that this is faster for bigger files because nothing is copied. And I guess that at some file size the other answers do not work at all while this one should. But I didn't test it.
Usage:
$file = fopen("path/to/file", "a+");
// remove lines 1 and 2 and the line containing only "line"
fremove_line($file, 1, 2, "line");
fclose($file);
The code of the fremove_line() function:
/**
* Remove the `$lines` by either their line number (as an int) or their content
* (without trailing new-lines).
*
* Example:
* ```php
* $file = fopen("path/to/file", "a+"); // must be opened writable
* // remove lines 1 and 2 and the line containing only "line"
* fremove_line($file, 1, 2, "line");
* fclose($file);
* ```
*
* #param resource $file The file resource opened by `fopen()`
* #param int|string ...$lines The one-based line number(s) or the full line
* string(s) to remove, if the line does not exist, it is ignored
*
* #return boolean True on success, false on failure
*/
function fremove_line($file, ..$lines): bool{
// set the pointer to the start of the file
if(!rewind($file)){
return false;
}
// get the stat for the full size to truncate the file later on
$stat = fstat($file);
if(!$stat){
return false;
}
$current_line = 1; // change to 0 for zero-based $lines
$byte_offset = 0;
while(($line = fgets($file)) !== false){
// the bytes of the lines ("number of ASCII chars")
$line_bytes = strlen($line);
if($byte_offset > 0){
// move lines upwards
// go back the `$byte_offset`
fseek($file, -1 * ($byte_offset + $line_bytes), SEEK_CUR);
// move the line upwards, until the `$byte_offset` is reached
if(!fwrite($file, $line)){
return false;
}
// set the file pointer to the current line again, `fwrite()` added `$line_bytes`
// already
fseek($file, $byte_offset, SEEK_CUR);
}
// remove trailing line endings for comparing
$line_content = preg_replace("~[\n\r]+$~", "", $line);
if(in_array($current_line, $lines, true) || in_array($line_content, $lines, true)){
// the `$current_line` should be removed so save to skip the number of bytes
$byte_offset += $line_bytes;
}
// keep track of the current line
$current_line++;
}
// remove the end of the file
return ftruncate($file, $stat["size"] - $byte_offset);
}
Convert text to array, remove first line and reconvert to text
$line=explode("\r\n",$text);
unset($line[0]);
$text=implode("\r\n",$line);
I think the best way to work with files is to act them like strings:
/**
* Removes the first found line inside the given file.
*
* #param string $line The line content to be searched.
* #param string $filePath Path of the file to be edited.
* #param bool $removeOnlyFirstMatch Whether to remove only the first match or
* the whole matches.
* #return bool If any matches found (and removed) or not.
*
* #throw \RuntimeException If the file is empty.
* #throw \RuntimeException When the file cannot be updated.
*/
function removeLineFromFile(
string $line,
string $filePath,
bool $removeOnlyFirstMatch = true
): bool {
// You can wrap it inside a try-catch block
$file = new \SplFileObject($filePath, "r");
// Checks whether the file size is not zero
$fileSize = $file->getSize();
if ($fileSize !== 0) {
// Read the whole file
$fileContent = $file->fread($fileSize);
} else {
// File is empty
throw new \RuntimeException("File '$filePath' is empty");
}
// Free file resources
$file = null;
// Divide file content into its lines
$fileLineByLine = explode(PHP_EOL, $fileContent);
$found = false;
foreach ($fileLineByLine as $lineNumber => $thisLine) {
if ($thisLine === $line) {
$found = true;
unset($fileLineByLine[$lineNumber]);
if ($removeOnlyFirstMatch) {
break;
}
}
}
// We don't need to update file either if the line not found
if (!$found) {
return false;
}
// Join lines together
$newFileContent = implode(PHP_EOL, $fileLineByLine);
// Finally, update the file
$file = new \SplFileObject($filePath, "w");
if ($file->fwrite($newFileContent) !== strlen($newFileContent)) {
throw new \RuntimeException("Could not update the file '$filePath'");
}
return true;
}
Here is a brief description of what is being done: Get the whole file content, split the content into its lines (i.e. as an array), find the match(es) and remove them, join all lines together, and save the result back to the file (only if any changes happened).
Let's now use it:
// $dir is your filename, as you mentioned
removeLineFromFile($line, $dir);
Notes:
You can use fopen() family functions instead of SplFileObject, but I do recommend the object form, as it's exception-based, more robust and more efficient (in this case at least).
It's safe to unset() an element of an array being iterated using foreach (There's a comment here showing it can lead unexpected results, but it's totally wrong: As you can see in the example code, $value is copied (i.e. it's not a reference), and removing an array element does not affect it).
$line should not have new line characters like \n, otherwise, you may perform lots of redundant searches.
Don't use
$fileLineByLine[$lineNumber] = "";
// Or even
$fileLineByLine[$lineNumber] = null;
instead of
unset($fileLineByLine[$key]);
The reason is, the first case doesn't remove the line, it just clears the line (and an unwanted empty line will remain).
Hope it helps.
Like this:
file_put_contents($filename, str_replace($line . "\r\n", "", file_get_contents($filename)));
I have a txt file that has a change-log.I'm trying to display the new changes only for the current version.
I wrote a function to read the file and check every line if it has the wanted words, if it finds those words it starts to get the content and push it to an array.
I searched to see if there is an example but everyone was talking about how to stop at a specified line, not to start from one.
Here is the code I use:
public function load($theFile, $beginPosition, $doubleCheck) {
// Open file (read-only)
$file = fopen($_SERVER['DOCUMENT_ROOT'] . '/home/' . $theFile, 'r');
// Exit the function if the the file can't be opened
if (!$file) {
return;
}
$changes = Array();
// While not at the End Of File
while (!feof($file)) {
// Read current line only
$line = fgets($file);
// This will check if the current line has the word we look for to start loading
$findBeginning = strpos($line, $beginPosition);
// Double check for the beginning
$beginningCheck = strpos($line, $doubleCheck);
// Once you find the beginning
if ($findBeginning !== false && $beginningCheck !== false) {
// Start storing the data to an array
while (!feof($file)) {
$line = fgets($file);
// Remove space and the first 2 charecters ('-' + one space)
$line = trim(substr($line, 2));
if (!empty($line)) { // Don't add empty lines
array_push($changes, $line);
}
}
}
}
// Close the file to save resourses
fclose($file);
return $changes;
}
It's working currently, but as you can see it's nested loops and that's not good and in case the txt file grows it will take more time!
I'm trying to improve the performance, so does is there any better way to do this ?
much simpler than you think
$found = false;
$changes = array();
foreach(file($fileName) as $line)
if($found)
$changes[] = $line;
else
$found = strpos($line, $whatever) !== false;
That nested loop will not degrade performance, cause it's not really a nested loop in the sense that it is a combinatorially-growing loop over multiple variables. It isn't necessary to write it like that though. Here's another way that avoids it. Try this (pseudocode here):
// skim through the beginning of the file, break upon finding the start
// of the portion I care about.
while (!feof($file)) {
if $line matches beginning marker, break;
}
// now read and process until the endmarker (or eof...)
while (!feof($file)) {
if $line matches endmarker, break;
filter/process/store line here.
}
Also, doublechecking is absolutely not necessary. Why is that there?