问题描述
chapter1.tex
chapter2.tex
chapter3.tex
...
chapter 10.tex
chapter 11.tex
etc.
我正在尝试使用 sed
在所有文件中查找 AAAAAA
的第一个实例并将其替换为 ZZZZZZ
。
sed -i "0,/AAAAAA/s//ZZZZZZ/" chapter*.tex
我试过上面这个命令,但有两个问题:
- 它在每个文件中查找并替换
AAAAAA
的第一个实例。我只想要所有文件中的第一个实例。 - 我怀疑,像许多 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)
这里假设 sed
和 sort
都来自 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
更改保持缓冲区的当前行,并检查交换后模式缓冲区中是否有任何内容。如果没有,它会将其交换回来并继续。如果存在,它会将其交换回并q
uits,跳过所有后续文件。
虽然有些复杂,但这不会为每个文件生成进程。
原版
使用双重条件 -
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 字符串并忽略它之前的所有记录。 }
关闭块。之后的所有记录都将保持不变。