Đọc và viết một tập tin: lệnh tee


10

Người ta biết rằng một lệnh như thế này:

cat filename | some_sed_command >filename

xóa tên tệp, như chuyển hướng đầu ra, được thực thi trước lệnh, làm cho tên tệp bị cắt ngắn.

Người ta có thể giải quyết vấn đề theo cách sau:

cat file | some_sed_command | tee file >/dev/null

nhưng tôi không chắc điều này sẽ hoạt động trong mọi trường hợp: điều gì xảy ra nếu tệp (và kết quả của lệnh sed) là rất lớn? Làm thế nào hệ điều hành có thể tránh ghi đè lên một số nội dung vẫn không được đọc? Tôi thấy rằng cũng có một lệnh bọt biển sẽ hoạt động trong mọi trường hợp: nó có "an toàn" hơn tee không?


Mục tiêu chính của bạn là gì? (nói một cách đơn giản)
Sergiy Kolodyazhnyy

@Serg hiểu đơn giản là mọi thứ hoạt động như thế nào ... Câu trả lời được viết bởi kos làm rõ vấn đề
VeryHardCoder

Câu trả lời:


10

Người ta có thể giải quyết vấn đề theo cách sau:

cat file | some_sed_command | tee file >/dev/null

Không .

Cơ hội filesẽ bị cắt giảm, nhưng không có gì đảm bảo cat file | some_sed_command | tee file >/dev/nullsẽ không bị cắt ngắn file.

Tất cả phụ thuộc vào lệnh nào được xử lý trước, trái ngược với những gì người ta có thể mong đợi, các lệnh trong một đường ống không được xử lý từ trái sang phải . Không có gì đảm bảo về lệnh nào sẽ được chọn trước, vì vậy người ta cũng có thể nghĩ rằng nó được chọn ngẫu nhiên và không bao giờ dựa vào vỏ không chọn lệnh vi phạm.

Vì cơ hội để lệnh vi phạm được chọn trước ở giữa ba lệnh thấp hơn cơ hội để lệnh vi phạm được chọn trước ở giữa hai lệnh, nên ít có khả năng nó filesẽ bị cắt ngắn, nhưng nó vẫn sẽ xảy ra .

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Vì vậy, không bao giờ sử dụng một cái gì đó như cat file | some_sed_command | tee file >/dev/null. Sử dụng spongenhư Oli đề nghị.

Để thay thế, đối với môi trường mạnh hơn và / hoặc các tệp tương đối nhỏ, người ta có thể sử dụng chuỗi ở đây và thay thế lệnh để đọc tệp trước khi chạy bất kỳ lệnh nào:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz

9

Đối với sedcụ thể, bạn có thể sử dụng nó -itranh luận tại chỗ. Nó chỉ lưu lại tập tin đã mở, vd:

sed -i 's/ /-/g' filename

Nếu bạn muốn làm một cái gì đó mạnh mẽ hơn, giả sử bạn đang làm nhiều hơn sed, vâng, bạn có thể đệm toàn bộ với sponge(từ moreutilsgói) sẽ "ngâm" tất cả các stdin trước khi viết ra tệp. Nó giống như teenhưng với ít chức năng hơn. Đối với việc sử dụng cơ bản, đó là một sự thay thế khá nhiều:

cat file | some_sed_command | sponge file >/dev/null

Có an toàn hơn không? Chắc chắn rồi. Nó có thể có giới hạn thông qua vì vậy nếu bạn đang thực hiện một cái gì đó khổng lồ (và không thể chỉnh sửa tại chỗ bằng sed), bạn có thể muốn thực hiện các chỉnh sửa của mình thành tệp thứ hai và sau mvđó tệp đó trở lại tên tệp gốc. Đó phải là nguyên tử (vì vậy mọi thứ tùy thuộc vào các tệp này sẽ không bị hỏng nếu chúng cần truy cập liên tục).


0

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

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

  2. ! Chạy lệnh

  3. x Lưu và đóng


0

Ồ, nhưng spongekhông phải là lựa chọn duy nhất; bạn không cần phải có được moreutilsđể làm cho điều này hoạt động đúng. Bất kỳ cơ chế nào cũng sẽ hoạt động miễn là đáp ứng hai yêu cầu sau:

  1. Nó chấp nhận tên của tệp đầu ra như một tham số.
  2. Nó chỉ tạo tập tin đầu ra khi tất cả đầu vào đã được xử lý.

Bạn thấy đấy, vấn đề nổi tiếng mà OP đang đề cập đến là shell sẽ tạo ra tất cả các tệp cần thiết cho các đường ống hoạt động trước cả khi bắt đầu thực hiện các lệnh trong đường ống, vì vậy đây là vỏ thực sự cắt ngắn tệp đầu ra (không may cũng là tệp đầu vào) trước khi bất kỳ lệnh nào thậm chí có cơ hội bắt đầu thực thi.

Các teelệnh không làm việc, mặc dù nó thỏa mãn yêu cầu đầu tiên, bởi vì nó không đáp ứng các yêu cầu thứ hai: nó sẽ luôn luôn tạo ra các tập tin đầu ra ngay khi bắt đầu, vì vậy nó thực chất là tồi tệ như việc tạo ra một ống thẳng vào tập tin đầu ra. (Nó thực sự tồi tệ hơn, vì việc sử dụng nó giới thiệu độ trễ ngẫu nhiên không xác định trước khi tệp đầu ra bị cắt bớt, vì vậy bạn có thể nghĩ rằng nó hoạt động, trong khi thực tế thì không.)

Vì vậy, tất cả những gì chúng ta cần để giải quyết vấn đề này là một số lệnh sẽ đệm tất cả đầu vào của nó trước khi tạo ra bất kỳ đầu ra nào và có khả năng chấp nhận tên tệp đầu ra như một tham số, để chúng ta không phải đưa đầu ra của nó vào các tập tin đầu ra. Một lệnh như vậy là shuf. Vì vậy, những điều sau đây sẽ thực hiện điều tương tự sponge:

    shuf --output=file --random-source=/dev/zero 

Phần --random-source=/dev/zerothủ thuật shufđể thực hiện công việc của nó mà không thực hiện bất kỳ sự xáo trộn nào, vì vậy nó sẽ đệm đầu vào của bạn mà không thay đổi nó.

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.