使用一个文件中的每行字符串替换多个文件中的字符串

使用一个文件中的每行字符串替换多个文件中的字符串

在使用这个网站的这么多年里,我从来不需要问一个问题,因为总是有一个答案(通常是很多)。我很确定这个也能,但我一生都找不到它。

我有一个包含一堆文件的目录,其中有许多随机长度的行。

a.txt
b.txt
c.txt
d.txt

然后我有一个eg.txt带有一组字符串列表的文件

opq  111
rst  222
uvw  333
xyz  444

每个 txt 文件都有一个我想要替换的字符串

a.txt has a#P#b
b.txt has c#P#d
c.txt has e#P#f
d.txt has g#P#h

我想替换#P#为字符串文件中的第二个“列”。每个文件只发生#P#一次(因为我把它放在那里)。结果将是

a.txt has a111b
b.txt has c222d
c.txt has e333f
d.txt has g444h

“不变”的假设是我的目录中有多少行eg.txt就有多少.txt个文件,并且它们按字母顺序排列。中的行eg.txt按照“列”1 按字母顺序排序

我一直在尝试在 for 循环中使用awkand sed(实际上是sd)来做到这一点,但我无法让它逐行读取“源”和“目标”。

我对如何实现结果并不挑剔。目前我没有处理很多行或文件(现在有 15 行和 15 个文件),但有时会有很多行或文件。我在基于 Arch 和 Debian 的 Linux 发行版(有时是 WSL 2)上使用 zsh 作为 shell

如果有答案,我们深表歉意。在过去的两天里,我在做这个项目时真的很努力地试图找到它,但我的大脑现在已经耗尽了。

编辑:更新以澄清目录中的文件有许多不同长度的行,并且我给定的字符串#P#每个文件仅出现一次

答案1

使用 GNU awk 进行“就地”编辑ARGIND

awk -i inplace '
    NR == FNR { map[NR]=$2 }
    NR != FNR { sub(/#P#/,map[ARGIND]) }
1' eg.txt ?.txt

上面假设替换文本eg.txt不包含空格或&s。

答案2

准备工作

每个文件中只有一行。

$ grep -- . ?.txt
a.txt:a#P#b
b.txt:c#P#d
c.txt:e#P#f
d.txt:g#P#h
$ cat input
opq  111
rst  222
uvw  333
xyz  444

解决方案

sed对每个文件进行 shell 循环调用:

for file in ?.txt; do
    read -r dummy new_string rest
    sed -- "s/#P#/$new_string/g" "$file"
done <input

a111b
c222d
e333f
g444h

如果您对文件更改的结果感到满意,请将其更改sed -i为 GNUsed或兼容版或sed -i ''FreeBSD或兼容版。sed

上面假设行input不包含&, /, 或\字符。如果可能的话,您必须首先转义那些带有反斜杠的内容。

答案3

#!/bin/sh
mv eg.txt eg.input
awk 'NR==FNR{a[++i]=$2;next}{sub("#P#",a[++j]);print>(FILENAME".new")}' eg.input ./*.txt &&
for f in *.txt; do mv "$f.new" "$f"; done
mv eg.input eg.txt

eg.txt重命名为eg.input,然后返回,以便*.txtawk 行仅扩展到应修改的文件。

NR==FNR{    #For the first file, eg.input
  a[++i]=$2   #Put the second field in the array `a`
  next        #Skip the rest of the code
}
{                        #For the other files
  sub("#P#",a[++j])        #Make the substitution
  print>(FILENAME".new")   #Print to the line to `FILENAME`.new
}

然后,在 for 循环中,旧*.txt文件内容将被文件内容覆盖*.new。您可能想要抑制 for 循环,直到确信*.new文件正确为止。


某些 awk 实现不能处理许多打开的文件(GNU awk 可以)。如果您的 awk 退出时出现“打开文件过多”错误,请使用此变体,

awk 'NR==FNR{a[++i]=$2;next}FNR==1{close(fn);fn=FILENAME".new"}{sub("#P#",a[++j]);print>fn}'

答案4

eg.txt

opq  111
rst  222
uvw  333
xyz  444

a.txt

a#P#b
12345
apple

b.txt

c#P#d
56788

命令

j=1;for i in "a.txt" "b.txt" ; do  b=`sed -n ''$j'p' eg.txt| awk '{print $2}'`;sed "s/#P#/$b/g" $i;echo "=================";j=$(($j+1)); done


output

below are the output of a.txt
a111b
12345
apple
=================
below are the output of b.txt
c222d
56788
=================

相关内容