I have a file that I'm opening for writing, like this:
if (!$idHandle = fopen($fileName, 'w')) {
echo "Cannot open file ($fileName)";
die();
}
I then enter a loop where I'm incrementing a variable, $beerId, during each iteration. I want to save that variable to the file I have open, so I use the following code:
if (fwrite($idHandle, $beerId) === FALSE) {
echo "Cannot write to file ($fileName)";
die();
}
$beerId++;
However, this ends up creating a massive string of every beerId I encounter. What I want is to have the file populated ONLY with the last id I left off on.
I realize I could put the write outside of the loop, but the script is volatile and likely to terminate prematurely with an error, so I want to have a reference to the last $beerId variable even in the event of an error that terminates the script before the loop is properly terminated.
You must go back to the beginning of the file because fwrite keeps track of where it is in the file. Use fseek. Opening and closing the file several times in a loop is expensive and I don't see a reason to do that in this case. You should of course close the file when you're done with it.
You should add this just before you write to the file:
fseek($idHandle, 0);
That will move you to the beginning of the file, since your incrementing values you won't have to worry about removing the previous value.
EDIT
In my answer above i assume that the id's encountered are incremented values, but you don't say that so, if for example you encounter id=10, and then encounter id=1
the above would still result in 10 in the file, to handle that just add some padding to the string that you're writing using str_pad:
str_pad($value_to_write, 10); //or whatever value is reasonable.
If you can, try memcache->increment().
http://php.net/manual/en/memcache.increment.php
Use $memcache->add('beer_id', 0); to initialize it to zero. Then fetch $beer_id like $memcache->get('beer_id') for an initial sanity check, and then $memcache->increment('beer_id'); for the next $beer_id.
Else, stick to file_get_contents() and file_put_contents():
http://php.net/manual/en/function.file-get-contents.php
http://php.net/manual/en/function.file-put-contents.php
Related
What is the best way to overwrite a specific line in a file? I basically want to search a file for the string '#parsethis' and overwrite the rest of that line with something else.
If the file is really big (log files or something like this) and you are willing to sacrifice speed for memory consumption you could open two files and essentially do the trick Jeremy Ruten proposed by using files instead of system memory.
$source='in.txt';
$target='out.txt';
// copy operation
$sh=fopen($source, 'r');
$th=fopen($target, 'w');
while (!feof($sh)) {
$line=fgets($sh);
if (strpos($line, '#parsethis')!==false) {
$line='new line to be inserted' . PHP_EOL;
}
fwrite($th, $line);
}
fclose($sh);
fclose($th);
// delete old source file
unlink($source);
// rename target file to source file
rename($target, $source);
If the file isn't too big, the best way would probably be to read the file into an array of lines with file(), search through the array of lines for your string and edit that line, then implode() the array back together and fwrite() it back to the file.
Your main problem is the fact that the new line may not be the same length as the old line. If you need to change the length of the line, there is no way out of rewriting at least all of the file after the changed line. The easiest way is to create a new, modified file and then move it over the original. This way there is a complete file available at all times for readers. Use locking to make sure that only one script is modifying the file at once, and since you are going to replace the file, do the locking on a different file. Check out flock().
If you are certain that the new line will be the same length as the old line, you can open the file in read/write mode (use r+ as the second argument to fopen()) and call ftell() to save the position the line starts at each time before you call fgets() to read a line. Once you find the line that you want to overwrite, you can use fseek() to go back to the beginning of the line and fwrite() the new data. One way to force the line to always be the same length is to space pad it out to the maximum possible length.
This is a solution that works for rewriting only one line of a file in place with sed from PHP. My file contains only style vars and is formatted:
$styleVarName: styleVarProperty;\n
For this I first add the ":" to the ends of myStyleVarName, and sed replaces the rest of that line with the new property and adds a semicolon.
Make sure characters are properly escaped in myStyleVarProp.
$command = "pathToShellScript folder1Name folder2Name myStyleVarName myStyleVarProp";
shell_exec($command);
/* shellScript */
#!/bin/bash
file=/var/www/vhosts/mydomain.com/$1/$2/scss/_variables.scss
str=$3"$4"
sed -i "s/^$3.*/$str;/" $file
or if your file isn't too big:
$sample = file_get_contents('sample');
$parsed =preg_replace('##parsethis.*#', 'REPLACE TO END OF LINE', $sample);
You'll have to choose delimiters '#' that aren't present in the file though.
If you want to completely replace the contents of one file with the contents of another file you can use this:
rename("./some_path/data.txt", "./some_path/data_backup.txt");
rename("./some_path/new_data.txt", "./some_path/data.txt");
So in the first line you backup the file and in the second line you replace the file with the contents of a new file.
As far as I can tell the rename returns a boolean. True if the rename is successful and false if it fails. One could, therefore, only run the second step if the first step is successful to prevent overwriting the file unless a backup has been made successfully. Check out:
https://www.php.net/manual/en/function.rename.php
Hope that is useful to someone.
Cheers
Adrian
I'd most likely do what Jeremy suggested, but just for an alternate way to do it here is another solution. This has not been tested or used and is for *nix systems.
$cmd = "grep '#parsethis' " . $filename;
$output = system($cmd, $result);
$lines = explode("\n", $result);
// Read the entire file as a string
// Do a str_repalce for each item in $lines with ""
This seems simple but I can't figure it out.
file_get_contents('count.txt');
$variable_from_file++;
file_put_contents('count.txt', $variable_from_file);
There is only one line of data in count.txt and it is the hits counter. Is there a way to do this?
If you want to be sure no increments go uncounted (which is what CodeCaster is referring to, the script may load count.txt, increment it, while another file is doing the same, then save that, and then only one increment would have been done and not the proper two), you should use fopen.
$fp = fopen('count.txt', 'c+');
flock($fp, LOCK_EX);
$count = (int)fread($fp, filesize('count.txt'));
ftruncate($fp, 0);
fseek($fp, 0);
fwrite($fp, $count + 1);
flock($fp, LOCK_UN);
fclose($fp);
This will lock the file, preventing any others from reading or writing to it while the count is incremented (meaning others would have to wait before they can increment the value).
There is a slightly funnier way:
file_put_contents("count.txt",#file_get_contents("count.txt")+1);
file_get_contents reads the contents of the counter file.
# tells PHP to ignore the error of a missing file. The returned false will then be interpreted as the count of 0.
+1 will cause the string to be converted to a number.
file_put_contents then stores the new value in the counter file as a string.
On a very busy system you might want to obtain a file lock first to prevent simultaneous writes.
The OS file cache usually makes this method extremely fast.
$variable_from_file = (int)file_get_contents('count.txt');
But notice that this is not thread-safe.
Exactly like you did it should work fine. Just capture the data from file_get_contents(), and check if both of those functions were successful.
$var = file_get_contents('count.txt');
if ($var === false) {
die('Some error message.');
}
$var++;
if (file_put_contents('count.txt', $var) === false) {
die('Some error message.');
}
This works for me though
$count = intval(file_get_contents('count.txt'));
file_put_contents('count.txt', ++$count);
echo file_get_contents('count.txt');
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.
I want to read everything from a textfile and echo it. But there might be more lines written to the text-file while I'm reading so I don't want the script to exit when it has reached the end of the file, instead I wan't it to wait forever for more lines. Is this possible in php?
this is just a guess, but try to pass through (passthru) a "tail -f" output.
but you will need to find a way to flush() your buffer.
IMHO a much nicer solution would be to build a ajax site.
read the contents of the file in to an array. store the number of lines in the session. print the content of the file.
start an ajax request every x seconds to a script which checks the file, if the line count is greater then the session count append the result to the page.
you could use popen() inststed:
$f = popen("tail -f /where/ever/your/file/is 2>&1", 'r');
while(!feof($f)) {
$buffer = fgets($f);
echo "$buffer\n";
flush();
sleep(1);
}
pclose($f)
the sleep is important, without it you will have 100% CPU time.
In fact, when you "echo" it, it goes to the buffer. So what you want is "appending" the new content if it's added while the browser is still receiving output. And this is not possible (but there are some approaches to this).
I solved it.
The trick was to use fopen and when eof is reached move the cursor to the previous position and continue reading from there.
<?php
$handle = fopen('text.txt', 'r');
$lastpos = 0;
while(true){
if (!feof($handle)){
echo fread($handle,8192);
flush();
$lastpos = ftell($handle);
}else{
fseek($handle,$lastpos);
}
}
?>
Still consumes pretty much cpu though, don't know how to solve that.
You may also use filemtime: you get latest modification timestamp, send the output and at the end compare again the stored filemtime with the current one.
Anyway, if you want the script go at the same time that the browser (or client), you should send the output using chunks (fread, flush), then check any changes at the end. If there are any changes, re-open the file and read from the latest position (you can get the position outside of the loop of while(!feof())).
What is the best way to overwrite a specific line in a file? I basically want to search a file for the string '#parsethis' and overwrite the rest of that line with something else.
If the file is really big (log files or something like this) and you are willing to sacrifice speed for memory consumption you could open two files and essentially do the trick Jeremy Ruten proposed by using files instead of system memory.
$source='in.txt';
$target='out.txt';
// copy operation
$sh=fopen($source, 'r');
$th=fopen($target, 'w');
while (!feof($sh)) {
$line=fgets($sh);
if (strpos($line, '#parsethis')!==false) {
$line='new line to be inserted' . PHP_EOL;
}
fwrite($th, $line);
}
fclose($sh);
fclose($th);
// delete old source file
unlink($source);
// rename target file to source file
rename($target, $source);
If the file isn't too big, the best way would probably be to read the file into an array of lines with file(), search through the array of lines for your string and edit that line, then implode() the array back together and fwrite() it back to the file.
Your main problem is the fact that the new line may not be the same length as the old line. If you need to change the length of the line, there is no way out of rewriting at least all of the file after the changed line. The easiest way is to create a new, modified file and then move it over the original. This way there is a complete file available at all times for readers. Use locking to make sure that only one script is modifying the file at once, and since you are going to replace the file, do the locking on a different file. Check out flock().
If you are certain that the new line will be the same length as the old line, you can open the file in read/write mode (use r+ as the second argument to fopen()) and call ftell() to save the position the line starts at each time before you call fgets() to read a line. Once you find the line that you want to overwrite, you can use fseek() to go back to the beginning of the line and fwrite() the new data. One way to force the line to always be the same length is to space pad it out to the maximum possible length.
This is a solution that works for rewriting only one line of a file in place with sed from PHP. My file contains only style vars and is formatted:
$styleVarName: styleVarProperty;\n
For this I first add the ":" to the ends of myStyleVarName, and sed replaces the rest of that line with the new property and adds a semicolon.
Make sure characters are properly escaped in myStyleVarProp.
$command = "pathToShellScript folder1Name folder2Name myStyleVarName myStyleVarProp";
shell_exec($command);
/* shellScript */
#!/bin/bash
file=/var/www/vhosts/mydomain.com/$1/$2/scss/_variables.scss
str=$3"$4"
sed -i "s/^$3.*/$str;/" $file
or if your file isn't too big:
$sample = file_get_contents('sample');
$parsed =preg_replace('##parsethis.*#', 'REPLACE TO END OF LINE', $sample);
You'll have to choose delimiters '#' that aren't present in the file though.
If you want to completely replace the contents of one file with the contents of another file you can use this:
rename("./some_path/data.txt", "./some_path/data_backup.txt");
rename("./some_path/new_data.txt", "./some_path/data.txt");
So in the first line you backup the file and in the second line you replace the file with the contents of a new file.
As far as I can tell the rename returns a boolean. True if the rename is successful and false if it fails. One could, therefore, only run the second step if the first step is successful to prevent overwriting the file unless a backup has been made successfully. Check out:
https://www.php.net/manual/en/function.rename.php
Hope that is useful to someone.
Cheers
Adrian
I'd most likely do what Jeremy suggested, but just for an alternate way to do it here is another solution. This has not been tested or used and is for *nix systems.
$cmd = "grep '#parsethis' " . $filename;
$output = system($cmd, $result);
$lines = explode("\n", $result);
// Read the entire file as a string
// Do a str_repalce for each item in $lines with ""