I have a csv file that is 2Mb size, and has pipe delimiter. I would like to take the first row and replace its data then resave the file. Here is what I did :
//Creating a new first row with the modified data.
$file = fopen($path,"r");//$path is where the file is located : outputs/my_file.csv
$size = filesize($path);
$firstLine = fgetcsv(fopen($path,"r")); //$firstLine has all the data of the first row as array
fclose($file);
$firstLine = explode("|", $firstLine[0]);//To get each column row
$newHeader = array();
for($i = 0; $i<sizeof($firstLine ); $i++){
if($i == 4){
array_push($newHeader, "modified column in row 1 ");//Only column 4 in row 1 is modified
}else{
array_push($newHeader, $firstLine [$i]);
}
}
$Header = implode("|", $newHeader);
//Creating the new csv file
$row = 0;
while (($data = fgetcsv(fopen($path,"r"), "|")) !== false) {
if($row == 0){
$data[0] = $Header;
}
$newCsvData[] = $data;
}
return $newCsvData; //I wanted to display the new content of the csv before saving it
This code should print the new content of the csv file that I will store but I get an error : Allowed memory size of 536870912 bytes exhausted (tried to allocate 332 bytes) How can I do that in a very fast way ? the file is about 19122 row.
Thanks
If it's only 2mb, maybe read the entire file into memory and then write out a new file (overwriting the previous file). Here are some helper functions to help you read and write the file, and I'm certain you're proficient in editing the resulting array:
/**
* Reads a file into an array
*
* #param $FILE string the file to open
*
* #return $lines The Lines of the file as an array
*/
public static function readFile($FILE) {
$lines = array(); // the array to store each line of the file in
$handle = fopen($FILE, "r");
if ($handle) {
// $FILE successfully opened for reading,
while (($line = fgets($handle)) !== false) {
$lines[] = $line; //add each line of the file to $lines
}
} else {
throw new Exception("error opening the file...");
}
fclose($handle); // close the file
return $lines; // return the lines of the file as an array
}
/**
* Writes the $lines of a file into $FILE
*
* #param $FILE string The file to write
* #param $lines array An array containing the lines of the file
*
* #return $result int|NULL The number of bytes written, or null on failure. See: php.net/fwrite#refsect1-function.fwrite-returnvalues
*/
public static writeFile($FILE, $lines) {
// Add newline at the end of each line of the array
// output is now a single string which we will write in one pass
// (instead of line-by-line)
$output = implode("\n", $lines);
$handle = fopen($FILE, "w+");
if ($handle) {
// $FILE successfully opened for writing, write to the file
$result = fwrite($handle, $output);
} else {
throw new Exception("error opening the file...");
}
fclose($handle); // close the file
return $result; // The number of bytes written to the file, or NULL on failure
}
<?php
$source = fopen('filename','r');
$destination = fopen('newfilename','w');
//write new header to new file
fwrite($destination,"your|replacement|header\n");
//set the pointer in the old file to the second row
fgets($source);
//copy the entire stream
stream_copy_to_stream($source,$destination);
//rename the newfilename to the old filename
rename('newfilename','filename');
//check what memory we used:
var_dump(memory_get_peak_usage());
That resulted in 142260 bytes used of memory at its peak for a 2MB file. BTW: the memory usage of a 2GB file is exactly if I test it here.
Related
I'm trying to delete one line from CSV file by its line number, which I get as a parameter in URL.
I saw some discussions here, but it was mainly "delete a line by its id stored in first column" and so on. I tried to make it in the same way as others in these discussions, but it does not work. I only changed the condition.
if (isset($_GET['remove']))
{
$RowNo = $_GET['remove']; //getting row number
$row = 1;
if (($handle = fopen($FileName, "w+")) !== FALSE)
{
while (($data = fgetcsv($handle, 1000, ";")) !== FALSE)
{
//Here, I don't understand, why this condition does not work.
if ($row != $RowNo)
{
fputcsv($handle, $data, ';');
}
$row++;
}
fclose($handle);
}
}
I supposed, that it should work for me too, BCS just condition was changed. But it does not. It clears the whole file. Could you help me with it, please?
Thank you very much for any advice. Daniel.
You could load the file as an array of lines by using file().
Then remove the line and write the file back.
// read the file into an array
$fileAsArray = file($FileName);
// the line to delete is the line number minus 1, because arrays begin at zero
$lineToDelete = $_GET['remove'] - 1;
// check if the line to delete is greater than the length of the file
if ($lineToDelete > sizeof($fileAsArray)) {
throw new Exception("Given line number was not found in file.");
}
//remove the line
unset($fileAsArray[$lineToDelete]);
// open the file for reading
if (!is_writable($fileName) || !$fp = fopen($fileName, 'w+')) {
// print an error
throw new Exception("Cannot open file ($fileName)");
}
// if $fp is valid
if ($fp) {
// write the array to the file
foreach ($fileAsArray as $line) {
fwrite($fp, $line);
}
// close the file
fclose($fp);
}
If you have a unix system you could also use sed command:
exec("sed -e '{$lineToDelete}d' {$FileName}");
Remember cleaning command parameters if user input used:
https://www.php.net/manual/de/function.escapeshellcmd.php
Option if your CSV can fit to memory:
// Read CSV to memory array
$lines = file($fileName, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
// Remove element from array
unset($lines[$rowNo - 1]); // Validate that element exists!
// Rewrite your CSV file
$handle = fopen($fileName, "w+");
for ($i = 0; $i < count($lines); $i++) {
fputcsv($handle, $data, ';');
}
fclose($handle);
Option if your CSV can not fit to memory:
Use code from question, just write to separate file and later replace it with actual file:
$handle = fopen($FileName, "r");
// Read file wile not End-Of-File
while (!feof($fn)) {
if ($row != $RowNo) {
file_put_contents($FileName . '.tmp', fgets($fn), FILE_APPEND);
}
$row++;
}
fclose($handle);
// Remove old file and rename .tmp to previously removed file
unlink($FileName);
rename($FileName . '.tmp', $FileName);
I have a task to implement resumable in Yii, and I implemented upload control, but never Resumable before.
public function actionUpload()
{
$model=new User;
if(isset($_POST['User'])) {
$model->attributes=$_POST['User'];
$model->image=CUploadedFile::getInstance($model,'image');
if($model->save()) {
$model->image->saveAs('upload/'.$model->image->name);
$this->redirect(array('view','id'=>$model->uUserID));
}
}
$this->render('upload',array('model'=>$model));
}
The task is to chunk file in small pieces.
Example: one file can be 1 GB. And I try to send that file with rest service.
See Sample server implementation in PHP
I copy-paste here essential part of the code, provided on that page:
/**
*
* Check if all the parts exist, and
* gather all the parts of the file together
* #param string $dir - the temporary directory holding all the parts of the file
* #param string $fileName - the original file name
* #param string $chunkSize - each chunk size (in bytes)
* #param string $totalSize - original file size (in bytes)
*/
function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) {
// count all the parts of this file
$total_files = 0;
foreach(scandir($temp_dir) as $file) {
if (stripos($file, $fileName) !== false) {
$total_files++;
}
}
// check that all the parts are present
// the size of the last part is between chunkSize and 2*$chunkSize
if ($total_files * $chunkSize >= ($totalSize - $chunkSize + 1)) {
// create the final destination file
if (($fp = fopen('temp/'.$fileName, 'w')) !== false) {
for ($i=1; $i<=$total_files; $i++) {
fwrite($fp, file_get_contents($temp_dir.'/'.$fileName.'.part'.$i));
_log('writing chunk '.$i);
}
fclose($fp);
} else {
_log('cannot create the destination file');
return false;
}
// rename the temporary directory (to avoid access from other
// concurrent chunks uploads) and than delete it
if (rename($temp_dir, $temp_dir.'_UNUSED')) {
rrmdir($temp_dir.'_UNUSED');
} else {
rrmdir($temp_dir);
}
}
}
I want to read a file line by line and add it into a variable till its string length is 1000 bytes . The file is relatively large,
Hence, what I am doing is
if(file_exists($file)
{
$fh = fopen($file, "r");
while(!feof($fh) or strlen($chunk) < 10001)
{
$line = fgets($fh, 1000);
$chunk = $chunk."**".$line;
}
}
Issue is how does I store each chunk into an array index till I encounter end of file ?
What about this:
if(file_exists($file)
{
$fh = fopen($file, "r");
$chunks = array();
while(!feof($fh) or strlen($chunk) < 10001)
{
$line = fgets($fh, 1000);
// add line to the buffer
$chunks []= $line;
}
}
? Or am I missing something?
I have got a large file in PHP of which I would like to replace the first 512 bytes with some other 512 bytes. Is there any PHP function that helps me with that?
If you want to optionally create a file and read and write to it (without truncating it), you need to open the file with the fopen() function in 'c+' mode:
$handle = fopen($filename, 'c+');
PHP then has the stream_get_contents() function which allows to read a chunk of bytes with a specific length (and from a specific offset in the file) into a string variable:
$buffer = stream_get_contents($handle, $length = 512, $offset = 0);
However, there is no stream_put_contents() function to write the string buffer back to the stream at a specific position/offset. A related function is file_put_contents() but it does not allow to write to a file-handle resource at a specific offset. But there is fseek() and fwrite() to do that:
$bytes_written = false;
if (0 === fseek($handle, $offset)) {
$bytes_written = fwrite($handle, $buffer, $length);
}
Here is the full picture:
$handle = fopen($filename, 'c+');
$buffer = stream_get_contents($handle, $length = 512, $offset = 0);
// ... change $buffer ...
$bytes_written = false;
if (0 === fseek($handle, $offset)) {
$bytes_written = fwrite($handle, $buffer, $length);
}
fclose($handle);
If the length of $buffer is not fixed this will not properly work. In that case it's better to work with two files and to use stream_copy_to_stream() as outlined in How to update csv column names with database table header or if the file is not large it is also possible to do that in memory:
$buffer = file_get_contents($filename);
// ... change $buffer ...
file_put_contents($filename, $buffer);
In PHP if you write to a file it will write end of that existing file.
How do we prepend a file to write in the beginning of that file?
I have tried rewind($handle) function but seems overwriting if current content is larger than existing.
Any Ideas?
$prepend = 'prepend me please';
$file = '/path/to/file';
$fileContents = file_get_contents($file);
file_put_contents($file, $prepend . $fileContents);
The file_get_contents solution is inefficient for large files. This solution may take longer, depending on the amount of data that needs to be prepended (more is actually better), but it won't eat up memory.
<?php
$cache_new = "Prepend this"; // this gets prepended
$file = "file.dat"; // the file to which $cache_new gets prepended
$handle = fopen($file, "r+");
$len = strlen($cache_new);
$final_len = filesize($file) + $len;
$cache_old = fread($handle, $len);
rewind($handle);
$i = 1;
while (ftell($handle) < $final_len) {
fwrite($handle, $cache_new);
$cache_new = $cache_old;
$cache_old = fread($handle, $len);
fseek($handle, $i * $len);
$i++;
}
?>
$filename = "log.txt";
$file_to_read = #fopen($filename, "r");
$old_text = #fread($file_to_read, 1024); // max 1024
#fclose(file_to_read);
$file_to_write = fopen($filename, "w");
fwrite($file_to_write, "new text".$old_text);
Another (rough) suggestion:
$tempFile = tempnam('/tmp/dir');
$fhandle = fopen($tempFile, 'w');
fwrite($fhandle, 'string to prepend');
$oldFhandle = fopen('/path/to/file', 'r');
while (($buffer = fread($oldFhandle, 10000)) !== false) {
fwrite($fhandle, $buffer);
}
fclose($fhandle);
fclose($oldFhandle);
rename($tempFile, '/path/to/file');
This has the drawback of using a temporary file, but is otherwise pretty efficient.
When using fopen() you can set the mode to set the pointer (ie. the begginng or end.
$afile = fopen("file.txt", "r+");
'r' Open for reading only; place
the file pointer at the beginning of
the file.
'r+' Open for reading and
writing; place the file pointer at the
beginning of the file.
$file = fopen('filepath.txt', 'r+') or die('Error');
$txt = "/n".$string;
fwrite($file, $txt);
fclose($file);
This will add a blank line in the text file, so next time you write to it you replace the blank line. with a blank line and your string.
This is the only and best trick.