tra-loi-cau-hoi-phat-trien-web.com

Linux dòng lệnh tìm kiếm toàn cầu và thay thế

Tôi đang cố gắng tìm kiếm và thay thế một chuỗi trong tất cả các tệp được khớp bởi grep trên máy linux. Tôi đã có một số phần của những gì tôi muốn làm, nhưng tôi không chắc làm thế nào tốt nhất để kết hợp tất cả chúng lại với nhau.

grep -n 'foo' * sẽ cho tôi đầu ra ở dạng:

[filename]:[line number]:[text]

Đối với mỗi tệp được trả về bởi grep, tôi muốn thay thế "foo" bằng "bar" và ghi lại kết quả vào tệp. Có một cách tốt để làm điều đó? Có lẽ một đường ống lạ mắt?

79
Michael Kristofik

Bạn có nghĩa là tìm kiếm và thay thế một chuỗi trong tất cả các tệp được khớp bởi grep?

Perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

Chỉnh sửa

Vì đây có vẻ là một câu hỏi khá phổ biến mà tôi muốn cập nhật.

Ngày nay tôi chủ yếu sử dụng ack-grep vì nó thân thiện với người dùng hơn. Vì vậy, lệnh trên sẽ là:

Perl -p -i -e 's/old/new/g' `ack -l searchpattern`

Để xử lý khoảng trắng trong tên tệp, bạn có thể chạy:

ack --print0 -l searchpattern | xargs -0 Perl -p -i -e 's/old/new/g'

bạn có thể làm nhiều hơn với ack-grep. Giả sử bạn muốn giới hạn tìm kiếm chỉ với các tệp HTML:

ack --print0 --html -l searchpattern | xargs -0 Perl -p -i -e 's/old/new/g'

Và nếu khoảng trắng không phải là vấn đề thì nó thậm chí còn ngắn hơn:

Perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
Perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files
70
armandino

Đây dường như là những gì bạn muốn, dựa trên ví dụ bạn đã đưa ra:

sed -i 's/foo/bar/g' *

Nó không được đệ quy (nó sẽ không rơi vào các thư mục con). Đối với một giải pháp Nice thay thế trong các tệp đã chọn trong toàn bộ cây tôi sẽ sử dụng find:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Các *.html là biểu thức mà các tệp phải khớp, .bak sau -i tạo một bản sao của tệp gốc, với phần mở rộng .bak (nó có thể là bất kỳ phần mở rộng nào bạn thích) và g ở cuối biểu thức sed nói với sed thay thế nhiều bản sao trên một dòng (thay vì hơn chỉ người đầu tiên). Các -print to find là một sự tiện lợi để hiển thị những tập tin nào đang được khớp. Tất cả điều này phụ thuộc vào các phiên bản chính xác của các công cụ này trên hệ thống của bạn.

108
MattJ

Nếu sed(1) của bạn có -i tùy chọn, sau đó sử dụng nó như thế này:

for i in *; do
  sed -i 's/foo/bar/' $i
done

Nếu không, có một số cách biến thể sau đây tùy thuộc vào ngôn ngữ bạn muốn chơi với:

Ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
Perl -pi.bak -e 's/foo/bar/' *
13
Keltia

Tôi thích và sử dụng giải pháp trên hoặc tìm kiếm trên toàn hệ thống và thay thế trong số hàng ngàn tệp:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Tôi giả sử với '* .htm?' thay vì .html, nó tìm kiếm và tìm thấy các tệp .htm và .html giống nhau.

Tôi thay thế .bak bằng dấu ngã được sử dụng trên toàn hệ thống (~) để làm sạch các tệp sao lưu dễ dàng hơn.

6
hans

Điều này hoạt động bằng cách sử dụng grep mà không cần sử dụng Perl hoặc find.

grep -rli 'old-Word' * | xargs [email protected] sed -i 's/old-Word/new-Word/g' @
4
pymarco

find . -type f -print0 | xargs -0 <sed/Perl/Ruby cmd> sẽ xử lý nhiều không gian chứa tên tệp cùng một lúc tải một trình thông dịch mỗi đợt. Nhanh hơn nhiều.

3
koolb

Điều này thực sự dễ dàng hơn nó có vẻ.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep đệ quy qua cây của bạn (-R) và chỉ in tên tệp (-l), bắt đầu từ thư mục hiện tại (./)
  • được chuyển thành xargs, xử lý chúng cùng một lúc (-n 1) và sử dụng% làm trình giữ chỗ (-I%) trong lệnh Shell (sh -c)
  • trong lệnh Shell, đầu tiên tên tệp được in (ls%;)
  • sau đó sed thực hiện một thao tác nội tuyến (-i), một biến thể ('s /') của foo với thanh (foo/bar), trên toàn cầu (/ g) trên tệp (một lần nữa, được biểu thị bằng%)

Dễ như ăn bánh. Nếu bạn nắm bắt tốt về find, grep, xargs, sed và awk, hầu như không có gì là không thể khi nói đến thao tác tệp văn bản trong bash :)

1
siliconrockstar

Câu trả lời đã được đưa ra khi sử dụng find sed

find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

có lẽ là câu trả lời chuẩn. Hoặc bạn có thể sử dụng Perl -pi -e s/foo/bar/g' thay vì lệnh sed.

Để sử dụng nhanh nhất, bạn có thể thấy lệnh rpl dễ nhớ hơn. Đây là sự thay thế (foo -> bar), đệ quy trên tất cả các tệp trong thư mục hiện tại:

rpl -R foo bar .

Nó không có sẵn theo mặc định trên hầu hết các bản phân phối Linux nhưng cài đặt nhanh (apt-get install rpl hoặc tương tự).

Tuy nhiên, đối với các công việc khó khăn hơn liên quan đến biểu thức chính quy và thay thế trở lại, hoặc đổi tên tệp cũng như tìm kiếm và thay thế, công cụ mạnh mẽ và tổng quát nhất mà tôi biết là repren , một tập lệnh nhỏ Python tôi đã viết một thời gian trước cho một số nhiệm vụ đổi tên và tái cấu trúc khó khăn hơn. Những lý do bạn có thể thích nó là:

  • Hỗ trợ đổi tên tệp cũng như tìm kiếm và thay thế nội dung tệp (bao gồm di chuyển tệp giữa các thư mục và tạo thư mục gốc mới).
  • Xem các thay đổi trước khi bạn cam kết thực hiện tìm kiếm và thay thế.
  • Hỗ trợ các biểu thức chính quy với các thay thế trở lại, toàn bộ từ, không phân biệt chữ hoa chữ thường và bảo quản trường hợp (thay thế các chế độ foo -> bar, Foo -> Bar, FOO -> BAR).
  • Hoạt động với nhiều thay thế, bao gồm hoán đổi (foo -> thanh và thanh -> foo) hoặc bộ thay thế không duy nhất (foo -> bar, f -> x).

Kiểm tra README để biết ví dụ.

1
jlevy