PHP replace line at the top of a text file with fgets - php

I'd like to use php to open a file and replace just one line that will be near the top. Since it's near the top I don't want to read the whole file into memory. Here is what I've tested but I think I am not understanding how to backspace over the line once I identify it using strpos():
<?php
$file = "test-file.php";
$new_value = "colors:orange green violet;";
$fhandle = fopen($file, "r+") or die("Unable to open file!");
$replaced = "false";
while ($replaced === "false") {
$line = fgets($fhandle);
if (strpos($line, "colors:")) { //find the colors line
//Should I be moving the file pointer back here?
//Once I find the line, how do I clear it?
$line = $new_value;
fputs($fhandle, $line);
$replaced = "true";
}
}
fclose($fhandle);
?>
Contents of test-file.php:
fruit:apples bananas;
colors:red blue green;
cars:ford chevy;
Note: Each line in test-file.php ends with a semicolon.

You will need to read the whole file in, in order to overwrite that line, because the file you describe is a set of non-fixed lines (or put another way is a stream of characters). You won't be able to replace a part of it in-place, of a different size, without affecting characters in other lines.
You don't have to read the whole thing into memory all at once. The fgets() approach lets you read just a line at a time. The least memory intensive way to do this is to write all values to a new file, then delete (unlink()) the old file, and rename() the new file to the old filename.

Related

How can i save all the variable results from a echo into a txt file using php?

I wrote a php script that generates random tokens, and I want to output these tokens into a .txt file.
Below is the code:
do {
$token = bin2hex(random_bytes(2));
echo("token: $token");
$myfile = fopen("output.txt", "w+") or die("Unable to open file!");
fwrite($myfile, $token);
fclose($myfile);
} while ($token != "e3b0");
It echos multiple tokens, until the echo = e3b0, but when I try to write the result on a txt file, it only writes "e3b0", is that a way to write all the results of the "echo" into a txt file?
As I see it the most efficient way to do this would be to do everything just enough times.
Meaning we have to loop and generate the codes, but we only need to write to the file once,same thing with the echo.
$code = "start value";
while ($code != "e3b0"){
$arr[] = $code = bin2hex(random_bytes(2));
}
echo $str = implode("\n", $arr);
file_put_contents("output.txt", $str);
This is do everything just enough times, and a more optimized code.
But if you run this in a browser then it will not output them on separate lines on screen, only in the txt file. But if you open the source it will be on separate lines.
That is because I did not use the br tag in the implode.
EDIT: Efficiency was never asked in original OP question. This post is being edited to include efficiency, namely no need to reopen and close a file.
Your use of w+ will always place the file pointer at the beginning of the file and truncate the file in the process. So as a result, you always end up with the last value written.
From php.net on fopen w+:
Open for reading and writing; place the file pointer at the beginning of the file
and truncate the file to zero length. If the file does not exist, attempt to create it.
Using your existing code, a solution then would be as follows:
$myfile = fopen("output.txt", "a+") or die("Unable to open file!");
do {
$token = bin2hex(random_bytes(2));
echo("token: $token");
fwrite($myfile, $token);
} while ($token != "e3b0");
fclose($myfile);
Where a+ in the same docs says:
Open for reading and writing; place the file pointer at the end of the file.
If the file does not exist, attempt to create it. In this mode, fseek()
only affects the reading position, writes are always appended.
Source:
https://www.php.net/manual/en/function.fopen.php
Amendments:
As #andreas mentions, opening and closing the file repeatedly inside the loop is not necessary (nor efficient). Since you are appending, you can open it once with a+ before the loop begins; and close it after the loop ends.
In terms of having a separator char between tokens written to the file, a carriage return (line break) is a good choice. In this way you can reduce the amount of parsing you would have to program when programmatically reading the file. For this, your writes could be written as follows:
fwrite($myfile, $token . "\n");

Get last line of a file ignoring empty lines or whitespace

I have a file I'm parsing with PHP. It seems the contents of the file apparently end in a blank new line, sometimes with spaces. How can I trim this out and get the true last line ?
My file can sometimes look like
Line 1
Line 2
File content are here
\n
or
Line 1
Line 2
File content are here
\r
or sometimes the correct way
Line 1
Line 2
File content are here
I need my script to parse the end of the file and return the true last line which is "File content here" (or anything really, just not a blank line).
I was using the line below to attempt to get the last line. The problem is, I don't always know what X is. And even if I do think I know, line breaks and empty lines at the end of the file are throwing me off.
fseek($file_handle, -X, SEEK_END);
You may use the following approach which doesn't load the entire file in memory (you can process extremely large files with it) and allows to have empty lines anywhere in the file, instead of only at the end.
while (($line = fgets($file_handle)) !== false) {
if (($line != "\n") && ($line != "\r")) {
// line is correct, process it
}
}
This code iterates over each line, then checks if the line isn't equal to \n nor \r and if that isn't the case it processes the line, otherwise it's silently discarded and the code skips to the next line.
You can also use the new SplFileObject interface introduced in PHP 5.1.0, the code is very similar and works the same way :
$file = new SplFileObject("file.txt");
while (!$file->eof()) {
$line = $file->fgets();
if (($line != "\n") && ($line != "\r")) {
// line is correct, process it
}
}
Also I overlooked the fact that you needed only the last line in the first revision of my answer; an easy solution would be to define a variable inside the loop if the line is correct (and not an empty/junk one), if there are lines after that the variable would just get overwritten, but it'll stay set on the last correct line and you can then do whatever you want with it.
while (($line = fgets($file_handle)) !== false) {
if (($line != "\n") && ($line != "\r")) {
$last = $line
}
}
if (isset($last)) {
echo "you've got your last line"
}
This may not be the best solution performance-wise but it's definitely the easiest, for better methods on getting the last line I refer you to this question that has quite long and complicated solutions that would need modifications if you want them to ignore empty lines.

What is the most efficient PHP way to read first and last line of a file?

I'm trying to open a file and determine if it is valid. It's valid if the first line is START and the last line is END.
I've seen different ways of getting the last line of a file, but it does not pay particular attention to the first line either.
How should I go about this? I was thinking of loading the file contents in an array and checking $array[0] and $array[x] for START and END. But this seems to be a waste for all the junk that could possibly be in the middle.
If its a valid file, I will be reading/processing the contents of the file between START and END.
Don't read entire file into an array if it is not needed. If file can be big you can do it that way:
$h = fopen('text.txt', 'r');
$firstLine = fgets($h);
fseek($h, -3, SEEK_END);
$lastThreeChars = fgets($h);
Memory footprint is much lower
That's from me:
$lines = file($pathToFile);
if ($lines[0] == 'START' && end($lines) == 'END') {
// do stuff
}
Reading whole file with fgets will be efficient for small siles. iF ur file is big then:
open It and read first line
use tail (i didn't check it but it looks OK) function I found in php.net in fseek documentation

Insert line on the fly in a file with PHP

I want to make a .php file downloadable by my users.
Every file is different from an user to another:
at the line #20 I define a variable equal to the user ID.
To do so I tried this: Copy the original file. Read it until line 19 (fgets) then fputs a PHP line, and then offer the file to download.
Problem is, the line is not inserted after line 19 but at the end of the .php file. Here is the code:
if (is_writable($filename)) {
if (!$handle = fopen($filename, 'a+')) {
echo "Cannot open file ($filename)";
exit;
}
for ($i = 1; $i <= 19; $i++) {
$offset = fgets($handle);
}
if (fwrite($handle, $somecontent) === FALSE) {
exit;
}
fclose($handle);
}
What would you do ?
append mode +a in fopen() places the handle's pointer at the end of the file. Your fgets() loop will fail as there's nothing left to read at the end of the file. You're basically doing 19 no-ops. Your fwrite will then output your new value at the end of the file, as expected.
To do your insert, you'd need to rewind() the handle to the beginning, then do your fgets() loop.
However, if you're just wanting people to get this modified file, why bother doing the "open file, scan through, write change, serve up file"? This'd leave a multitude of near-duplicates on your system. A better method would be to split your file into two parts, and then you could do a simple:
readfile('first_part.txt');
echo "The value you want to insert";
readfile('last_part.txt');
which saves you having to save the 'new' file each time. This would also allow arbitrary length inserts. Your fwrite method could potentially trash later parts of the file. e.g. You scan to offset "10" and write out 4 bytes, which replaces the original 4 bytes at that location in the original file. At some point, maybe it turns into 5 bytes of output, and now you've trashed a byte in the original and maybe have a corrupted file.
The a+ mode means:
'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does not exist, attempt to create it.
You probably want r+
'r+' Open for reading and writing; place the file pointer at the beginning of the file.
Put your desired code in one string variable. Where you will have %s at point where you want to customize your code. After that just respond with php MIME type.
eg;
$phpCode = "if (foo == blah) { lala lala + 4; %s = 5; }", $user_specific_variable;
header('Content-type: text/php');
echo $phpCode;
Voila.
NB: Maybe mime type is not correct, I am talking out of my ass here.
I think instead of opening the file in "a+" mode, you should open the file in "r+" mode, because "a" always appends to the file. But I think the write will anyways overwrite your current data. So, the idea is that you'll need to buffer the file, from the point where you intend to write to the EOF. Then add your line followed by what you had buffered.
Another approach might be to keep some pattern in your PHP file, like ######. You can then:
1. copy the original PHP script
2. read the complete PHP script into a single variable, say $fileContent, using file_get_contents()
3. use str_replace() function to replace ###### in $fileContent with desired User ID
4. open the copied PHP script in "a" mode and rewrite $fileContent to it.

How do I loop through two files and combine the same?

I do have two text files and want to loop through both files then combine both line (line 1 of first test file and line1 of second text file. like that for thousands of lines) and do some function
I am familiar with loop through one file and for that code is given below:
$lines = file('data.txt');
foreach ($lines as $line) {
//some function
}
but how will I do for two files and combine bothe lines?
Not sure what you mean by search through the table, but to open both files and do stuff with them:
$file1 = fopen("/path/to/file1.txt","r"); //Open file with read only access
$file2 = fopen("/path/to/file2.txt","r");
$combined = fopen("/path/to/combined.txt","w"); //in case you want to write the combined lines to a new file
while(!feof($file1) && !feof($file2))
{
$line1 = trim(fgets($file1)); //Grab a line of the first file, note the trim will clip off the carriage return/new line at the end of the line, can remove it if you don't need it.
$line2 = trim(fgets($file2)); //Grab a line of the second file
$combline = $line1 . $line2;
fwrite($combined,$combline . "\r\n"); //Write to new combined file, and add a new carriage return/newline at the end of the combined line to replace the one trimmed off.
//You can do whatever with data from $line1, $line2, or the combined $combline after getting them.
}
Note: You might run into trouble if you hit the end of file on one file before the other, which would only happen if they aren't the same length, might need some if control statements to set $line1 or $line2 to "" or something else if feof() their respective files, once both hit the end of file, the while loop will end.
You can do this programmatically as Crayon and Tim have shown. If both files have the same number of lines, it should work. If the line number is different you will have to loop over the larger file to make sure you get all lines or check EOF on both.
To combine line by line, I often use the unix command paste which is very fast. This also accounts for files with different lengths. Run this on the command line:
paste file1 file2 > output.txt
See the manpage for paste for command line options, field delimiters.
man paste
Example:
$file1 = fopen("file1.txt", "rb");
$file2 = fopen("file2.txt", "rb");
while (!feof($file1)) {
$combined = fread($file1, 8192) . " " . fread($file2, 8192);
// now insert $combined into db
}
fclose($file1);
fclose($file2);
you will want to use the longer of the two files in the while condition.
you may need to adjust the bytes read in fread depending on how long your lines are
change " " to whatever delimiter you want

Categories