替换找到的第一个文件中的字符串 原版

问题描述

我有一堆这样命名的文件

chapter1.tex
chapter2.tex
chapter3.tex
...
chapter 10.tex
chapter 11.tex
etc.

我正在尝试使用 sed 在所有文件中查找 AAAAAA 的第一个实例并将其替换为 ZZZZZZ

sed -i "0,/AAAAAA/s//ZZZZZZ/" chapter*.tex

我试过上面这个命令,但有两个问题:

  1. 它在每个文件中查找并替换 AAAAAA 的第一个实例。我只想要所有文件中的第一个实例。
  2. 我怀疑,像许多 Bash 工具一样,它没有按顺序正确地对我的文件进行排序。例如。如果我输入 ls,则 chapter10.tex 列在 chapter1.tex 之前。按章节顺序搜索文件至关重要。

如何使用 Bash 工具从一大堆文件中查找和替换第一个实例,所以只替换第一个找到的文件中的第一个实例,同时也尊重文件顺序(chapter1.tex 是第一个,chapter10.tex 是第十)?

解决方法

这是一个基于 bash 循环的解决方案,可以处理诸如 chapter 10.tex 之类的文件名,即带有空格等的文件名:

while IFS= read -r -d '' file; do
   if grep -q 'AAAAAA' "$file"; then
      echo "changing $file"
      sed -i '0,/AAAAAA/s//ZZZZZZ/' "$file"
      break
   fi
done < <(printf '%s\0' chapter*.tex | sort -z -V)

这里假设 sedsort 都来自 gnu utils。


如果您有支持就地编辑的 gnu awk 4+ 版本,即 -i inplace,那么您可以将 grep + sed 替换为单个 awk

while IFS= read -r -d '' file; do
   awk -i inplace '!n {n=sub(/AAAAAA/,"ZZZZZZ")} 1;
   END {exit !n}' "$file" && break
done < <(printf '%s\0' chapter*.tex | sort -z -V)
,

有了完整的 GNU 工具箱,您就不需要循环了。

<?php
    $url='https://api.remove.bg/v1.0/removebg'; 
    $ch = curl_init($url);
    $data = array('image_url'=> 'https://www.requestingservicebyme.com/upload/imageexample.jpg');
    $headers1=['X-API-Key:xxxxxxxxxxxxxxx','Content-Type:application/json'];
    curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
    curl_setopt($ch,CURLOPT_HTTPHEADER,$headers1);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch,CURLOPT_POST,1);
    $buffer = curl_exec($ch);
        
      if (empty($buffer)) {
         echo " buffer is empty ";
      } else{
         echo $buffer;
      }
      curl_close($ch);
?>
,

这可能对您有用(GNU sed 和 grep):

grep -ns 'AAAAAA' chapter{1..9999}.txt | head -1 |
sed -nE 's#([^:]*):([^:]*):.*#sed -i "\2s/AAAAAA/ZZZZZZ/" \1#e'

使用 grep 和 bash 的大括号扩展来识别一个可能匹配的文件和行号,并构建一个 sed 脚本来更新该行号处的文件。

注意大括号扩展以正确的顺序生成文件名,grep 的 -s 命令行选项会抑制不存在的文件消息。


使用 GNU 并行的替代方法:

grep -sno 'AAAAAA' chapter{1..9999}.txt | head -1 |
parallel --colsep : sed '{2}s/{3}/ZZZZZZ/' {1}
,

#update

我站在巨人的背上,哈哈

感谢@potong 提供了带有支架扩展的出色排序解决方案!这意味着整个事情可以简化为单进程单行:

sed -i '0,/^AAA/{ /^AAA/{ s/AAA/ZZZ/; h; } }; ${ x; /./{x;q;}; x; }' chapter\ {[0-9],[0-9][0-9]}.tex 

#edit

正如所指出的,下面的原始解决方案会处理和更改每个文件中的第一次出现,并且不会更正文件顺序。 @anubhava 已经提供了一个优秀、优雅的排序解决方案,我不会尝试改进。

while IFS= read -r -d '' file; do lst+=( "$file" ); done < <(printf '%s\0' chapter*.tex | sort -z -V)

这会按正确顺序创建文件名列表,可以将其传递给 sed 的单个调用以整体处理它们。

要将其应用于基于 sed 的解决方案的排序,并且仅命中任何文件中的第一次出现 -

sed -i '0,/^AAA/{ /^AAA/{ s/AAA/ZZZ/; h; } }; ${ x; /./{x;q;}; x; }' "${lst[@]}"

这将查看每个文件并更改它在该文件中找到的第一个匹配项,h将第一次找到它的行变老。在每个文件的最后一行,它x更改保持缓冲区的当前行,并检查交换后模式缓冲区中是否有任何内容。如果没有,它会将其交换回来并继续。如果存在,它会将其交换回并quits,跳过所有后续文件。

虽然有些复杂,但这不会为每个文件生成进程。


原版


使用双重条件 -

sed -i '0,/AAAAAA/{ /AAAAAA/s/AAAAAA/ZZZZZZ/ }' chapter*.tex

要查看相同的一般逻辑:

$: cat a.tex b.tex
111
AAA
BBB
AAA
222

111
AAA
BBB
AAA
222

$: sed -i '0,/^AAA/{ /^AAA/s/AAA/ZZZ/; }' *.tex
$: cat a.tex b.tex
111
ZZZ
BBB
AAA
222

111
ZZZ
BBB
AAA
222

'0,/^AAA/ 是正确的,因为它的范围从文件的开头到目标字符串的第一次出现。

{ 打开一个块,我们可以在其中使用第二次搜索来确保它只影响目标字符串。

在块内,/^AAA/s/AAA/ZZZ/; 替换 AAA 字符串并忽略它之前的所有记录。 } 关闭块。之后的所有记录都将保持不变。