Linux: Làm thế nào để sử dụng một tệp làm đầu vào và đầu ra cùng một lúc?


55

Tôi vừa mới chạy phần sau trong bash:

uniq .bash_history > .bash_history

và tập tin lịch sử của tôi đã hoàn toàn trống rỗng.

Tôi đoán tôi cần một cách để đọc toàn bộ tập tin trước khi viết cho nó. Làm thế nào được thực hiện?

PS: Tôi rõ ràng đã nghĩ đến việc sử dụng một tập tin tạm thời, nhưng tôi đang tìm kiếm một giải pháp thanh lịch hơn.


Đó là vì các tập tin được mở từ phải sang trái. Xem thêm stackoverflow.com/questions/146435/ Lời
WheresAlice

Bạn phải ghi đầu ra vào một tệp mới trong cùng thư mục và đổi tên nó ở trên cùng của tệp cũ. Bất kỳ phương pháp nào khác sẽ có nguy cơ mất dữ liệu của bạn nếu nó bị gián đoạn giữa chừng. Một số công cụ có thể ẩn bước này từ bạn.
kasperd

Hoặc, bashsẽ không đặt các bản sao liên tiếp trong lịch sử của nó nếu bạn đặt HISTCONTROL để bao gồm các phần bị bỏ qua; xem trang hướng dẫn.
dave_thndry_085

Câu trả lời:


49

Tôi khuyên bạn nên sử dụng spongetừ moreutils . Từ trang hướng dẫn:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Để áp dụng điều này cho vấn đề của bạn, hãy thử:

uniq .bash_history | sponge .bash_history

6
Nó giống như con mèo, nhưng với khả năng mút tay: D
MilliaLover

77

Tôi chỉ muốn đóng góp một câu trả lời đơn giản và không sử dụng bọt biển (vì nó thường không được bao gồm trong môi trường nhẹ).

echo "$(uniq .bash_history)" > .bash_history

nên có kết quả mong muốn. Subshell được thực thi trước khi .bash_history được mở để viết. Như đã giải thích trong câu trả lời của Phil P, vào thời điểm .bash_history được đọc trong lệnh ban đầu, nó đã bị toán tử '>' cắt ngắn.


15
Bình thường tôi không phải là người hâm mộ những câu trả lời nạo vét một câu hỏi cổ xưa đã có câu trả lời hợp lệ, được chấp nhận - nhưng đây là câu trả lời tao nhã, được viết tốt và đưa ra lập luận mạnh mẽ cho sự cần thiết của nó (môi trường nhẹ); Đối với tôi, nó thực sự bổ sung một cái gì đó vào bộ câu trả lời hiện có. Chào mừng bạn đến với SF, Hart (bạn đã ở đây một tháng, nhưng tôi nghĩ đây là bài đăng thực sự đầu tiên của bạn). Tôi hy vọng sẽ đọc thêm câu trả lời từ bạn như thế này!
MadHatter

4
Đây là giải pháp tốt nhất. Tôi đã phải sử dụng một subshell $()thay vì backticks do một số vấn đề thoát.
CMCDragonkai

3
Tôi tự hỏi nếu giải pháp này quy mô thành các tệp lớn, giả sử ... 20 hoặc 50 GB.
Amit N Nikol

1
Đây thực sự nên là câu trả lời chấp nhận.
maxywb

1
Tôi đã sử dụng câu trả lời này để làm echo "$(fmt -p '# ' -w 50 readme.txt)" > readme.txtngày hôm nay. Đã tìm kiếm xung quanh trong một thời gian dài cho một giải pháp thanh lịch. Rất cám ơn, @Hart Simha!
shredalert

12

Vấn đề là shell của bạn đang thiết lập đường ống lệnh trước khi chạy các lệnh. Đây không phải là vấn đề "đầu vào và đầu ra", đó là nội dung của tệp đã bị xóa trước khi uniq thậm chí chạy. Nó đi một cái gì đó như:

  1. Shell mở >tệp đầu ra để ghi, cắt bớt nó
  2. Shell thiết lập để có bộ mô tả tệp 1 (cho thiết bị xuất chuẩn) được sử dụng cho đầu ra đó
  3. Shell thực thi uniq, có lẽ là một cái gì đó như execlp ("uniq", "uniq", ".bash_history", NULL)
  4. uniq chạy, mở .bash_history và không tìm thấy gì ở đó

Có nhiều giải pháp khác nhau, bao gồm chỉnh sửa tại chỗ và sử dụng tệp tạm thời mà người khác đề cập, nhưng điều quan trọng là phải hiểu vấn đề, điều gì thực sự sai và tại sao.


9

Một mẹo khác để làm điều này, mà không sử dụng sponge, là lệnh sau:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Đây là một trong những mánh gian lận được mô tả trong bài viết xuất sắc Chỉnh sửa các tập tin tại chỗ trên backreference.org.

Về cơ bản, nó mở tệp để đọc, sau đó "xóa" nó. Mặc dù vậy, nó không thực sự bị xóa: Có một bộ mô tả tệp đang mở trỏ đến nó và miễn là nó vẫn mở, tệp vẫn ở xung quanh. Sau đó, nó tạo ra một tệp mới có cùng tên và ghi các dòng duy nhất vào nó.

Nhược điểm của giải pháp này: Nếu uniqthất bại vì một số lý do, lịch sử của bạn sẽ biến mất.



3

sedKịch bản này loại bỏ các bản sao liền kề. Với -itùy chọn, nó thực hiện sửa đổi tại chỗ. Đó là từ sed infotập tin:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

sed vẫn sử dụng tệp tạm thời, thêm một câu trả lời có stracehình minh họa (không phải là nó thực sự quan trọng) :-)
Kyle Brandt

3
@Kyle: Đúng, nhưng "khuất mắt, mất trí". Cá nhân, tôi sẽ sử dụng tệp tạm thời rõ ràng vì một cái gì đó process input > tmp && mv tmp inputđơn giản và dễ đọc hơn nhiều so với sử dụng sedthủ thuật đơn giản để tránh tệp tạm thời và nó sẽ không ghi đè lên bản gốc của tôi nếu thất bại (Tôi không biết nếu sed -ithất bại một cách duyên dáng - tôi sẽ nghĩ rằng nó sẽ mặc dù). Ngoài ra, có rất nhiều điều bạn có thể làm với phương thức xuất ra tệp tạm thời không thể thực hiện tại chỗ mà không có thứ gì liên quan nhiều hơn sedtập lệnh này . Tôi biết bạn biết tất cả những điều này, nhưng nó có thể có lợi cho một số người xem.
Dennis Williamson

3

Là một mẩu tin thú vị, sed cũng sử dụng tệp tạm thời (điều này chỉ giúp bạn):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Mô tả:
tempfile ./sedPmPv9ztrở thành fd 4 và các footệp trở thành fd 3. Các thao tác đọc nằm trên fd 3 và ghi trên fd 4 (tệp tạm thời). Tệp foo sau đó được ghi đè bằng tệp tạm thời trong cuộc gọi đổi tên.



0

Một tệp tạm thời là khá nhiều, trừ khi lệnh trong câu hỏi xảy ra để hỗ trợ chỉnh sửa vị trí ( uniqkhông - một số seds do ( sed -i)).


0

Bạn có thể sử dụng Vim trong chế độ Ex:

ex -sc '%!uniq' -cx .bash_history
  1. % chọn tất cả các dòng

  2. ! chạy lệnh

  3. x lưu và đóng


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.