Hi!
I am stuck for a few days - I assume with the control algorithm - to
(1) search for a given string in a line of an array,
(2) delete this line if string exists and
(3) replace this line ONCE ONLY with the search string.

Array example:/B
peter;
paul;
john;
jonathan;
william;
alan;
theresa;
johnny;

my code at the present time:


copy($file,$file2);
unlink($file);
$finput=file($file2);
$foutput=fopen($file,"a+");

	for($i=0;$i<count($finput);$i++) 
	{

		if ( (stripos($finput[$i], 'john') === false ) )
		{
			fwrite($foutput,$finput[$i]);
		}


	}
	fwrite($foutput,Chr(13).'john;');

fclose($foutput);
unlink($file2);

Problem
the line (fwrite($foutput,Chr(13).'john;')😉 - and its position in the control algorithm - gives a wrong result as it writes the search/replacement string ('john' in this example) even when 'john' is absent from any line of the array.

Hint for solution (?):
Improve the defective line and put it at the right place, but ..how and where?

Help me please
Thanks a lot

    $lines = array_map('trim', file($file));
    $lines_mentioning_john = array_keys(preg_grep('/john/i', $lines));
    if(!empty($lines_mentioning_john))
    {
        $first_line_mentioning_john = $lines_mentioning_john[0];
        unset($lines[$first_line_mentioning_john]);
        $lines[] = 'john';
    }
    file_put_contents($file, join("\n", $lines));
    

      Many thanks weed packet for this speedy solution.
      I've started checking it and it seems to work: no unwanted 'john' added!

      I am now trying to do the same treatment throughout the file with several search strings ie not 'john' only but 'peter' also, etc..
      more hastle for myself!

      I assume that what my code was missing was this line with unset()???

      Thank you again as it's the first t ime for me to come accross preg_grep().

        Hi weedpacket!

        This is me again...as I feared I am still in trouble with iterating this code as follows:

        • *one only line with 'john' is deleted. Meaning that if more than one line with 'john', the other lines with 'john' will remain.
          'John' is added correctly at the end of the file. The file is not truncated.

        • (in the real world) the file is truncated to keep 'john' as single line for the whole file, meaning that all other lines are deleted.
          Doing another search/replace with say 'peter' will give the same result.
          'peter' will be the only line if searched last.

        please note that I use a mutualised server supposed to be the best in the region...
        Sorry to impose on you again
        Thanking you in advance

          Sounds strange.
          Are you sure you have exactly same code in both places?

          Because:
          $lines[] = 'john'; ... means correctly to add 'john' as a new element at the end
          But:
          $lines = 'john'; ... will truncate and have only one element 'john'

          Another thing, say we have:
          peter;
          johnny;
          paul;
          john;
          jonathan;
          william;
          alan;
          theresa;

          If we use: preg_grep('/john/i', $lines)
          then johnny will be deleted, but john be left untouched

          But if we use: preg_grep('/john$/i', $lines)
          then 'john' will be deleted, as this one matches exactly the word.
          And so get not any match of 'johnny'

            I don't know why it would be have differently in different situations; one guess is that the file was written on a operating system using one type of line-ending, and the server is running on an OS using a different one (i.e., Unix-based machines use "\n" to mark the end of a line, MacOS uses "\r", and Windows uses "\r\n"), so the entire file is ending up in one line (which naturally contains "john").

            One thing that should be done to improve that code is change that [font=monospace]"\n"[/font] at the end to [font=monospace]PHP_EOL[/font]. Then at least when the file is written it will use the correct line break for the system the program's running on.

            cosmexpert wrote:

            I am now trying to do the same treatment throughout the file with several search strings ie not 'john' only but 'peter' also, etc..
            more hastle for myself!

            If you're repeating code you're probably doing it wrong: this is why functions were created.

            function move_name_to_end_of_file($file, $name)
            {
                // That code, with "john" replaced by $name;
                // you'd want to change the expression in preg_grep to '/'.preg_quote($name).'/i'
            }
            

            I assume that what my code was missing was this line with unset()???

            No; the problem was that you added john's line at the end regardless of whether you found the name or not. The other changes I wrote were just for efficiency (you were already reading the entire file in one step; it was simpler to write the entire file in one step, too. The temporary file was unnecessary also - although it would have been needed if you were originally reading line-by-line and writing concurrently).

            Actually, now that I look at your original code again, something occurs to me..... You open the new file for appending: are you really planning on doubling the size of the file each time?

            halojoy makes another observation (although the description and solution are incorrect); do you want "johnny" to be found when you look for "john"? Or "upjohn"? If not, then the pattern should be [font=monospace]'/\b'.preg_quote($name).'\b/i'[/font] (assuming you rewrite this as a function) so that it only matches entire words.

            Oh, and I guess when you push the name onto the end of the array, you'd want to attach a semicolon, too, if those are there for some purpose. When posting code, please use the formatting tags described in the FAQ. Another thread stickied in the Coding Forum has some posters' observations about formatting code. I think I might have mistaken those semicolons in the text for PHP's end-of-statement semicolons.

              Many thanks to both of you (weedpacket and halojoy) for your careful attention to my problem.
              Back from sleep now!

              Firstly the system tech info for the server is as follows:
              ' Linux imu215 2.6.27.42-imu-x86 #1 SMP Mon Dec 21 10:06:10 CET 2009 i686'

              Secondly (and consequently ?), PHP_EOL makes no change compared to "\n" both locally and on the server.

              I obviously do understand the necessity of the operator [], I do not want to double the file at every iteration, the reg expression \ 'john'\i is enough at present and I use ';' as delimiter but I add it easily after each search/delete/replace as you may check below.

              I tried using the function you suggested as follows:
              '
              function move_name_to_end_of_file($file, $name)
              {
              $lines = array_map('trim', file($file));
              $lines_search = array_keys(preg_grep('/'.preg_quote($name).'/i', $lines));
              if(!empty($lines_search))
              {
              $first_line_search = $lines_search[0];
              unset($lines[$first_line_search]);
              $lines[] = '$name;';
              }
              file_put_contents($file, join("\n", $lines));
              }

              $lines = array_map('trim', file($file));
              $i=0;
              while ($i<count($lines))
              {
              if ($lines[$i]='/john/i' ||$lines[$i]='/peter/i' ) //line_to_change????
              {
              $lines[$i]=move_name_to_end_of_file($file, $lines[$i]);
              $i++;
              }

              }
              '

              It has no effect on the file whatsoever.
              If I modifiy the so-called line_to_change to:
              if (stripos($lines[$i], 'john') === true || stripos($lines[$i], 'peter') === true )
              then browser loops indefinitely.
              Above tested locally only.

              Thank you for your patience in keeping up with me...I need it.

                Well, it looks like you are writing to the file in the loop by calling move_name_to_end_of_file() on each iteration. I think that a correct approach would be to modify the array of lines for every search string, and only when you are done with all of them do you write the new contents to the file.

                  Hello beautiful Singapore and thanks

                  This way of tackling the problem is not suitable according to weedpacket.
                  I'll wait for his reply until moving ahead again.

                    cosmexpert wrote:

                    This way of tackling the problem is not suitable according to weedpacket.

                    Where exactly does Weedpacket say that? If he did, I daresay this is one the rare times that we've caught him being wrong 🙂

                      just above , before my reply, which is why I tried this function he suggests

                        cosmexpert wrote:

                        just above , before my reply, which is why I tried this function he suggests

                        You should quote him. Frankly, I cannot find anything in what he wrote that contradicts my suggestion. In fact, notice:

                        Weedpacket wrote:

                        The other changes I wrote were just for efficiency (you were already reading the entire file in one step; it was simpler to write the entire file in one step, too.

                        Here's a problem: if you call move_name_to_end_of_file() in a loop, only the last call of move_name_to_end_of_file() counts, since it is only that call that finally causes the file to be overwritten. Effectively, you are writing to the file in many steps, and only that last step provides the net effect.

                        EDIT:
                        Oh wait, I just noticed the use of file() in the function. That means that there should be a net effect for each iteration... but again, this is a case of writing and then reading to/from the file in many steps, when you just need to read once and write once.

                          What would be unsuitable would be writing a whole chunk of code over and over with just the name changing each time.

                          But it's also expensive to be reading and writing a file over and over with only one name shifting each time. It would make more sense to read the file once, make all of the desired changes, and then write it (once).

                          That would involve changing the function so that instead of the file name, it took the array of lines read from the file and returned the rearranged lines so that it could be written back out.

                          Then you'd have the names to shift in an array. Looping through the elements of that array, pass each of them (along with the lines) to the function and get back the rearranged lines, then go on to the next name.

                          The next idea would be to have that loop inside the function, and pass it both arrays.

                          One thing is certain: the whole point of having the function is to look for a name and shift it if necessary. Which means that

                          $lines = array_map('trim', file($file));
                          $i=0;
                          while ($i<count($lines))
                          {
                          if ($lines[$i]='/john/i' ||$lines[$i]='/peter/i' ) //line_to_change????
                          {
                          $lines[$i]=move_name_to_end_of_file($file, $lines[$i]);
                          $i++;
                          }
                          
                          }

                          pretty much totally ignores what the function does (what would be the point of reading the file, looping through every line to check and maybe move it, and then writing the file if on every line you read the file, looping through every line to check and maybe move it...? Please, read what you're writing and ask yourself honestly if it makes the faintest bit of sense. (Incidentally, you'll notice that using [noparse]

                          ...

                          [/noparse] around your code gives a better result than single quotes. See the FAQ for more).

                          But any how, where I left the code was passing it two arrays - one of lines and one of names. That looks like

                          function move_names_to_end($lines, $names)
                          {
                              foreach($names as $name)
                              {
                                  $lines_mentioning_name = array_keys(preg_grep('/'.preg_quote($name).'/i', $lines));
                                  if(!empty($lines_mentioning_name))
                                  {
                                      $first_line_mentioning_name = $lines_mentioning_name[0];
                                      unset($lines[$first_line_mentioning_name]);
                                      $lines[] = $name;
                                  }
                              }
                              return $lines;
                          }
                          
                          // And to use it...
                          
                          $names = array('john', 'peter', 'whatever');
                          
                          $lines = array_map('trim', file($file));
                          $lines = move_names_to_end($lines, $names);
                          file_put_contents($file, join(PHP_EOL, $lines)); 
                          

                          Note the absence of all that other cruft - it's been moved into the function. It could be altered further, especially with some added domain knowledge, but it works and is pretty straightforward.

                            Thanks for your efforts...it is above my reach I have to say!

                            It seems to me though that there is a confusion about the file I need to manipulate.
                            Lines are (difficult) words separated by space (ie not a single word) and ended by one semicolon. Actually, it looks more like:

                            paul;
                            peter paul;
                            jonathan;
                            william johnny;
                            alan peter;
                            theresa;
                            peter alfred richard;
                            john;

                            As a consequence, the search/delete/replace treatment for 'peter and 'john' gives the following result (locally):

                            paul;
                            jonathan;
                            alan peter;
                            theresa;
                            peter alfred richard;
                            john;
                            peter
                            john

                            Rather messy isn't it?

                            and again
                            Thanks for your efforts...it is above my reach I have to say!

                              I have done a script that works.
                              Hope you can try it and see how it works for you.

                              One important thing is how you add new names to that file.
                              I think you should not use semicolon at the end.
                              Just add one "\n" (newline) at the end of each line.

                              This is how my script suppose you to add new line/name.
                              I use file_put_contents() in FILE_APPEND mode.

                              // To append, add one new name
                              $new = 'Laura';
                              file_put_contents($file, $new."\n", FILE_APPEND);

                              My script and the new function
                              that takes several search names at one time.

                              <?php
                              
                              // Make a testfile
                              $file = 'names.txt';
                              $names = array('peter','paul','john','jonathan',
                              				'william','alan','theresa','johnny');
                              $fp = fopen($file, 'w');
                              foreach($names as $name){
                              	fwrite($fp,$name."\n");
                              }
                              fclose($fp);
                              
                              
                              // The Function
                              function move_name($lines, $search){
                              	foreach($search as $name){
                              		$pattern = '/\b'.preg_quote($name).'\b/i';
                              		foreach($lines as $k=>$v){
                              			if(preg_match($pattern,$v)){
                              				unset($lines[$k]);
                              				$lines[] = $name;
                              				break;
                              			}
                              		}
                              	}
                              	return $lines;
                              }
                              ////////////////
                              
                              // Get file contents and run the test
                              $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                              
                              // Same as:  $search = array('john', 'peter');
                              $search[] = 'john';
                              $search[] = 'peter';
                              
                              // Do it
                              $newlines = move_name($lines, $search);
                              // Save it
                              file_put_contents($file, implode("\n",$newlines)."\n");
                              
                              // View $newlines
                              echo '<pre>'; var_export($newlines);
                              
                              // To append, add one new name
                              $new = 'Laura';
                              file_put_contents($file, $new."\n", FILE_APPEND);
                              
                              ?>

                                Thanks halojoy

                                Appending a name is fairly straightforward..

                                I can see that my explanations about the file to manipulate were not accurate enough. Sorry about that!
                                This last code does seem to work (locally at least) with an array of single words. Unfortunatelky it does not (sigh!) with an array like:
                                array('peter','paul john','john','jonathan',
                                'william','alan peter','theresa','johnny john');

                                where the search/delete/replace names (here peter and john) may be alone or next to any other word(s) or part of another word(s).
                                For instance: 'johnny john' must be replaced by 'john', once only.

                                Shall I dare sign again with:
                                it is above my reach
                                thanks for your efforts

                                  So you want to drop all the occurrences that match "john" and append "john;"? (I missed that semicolon again. Yay!) I misread your original description (I thought you only wanted to replace one line).

                                  That simplifies things.

                                  // Note; if there are any names in $names that are not in the $list,
                                  // they will be added also.
                                  function move_names_to_end($lines, $names)
                                  {
                                  	// We can search for all the names at once
                                  	$pattern = '/\b'.join('|', array_map('preg_quote', $names)).'\b/i';
                                  
                                  // PREG_GREP_INVERT: *drop* lines matching the pattern
                                  // (i.e. any line containing one of the supplied names)
                                  // and keep the rest.
                                  $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
                                  
                                  // Now put those matched names back on the end of the list.
                                  // First we add semicolons.
                                  foreach($names as &$name)
                                  {
                                  	$name = "$name;";
                                  }
                                  // Then append
                                  $lines = array_merge($lines, $names);
                                  
                                  return $lines;
                                  }
                                  

                                    Thanks for being still with me
                                    Let me first repeat what I understand from your several suggestions
                                    file ($file)
                                    paul;
                                    jonathan johnny;
                                    alan peter;
                                    theresa paul;
                                    peter alfred richard;
                                    johnny george;
                                    william john;

                                    code:
                                    function move_names_to_end($lines, $names)
                                    {
                                    // We can search for all the names at once
                                    $pattern = '/\b'.join('|', array_map('preg_quote', $names)).'\b/i';

                                    // PREG_GREP_INVERT: *drop* lines matching the pattern
                                    // (i.e. any line containing one of the supplied names)
                                    // and keep the rest.
                                    $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
                                    
                                    // Now put those matched names back on the end of the list.
                                    // First we add semicolons.
                                    foreach($names as &$name)
                                    {
                                        $name = "$name;";
                                    }
                                    // Then append
                                    $lines = array_merge($lines, $names);
                                    
                                    return $lines;

                                    }

                                    // And to use it...

                                    $lines = array_map('trim', file($file));

                                    $names = array('peter', 'john');

                                    $lines = array_map('trim', file($file));
                                    $lines = move_names_to_end($lines, $names);
                                    file_put_contents($file, join(PHP_EOL, $lines));

                                    result:
                                    jonathan johnny;
                                    theresa paul;
                                    peter;
                                    john;

                                    expected result :
                                    paul;
                                    john;
                                    peter;
                                    theresa paul;

                                    (tested locally only)
                                    Getting rid of eventual duplicates would be easy adding array_unique(), but,here again result is not what I am aiming for.

                                    Not begging though, but I feel I am unable to find the solution on my own.
                                    I do appreciate your efforts to help me at this late hour in moonlit Paris

                                      cosmexpert;10950204 wrote:

                                      Thanks halojoy

                                      Appending a name is fairly straightforward..

                                      I can see that my explanations about the file to manipulate were not accurate enough. Sorry about that!
                                      This last code does seem to work (locally at least) with an array of single words. Unfortunatelky it does not (sigh!) with an array like:
                                      array('peter','paul john','john','jonathan',
                                      'william','alan peter','theresa','johnny john');

                                      where the search/delete/replace names (here peter and john) may be alone or next to any other word(s) or part of another word(s).
                                      For instance: 'johnny john' must be replaced by 'john', once only.

                                      You are right.
                                      Whenever someone has got a question it is 100% important
                                      that they try to be very precise to tell what they want to do.
                                      All conditions and details that can be of importance must be told.

                                      If somebody has managed to tell the situation in a proper way
                                      then there will be a quick solution.
                                      And somebody will have no problem to post such a good solution.

                                      Most times when people do not get a quick and good working answer
                                      is because they have not been able to explain as they should.
                                      Most often they do not provide the full PHP Error message.

                                      Youur issue has many details and is a lot to think about and to know about.

                                      About your project.
                                      Still I can not accept that you use semicolons after those names.
                                      - john;
                                      Because in my mind there is more negative than any good with it.
                                      And I can see no useful thing about it.
                                      If you need semicolons when displaying names
                                      they can be added later whenever you want to display

                                      echo $name . ';';

                                        Thank you halojoy for your help and remarks
                                        These semicolons might not be necessary. They helped me during the first steps of this particular code.
                                        I hope that my last reply to weedpacket helps clarifying my intention.
                                        To explain more, I further use the search strings (john and peter) in a mysql request, after adding jokers, to extract similar strings from that table.
                                        'john' would give a result whereas 'johnny' would not.