在文本文件中查找特定单词并替换整行

52

我怎样使用php替换文件中特定行的文本?

我不知道这一行的编号。我想要替换一个包含特定单词的行。


这个问题缺少它的 [mcve]。 - mickmackusa
12个回答

72

如果文件大小较小,可以采用一种方法,将其存储在内存中两次:

$data = file('myfile'); // reads an array of lines
function replace_a_line($data) {
   if (stristr($data, 'certain word')) {
     return "replacement line!\n";
   }
   return $data;
}
$data = array_map('replace_a_line', $data);
file_put_contents('myfile', $data);

快速提醒,PHP > 5.3.0 支持 lambda 函数,因此您可以删除命名函数声明并将 map 缩短为:

$data = array_map(function($data) {
  return stristr($data,'certain word') ? "replacement line\n" : $data;
}, $data);

您可以在理论上将此转换为单个(更难理解)的PHP语句:
file_put_contents('myfile', implode('', 
  array_map(function($data) {
    return stristr($data,'certain word') ? "replacement line\n" : $data;
  }, file('myfile'))
));

对于较大的文件,您应该使用另一种(内存占用较少)方法:

$reading = fopen('myfile', 'r');
$writing = fopen('myfile.tmp', 'w');

$replaced = false;

while (!feof($reading)) {
  $line = fgets($reading);
  if (stristr($line,'certain word')) {
    $line = "replacement line!\n";
    $replaced = true;
  }
  fputs($writing, $line);
}
fclose($reading); fclose($writing);
// might as well not overwrite the file if we didn't replace anything
if ($replaced) 
{
  rename('myfile.tmp', 'myfile');
} else {
  unlink('myfile.tmp');
}

较少占用内存的方法正是我所需要的(简单易懂)......谢谢!!! - user321531
为避免在使用fclose()之后太快运行rename()时出现"access denied"代码5错误,我不得不对两个处理器进行fseek()操作,并使用stream_copy_to_stream()进行复制,然后关闭处理器。 - Leopoldo Sanczyk
PHP手册明确指出,不应使用strstr()函数来检查字符串在另一个字符串中的存在性,而应该使用更高效的strpos()函数来完成此任务。 - mickmackusa

8

你需要覆盖整个文件。

所以,对于相对较小的文件,将文件读入数组,搜索单词,替换找到的行,将剩余部分写入文件

对于大文件,算法略有不同,但总体上相同。

重要的部分是文件锁定

这就是为什么我们更喜欢使用数据库的原因。


3

您还可以使用正则表达式的多行模式

preg_match_all('/word}/m', $textfile, $matches);

当然,这是假设文档较小而且已准备好并加载的情况。否则,其他答案将更加符合“实际应用”的解决方案。


这个答案非常误导人,它实际上并没有替换任何东西。那个流氓的 } 除了让研究人员感到困惑之外,还有什么作用吗?对于给定的模式,m 标志是无用的,它不会影响任何东西。我不明白这个答案获得的分数是怎么来的。这看起来像一个优秀的“遵纪守法”徽章候选者。 - mickmackusa

3

2
$filedata = file('filename');
$newdata = array();
$lookfor = 'replaceme';
$newtext = 'withme';

foreach ($filedata as $filerow) {
  if (strstr($filerow, $lookfor) !== false)
    $filerow = $newtext;
  $newdata[] = $filerow;
}

现在$newdata包含文件内容的数组(如果不想使用数组,请使用implode()),其中包含“replaceme”的行已替换为“withme”。


PHP手册指出,不应使用strstr()函数来检查字符串在另一个字符串中的存在性,而应该使用更高效的strpos()函数来完成此任务。 - mickmackusa

2

如果你正在查找一行中的子字符串(ID),并希望用新的替换旧的,这种方法非常适合。

代码:

$id = "123";
$new_line = "123,Programmer\r"; // We're not changing the ID, so ID 123 remains.
$contents = file_get_contents($dir);
$new_contents= "";
if( strpos($contents, $id) !== false) { // if file contains ID
    $contents_array = preg_split("/\\r\\n|\\r|\\n/", $contents);
    foreach ($contents_array as &$record) {    // for each line
        if (strpos($record, $id) !== false) { // if we have found the correct line
            $new_contents .= $new_line; // change record to new record
        }else{
            $new_contents .= $record . "\r";
        }
    }
    file_put_contents($dir, $new_contents); // save the records to the file
    echo json_encode("Successfully updated record!");
}
else{
    echo json_encode("failed - user ID ". $id ." doesn't exist!");
}

示例:

旧文件:

ID,职业

123,学生

124,砖层工人

运行代码将更改文件为:

新文件:

ID,职业

123,程序员

124,砖层工人


\\r\\n|\\r|\\n 可以完全被 \R 替换。我不会使用 $record . "\r" - mickmackusa

1
您可以使用explode();函数将文件转换为数组,编辑数组中的任何项,使用implode();函数将数组转换回字符串,然后您可以使用file_put_contents();函数将字符串放回文件中。以下是示例函数:
function file_edit_contents($file_name, $line, $new_value){
  $file = explode("\n", rtrim(file_get_contents($file_name)));
  $file[$line] = $new_value;
  $file = implode("\n", $file);
  file_put_contents($file_name, $file);
}

1
如果您想逐行处理文本文件,请使用file()而不是explode(file_get_contents())。更重要的是,原帖明确说明:“_我不知道行号_”。 - mickmackusa

0

或许这可以帮助:

$data = file("data.php");

for($i = 0;$i<count($data);$i++){
    echo "<form action='index.php' method='post'>";
    echo "<input type='text' value='$data[$i]' name='id[]'><br>";
}

echo "<input type='submit' value='simpan'>";
echo "</form>";

if(isset($_POST['id'])){
    file_put_contents('data.php',implode("\n",$_POST['id'])) ;
}

这个回答似乎完全忽略了“阅读问题”的重要步骤。 - mickmackusa

0

这个函数应该替换文件中的一整行:

function replace($line, $file) {
    if ( file_get_contents($file) == $line ) {
        file_put_contents($file, '');
    } else if ( file($file)[0] == $line.PHP_EOL ) {
        file_put_contents($file, str_replace($line.PHP_EOL, '', file_get_contents($file)));
    } else {
        file_put_contents($file, str_replace(PHP_EOL.$line, '', file_get_contents($file)));
    }
}

第一个if语句(第2行)检查要删除的行是否是唯一的行。然后它清空文件。第二个if语句(第4行)检查要删除的行是否是文件中的第一行。如果是,则使用str_replace($line.PHP_EOL, '', file_get_contents($file))来删除该行。 PHP_EOL是一个新行符,因此这将删除行内容,然后删除行尾换行符。最后,else语句仅在要删除的行不是唯一内容且不在文件开头时才会被调用。然后再次使用str_replace,但这次使用PHP_EOL.$line而不是$line.PHP_EOL。这样,如果该行是文件的最后一行,则会在其前面删除换行符,然后删除该行。

用法:

replace("message", "database.txt");

如果存在包含message的行,则从文件database.txt中删除该行。如果您想缩短代码,可以尝试以下方法:

function replace($line,$file){if(file_get_contents($file)==$line){file_put_contents($file,'');}else if(file($file)[0]==$line.PHP_EOL){file_put_contents($file,str_replace($line.PHP_EOL,'', file_get_contents($file)));}else{file_put_contents($file,str_replace(PHP_EOL.$line,'',file_get_contents($file)));}}

希望这回答了你的问题 :)


这个答案中有太多的文件读取——不仅会降低性能,而且会增加竞争条件冲突的机会。调用file()——它将从每一行文本完全填充一个数组——只是为了读取第一行并不是性能最优的选择。与其将代码拼凑在一起使其“更短”(更难读懂),不如编写变量来保存被多次访问的值。DRY代码是好代码。 - mickmackusa

0

如果您在处理文件时不打算 锁定文件,那么

  1. 访问该文件,
  2. 修改该行(如果找到)并停止查找其他要替换的行,
  3. 并尽可能快地重新保存它(如果找到)。

话虽如此,为了速度而牺牲准确性是没有意义的。这个问题说明必须在行中匹配一个单词。因此,必须防止部分匹配 - 正则表达式提供了单词边界 (\b)。

$filename = 'test.txt';
$needle = 'word';

$newText = preg_replace(
    '/^.*\b' . $needle . '\b.*/mui',
    'whole new line',
    file_get_contents($filename)
    1,
    $count
);
if ($count) {
    file_put_contents($filename, $newText);
}

模式:

/     #starting pattern delimiter
^     #match start of a line (see m flag)
.*    #match zero or more of any non-newline character
\b    #match zero-width position separating word character and non-word character
word  #match literal string "word"
\b    #match zero-width position separating word character and non-word character
.*    #match zero or more of any non-newline character to end of line
/     #ending pattern delimiter
m     #flag tells ^ character to match the start of any line in the text
u     #flag tells regex engine to read text in multibyte modr
i     #flag tells regex engine to match letters insensitively

如果使用不区分大小写的搜索,但需要在替换字符串中使用实际匹配的单词,请在模式中括号中写入针,然后在替换字符串中使用$1


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接