我有 2 个文件(FileA 和 FileB),
文件A:
s12 >g01
s16 >g02
s48 >g03
s52 >g04
s80 >g05
s81 >g06
s87 >g07
s91 >g08
s92 >g09
s93 >g10
s94 >g11
s96 >g12
s97 >g13
s98 >g14
s99 >g15
s100 >g16
文件B:
s12:1148.1652412 [PCC6803]
ABCDEFGHIJKLMNOPQRST
s16:1235.1653193 [PCC6803]
UVWXYZABCDEFGHIJKLMN
s48:5877.1652308 [PCC6803]
OPQRSTUVWXYZABCDEFGH
.
.
.
我想编辑 FileB,以便 FileB 中存在的所有“FileA 的第 1 列字符串”将更改为“FileA 的第 2 列字符串”
期望的输出:
>g01 [PCC6803]
ABCDEFGHIJKLMNOPQRST
>g02 [PCC6803]
UVWXYZABCDEFGHIJKLMN
>g03 [PCC6803]
OPQRSTUVWXYZABCDEFGH
我需要对大约 20 个与 FileB 格式相同的文件进行编辑。
有没有什么命令可以进行这种编辑?是同时执行还是在 Linux 终端中使用一行命令?提前致谢!
更新:我已经尝试过以下示例用不同的映射字符串集替换多个字符串 但它不起作用。
replacements=(
s12:\>g01
s16:\>g02
s48:\>g03
s52:\>g04
s80:\>g05
s81:\>g06
s87:\>g07
s91:\>g08
s92:\>g09
s93:\>g10
s94:\>g11
s96:\>g12
s97:\>g13
s98:\>g14
s99:\>g15
s100:\>g16
)
for row in "${replacement[@]}"; do
original="$(echo $row | cut -d: -f1)";
new="$(echo $row | cut -d: -f2)";
sed -i -e "s/${original}/${new}/g" FileB;
done
答案1
$ awk 'FNR==NR { id[$1]=$2; next } { split($1,a,":"); if (a[1] in id) $1=id[a[1]]; print }' fileA fileB
>g01 [PCC6803]
ABCDEFGHIJKLMNOPQRST
>g02 [PCC6803]
UVWXYZABCDEFGHIJKLMN
>g03 [PCC6803]
OPQRSTUVWXYZABCDEFGH
第一个块仅在读取第一个文件 ( fileA
) 时才会被触发。它将s*
字符串到字符串的映射读取到以字符串作为键的>g*
关联数组中。id
s*
仅当读取第二个文件 ( ) 时才会触发第二个块fileB
。它将把每行的第一个字段分割:
成一个临时数组a
。如果拆分结果的第一个元素是数组中的键id
,则整个第一个字段将替换为该键的值。然后打印可能修改的行。
FNR
是行号(实际上是记录号,但默认情况下记录是行)当前的文件,whileNR
是总行号。因此,如果FNR==NR
我们从第一个文件中读取。
答案2
一种方法是利用 的内容sed
形成要对 的内容进行操作的命令。s///
fileA
fileB
$ sed -Ee 's/(.*) (>.*)/s|^\1:\\S+|\2|;t/' fileA | sed -Ef - fileB
输出:
>g01 [PCC6803]
ABCDEFGHIJKLMNOPQRST
>g02 [PCC6803]
UVWXYZABCDEFGHIJKLMN
>g03 [PCC6803]
OPQRSTUVWXYZABCDEFGH
解释:
让我们从反面看问题,即更改文件B。现在编辑 fileB 的第一行的 sed 命令会是什么样子?
- 类似这样的事情:
s/^s12:\S+/>g01/
然后你就完成了这一行。因此,您标记一个空t
行来告诉 sed 该行不需要进行更多编辑。 - 其余行也类似。
- 因此,我现在必须构建查看 fileA 的 sed 命令,您在其中指定了要执行的搜索 n 替换的所有可能映射。
- 所需的任务是以某种方式将 fileA 转换为有效的 sed s/// 命令,这样当它们应用于 fileB 时,我们应该得到所需的结果。
- 该任务由第一个 sed 命令执行:
s/(.*) (>.*)/s|^\1:\\S+|\2|;t/
- 第一部分:
s/(.*) (>.*)/
是 sed 替换命令的 lhs,是一个正则表达式,其中我们抓取并存储 fileA 任何给定行中的两个字段,例如,s12 >g01
So\1
Should stores12
和\2
Should store>g01
。当然,这里未提及的假设是,这些行恰好包含 2 个字段,其中有一个空格,没有前导空格,并且第二个字段以大于符号开头>
。 - 因此 fileA 的行将根据 sed 命令的 rhs
s12 >g01
进行转换。s|^s12:\S+|>g01|;t
然后将转换后的行应用于 fileB,我们得到结果。 - 为了便于理解,注释管道并查看第一个 sed 命令生成的内容,它就会开始变得清晰。 HTH。
答案3
你的sed
命令几乎是正确的。您已经定义了一个名为 的数组replacements
,但在for
循环中,您使用了replacement
。这就是它不起作用的原因。另外,您想要替换整行直到第一个空格,所以不仅仅是s/$original/$new/
.这一个应该做你想做的事:
replacements=(
s12:\>g01
s16:\>g02
s48:\>g03
s52:\>g04
s80:\>g05
s81:\>g06
s87:\>g07
s91:\>g08
s92:\>g09
s93:\>g10
s94:\>g11
s96:\>g12
s97:\>g13
s98:\>g14
s99:\>g15
s100:\>g16
)
for row in "${replacements[@]}"; do
original="$(echo $row | cut -d: -f1)";
new="$(echo $row | cut -d: -f2)";
sed -i -e "s/^${original}:[^ ]*/${new}/g" FileB;
done
现在这不是一种非常有效的方法,因为您需要为每次替换处理整个 fileB。更快的方法可能是:
$ awk 'NR==FNR{a[$1]=$2; next}{split($1, b, /:/); if(b[1] in a){$1=a[b[1]]}}1;' FileA FileB
>g01 [PCC6803]
ABCDEFGHIJKLMNOPQRST
>g02 [PCC6803]
UVWXYZABCDEFGHIJKLMN
>g03 [PCC6803]
OPQRSTUVWXYZABCDEFGH
并更改多个文件名:
awk 'NR==FNR{
a[$1]=$2;
next
}
{
split($1, b, /:/);
if(b[1] in a){
$1=a[b[1]]
};
print > FILENAME".fixed"
}' FileA FileB FileC FileD ... FileN
这将创建fileB.fixed
,fileC.fixed
等fileD.fixed
直到FileN.fixed
。如果您对它的工作感到满意,您可以将它们重命名回原始文件名(假设您有 perl-rename,这是 Ubuntu 和 Debian 上的默认设置):
rename 's/fixed//' *fixed
或者,如果您没有perl-rename
:
for f in *fixed; do mv -- "$f" "${f%%.fixed}"; done
答案4
只需使用一次 GNU sed 调用即可完成此操作。您可以以 FileB 的格式提供任意数量的文件,而不是 FileB,但必须首先提供 FileA。为了安全起见,该命令将对输入文件进行备份。如果您对修改后的文件感到满意,则可以在此之后删除备份文件。
sed -ri.bk '1{x;s:^:cat /dev/fd/3:e;x};/:/{G;s/^([^:]+)\S+(\s+)([^\n]+).*\1\s+(>[^\n]+).*/\4\2\3/}' 3< FileA FileB
感谢@Stéphane Chazelas 给了我使用自定义文件描述符的想法,以解决使用 -i 时每个新文件上的保留空间被丢弃的问题。