【问题标题】:replace in-place using perl changed soft link/symlink to static file使用 perl 就地替换将软链接/符号链接更改为静态文件
【发布时间】:2019-04-06 06:45:04
【问题描述】:

给定以下命令:

git ls-files | xargs perl -i -pe 's/SEARCHTERM/REPLACETERM/g'

所有作为符号链接的 perl 输出(来自 git ls-files)现在都是目标文件的副本

我有两个问题:

1) 我想我模糊地理解为什么会发生这种情况,但只是模糊地。有人可以详细解释一下吗?并提出避免这种情况的最佳机制?预期的行为是符号链接目标将是读取和写入的目标,而不仅仅是读取。

2) 是否有更好的通用方法在本地 git 分支上进行搜索和替换?

可能值得注意的是,当我想确保处理文件的 内容 而不是文件列表时,我的 bash 非常初级,xargs 只是我的默认 goto。除此以外没有使用。

【问题讨论】:

    标签: bash git perl


    【解决方案1】:

    上面写着perlrun

    请注意,由于-i 在创建同名新文件之前会重命名或删除原始文件,因此不会保留 Unix 样式的软链接和硬链接。

    所以-i 无法做到这一点。

    这是 Perl 的另一种方式(标记为 -- 即使有一个干净的解决方案,sed

    我使用文件a.txtb.txt、它们的符号链接(ln -s a.txt ln_a.txt 等)和c.txt(任何内容都可以用于此测试),并在文件中列出链接名称和c.txt

    ln_a.txt ln_b.txt c.txt # 文件“input_list.txt”

    input_list.txt 中的文件/链接名称可以用空格或换行符分隔。

    然后,在每个输入文件的第一行打开一个临时输出文件,并将每个已处理的行写入其中。一旦到达输入文件的末尾,将该临时输出重命名为其输入文件,或者如果它是一个链接,则重命名为它的目标。因此,对于每个输入文件,使用输出文件覆盖文件或其目标(如果链接)。

    cat input_list.txt | xargs perl -MPath::Tiny -ne'
        if ($.==1) { $tf = $ARGV."_tmp.$$"; $fh = path($tf)->openw };
        s/(\w+)/$1-NEW/; 
        print $fh $_; 
        if (eof) { close ARGV; rename $tf, (-l $ARGV ? readlink $ARGV : $ARGV) }
    '
    

    这会更改目标的内容并单独保留链接。它也适用于常规文件。

    临时输出文件名 (filename_tmp.$$) 可以使用 File::Temp 正确生成,或者更确切地说是使用 Path::Tiny::tempfile,因为该模块已被使用。

    为了可移植性,rename 可能应该从 File::Copy 更改为 move

    使用的eof 检查每个输入文件的文件是否已用尽,此时输出文件被重命名为输入文件或其目标。 -l 是一个file-test operator,用于测试手头文件是否为符号链接,如果是,则readlink 解析链接。

    此时rename输入文件或目标是安全的,因为它已被读取和处理。

    $ARGV 是名称 当前处理的文件,ARGV 是它的文件句柄。

    显式close ARGV 重置行计数器,因此我们可以通过测试行号计数器$.1 来打开每个新输入文件开头的临时输出。

    【讨论】:

    • 谢谢。我希望我永远不必使用它,但如果我这样做了,它将是无价的。
    • @zzxyz 是的,很高兴有 sed 功能和揭开它的答案:)。这只是-i 的一个简单(有点)实现,它尊重符号链接。它包含一堆我希望通常有用的花絮。
    【解决方案2】:

    如果您不反对使用 sed,请试一试:

    git ls-files | xargs -I{} -P4 sed --follow-symlinks -i'' 's/SEARCHTERM/REPLACETERM/g' {} 
    

    【讨论】:

    • 同样的结果,恐怕。 (虽然我不得不删除'' fwiw)
    • 是的,有效。谢谢! “follow-symlinks”也是一个有用的谷歌搜索,让我找到了这个:unix.stackexchange.com/questions/9318/…
    • @zzxyz 老板真漂亮! Def 将阅读有关 moreutils 的更多信息。
    【解决方案3】:

    另一个 Perl 选项:

    git ls-files | xargs perl -MPath::Tiny -Mutf8 -E'
      path($_)->realpath->edit_lines_utf8(sub { s/SEARCHTERM/REPLACETERM/g })
      for @ARGV'
    

    或者没有 xargs,只从 STDIN 读取文件名:

    git ls-files | perl -MPath::Tiny -Mutf8 -E'
      path($_)->realpath->edit_lines_utf8(sub { s/SEARCHTERM/REPLACETERM/g })
      for map { chomp; $_ } readline'
    

    realpath 确保您始终使用符号链接目标,edit_lines 本质上是 -i 选项的 Path::Tiny 实现。使用 edit_lines_utf8-Mutf8 意味着您的源代码(搜索和替换术语)和文件内容将在运行搜索/替换时从 UTF-8 解码,这通常很有帮助(但如果您的文件不是UTF-8 编码,删除-Mutf8 并使用edit_lines)。您还可以通过在for 之后添加grep { -T } 来过滤掉基于-T heuristic 的非文本文件。

    作为奖励,edit_lines 将始终输出到一个新文件,然后将其重命名为原始文件,这比破坏原始文件(-i didn't do until Perl 5.28)更安全。

    CAVEAT:Path::Tiny 将根据你当前的 umask 设置它编辑的所有文件的权限,而不是现有的权限;我打开an issue 看看它是否被认为是一个错误。

    【讨论】:

      【解决方案4】:

      你只需要

      git ls-files | xargs readlink -e | xargs perl -i -pe'...'
      

      您也可以在 Perl 中展开链接。

      git ls-files | xargs perl -i -pe'BEGIN { @ARGV = map readlink($_) // $_, @ARGV } ...'
      

      【讨论】:

        【解决方案5】:

        我最初的解决方案是让符号链接被破坏,然后运行:

        git status --porcelain | awk '{if ($1 == "T"){print $2}}' | xargs git checkout
        

        这对于各种工具非常有用,如果它们碰巧是偶然的并且与搜索并不真正相关,它们可能会破坏符号链接。

        这里的链接有一些有趣的想法: https://unix.stackexchange.com/questions/9318/is-there-a-way-to-make-perl-i-not-clobber-symlinks

        我认为,zdim 的答案是其中之一的最佳实现。不过有点毛茸茸。

        该链接上的另一个答案,使用sponge,有效,但创建了一个非常复杂的 xargs 管道。我相信它还需要使用xargs -n1,这是在大型文件系统上运行 perl 的一种灾难性缓慢的方式。所以这是不行的。

        Rafael 的回答(只使用 sed)可能是……解决这个问题的最明智的方法 :)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-10-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-15
          • 2014-01-11
          • 2012-12-08
          相关资源
          最近更新 更多