มีวิธีแก้ไขไฟล์ในสถานที่หรือไม่?


54

ฉันมีไฟล์ขนาดใหญ่พอสมควร (35Gb) และฉันต้องการกรองไฟล์นี้ในแหล่งกำเนิด (เช่นฉันมีพื้นที่ดิสก์ไม่เพียงพอสำหรับไฟล์อื่น) โดยเฉพาะฉันต้องการ grep และไม่สนใจรูปแบบบางอย่าง - มีวิธีในการ ทำเช่นนี้โดยไม่ใช้ไฟล์อื่น

สมมติว่าฉันต้องการกรองบรรทัดทั้งหมดที่มีfoo:ตัวอย่างเช่น ...


3
@Tshepang: ฉันคิดว่าเขาต้องการที่จะเขียนกลับไปที่ไฟล์เดียวกัน
Faheem Mitha

5
"in situ" เป็นคำภาษาละตินหมายถึง "ในสถานที่" ตามตัวอักษร "ในตำแหน่ง"
Faheem Mitha

3
ในกรณีนั้นคำถามควรชัดเจนกว่ามีวิธีแก้ไขไฟล์ในสถานที่หรือไม่?
tshepang

5
@Tepepang, "in situ" เป็นวลีที่ใช้กันทั่วไปในภาษาอังกฤษเพื่ออธิบายว่า - ฉันคิดว่าชื่อค่อนข้างอธิบายตนเอง ... @Gilles ฉันคิดมากและง่ายต่อการรอพื้นที่ดิสก์เพิ่มเติม! ;)
Nim

2
@Nim: ดีฉันคิดว่าในสถานที่เป็นเรื่องธรรมดามากขึ้นกว่าในแหล่งกำเนิด
tshepang

คำตอบ:


41

ในระดับการเรียกระบบนี้ควรจะเป็นไปได้ โปรแกรมสามารถเปิดไฟล์เป้าหมายของคุณเพื่อเขียนโดยไม่ต้องตัดทอนและเริ่มเขียนสิ่งที่อ่านจาก stdin เมื่ออ่าน EOF ไฟล์เอาต์พุตสามารถถูกตัดทอน

เนื่องจากคุณกำลังกรองบรรทัดจากอินพุตตำแหน่งการเขียนไฟล์เอาต์พุตควรน้อยกว่าตำแหน่งการอ่านเสมอ ซึ่งหมายความว่าคุณไม่ควรทำให้อินพุตของคุณเสียหายด้วยเอาต์พุตใหม่

อย่างไรก็ตามการค้นหาโปรแกรมที่เป็นปัญหา dd(1)มีตัวเลือกconv=notruncที่ไม่ตัดทอนไฟล์เอาต์พุตเมื่อเปิด แต่ก็ไม่ตัดทอนในตอนท้ายโดยปล่อยให้เนื้อหาไฟล์ต้นฉบับอยู่หลังเนื้อหา grep (ด้วยคำสั่งเช่นgrep pattern bigfile | dd of=bigfile conv=notrunc)

เนื่องจากมันง่ายมากจากมุมมองการโทรของระบบฉันจึงเขียนโปรแกรมขนาดเล็กและทดสอบในระบบไฟล์ลูปแบ็คเต็มรูปแบบขนาดเล็ก (1MiB) มันทำในสิ่งที่คุณต้องการ แต่คุณต้องการทดสอบกับไฟล์อื่นก่อน การเขียนทับไฟล์จะมีความเสี่ยงเสมอ

overwrite.c

/* This code is placed in the public domain by camh */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
        int outfd;
        char buf[1024];
        int nread;
        off_t file_length;

        if (argc != 2) {
                fprintf(stderr, "usage: %s <output_file>\n", argv[0]);
                exit(1);
        }
        if ((outfd = open(argv[1], O_WRONLY)) == -1) {
                perror("Could not open output file");
                exit(2);
        }
        while ((nread = read(0, buf, sizeof(buf))) > 0) {
                if (write(outfd, buf, nread) == -1) {
                        perror("Could not write to output file");
                        exit(4);
                }
        }
        if (nread == -1) {
                perror("Could not read from stdin");
                exit(3);
        }
        if ((file_length = lseek(outfd, 0, SEEK_CUR)) == (off_t)-1) {
                perror("Could not get file position");
                exit(5);
        }
        if (ftruncate(outfd, file_length) == -1) {
                perror("Could not truncate file");
                exit(6);
        }
        close(outfd);
        exit(0);
}

คุณจะใช้มันเป็น:

grep pattern bigfile | overwrite bigfile

ฉันมักจะโพสต์สิ่งนี้เพื่อให้ผู้อื่นแสดงความคิดเห็นก่อนที่คุณจะลอง บางทีคนอื่นอาจรู้จักโปรแกรมที่ทำสิ่งที่คล้ายกันซึ่งผ่านการทดสอบมากกว่า


ฉันต้องการที่จะดูว่าฉันสามารถหนีไปได้โดยไม่ต้องเขียนอะไรให้มัน! :) ฉันเดาว่านี่จะเป็นการหลอกลวง! ขอบคุณ!
Nim

2
+1 สำหรับ C; ดูเหมือนจะใช้งานได้ แต่ฉันเห็นปัญหาที่อาจเกิดขึ้น: ไฟล์กำลังอ่านจากด้านซ้ายในขณะที่ด้านขวากำลังเขียนไปยังไฟล์เดียวกันและถ้าคุณไม่ประสานงานทั้งสองกระบวนการคุณจะมีปัญหาเขียนทับเหมือนเดิม บล็อก อาจดีกว่าสำหรับความสมบูรณ์ของไฟล์ในการใช้ขนาดบล็อกที่เล็กลงเนื่องจากเครื่องมือหลักส่วนใหญ่จะใช้ 8192 ซึ่งอาจทำให้โปรแกรมช้าลงพอที่จะหลีกเลี่ยงความขัดแย้ง (แต่ไม่สามารถรับประกันได้) อาจอ่านส่วนที่มีขนาดใหญ่กว่าลงในหน่วยความจำ (ไม่ใช่ทั้งหมด) และเขียนในบล็อกขนาดเล็ก ยังสามารถเพิ่ม nanosleep (2) / usleep (3)
Arcege

4
@Arcege: การเขียนไม่ได้ทำในบล็อก หากกระบวนการอ่านของคุณมีการอ่าน 2 ไบต์และกระบวนการเขียนของคุณเขียน 1 ไบต์เฉพาะไบต์แรกเท่านั้นที่จะเปลี่ยนและกระบวนการอ่านจะสามารถอ่านต่อได้ที่ไบต์ 3 โดยมีเนื้อหาต้นฉบับอยู่ที่จุดนั้นไม่เปลี่ยนแปลง เนื่องจากgrepจะไม่ส่งออกข้อมูลมากกว่าที่อ่านดังนั้นตำแหน่งเขียนควรอยู่ด้านหลังตำแหน่งอ่านเสมอ แม้ว่าคุณจะเขียนในอัตราเดียวกับการอ่านมันก็ยังคงโอเค ลอง rot13 ด้วยสิ่งนี้แทน grep จากนั้นอีกครั้ง md5sum ก่อนและหลังและคุณจะเห็นมันเหมือนเดิม
camh

6
ดี นี้อาจจะยังมีคุณค่าให้โจอี้เดิมของmoreutils คุณสามารถใช้ddแต่มันยุ่งยาก
Gilles 'หยุดความชั่วร้าย'

'grep รูปแบบ bigfile | เขียนทับ bigfile '- ฉันใช้งานได้โดยไม่มีข้อผิดพลาด แต่สิ่งที่ฉันไม่เข้าใจคือ - ไม่ต้องการแทนที่สิ่งที่อยู่ในรูปแบบด้วยข้อความอื่น ดังนั้นจึงไม่ควรเป็นเช่น: 'grep pattern bigfile | เขียนทับ / replace-text / bigfile '
Alexander Mills

20

คุณสามารถใช้sedเพื่อแก้ไขไฟล์ในสถานที่ (แต่สิ่งนี้จะสร้างไฟล์ชั่วคราวระดับกลาง):

หากต้องการลบบรรทัดทั้งหมดที่มีfoo:

sed -i '/foo/d' myfile

เพื่อให้ทุกบรรทัดประกอบด้วยfoo:

sed -i '/foo/!d' myfile

น่าสนใจไฟล์ temp นี้จะต้องมีขนาดเท่ากับต้นฉบับหรือไม่?
Nim

3
ใช่ดังนั้นอาจไม่ดี
pjc50

17
นี่ไม่ใช่สิ่งที่ OP ขอมาเนื่องจากสร้างไฟล์ที่สอง
Arcege

1
โซลูชันนี้จะล้มเหลวในระบบไฟล์แบบอ่านอย่างเดียวโดยที่ "อ่านอย่างเดียว" หมายความว่าคุณ$HOME จะเขียนได้ แต่/tmpจะอ่านอย่างเดียว (โดยค่าเริ่มต้น) ตัวอย่างเช่นหากคุณมี Ubuntu และคุณได้บูทเข้าสู่ Recovery Console นี่เป็นกรณีปกติ นอกจากนี้ผู้ประกอบการเอกสารที่นี่<<<จะไม่ทำงานที่นั่นอย่างใดอย่างหนึ่งเพราะมันจะต้อง/tmpเป็นr / wเพราะมันจะเขียนไฟล์ชั่วคราวลงไปที่นั่นเช่นกัน (เทียบกับคำถามนี้รวมstraceเอาท์พุท 'd)
syntaxerror

ใช่สิ่งนี้จะไม่ทำงานสำหรับฉันเช่นกันคำสั่ง sed ทั้งหมดที่ฉันได้ลองจะแทนที่ไฟล์ปัจจุบันด้วยไฟล์ใหม่ (แม้จะมีแฟล็ก --in-place)
Alexander Mills

19

ฉันจะสมมติว่าคำสั่งตัวกรองของคุณคือสิ่งที่ฉันจะเรียกตัวกรองคำนำหน้าการหดตัวซึ่งมีคุณสมบัติที่ไบต์ N ในการส่งออกไม่เคยเขียนก่อนที่จะอ่านอย่างน้อย N ไบต์ของอินพุต grepมีคุณสมบัตินี้ (ตราบใดที่มันเป็นเพียงการกรองและไม่ทำสิ่งอื่นเช่นเพิ่มหมายเลขบรรทัดสำหรับการจับคู่) ด้วยตัวกรองดังกล่าวคุณสามารถเขียนทับอินพุตตามที่คุณต้องการ แน่นอนคุณต้องแน่ใจว่าจะไม่ทำผิดพลาดเนื่องจากส่วนที่เขียนทับที่จุดเริ่มต้นของไฟล์จะหายไปตลอดกาล

เครื่องมือ unix ส่วนใหญ่มีตัวเลือกให้ต่อท้ายไฟล์หรือตัดทอนเท่านั้นโดยไม่สามารถเขียนทับได้ หนึ่งข้อยกเว้นในกล่องเครื่องมือมาตรฐานคือddซึ่งสามารถบอกได้ว่าจะไม่ตัดทอนไฟล์ที่ส่งออก dd conv=notruncดังนั้นแผนคือการกรองคำสั่งลง สิ่งนี้ไม่เปลี่ยนขนาดของไฟล์ดังนั้นเราจึงคว้าความยาวของเนื้อหาใหม่และตัดไฟล์ให้มีความยาวนั้น (อีกครั้งด้วยdd) โปรดทราบว่างานนี้ไม่ได้มีประสิทธิภาพโดยเนื้อแท้ - หากมีข้อผิดพลาดเกิดขึ้นคุณเอง

export LC_ALL=C
n=$({ grep -v foo <big_file |
      tee /dev/fd/3 |
      dd of=big_file conv=notrunc; } 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek=$n

คุณสามารถเขียน Perl ที่เทียบเท่าได้อย่างแน่นอน ต่อไปนี้เป็นการนำไปปฏิบัติอย่างรวดเร็วที่ไม่พยายามมีประสิทธิภาพ แน่นอนคุณอาจต้องการทำการกรองเริ่มต้นโดยตรงในภาษานั้นเช่นกัน

grep -v foo <big_file | perl -e '
  close STDOUT;
  open STDOUT, "+<", $ARGV[0] or die;
  while (<STDIN>) {print}
  truncate STDOUT, tell STDOUT or die
' big_file

16

ด้วยเปลือกแบบบอร์นใด ๆ :

{
  cat < bigfile | grep -v to-exclude
  perl -e 'truncate STDOUT, tell STDOUT'
} 1<> bigfile

ด้วยเหตุผลบางอย่างดูเหมือนว่าผู้คนมักจะลืมเรื่องอายุ 40 ปีและผู้ดำเนินการเปลี่ยนเส้นทางการอ่าน + เขียนมาตรฐาน

เราเปิดให้บริการbigfileในการอ่าน + โหมดการเขียนและ (สิ่งที่สำคัญที่สุดที่นี่) โดยไม่ต้องตัดในstdoutขณะที่bigfileมีการเปิด (แยกต่างหาก) บน'scat stdinหลังจากgrepยุติแล้วและหากมีการลบบางบรรทัดstdoutตอนนี้จุดที่อยู่ภายในbigfileเราต้องกำจัดสิ่งที่เกินกว่าจุดนี้ ดังนั้นperlคำสั่งที่ตัดทอนไฟล์ ( truncate STDOUT) ที่ตำแหน่งปัจจุบัน (ตามที่ส่งคืนโดยtell STDOUT)

( catสำหรับ GNU grepที่จะบ่นว่า stdin และ stdout ชี้ไปที่ไฟล์เดียวกัน)


¹ดีในขณะที่<>ได้รับในบอร์นเชลล์จากจุดเริ่มต้นในช่วงปลายยุคมันเป็นครั้งแรกที่ไม่มีเอกสารและไม่ได้ดำเนินการอย่างถูกต้อง มันไม่ได้อยู่ในการดำเนินการashเริ่มต้นตั้งแต่ปี 1989 และในขณะที่มันเป็นตัวshดำเนินการเปลี่ยนเส้นทางPOSIX (ตั้งแต่ต้น 90s เนื่องจาก POSIX shขึ้นอยู่กับksh88ว่ามีเสมอ) มันไม่ได้ถูกเพิ่มลงใน FreeBSD shจนถึงปี 2000 ดังนั้น15 ปี เก่าน่าจะแม่นยำมากกว่า นอกจากนี้โปรดทราบว่าตัวบ่งชี้ไฟล์เริ่มต้นเมื่อไม่ได้ระบุอยู่<>ในทุกเชลล์ยกเว้นว่าksh93มันเปลี่ยนจาก 0 เป็น 1 ใน ksh93t + ในปี 2010 (แยกความเข้ากันได้ย้อนหลังและความสอดคล้อง POSIX)


2
คุณอธิบายได้perl -e 'truncate STDOUT, tell STDOUT'ไหม มันใช้งานได้สำหรับฉันโดยไม่รวมถึงสิ่งนั้น วิธีใดที่จะประสบความสำเร็จในสิ่งเดียวกันโดยไม่ใช้ Perl?
Aaron Blenkush

1
@AaronBlenkush ดูการแก้ไข
Stéphane Chazelas

1
ยอดเยี่ยมอย่างแน่นอน - ขอบคุณ ผมมีแล้ว แต่จำไม่ได้ว่านี้ .... สำหรับการอ้างอิงมาตรฐาน "36 ปีเก่า" จะสนุกเพราะมันไม่ได้กล่าวถึงที่en.wikipedia.org/wiki/Bourne_shell แล้วมันใช้ทำอะไร? ฉันเห็นการอ้างอิงถึงการแก้ไขข้อบกพร่องใน SunOS 5.6: redirection "<>" fixed and documented (used in /etc/inittab f.i.). ซึ่งเป็นคำใบ้อย่างหนึ่ง
nealmcb

2
@nealmcb ดูการแก้ไข
Stéphane Chazelas

@ StéphaneChazelasโซลูชันของคุณเปรียบเทียบกับคำตอบนี้อย่างไร เห็นได้ชัดว่ามันทำสิ่งเดียวกัน แต่ดูง่ายขึ้น
akhan

9

แม้ว่านี่จะเป็นคำถามเก่า แต่สำหรับฉันแล้วมันเป็นคำถามที่ยืนต้นและมีวิธีแก้ปัญหาที่ชัดเจนและชัดเจนกว่าที่เคยแนะนำ เครดิตที่ครบกำหนดเครดิต: ฉันไม่แน่ใจว่าฉันจะได้รับมันโดยไม่พิจารณาถึงการกล่าวถึง<>ผู้ดำเนินการอัพเดท ของStéphane Chazelas

การเปิดไฟล์เพื่ออัปเดตในเชลล์เป้าหมายนั้นมีประโยชน์อย่าง จำกัด เชลล์ไม่ให้คุณค้นหาไฟล์และไม่มีวิธีตั้งค่าความยาวใหม่ (ถ้าสั้นกว่าเก่า) /usr/binแต่ที่แก้ได้ง่ายเพื่อให้ได้อย่างง่ายดายฉันประหลาดใจมันไม่อยู่ในกลุ่มสาธารณูปโภคมาตรฐานในการ

งานนี้:

$ grep -n foo T
8:foo
$ (exec 4<>T; grep foo T >&4 && ftruncate 4) && nl T; 
     1  foo

เช่นนี้ (ปลายหมวกถึงStéphane):

$ { grep foo T && ftruncate; } 1<>T  && nl T; 
     1  foo

(ฉันใช้ grep GNU บางทีอาจมีบางอย่างเปลี่ยนแปลงไปตั้งแต่เขาเขียนคำตอบ)

ยกเว้นคุณไม่มี/ usr / bin / ftruncate สำหรับ C สองสามบรรทัดคุณสามารถดูด้านล่าง ยูทิลิตีftruncateนี้จะตัดทอนของไฟล์ descriptor ตามความยาวโดยพลการเริ่มต้นที่เอาต์พุตมาตรฐานและตำแหน่งปัจจุบัน

คำสั่งดังกล่าว (ตัวอย่างที่ 1)

  • เปิด file descriptor 4 Tสำหรับการอัพเดท เช่นเดียวกับ open (2) การเปิดไฟล์ด้วยวิธีนี้ตำแหน่งออฟเซ็ตปัจจุบันที่ 0
  • จากนั้นgrepจะประมวลผลTตามปกติและเชลล์เปลี่ยนทิศทางเอาต์พุตไปยังTผ่าน descriptor 4
  • ftruncate การเรียก ftruncate (2) บน descriptor 4 การตั้งค่าความยาวเป็นค่าของออฟเซ็ตปัจจุบัน (ตรงที่grepเหลือไว้)

จากนั้น subshell จะออก, ปิด descriptor 4 นี่คือftruncate :

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main( int argc, char *argv[] ) {
  off_t i, fd=1, len=0;
  off_t *addrs[2] = { &fd, &len };

  for( i=0; i < argc-1; i++ ) {
    if( sscanf(argv[i+1], "%lu", addrs[i]) < 1 ) {
      err(EXIT_FAILURE, "could not parse %s as number", argv[i+1]);
    }
  }

  if( argc < 3 && (len = lseek(fd, 0, SEEK_CUR)) == -1 ) {
    err(EXIT_FAILURE, "could not ftell fd %d as number", (int)fd);
  }


  if( 0 != ftruncate((int)fd, len) ) {
    err(EXIT_FAILURE, argc > 1? argv[1] : "stdout");
  }

  return EXIT_SUCCESS;
}

NB, ftruncate (2) ไม่สามารถพกพาได้เมื่อใช้ในวิธีนี้ สำหรับความรู้ทั่วไปอย่างแท้จริงให้อ่านไบต์ที่เขียนล่าสุดเปิดไฟล์ O_WRONLY ใหม่ค้นหาแสวงหาเขียนไบต์และปิด

ระบุว่าคำถามมีอายุ 5 ปีฉันจะบอกว่าวิธีนี้ไม่ชัดเจน มันใช้ประโยชน์จากexecเพื่อเปิด descriptor ใหม่และ<>โอเปอเรเตอร์ซึ่งทั้งคู่เป็นอาร์เคน ฉันไม่สามารถนึกถึงยูทิลิตี้มาตรฐานที่จัดการ inode by file descriptor ได้ (ไวยากรณ์อาจเป็นไปได้ftruncate >&4แต่ฉันไม่แน่ใจว่าเป็นการปรับปรุง) มันสั้นกว่าคำตอบเชิงสำรวจที่มีความสามารถของ camh อย่างมาก เป็นเพียงเล็กน้อยที่ชัดเจนกว่าของStéphane, IMO เว้นแต่คุณจะชอบ Perl มากกว่าที่ฉันทำ ฉันหวังว่าบางคนจะพบว่ามีประโยชน์

วิธีที่แตกต่างในการทำสิ่งเดียวกันจะเป็นเวอร์ชันที่สามารถเรียกทำงานได้ของ lseek (2) ที่รายงานออฟเซ็ตปัจจุบัน เอาต์พุตสามารถใช้สำหรับ/ usr / bin / truncateซึ่ง Linuxi บางตัวจัดเตรียมไว้


5

ed อาจเป็นตัวเลือกที่ถูกต้องในการแก้ไขไฟล์แบบแทนที่:

ed my_big_file << END_OF_ED_COMMANDS
g/foo:/d
w
q 
END_OF_ED_COMMANDS

ฉันชอบความคิด แต่ถ้าedรุ่นที่แตกต่างกันมีพฤติกรรมที่แตกต่างกัน ..... นี้มาจากman ed(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
Peter.O

@ เฟร็ดหากคุณบอกเป็นนัยว่าการบันทึกการเปลี่ยนแปลงจะไม่มีผลกับไฟล์ที่ระบุชื่อแสดงว่าคุณไม่ถูกต้อง ฉันตีความข้อความอ้างอิงเพื่อบอกว่าการเปลี่ยนแปลงของคุณจะไม่ปรากฏจนกว่าคุณจะบันทึก ฉันยอมรับว่าedไม่ใช่โซลูชัน gool สำหรับการแก้ไขไฟล์ 35GB เนื่องจากไฟล์ถูกอ่านเป็นบัฟเฟอร์
เกล็นแจ็คแมน

2
ฉันคิดว่ามันหมายถึงไฟล์เต็มรูปแบบจะถูกโหลดลงในบัฟเฟอร์ .. แต่อาจมีเพียงบางส่วนเท่านั้นที่มีการโหลดลงในบัฟเฟอร์ .. ฉันอยากรู้อยากเห็น ed ในขณะที่ ... ฉันคิดว่ามันจะดำเนินการแก้ไขในแหล่งกำเนิด ... ฉันก็จะต้องลองขนาดใหญ่ไฟล์ ... ถ้ามันทำงานมันเป็นวิธีการแก้ปัญหาที่เหมาะสม แต่ดังที่ฉันเขียนฉันเริ่มที่จะคิดว่านี่อาจจะเป็นแรงบันดาลใจอะไรsed ( เป็นอิสระจากการทำงานกับกลุ่มข้อมูลขนาดใหญ่ ... ฉันสังเกตเห็นว่า 'ed' สามารถรับอินพุตแบบสตรีมจากสคริปต์ (นำหน้าด้วย!) ดังนั้นจึงอาจมีลูกเล่นที่น่าสนใจเพิ่มขึ้นเล็กน้อย
Peter.O

ฉันค่อนข้างมั่นใจว่าการดำเนินการเขียนในการedตัดทอนไฟล์และเขียนใหม่ ดังนั้นสิ่งนี้จะไม่เปลี่ยนแปลงข้อมูลบนดิสก์ในสถานที่ตามที่ต้องการ OP นอกจากนี้มันไม่สามารถใช้งานได้หากไฟล์มีขนาดใหญ่เกินไปที่จะโหลดในหน่วยความจำ
Nick Matteo

5

คุณสามารถใช้อธิบายไฟล์ bash อ่าน / เขียนเพื่อเปิดไฟล์ของคุณ (เพื่อเขียนทับมันในแหล่งกำเนิด) แล้วsedและtruncate... แต่แน่นอนไม่เคยอนุญาตให้การเปลี่ยนแปลงของคุณมีขนาดใหญ่กว่าปริมาณข้อมูลที่อ่าน .

นี่คือสคริปต์ (ใช้: ตัวแปร bash $ BASHPID)

# Create a test file
  echo "going abc"  >junk
  echo "going def" >>junk
  echo "# ORIGINAL file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )
#
# Assign file to fd 3, and open it r/w
  exec 3<> junk  
#
# Choose a unique filename to hold the new file size  and the pid 
# of the semi-asynchrounous process to which 'tee' streams the new file..  
  [[ ! -d "/tmp/$USER" ]] && mkdir "/tmp/$USER" 
  f_pid_size="/tmp/$USER/pid_size.$(date '+%N')" # %N is a GNU extension: nanoseconds
  [[ -f "$f_pid_size" ]] && { echo "ERROR: Work file already exists: '$f_pid_size'" ;exit 1 ; }
#
# run 'sed' output to 'tee' ... 
#  to modify the file in-situ, and to count the bytes  
  <junk sed -e "s/going //" |tee >(echo -n "$BASHPID " >"$f_pid_size" ;wc -c >>"$f_pid_size") >&3
#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# The byte-counting process is not a child-process, 
# so 'wait' doesn't work... but wait we must...  
  pid_size=($(cat "$f_pid_size")) ;pid=${pid_size[0]}  
  # $f_pid_size may initially contain only the pid... 
  # get the size when pid termination is assured
  while [[ "$pid" != "" ]] ; do
    if ! kill -0 "$pid" 2>/dev/null; then
       pid=""  # pid has terminated. get the byte count
       pid_size=($(cat "$f_pid_size")) ;size=${pid_size[1]}
    fi
  done
  rm "$f_pid_size"
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#
  exec 3>&- # close fd 3.
  newsize=$(cat newsize)
  echo "# MODIFIED file (before truncating)";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
 truncate -s $newsize junk
 echo "# NEW (truncated) file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
exit

นี่คือผลการทดสอบ

# ORIGINAL file
going abc
going def
# 2 lines, 20 bytes

# MODIFIED file (before truncating)
abc
def
c
going def
# 4 lines, 20 bytes

# NEW (truncated) file
abc
def
# 2 lines, 8 bytes

3

ฉันใช้หน่วยความจำ - แมปไฟล์ทำทุกอย่างในสถานที่โดยใช้ตัวชี้ถ่าน * ไปยังหน่วยความจำเปล่าแล้วถอดไฟล์และตัดทอนไฟล์


3
+1 แต่เนื่องจากความพร้อมใช้งานที่แพร่หลายของ CPU 64 บิตและระบบปฏิบัติการทำให้เป็นไปได้ที่จะทำเช่นนั้นด้วยไฟล์ 35 GB ในขณะนี้ ผู้ที่ยังอยู่ในระบบ 32 บิต (ส่วนใหญ่แม้แต่ผู้ชมเว็บไซต์นี้ฉันสงสัย) จะไม่สามารถใช้โซลูชันนี้ได้
Warren Young

2

ไม่ได้อยู่ในแหล่งกำเนิดอย่างแน่นอนแต่ - สิ่งนี้สามารถนำไปใช้ในสถานการณ์ที่คล้ายคลึงกันได้
หากพื้นที่ดิสก์มีปัญหาให้บีบอัดไฟล์ก่อน (เนื่องจากเป็นข้อความซึ่งจะทำให้ลดลงอย่างมาก) จากนั้นใช้ sed (หรือ grep หรืออะไรก็ตาม) ตามปกติในช่วงกลางของไปป์ uncompress / compress

# Reduce size from ~35Gb to ~6Gb
$ gzip MyFile

# Edit file, creating another ~6Gb file
$ gzip -dc <MyFile.gz | sed -e '/foo/d' | gzip -c >MyEditedFile.gz

2
แต่แน่นอนว่า gzip กำลังเขียนเวอร์ชันที่ถูกบีบอัดไปยังดิสก์ก่อนที่จะแทนที่ด้วยเวอร์ชั่นที่ถูกบีบอัดดังนั้นคุณต้องมีพื้นที่ว่างเพิ่มขึ้นอย่างน้อยซึ่งต่างจากตัวเลือกอื่น ๆ แต่มันก็เป็นความปลอดภัยมากขึ้นถ้าคุณได้มีพื้นที่ (ซึ่งผมไม่ได้ .... )
nealmcb

นี่เป็นโซลูชันที่ชาญฉลาดที่สามารถปรับให้เหมาะสมต่อการบีบอัดเพียงครั้งเดียวแทนที่จะเป็นสอง:sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
ทอดด์โอเว่น

0

เพื่อประโยชน์ของทุกคนที่ googling คำถามนี้คำตอบที่ถูกต้องคือหยุดมองหาคุณสมบัติที่ไม่ชัดเจนของเชลล์ที่เสี่ยงต่อการทำลายไฟล์ของคุณเพื่อให้ได้ประสิทธิภาพที่ลดลงและใช้รูปแบบนี้แทน:

grep "foo" file > file.new && mv file.new file

เฉพาะในสถานการณ์ที่ผิดปกติอย่างยิ่งที่เป็นเช่นนี้ด้วยเหตุผลบางอย่างที่ไม่สามารถทำได้คุณควรพิจารณาคำตอบอื่น ๆ ในหน้านี้อย่างจริงจัง (แม้ว่าพวกเขาจะน่าสนใจที่จะอ่าน) ฉันจะยอมรับว่า conundrum ของ OP ที่ไม่มีพื้นที่ดิสก์ในการสร้างไฟล์ที่สองเป็นสถานการณ์แบบนั้น แม้ว่าจะเป็นเช่นนั้น แต่ก็มีตัวเลือกอื่น ๆ เช่น @Ed Randall และ @Basile Starynkevitch


1
ฉันอาจจะเข้าใจผิด แต่ไม่มีส่วนเกี่ยวข้องกับสิ่งที่ OP ดั้งเดิมถาม aka การแก้ไขแบบอินไลน์ของ bigfile โดยไม่มีพื้นที่ว่างเพียงพอสำหรับไฟล์ชั่วคราว
Kiwy

@ Kiwy มันเป็นคำตอบสำหรับผู้ชมคนอื่น ๆ ของคำถามนี้ (ซึ่งมีมาเกือบ 15,000 แล้ว) คำถาม "มีวิธีแก้ไขไฟล์ในสถานที่หรือไม่" มีความเกี่ยวข้องที่กว้างกว่ากรณีการใช้งานเฉพาะของ OP
Todd Owen

-3

echo -e "$(grep pattern bigfile)" >bigfile


3
สิ่งนี้ไม่ทำงานหากไฟล์มีขนาดใหญ่และgreppedข้อมูลมีขนาดเกินความยาวของบรรทัดคำสั่งที่อนุญาต จากนั้นจะทำลายข้อมูล
Anthon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.