I create a CSV file for download by our client using
$output = fopen('php://output', 'w');
and using fputcsv() to write data to a CSV file which is downloaded by the client.
I am running PHP on Linux and consequently the line endings are not interpreted by many Windows applications.
I could write the CSV file to a directory on the server, read it back in and perform a str_replace() from \n to \r\n, but this seems a rather clunky way of solving the problem. Is there a way to perform the conversion without creating a physical file?
You could use stream filters to accomplish this. This example writes to a physical file, but it should work fine for php://output as well.
// filter class that applies CRLF line endings
class crlf_filter extends php_user_filter
{
function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
// make sure the line endings aren't already CRLF
$bucket->data = preg_replace("/(?<!\r)\n/", "\r\n", $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
// register the filter
stream_filter_register('crlf', 'crlf_filter');
$f = fopen('test.csv', 'wt');
// attach filter to output file
stream_filter_append($f, 'crlf');
// start writing
fputcsv($f, array('1 1', '2 2'));
fclose($f);
Not sure if you can do this with PHP itself. There may be a way to change PHP's EOL for file writing, but it's probably system dependent. You don't have a windows system you could ping, do you? ;)
As for a real solution, instead of str_replace line-by-line, you could use the Linux program unix2dos (inverse of dos2unix) assuming you have it installed:
fputcsv($fh ...)
exec("unix2dos " . escapeshellarg($filename));
Create the file with \n line endings on a Linux machine
FTP the file as ASCII from the Linux machine to a Windows machine
Hey presto! All line endings are now \r\n in the file on the Windows machine
Rather than writing the file, a better solution is to use output buffering
function output($buffer) {
return str_replace("\n", "\r\n", $buffer);
}
ob_start('output');
fputcsv(....);
Since PHP 8.1 fputcsv accepts a new $eol parameter to do this. By default is "\n" but can change it to "\r\n".
fputcsv($stream, $fields, eol: "\r\n");
If PHP is not properly recognizing the line endings when reading files either on or created by a Macintosh computer, enabling the auto_detect_line_endings run-time configuration option may help resolve the problem.
ini_set("auto_detect_line_endings", true);
Related
It's strange PHP Reading my Excel generated CSV file into a single line. Code is:
if ($file) {
while (($line = fgets($file)) !== false) {
print '<div>'.$line.'</div>'."<br/>";
}
} else {
// error opening the file.
}
fclose($file);
CSV
Name, City
Jon,Paris
Doe,Madrid
Add this code before reading the file.
ini_set("auto_detect_line_endings", true);
When turned on, PHP will examine the data read by fgets() and file() to see if it is using Unix, MS-Dos or Macintosh line-ending conventions.
This enables PHP to interoperate with Macintosh systems, but defaults to Off, as there is a very small performance penalty when detecting the EOL conventions for the first line, and also because people using carriage-returns as item separators under Unix systems would experience non-backwards-compatible behaviour.
Most likely, PHP is not correctly detecting the line endings in your file. The fgets documentation points this out.
You will probably want to write code like this:
$oldLineEndings = ini_set('auto_detect_line_endings', true);
//your while loop here
ini_set('auto_detect_line_endings', $oldLineEndings);
If you need to actually parse the csv, you may also want to look at fgetcsv.
Hi I am trying to create some code that first reads the existing contents of the file in and then adds the new line of code on a new line but the code i am using just adds it on the new text on to the already existing line instead of the new line...
Here is the code i am using:
<?php
$id = $_GET['id'];
$userfile = "user1.txt";
$fo = fopen($userfile, 'r') or die("can't open favourites file");
$currentdata = fread($fo, filesize($userfile));
fclose($fo);
$fw = fopen($userfile, 'w') or die("can't open favourites file");
$currentprocessed = "$currentdata\n";
fwrite($fw, $currentprocessed);
fwrite($fw, $id);
fclose($fw);
?>
I have tried a whole range of different ideas but nothing has worked, any help would be appreciated.
Line endings per OS
Unix / Linux
\n
DOS / Windows
\r\n
Invalid
\r and \n\r
The value of PHP_EOL constant depends on the platform php is running on.
It doesn't detect the line-endings in the current file or anything magic.
Instead of appending \n, concatenate the constant PHP_EOL which is always the correct newline character for the current platform.
It might also be an issue with the program you're using to open the text file with. For instance, Notepad on Windows is incapable of understanding unix style newlines.
I ran into this same issue. What application are you using to read the file? I found that for some reason Notepad (my default for .txt files) didn't recognize the "\n\r" escape characters. I opened my .txt file that I was writing to using Notepad++, Atom (my text editor of choice), or in a browser and they all showed the line breaks just fine.
My code:
$i = 0;
$file = fopen('ids.txt', 'w');
foreach ($gemList as $gem)
{
fwrite($file, $gem->getAttribute('id') . '\n');
$gemIDs[$i] = $gem->getAttribute('id');
$i++;
}
fclose($file);
For some reason, it's writing \n as a string, so the file looks like this:
40119\n40122\n40120\n42155\n36925\n45881\n42145\n45880
From Google'ing it tells me to use \r\n, but \r is a carriage return which doesn't seem to be what I want to do. I just want the file to look like this:
40119
40122
40120
42155
36925
45881
42145
45880
Thanks.
Replace '\n' with "\n". The escape sequence is not recognized when you use '.
See the manual.
For the question of how to write line endings, see the note here. Basically, different operating systems have different conventions for line endings. Windows uses "\r\n", unix based operating systems use "\n". You should stick to one convention (I'd chose "\n") and open your file in binary mode (fopen should get "wb", not "w").
PHP_EOL is a predefined constant in PHP since PHP 4.3.10 and PHP 5.0.2. See the manual posting:
Using this will save you extra coding on cross platform developments.
IE.
$data = 'some data'.PHP_EOL;
$fp = fopen('somefile', 'a');
fwrite($fp, $data);
If you looped through this twice you would see in 'somefile':
some data
some data
Use PHP_EOL which outputs \r\n or \n depending on the OS.
You can also use file_put_contents():
file_put_contents('ids.txt', implode("\n", $gemList) . "\n", FILE_APPEND);
I have a php function I wrote that will take a text file and list each line as its own row in a table.
The problem is the classic "works fine on my machine", but of course when I ask somebody else to generate the .txt file I am looking for, it keeps on reading in the whole file as 1 line. When I open it in my text editor, it looks just as how I would expect it with a new name on each line, but its the newline character or something throwing it off.
So far I have come to the conclusion it might have something to do with whatever text editor they are using on their Mac system.
Does this make sense? and is there any easy way to just detect this character that the text editor is recognizing as a new line and replace it with a standard one that php will recognize?
UPDATE: Adding the following line solved the issue.
ini_set('auto_detect_line_endings',true);
Function:
function displayTXTList($fileName) {
if(file_exists($fileName)) {
$file = fopen($fileName,'r');
while(!feof($file)) {
$name = fgets($file);
echo('<tr><td align="center">'.$name.'</td></tr>');
}
fclose($file);
} else {
echo('<tr><td align="center">placeholder</td></tr>');
}
}
This doesn't work for you?
http://us2.php.net/manual/en/filesystem.configuration.php#ini.auto-detect-line-endings
What's wrong with file()?
foreach (file($fileName) as $name) {
echo('<tr><td align="center">'.$name.'</td></tr>');
}
From the man page of fgets:
Note: If PHP is not properly recognizing the line endings when reading files either on or created by a Macintosh computer, enabling the auto_detect_line_endings run-time configuration option may help resolve the problem.
Also, have you tried the file function? It returns an array; each element in the array corresponds to a line in the file.
Edit: if you don't have access to the php.ini, what web server are you using? In Apache, you can change PHP settings using a .htaccess file. There is also the ini_set function which allows changing settings at runtime.
This is a classic case of the newline problem.
ASCII defines several different "newline" characters. The two specific ones we care about are ASCII 10 (line feed, LF) and 13 (carriage return, CR).
All Unix-based systems, including OS X, Linux, etc. will use LF as a newline. Mac OS Classic used CR just to be different, and Windows uses CR LF (that's right, two characters for a newline - see why no one likes Windows? Just kidding) as a newline.
Hence, text files from someone on a Mac (assuming it's a modern OS) would all have LF as their line ending. If you're trying to read them on Windows, and Windows expects CR LF, it won't find it. Now, it has already been mentioned that PHP has the ability to sort this mess out for you, but if you prefer, here's a memory-hogging solution:
$file = file_get_contents("filename");
$array = split("/\012\015?/", $file); # won't work for Mac Classic
Of course, you can do the same thing with file() (as has already been mentioned).
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 ""