AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 784361
Accepted
ScottishTapWater
ScottishTapWater
Asked: 2024-10-02 09:36:38 +0800 CST2024-10-02 09:36:38 +0800 CST 2024-10-02 09:36:38 +0800 CST

不使用临时文件替换文件中的字符串

  • 772

我有一个多 GB 的文本文件,我想用 替换utf8mb4_0900_ai_ci其中的所有utf8mb4_unicode_520_ci。

通常,我会sed -i按照这里的建议使用它:使用 SED 在文件中查找并替换字符串,而不使用临时文件

但是,这会在后台创建一个临时文件,而我需要在没有磁盘空间支持该操作的环境中进行此替换。

我如何才能就地修改文件?

bash
  • 1 1 个回答
  • 73 Views

1 个回答

  • Voted
  1. Best Answer
    meuh
    2024-10-02T20:22:34+08:002024-10-02T20:22:34+08:00

    只是为了好玩,我尝试了一个就地替换 bash 脚本,myreplace。显然,在未先保存原始数据并进行大量测试的情况下,请勿使用此脚本。它可能会对超过 4G 字节的文件产生问题,因为数字超过 32 位。此外,如果有数百万个匹配项,tac将耗尽内存或临时文件空间。我还必须编写一个小的 perl 脚本来执行seek(2),但肯定已经有一个了。

    #!/bin/bash
    # https://unix.stackexchange.com/q/784361/119298
    file=${1?}
    str1=utf8mb4_0900_ai_ci
    str2=utf8mb4_unicode_520_ci
    
    len1=${#str1}
    len2=${#str2}
    let len3=len2-len1
    if [ "$len3" -lt 0 ]
    then echo "bad len $len3. dont need this script"; exit 1
    fi
    echo "2nd str bigger by $len3"
    
    # grep -c counts lines so ignores 2 matches on a line, not what we want
    nummatches=$(grep -a -o -b -F "$str1" "$file" | wc -l)
    let need=nummatches*len3
    echo "$nummatches matches, need $need bytes"
    filesize=$(stat --format=%s "$file")
    echo "filesize $filesize"
    let src=filesize
    let dest=filesize+need
    let i=nummatches
    
    # open 2 filedescriptors on same file, to read from and write at
    exec {fdr}<"$file" {fdw}<>"$file"
    seek <&$fdr $src; seek <&$fdw $dest # seek to both eofs
    
    blocksize=10240 # arbitrary optimisation
    # move overlapping from,to,numbytes
    domove(){
        local from=${1?} to=${2?} numbytes=${3?} partlen
        while [ $numbytes -gt 0 ]
        do  if [ $numbytes -gt $blocksize ]
            then    partlen=$blocksize
            else    partlen=$numbytes
            fi
            seek <&$fdr -$partlen; seek <&$fdw -$partlen
            dd <&$fdr >&$fdw ibs=$partlen count=1 iflag=fullblock status=none
            seek <&$fdr -$partlen; seek <&$fdw -$partlen
            let numbytes=numbytes-partlen
        done
        seek <&$fdw -$len2
        printf "%s" "$str2" >&$fdw
        seek <&$fdw -$len2
        seek <&$fdr -$len1
    }
    
    grep -a -o -b -F "$str1" "$file" |
    sed 's/:.*//' |
    tac |
    while read offset
    do  echo "match $i at src $offset"
        let tomove="src-(offset+len1)"
        echo "move all from $offset+$len1 .. $src ($tomove bytes) to $dest-$tomove"
        echo "insert $len2 bytes of 2nd string to $dest-$tomove-$len2"
        echo "skip back over $len1 bytes of 1st string"
        domove $(($offset+$len1)) $(($dest-$tomove)) $tomove
        let src=$offset
        let dest=dest-tomove-len2
        let i=i-1
    done
    
    

    原理是使用grep找到匹配的字节偏移量,然后使用tac反转此列表,以便我们从末尾开始。我们在文件上打开 2 个文件描述符。fdr将是我们当前的读取位置和fdw写入位置。它们都从文件末尾开始,但位于新的名义末尾,该末尾fdw比 更远 ,即替换字符串的长度差为 倍。nummatcheslen3

    我们使用函数domove在读取器上回溯一个数量,在写入器上回溯相同的数量,读取并将该数量复制到写入器。然后我们需要再次回溯到我们的新位置。

    我们在读取器中回溯以跳过旧字符串。在写入器中我们回溯,写入替换字符串,然后回溯覆盖它。

    我创建了一个演示文件来测试(str1来自脚本):

    file=/tmp/myfile
    man bash | sed 's/ brace / '"$str1"' /g' >"$file"
    cp "$file" /tmp/orig
    ./myreplace "$file"
    diff -u /tmp/orig "$file"
    

    我的 perl 有点生疏了,但是这里是 perl 脚本“seek”:

    #!/usr/bin/perl
    # seek on stdin to given position
    use strict;
    use Fcntl 'SEEK_SET','SEEK_CUR';
    sub usage{
        printf STDERR "usage: [+|-]9999  where sign means relative\n";
        exit 1;
    }
    my $offset = shift @ARGV;
    my $flag = SEEK_SET;
    my $sign = 1;
    if($offset =~ s/^-//){$flag = SEEK_CUR; $sign = -1;}
    elsif($offset =~ s/^\+//){$flag = SEEK_CUR;}
    if($offset!~/^\d+$/){ usage(); }
    usage() if(scalar @ARGV!=0);
    $offset *= $sign;
    if(!seek(STDIN,$offset,$flag)){
        printf STDERR "failed to seek to $offset: $!\n";
        exit 2;
    }
    
    • 1

相关问题

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve