Dòng lệnh: tìm kiếm và thay thế trong tất cả các tên tệp được khớp bởi grep


80

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 đối sánh bởi grep:

grep -n 'foo' * sẽ cung cấp 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 sửa đổi tệp bằng cách thay thế foobằng bar.

Câu trả lời:


70

Ý của bạn là tìm kiếm và thay thế một chuỗi trong tất cả các tệp được đối sánh bởi grep?

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

Biên tập

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

Ngày nay tôi chủ yếu sử dụng ack-grepvì 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ỉ trong 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 đề, 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

3
Tôi nghĩ rằng điều này có thể có vấn đề với các tệp / thư mục chứa khoảng trắng. Can't open Untitled: No such file or directory, <> line 5khi thử "Thư mục không có tiêu đề / file.txt".
Xeoncross

108

Đâ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 phải là đệ quy (nó sẽ không xuống thư mục con). Để có một giải pháp tốt thay thế trong các tệp đã chọn trong một cây, tôi sẽ sử dụng, hãy tìm:

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

Đây *.htmllà biểu thức mà các tệp phải khớp, .baksau khi -itạ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 cho biết sed thay thế nhiều bản sao trên một dòng (thay vì chỉ dòng đầu tiên). Việc -printtìm kiếm là một sự thuận tiện để hiển thị các tệp đang được khớp. Tất cả điều này phụ thuộc vào 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.


1
Một lời cảnh báo cho người dùng cygwin. combo find và sed dường như thay đổi quyền của người dùng đối với các tệp được truyền trực tuyến. Điều này có thể được khắc phục một cách đơn giản bằng cách sử dụng lệnh chmod -R 644 * từ cùng một mức dir đã được sử dụng khi vận hành find / sed.
kaskelotti

Một lời cảnh báo cho người dân mà không làm muốn sử dụng đối số -i: nếu bạn không sử dụng nó, nó không làm việc (đừng hỏi tôi tại sao)
knocte

4
@knocte -i yêu cầu sed sửa đổi tệp, nếu không nó chỉ in phiên bản đã sửa đổi thành stdout. Nếu bạn không muốn tạo tệp .bak, chỉ cần bỏ qua phần '.bak', -i cũng hoạt động độc lập.
MattJ

2
Trên OSX, bạn cần cung cấp cho findlệnh một thư mục để bắt đầu, chẳng hạn find . -name '*.html'hoặc find directoryname/ -name '*'.
Michiel Kauw-A-Tjoe

tôi cần thêm một -e, nếu không nó nghĩ 's ...' một phần là hậu tốsed -ie 's/foo/bar/g' *
Vish

14

Nếu bạn sed(1)có một -itùy chọn, hãy sử dụng nó như sau:

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:

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *

2
for i in *; do ...là thừa, sed có thể lấy một danh sách các tệp làm đối số.
Jens

2
@Jens tại sao không cải thiện câu trả lời này bằng cách thêm ví dụ của riêng bạn vào ví dụ ở trên?
Magpie

Các biến phải luôn được trích dẫnfor i in *; do; sed -i 's/foo/bar/g' "$i"; done
cat

6

Tôi thích và sử dụng giải pháp trên hoặc tìm kiếm và thay thế trên toàn hệ thống trong số hàng nghì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 như nhau.

Tôi thay thế .bak bằng dấu ngã (~) được sử dụng rộng rãi hơn trong hệ thống để làm cho việc dọn dẹp các tệp sao lưu dễ dàng hơn.


4

Đ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 tìm.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

xargs không có -i trên OSX hoặc BSD openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/… ý của bạn là sử dụng chữ hoa "I"?
Tony Adams

Tôi không biết -inó không hoạt động cho hệ điều hành khác. Hoạt động cho tôi trên ubuntu.
pymarco

Trang chủ của tôi xargs(trên Ubuntu) nói rằng -inó không được dùng nữa và để sử dụng -Ithay thế. Vì vậy, chúng ta nên nói:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath
Max Wallace

3

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 khi tải một trình thông dịch cho mỗi lô. Nhanh hơn nhiều.


@knocte, "cmd" là mã thông báo cho toàn bộ lệnh tìm kiếm và thay thế cho bất kỳ công cụ nào mà người ta chọn. Câu trả lời này trả lời câu hỏi về cách xử lý các tên tệp chứa khoảng trắng.
Tony Adams

1

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

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

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

Đối với hầu hết các cách sử dụng nhanh, bạn có thể thấy lệnh rpl dễ nhớ hơn. Đây là 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 chóng ( apt-get install rplhoặc tương tự).

Tuy nhiên, đối với những công việc khó khăn hơn liên quan đến biểu thức chính quy và thay thế ngược hoặc đổi tên tệp cũng như tìm kiếm và thay thế, công cụ chung và mạnh mẽ nhất mà tôi biết là repren , một tập lệnh Python nhỏ mà tôi đã viết một thời gian. nhiệm vụ đổi tên và cấu trúc lại gai góc 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 mẹ 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 chế độ thay thế lùi, toàn bộ từ, không phân biệt chữ hoa chữ thường và chữ hoa chữ thường (thay thế foo -> thanh, Foo -> Thanh, FOO -> BAR).
  • Hoạt động với nhiều thay thế, bao gồm hoán đổi (foo -> thanh và thanh -> foo) hoặc tập hợp các thay thế không duy nhất (foo -> thanh, f -> x).

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


1

Điều này thực sự dễ dàng hơn tưởng tượng.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep đệ quy thông 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 đến xargs, xử lý chúng tại một thời điểm (-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 hoạt động nội tuyến (-i), một phụ ('s /') của foo với thanh (foo / bar), toàn cục (/ g) trên tệp (một lần nữa, được đại diện bởi%)

Dễ như ăn bánh. Nếu bạn hiểu rõ 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 với tệp văn bản trong bash :)


Bah, mặc kệ, pymarco đã cover điều này ở trên. Để lại câu trả lời của tôi cho lời giải thích.
siliconrockstar
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.