2

I have a text file (data.txt) with the following lines

# one
# two
a-two
b-two
c-two
# three
a-three
b-three
# four

I would like to replace # with incremental number per line so to be like this

1 one
2 two
a-two
b-two
c-two
3 three
a-three
b-three
4 four

I have try to do this

    <?php
    $path_to_file = 'data.txt';
    $what_to_replace = '#';
    $i = 0; // initial count
    
    $file_contents = file_get_contents($path_to_file);
    $file_contents = str_replace($what_to_replace, $i++,$file_contents);
    file_put_contents($path_to_file,$file_contents);
    ?>

it did changed in all lines with # but not incrementally

Reham Fahmy
  • 4,937
  • 15
  • 50
  • 71

4 Answers4

4

Here's a simple way via preg_replace_callback():

$i = 1;
$file_contents = preg_replace_callback('/^#/m', function($match) use (&$i) {
    return $i++;
}, $file_contents);

The m (multiline) flag is important - it will force our pattern to match # at the start of any line, not just the first.

Mitya
  • 33,629
  • 9
  • 60
  • 107
2

I think you have to iterate over all the lines and replace the hash one by one. Of course, you can use some preg_replace as well to only replace the # at the line beginning: https://www.php.net/manual/en/function.preg-replace.php

$result = '';
$handle = fopen("data.txt", "r");
$i = 1;
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $content .= str_replace('#', $i++,$line) . "\n";
    }

    fclose($handle);
} else {
    // error opening the file.
}

file_put_contents('data.txt', $result);
HSLM
  • 1,692
  • 10
  • 25
  • This risks replacing all `#` in a line. It is unaware of the location of the `#` and has no means to limit the number of replacements it makes in a line. While this may work for the sample input, it is not suitable for general use cases. – mickmackusa Dec 19 '20 at 22:30
1

We can split the text line-by-line, then process each line individually, looking for a # at the start of the line & modifying the output in those cases.
The preg_replace_callback approach is better than this one, IMO.

$text = file_get_contents(...);
$lines = explode("\n", $text); // split by unix-style line break 
$out = []; // For storing the modified lines
$i=0;
foreach ($lines as $line){
    if (substr($line,0,1)=='#'){
        //replace the first character & increment the counter if line starts with #
        $i++;
        $line = $i.substr($line,1);
    }
    //append the SOMETIMES modified line to the output
    $out[] = $line;
}
// convert array of lines to string
$fixedText = implode("\n", $out);

See https://stackoverflow.com/a/28725803/802469 for a more robust version of the explode("\n", $text) line.

Personally, I like the $out = []; ...; implode(...) approach. I find it easier to work with & to visualize in my mind. Using an $out = ""; $out .= $line."\n"; approach would work just as well. I suspect any performance increase from going with string would be negligible, but might lower your carbon footprint a small amount.

If you have serious performance/resource concerns (extremely large files) then the fopen()/while fgets() approach would be better, and fopen/fwrite() could be used to append each output line to a file without using up memory for an output string.

Reed
  • 14,703
  • 8
  • 66
  • 110
  • @mickmackusa, I absolutely agree. Thanks for pointing it out. :) Iiii think I was feeling a bit lazy by the time I was done with the code snippet lol. Updated it now! – Reed Dec 20 '20 at 20:38
1
$path_to_file = 'data.txt';
$what_to_replace = '#';
$i = 1; // initial count

$file_contents = file_get_contents($path_to_file);

$lines = explode(PHP_EOL, $file_contents);
$new_lines = [];

foreach ($lines as $line) {
  if (strpos($line, '#') !== false) {
    $new_lines[] = str_replace('#', $i, $line);
    $i++;
  } else {
    $new_lines[] = $line;
  }
}

file_put_contents($path_to_file, implode(PHP_EOL, $new_lines));
Cameron Hurd
  • 4,836
  • 1
  • 22
  • 31
  • This risks replacing all `#` in a qualifying line. It is has no means to limit the number of replacements it makes in a line. While this may work for the sample input, it is not suitable for general use cases. This answer is missing its educational explanation. I would not use this snippet in my own project because it is making multiple iterated function calls and declares a temporary array before producing the mutated string -- it's just too indirect for my tastes. – mickmackusa Dec 19 '20 at 22:34
  • Yeah man lol no argument here – Cameron Hurd Dec 19 '20 at 22:34