Làm thế nào để thay thế văn bản trong một hệ thống phân cấp thư mục lớn?


11

Tôi muốn tìm kiếm và thay thế một số văn bản trong một tập hợp lớn các tệp trừ một số trường hợp. Đối với mỗi dòng, tôi muốn một lời nhắc hỏi tôi có cần thay thế dòng đó hay không. Một cái gì đó tương tự như của vim :%s/from/to/gc(với cdấu nhắc để xác nhận), nhưng trên một tập hợp các thư mục. Có một số công cụ dòng lệnh hay kịch bản tốt có thể được sử dụng?


Về tầm quan trọng của định dạng đúng: Ban đầu tôi sẽ đọc lệnh của bạn như s/from/to/gmột trục trặc định dạng sau nó, thay vì s/from/to/gcnhấn mạnh vào cnhư bạn đã cố gắng viết (bạn không thể làm điều đó với Markdown, bạn có thể làm điều đó với Markdown <code><strong>thẻ HTML).
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


19

Tại sao không sử dụng vim?

Mở tất cả các tập tin trong vim

vim $(find . -type f)

Hoặc chỉ mở các tệp có liên quan (theo đề xuất của Caleb)

vim $(grep 'from' . -Rl)

Và sau đó chạy thay thế trong tất cả các bộ đệm

:bufdo %s/from/to/gc | update

Bạn cũng có thể làm điều đó với sed, nhưng kiến ​​thức sed của tôi còn hạn chế.


Cảm ơn, câu trả lời của bạn đã khiến tôi thực hiện một cú đúp muộn màng: Tôi nhận ra rằng tôi đã hoàn toàn bỏ lỡ bit tương tác. Tôi không nghĩ điều đó thậm chí có thể xảy ra với sed (không đủ kênh đầu vào / đầu ra).
Gilles 'SO- ngừng trở nên xấu xa'

1
Bạn có thể tăng tốc độ này bằng cách không mở TẤT CẢ các tệp trong bộ đệm hiện tại bằng cách sử dụng grepthay vì findđể bạn chỉ mở các tệp đã biết khớp. vim $(grep 'from' . -Rl)
Caleb

Thanks . C (Astreriks quanh c) là cần thiết? hoặc nó là một vấn đề định dạng?
balki

@balki đó là một vấn đề "định dạng". Đã sửa lỗi
Gert

5

Bạn có thể làm một cái gì đó thô thiển với một tập lệnh Perl nhỏ được hướng dẫn để thực hiện thay thế từng dòng ( -l -pe) tại chỗ trên các tệp được truyền dưới dạng đối số ( -i):

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

Những cải tiến có thể sẽ là tô màu các phần của dấu nhắc và hỗ trợ những thứ như Thay thế tất cả các lần xuất hiện trong tệp hiện tại. Việc nhắc riêng cho mỗi lần xuất hiện trên một dòng sẽ khó hơn.

Phần thứ hai, phù hợp với các tập tin. nếu không có quá nhiều tệp liên quan và bạn đang chạy zsh, bạn có thể khớp tất cả các tệp trong thư mục hiện tại và các thư mục con của nó theo cách đệ quy:

perl -i -l -pe '…' **/*(.)

Nếu shell của bạn là bash 4, bạn có thể chạy perl … **/*, nhưng điều đó sẽ tạo ra các thông báo lỗi giả vì sed sẽ thử (và không thành công) để chạy trên các thư mục. Nếu bạn chỉ muốn thực hiện thay thế trong một tập hợp các tệp như tệp C, bạn có thể hạn chế các kết quả khớp (hoạt động trong bash ≥4 hoặc zsh):

perl -i -l -pe '…' **/*.[hc]

Nếu bạn cần kiểm soát tốt hơn những tập tin nào bạn sẽ thay thế hoặc trình bao của bạn không có cấu trúc khớp thư mục đệ quy **hoặc nếu bạn có quá nhiều tập tin và nhận được một dòng lệnh quá dài lỗi, hãy sử dụng find. Ví dụ: để thực hiện thay thế trong tất cả các tệp có tên *.hhoặc *.ctrong thư mục hiện tại và thư mục con của nó (trên các hệ thống cũ hơn, bạn có thể cần sử dụng \;thay vì +ở cuối dòng ( +biểu mẫu nhanh hơn nhưng không khả dụng ở mọi nơi).

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

Điều đó đang được nói, tôi sẽ gắn bó với một trình soạn thảo tương tác nếu bạn cần tương tác. Gert đã chỉ ra một cách để làm điều này trong Vim , mặc dù nó yêu cầu mở tất cả các tệp mà bạn muốn tìm kiếm, đây có thể là một vấn đề nếu có rất nhiều.

Trong Emacs, đây là cách bạn có thể làm điều này:

  1. Tập hợp các tên tệp bằng M-x find-name-dired(chỉ định thư mục toplevel) hoặc M-x find-dired(chỉ định một finddòng lệnh tùy ý ).
  2. Trong bộ đệm dired kết quả , nhấn tđể đánh dấu tất cả các tệp, sau đó Q( dired-do-query-replace-regexp) để thực hiện thay thế bằng lời nhắc trên các tệp được đánh dấu.

1

sdiff(xem http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) có thể có ích ở đây. Với nó, bạn có thể thực hiện vá tương tác. Vì vậy, thực hiện nó với một tệp tạm thời bạn đã tạo bằng cách thực hiện các thao tác thay thế bằng cách sử dụng sedcó thể là một giải pháp khả thi:

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(Vòng lặp tệp sử dụng thay thế quy trình không phải POSIX và read -dnhư được hỗ trợ, ví dụ bash:)

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.