【问题标题】:how to replace a block of text with new block of text?如何用新的文本块替换一个文本块?
【发布时间】:2021-08-29 12:52:13
【问题描述】:

正如问题标题所指定的,我必须用新的文本块替换文件中的文本块

我已经到处寻找这个东西,但我找到的每一个解决方案都对这个问题来说太具体了。难道不能创建一个灵活/可重用的函数吗?

要非常具体,我需要一些具有类似选项的东西

1) File ( where changes are to be done )
2) Exiting block of text
3) New block of text
( 2nd & 3 option could be either as manually pasted text or cat $somefile)

我可以更改这 3 个并将脚本用于所有文本块替换的情况,我相信它也会帮助许多其他人

例如,目前我需要用底部的一个文本块替换下面的文本块,并说文件是 $HOME/block.txt 。虽然我需要上面提到的易于重复使用/灵活的解决方案

- name: Set default_volumes variable
  set_fact:
    default_volumes:
      - "/opt/lidarr:/config"
      - "/opt/scripts:/scripts"
      - "/mnt:/mnt"
      - "/mnt/unionfs/Media/Music:/music"
- name: Set default_volumes variable
  set_fact:
    default_volumes:
      - "/opt/lidarr:/config"
      - "/opt/scripts:/scripts"
      - "/mnt:/mnt"
      - "/mnt/unionfs/Media/Music:/music"
      - "/mnt/unionfs/downloads/lidarr:/downloads-amd"

PS / 替换时我需要保留间距和缩进。

【问题讨论】:

  • 一个天真的解决方案是 a) 将文件读入变量 b) 用 s{\$original_text}{$replacement_text}s 进行替换 c) 将更改后的变量保存到文件中。在给定文件名、原始文本和替换文本的情况下,创建一个非常短的函数非常简单。
  • @SteffenUllrich 你的意思是{\Q$original_text} 而不是{\$original_text}
  • @Sundeep:是的,当然。感谢您纠正我。
  • 这看起来像 YAML。因此,最好的方法是 a) 将 YAML 读入 Perl 数据结构(使用 YAML.pm),b) 以您需要的任何方式更改 Perl 数据结构,c) 将数据结构转换回 Perl 并写入一个文件。
  • 这是YAML,最好使用该数据格式的库进行处理

标签: perl awk yaml text-processing


【解决方案1】:

您的数据使用YAML 进行序列化。你应该这样对待它。

使用yq

yq eval '
   .[0].set_fact.default_volumes +=
      [ "/mnt/unionfs/downloads/lidarr:/downloads-amd" ]
'

yq 本身不支持就地编辑,但您可以使用sponge 来实现相同的目的。

yq eval '
   .[0].set_fact.default_volumes +=
      [ "/mnt/unionfs/downloads/lidarr:/downloads-amd" ]
' a.yaml | sponge a.yaml

使用 Perl

perl -MYAML -0777ne'
   my $d = Load($_);
   push @{ $d->[0]{set_fact}{default_volumes} },
      "/mnt/unionfs/downloads/lidarr:/downloads-amd";
   print Dump($d);
'

根据specifying file to process to Perl one-liner,就地编辑将如下所示:

perl -i -MYAML -0777ne'
   my $d = Load($_);
   push @{ $d->[0]{set_fact}{default_volumes} },
      "/mnt/unionfs/downloads/lidarr:/downloads-amd";
   print Dump($d);
' file.yaml

【讨论】:

    【解决方案2】:

    将 GNU awk 用于多字符 RS 和 ARGIND,这将适用于旧文本或新文本中的任何字符,包括正则表达式元字符、分隔符、引号和反向引用,因为它只是进行文字字符串搜索和替换:

    awk -v RS='^$' -v ORS= '
        ARGIND==1 { old=$0; next }
        ARGIND==2 { new=$0; next }
        s=index($0,old) {
            $0 = substr($0,1,s-1) new substr($0,s+length(old))
        }
    1' old new file
    

    或者你可以在每个 Unix 机器上的任何 shell 中使用任何 awk 来做同样的事情:

    awk -v ORS= '
        { rec = (FNR>1 ? rec RS : "") $0 }
        FILENAME==ARGV[1] { old=rec; next }
        FILENAME==ARGV[2] { new=rec; next }
        END {
            $0 = rec
            if ( s=index($0,old) ) {
                $0 = substr($0,1,s-1) new substr($0,s+length(old))
            }
            print
        }
    ' old new file
    

    例如:

    $ head old new file
    ==> old <==
    - name: Set default_volumes variable
      set_fact:
        default_volumes:
          - "/opt/lidarr:/config"
          - "/opt/scripts:/scripts"
          - "/mnt:/mnt"
          - "/mnt/unionfs/Media/Music:/music"
    
    ==> new <==
    - name: Set default_volumes variable
      set_fact:
        default_volumes:
          - "/opt/lidarr:/config"
          - "/opt/scripts:/scripts"
          - "/mnt:/mnt"
          - "/mnt/unionfs/Media/Music:/music"
          - "/mnt/unionfs/downloads/lidarr:/downloads-amd"
    
    ==> file <==
    foo
    - name: Set default_volumes variable
      set_fact:
        default_volumes:
          - "/opt/lidarr:/config"
          - "/opt/scripts:/scripts"
          - "/mnt:/mnt"
          - "/mnt/unionfs/Media/Music:/music"
    bar
    

    $ awk -v RS='^$' -v ORS= 'ARGIND==1{old=$0; next} ARGIND==2{new=$0; next} s=index($0,old){ $0=substr($0,1,s-1) new substr($0,s+length(old))} 1' old new file
    foo
    - name: Set default_volumes variable
      set_fact:
        default_volumes:
          - "/opt/lidarr:/config"
          - "/opt/scripts:/scripts"
          - "/mnt:/mnt"
          - "/mnt/unionfs/Media/Music:/music"
          - "/mnt/unionfs/downloads/lidarr:/downloads-amd"
    bar
    

    【讨论】:

    • 这真的很好用,正是我所需要的。只是一个最终的编辑请求-而不是在最后打印最终结果,你可以让它编辑文件吗?谢谢
    • 没有 Unix 文本操作工具(awk、sed、perl、ruby 等)实际上就地编辑文件。有些人有一个-i 或类似的选项来告诉他们在后台创建一个临时文件,但他们仍在使用临时文件。您可以使用tmp=$(mktemp) &amp;&amp; cmd 'script' file &gt; "$tmp" &amp;&amp; mv -- "$tmp" file 对任何有或没有-i 选项的命令手动执行完全相同的操作。 GNU awk 有 -i inplace 就像 GNU sed 有 -i 但是你需要添加 prints 来重新创建 oldnew 文件或以其他方式增加复杂性所以我建议你只创建手动创建临时文件。
    • Re "对于任何有或没有-i 选项的命令,您都可以手动执行完全相同的操作",或者使用some_cmd a.yaml | sponge a.yaml
    【解决方案3】:

    对于这样的任务,您可以只使用现有命令而不是 重新发明轮子:

    sed '/some text to change/,/with indentation/d; /a bit more/r new_file' your_file
    

    我使用了两个示例文件:

    # original file
    some original text to keep
    a bit more
    some text to remove
        - with indentation
    rest of original text
    is kept
    

    和:

    # replacement text
    SOME TEXT TO ADD
        - WITH DIFFERENT INDENTATION
            - ANOTHER LEVEL
    

    然后该命令通过首先删除两行之间的行来工作 线条匹配模式:

    sed '/some text to change/,/with indentation/d;
    

    然后使用模式从其他文件中读取替换文本 仅匹配旧文本开始的位置:

    /a bit more/r new_file' your_file
    

    产生结果:

    some original text to keep
    a bit more
    SOME TEXT TO ADD
        - WITH DIFFERENT INDENTATION
            - ANOTHER LEVEL
    rest of original text
    is kept
    

    编辑

    以上方式比我原来的方式好:

    sed '/a bit more/q' your_file > composite; cat new_file >> composite; sed -n '/rest of original text/,/$/p' your_file >> composite
    

    【讨论】:

    • sed 不是一个好的选择,因为您必须转义您尝试替换的文本中的每个正则表达式元字符、/'(请参阅is-it-possible-to-escape-regex-metacharacters-reliably-with-sed ) 并且范围表达式是一种不好的方法,因为您不能假设 /foo/,/bar/ 唯一标识 foo 和 bar 之间要替换的多行文本块。
    猜你喜欢
    • 2020-01-09
    • 2012-08-22
    • 2019-08-27
    • 2019-10-02
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多