运行bash/perl代码替换多个文件中的字符串的奇怪效果

vpfxa7rd  于 2023-02-09  发布在  Perl
关注(0)|答案(5)|浏览(162)

我有以下三个文件,内容如下:

1.txt             2.txt           3.txt
    ------------------------------------------
    1.txt             2.txt           3.txt
    text sample       text sample     text sample
    sample text       sample text     sample text

在使用单个bash命令行和perl代码编写代码以将所有三个文件中的单词“sample”替换为单词“changed”时,我意识到为了修复使用以下代码时遇到的问题,我错过了理解bash和/或perl的一些重要部分:

$ perl -pe 's/sample/changed/' < 1.txt 2.txt 3.txt > 1a.txt 2a.txt 3a.txt

执行上面一行的结果是:

1a.txt            2a.txt          3a.txt
    ------------------------------------------
    2.txt               NOT             NOT
    text changed      CREATED         CREATED
    changed text
    3.txt
    text changed
    changed text

为了解决以下问题:

Can't open 2a.txt: No such file or directory, <> line 6.
Can't open 3a.txt: No such file or directory, <> line 6.

我已将代码更改为:

$ perl -pe 's/sample/changed/' < 1.txt 2.txt 3.txt > 1a.txt > 2a.txt > 3a.txt

其运行没有任何抱怨,但作为结果给出:

1a.txt            2a.txt          3a.txt
    ------------------------------------------
    EMPTY             EMPTY           2.txt
     FILE              FILE           text changed
                                      changed text
                                      3.txt
                                      text changed
                                      changed text

而不是我所期望的

1a.txt            2a.txt          3a.txt
    ------------------------------------------
    1.txt             2.txt           3.txt
    text changed      text changed    text changed
    changed text      changed text    changed text

在这方面,我的问题是:

我在理解bash和perl是如何工作的,以便提供正确的命令来执行替换时,我遗漏了什么信息?尤其是,我遗漏了什么信息,无法理解文件1.txt的结果是如何不以两种大写形式写入输出文件的?

wpx232ag

wpx232ag1#

缺失的信息:重定向只接受一个文件。
如何修复代码:使用-i标志。

perl -i~ -pe 's/sample/changed/' -- 1.txt 2.txt 3.txt

它将“原地”更改文件,保留1. txt ~、2. txt ~和3. txt ~作为备份。

cwdobuhd

cwdobuhd2#

perl -pe's/sample/changed/' <1.txt 2.txt 3.txt >1a.txt 2a.txt 3a.txt

等于

perl -pe's/sample/changed/' 2.txt 3.txt 2a.txt 3a.txt <1.txt >1a.txt

显然错了。
因为只有一个标准输出。

perl -pe's/sample/changed/' <1.txt 2.txt 3.txt >1a.txt >2a.txt >3a.txt

等于

perl -pe's/sample/changed/' 2.txt 3.txt <1.txt >3a.txt

显然错了。
你可以用

for f in 1.txt 2.txt 3.txt; do
   perl -pe's/sample/changed/' "$f" >"$f.new"
done

如果确实需要1a.txt,可以使用以下命令:

for f in {1..3}; do
    perl -pe's/sample/changed/' "$f.txt" > "${f}a.txt"
done

for f in 1.txt 2.txt 3.txt; do
   d="$( dirname "$f" )
   b="$( basename "$f" .txt)
   nf="$d/${b}a.txt"
   perl -pe's/sample/changed/' "$f" >"$nf"
done
atmip9wb

atmip9wb3#

你需要理解文件描述符的概念。默认情况下,shell和大多数程序将输出发送到FD 1,将stderr发送到FD 2,并从FD 0读取输入。简化后的重定向就是分配这些文件描述符所代表的文件或设备。如果你指定了多个>,那么只有最后一个文件描述符才能被FD 1有效地表示。至少在概念上是这样。重定向也是由shell管理的,而不是由被调用的程序管理的。
如果希望单独处理1.txt2.txt3.txt,并将结果保存在它们的“a”对应项上,可以使用循环。

for f in {1..3}; do
    perl -pe 's/sample/changed/' "$f.txt" > "${f}a.txt"
done

我认为这也可以在Perl内部完成,但我不了解Perl。

tez616oj

tez616oj4#

您遗漏的信息以及导致您混淆的信息是对shell重定向缺乏正确的理解。

在此情况下,必须理解:

*重定向是一个独立的命令/指令,不需要命令行上的任何其他内容即可执行。因此,**独立的~ $ > fname.ext**可以

    • 创建一个新的fname.ext文件(如果它还不存在)
    • 截断现有文件(!删除其全部内容!
      *shell重定向由shell管理,而不是由被调用的程序管理
      *重定向发生在单个指令中的何处并不重要。因此
    • > txt.1 echo "sample text"的作用与echo "sample text" > txt.1相同
      *重定向仅采用一个文件因此< 1.txt 2.txt 3.txt仅使1.txt文件的内容在stdin上可用(不包括2.txt和3.txt的内容)
      *重定向发生(执行)在其他操作之前
  • 在命令行文本中的任意位置使用> file.ext(也包括单独出现的位置)将导致:
    • 如果file.ext不存在,则创建它
    • 截断file.ext(如果存在)(使其为空/无内容)
    • stdout分配给文件

现在,与所有上述牢记让我们解释它是如何来你在你的问题中描述:
执行时的原因

$ perl -pe 's/sample/changed/' < 1.txt 2.txt 3.txt > 1a.txt 2a.txt 3a.txt

未创建文件2a.txt和3a.txt的原因是重定向仅采用一个文件,即1a.txt,因此将2a.txt和3a.txt作为参数传递给perl,perl随后抱怨它们不存在。
执行时的原因

$ perl -pe 's/sample/changed/' < 1.txt 2.txt 3.txt > 1a.txt > 2a.txt > 3a.txt

文件1.txt被pearl从考虑中跳过是重定向< 1.txt,它从传递给pearl的命令行参数列表中取出1.txt文件。将stdin分配给1.txt在这里没有效果,因为pearl将不使用stdin,因为它发现2.txt和3.txt作为文件使用作为输入,因此忽略stdin。
1a.txt和2a.txt为空的原因是重定向> 1a.txt> 2a.txt创建/截断了这些文件。
3.txt文件包含perl脚本输出,因为命令行是由bash从左到右评估的,结果是对stdout的最后一次分配是将3a.txt文件分配给它,然后对stdout的所有perl输出都转到3a.txt。

ac1kyiln

ac1kyiln5#

主要的问题,关于重定向,在这个例子中,已经得到了回答。然而,如果有更多的事情要做,在shell中处理重定向可能会变得混乱,我想提醒的是,一旦Perl程序 * 是 * 无论如何,它是简单的,也处理它的文件。
因此,读取和处理输入文件以及写入相应的输出文件都是用Perl一个接一个地完成的。

perl -MPath::Tiny -we'
    path("new_$_")->spew( path($_)->slurp =~ s/sample/changed/gr ) 
        for @ARGV;
' *.txt

这使用一个库来方便地读写文件,我使用Path::Tiny。还有其他库可以完成这些任务,或者可以手工完成(简单但更冗长)。
一些细节

  • @ARGV包含命令行参数,因此这里是要处理的文件名
  • Path::Tiny库通过为现有文件或新(要写入的)文件创建对象path($filename)并对其应用方法来操作
  • path($_)->slurp将名为$_的文件(来自@ARGV)读入一个字符串,正则表达式可以直接绑定到该字符串
  • 使用修饰符/r时,正则表达式返回经过处理的字符串
  • 返回的字符串被写入到path("new_$_")->spew(...)文件中

相关问题