Chuyển hướng IO và lệnh head


9

Tôi đã cố gắng nhanh chóng chỉnh sửa một .hgignoretệp từ shell bash Cygwin hôm nay và tôi đã thêm một dòng đó là một sai lầm. Tôi không chắc đây có phải là cách tốt nhất để làm hay không, nhưng tôi đã nhanh chóng nghĩ đến việc sử dụng head -1 .hgignoređể xóa dòng vi phạm (trước đây tôi chỉ có một dòng trong tệp). Chắc chắn, khi thực hiện nó sẽ cho dòng đầu tiên là đầu ra duy nhất.

Nhưng khi tôi cố chuyển hướng đầu ra và viết lại tập tin bằng cách sử dụng head -1 .hgignore > .hgignore, tập tin trống rỗng. Lý do tại sao điều này xảy ra? Nếu tôi thử nối thêm head -1 .hgignore >> .hgignore, nó sẽ nối đúng nhưng rõ ràng đây không phải là kết quả mong muốn. Tại sao một chuyển hướng cắt ngắn không hoạt động trong trường hợp này?


Câu trả lời:


10

Khi shell nhận được một dòng lệnh như: command > file.outshell sẽ tự mở (và có thể tạo) tệp có tên file.out. Shell đặt bộ mô tả tệp 0 cho bộ mô tả tệp mà nó nhận được từ khi mở. Đó là cách chuyển hướng I / O hoạt động: mọi quy trình đều biết về mô tả tệp 0, 1 và 2.

Phần khó về điều này là làm thế nào để mở file.out. Hầu hết thời gian, bạn muốn file.outmở để ghi ở offset 0 (nghĩa là cắt ngắn) và đây là những gì shell đã làm cho bạn. Nó cắt ngắn .hgignore, mở nó ra để viết, nhân đôi bộ lọc thành 0, rồi thực hiện head. Tập tin ngay lập tức bị ghi đè.

Trong bash shell, bạn làm một set noclobberđể thay đổi hành vi này.


Aha, tôi hiểu rồi. Tôi đã nghĩ rằng trình bao đã cắt bớt tệp trước khi chạy lệnh, nhưng tôi không biết tại sao. Cảm ơn đã giải thích!
voithos

10

Tôi nghĩ Bruce trả lời những gì đang diễn ra ở đây với đường ống vỏ.

Một trong những tiện ích nhỏ yêu thích của tôi là spongelệnh từ moreutils . Nó giải quyết chính xác vấn đề này bằng cách "ngâm" tất cả các đầu vào có sẵn trước khi mở tệp đầu ra đích và ghi dữ liệu. Nó cho phép bạn viết các đường ống chính xác như bạn mong đợi:

$ head -1 .hgignore | sponge .hgignore

Giải pháp của người nghèo là chuyển đầu ra thành một tệp tạm thời, sau đó sau khi hoàn thành đường ống (ví dụ lệnh tiếp theo bạn chạy) là di chuyển tệp tạm thời trở lại vị trí tệp ban đầu.

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}

Nhìn vào điều này một vài năm sau đó, một ý nghĩ đã xảy ra với tôi: chúng ta không thể làm gì head -1 .hgignore | tee .hgignore? teelà trong coreutils, và như một perk / tác dụng phụ, điều này cũng ghi vào STDOUT
voithos

@voithos Theo hiểu biết của tôi teesẽ mở và cắt bớt tệp mà nó đang ghi khi nó được khởi tạo giống như mọi thứ khác vì vậy nó không giải quyết được vấn đề chính ở đây về điều kiện cuộc đua khi đọc nội dung tệp trước khi bạn cắt nó bằng cách ghi.
Caleb

Thực tế, bạn đưa ra một điểm mà tôi không biết - cụ thể là, các lệnh đường ống được bắt đầu ngay lập tức, thay vì tuần tự. Điều đó có chính xác không? Tôi đã làm, tuy nhiên, kiểm tra nó và tee dường như làm điều mong muốn. Tôi đã có phiên bản 8.13trên máy của mình.
voithos

1
@voithos Có các lệnh trong một đường ống và tất cả các kênh đầu vào / đầu ra liên quan được bắt đầu theo thứ tự ngược lại để đường ống sẵn sàng nhận dữ liệu khi kênh đầu tiên bắt đầu cung cấp. Tôi nghi ngờ bài kiểm tra của bạn là thiếu sót vì có lẽ bạn đã sử dụng một đoạn dữ liệu quá nhỏ và nó có toàn bộ nội dung được lưu trong bộ đệm đọc trước khi bạn cần. Các teechương trình sẽ cắt tập tin của bạn, nó không phải là thiết lập để tăng gấp đôi đệm họ.
Caleb

3

Trong

head -n 1 file > file

filebị cắt ngắn trước khi headbắt đầu, nhưng nếu bạn viết nó:

head -n 1 file 1<> file

Nó không như fileđược mở trong chế độ đọc-ghi. Tuy nhiên, khi headviết xong, nó không cắt bớt tệp, vì vậy dòng trên sẽ là no-op ( headsẽ chỉ viết lại dòng đầu tiên trên chính nó và không để lại các dòng khác).

Tuy nhiên, sau khi headđã trở lại và trong khi fdvẫn mở, bạn có thể gọi một lệnh khác thực hiện truncate.

Ví dụ:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

Vấn đề ở đây là truncateở trên, headchỉ cần di chuyển con trỏ cho fd 1 bên trong tệp ngay sau dòng đầu tiên. Nó viết lại dòng đầu tiên mà chúng tôi không cần nó, nhưng điều đó không có hại.

Với đầu POSIX, chúng tôi thực sự có thể thoát khỏi mà không cần viết lại dòng đầu tiên:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

Ở đây, chúng ta đang sử dụng thực tế là headdi chuyển vị trí con trỏ trong stdin của nó. Mặc dù headthường đọc đầu vào của nó bằng các khối lớn để cải thiện hiệu suất, POSIX sẽ yêu cầu nó (nếu có thể) phải seekquay lại ngay sau dòng đầu tiên nếu nó vượt ra ngoài nó. Lưu ý tuy nhiên không phải tất cả các thực hiện đều làm điều đó.

Ngoài ra, bạn có thể sử dụng readlệnh của shell thay vào đó trong trường hợp này:

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file

1
Stephane, bạn có biết một lệnh tiêu chuẩn hoặc coreutils có thể cắt ngắn STDINtương tự như những gì bạn đã hoàn thành bằng cách sử dụng perlở trên
iruvar

2
@ 1_CR, không. mặc dù ddcó thể cắt ở bất kỳ độ lệch tuyệt đối tùy ý trong tệp. Vì vậy, bạn có thể xác định độ lệch byte của dòng thứ hai và cắt ngắn từ đó vớidd bs=1 seek="$offset" of=file
Stéphane Chazelas

1

Giải pháp của Real Man là

ed .hgignore
$d
wq

hoặc như một lớp lót

printf '%s\n' '$d' 'wq' | ed .hgignore

Hoặc với GNU sed:

sed -i '$d' .hgignore

(Không, tôi đang đùa. Tôi sẽ sử dụng trình chỉnh sửa tương tác. vi .hgignore GddZZ)


Tôi đã tự hỏi, có bất kỳ lợi thế để sử dụng :wqhơn ZZ?
voithos

Ngoài ra, :xđó là những gì ngón tay của tôi tự động làm
glenn jackman

ZQgiống như:q!
glenn jackman

ZZ và: x chỉ ghi nếu có nội dung cần ghi ...: w luôn luôn gửi tệp vào đĩa bất kể có cần hay không. Tôi dùng: xa vì tôi dùng tab.
xenoterracide

1

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

ex -sc '2,d|x' .hgignore
  1. 2, chọn dòng 2 cho đến khi kết thúc

  2. d xóa bỏ

  3. x lưu và đóng


0

Để chỉnh sửa tệp tại chỗ, bạn cũng có thể sử dụng thủ thuật xử lý tệp mở như được hiển thị bởi Jürgen Hötzel trong đầu ra Redirect từ sed 's / c / d /' myFile sang myFile .

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed

2
Và ngay sau khi rm .hgignoresức mạnh của bạn thất bại, lấy đi hàng giờ làm việc chăm chỉ. Ok, nó không thành vấn đề .hgignore, nhưng tại sao bạn lại làm điều gì đó phức tạp như vậy? Vì vậy, downvote của tôi: đúng kỹ thuật nhưng một ý tưởng rất xấu.
Gilles 'SO- đừng trở nên xấu xa'

@Gilles, có thể không phải là một ý tưởng hay, nhưng đó là những gì perl -i(để chỉnh sửa tại chỗ), và tôi sẽ không ngạc nhiên nếu một số triển khai sed -iđã làm điều đó (mặc dù phiên bản GNU mới nhất seddường như không).
Stéphane Chazelas
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.