Làm thế nào tôi có thể thực hiện tìm và đệ quy thay thế từ dòng lệnh?


84

Sử dụng shell như bash hoặc zshell, làm cách nào tôi có thể thực hiện đệ quy 'tìm và thay thế'? Nói cách khác, tôi muốn thay thế mọi lần xuất hiện của 'foo' bằng 'bar' trong tất cả các tệp trong thư mục này và các thư mục con của nó.


Một câu trả lời thay thế cho các câu hỏi tương tự có thể được tìm thấy ở đây stackoverflow.com/questions/9704020/ trên
dunxd


Nó có thể là một ý tưởng tốt để thử điều này trong vim. Bằng cách đó, bạn có thể sử dụng tính năng xác nhận để đảm bảo rằng bạn không trao đổi thứ gì đó mà bạn không có ý định. Tôi không chắc chắn nếu nó có thể được thực hiện thư mục rộng.
Samy Bencherif

Câu trả lời:


106

Lệnh này sẽ thực hiện (được thử nghiệm trên cả Mac OS X Lion và Kubfox Linux).

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Đây là cách nó hoạt động:

  1. find . -type f -name '*.txt'tìm thấy, trong thư mục hiện tại ( .) và bên dưới, tất cả các tệp thông thường ( -type f) có tên kết thúc bằng.txt
  2. | chuyển đầu ra của lệnh đó (danh sách tên tệp) sang lệnh tiếp theo
  3. xargs tập hợp những tên tập tin đó và đưa chúng từng cái một sed
  4. sed -i '' -e 's/foo/bar/g'có nghĩa là "chỉnh sửa tệp tại chỗ, không có bản sao lưu và thực hiện thay thế sau ( s/foo/bar) nhiều lần trên mỗi dòng ( /g)" (xem man sed)

Lưu ý rằng phần 'không có bản sao lưu' trong dòng 4 là phù hợp với tôi, vì dù sao các tệp tôi đang thay đổi đều nằm dưới sự kiểm soát phiên bản, vì vậy tôi có thể dễ dàng hoàn tác nếu có lỗi.


9
Không bao giờ đường ống tìm đầu ra cho xargs mà không có -print0tùy chọn. Lệnh của bạn sẽ thất bại trên các tệp có khoảng trắng, v.v. trong tên của chúng.
slhck

20
Ngoài ra, find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +sẽ làm tất cả điều này với GNU find.
Daniel Andersson

6
Tôi nhận được sed: can't read : No such file or directorykhi tôi chạy find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', nhưng find . -name '*.md' -print0đưa ra một danh sách nhiều tệp.
Martin Thoma

9
Điều này hiệu quả với tôi nếu tôi xóa khoảng cách giữa -i''
Canada Luke

3
Ý nghĩa của những gì là ''sau khi sed -isự là những gì ''vai trò?
Jas

27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Điều này loại bỏ sự xargsphụ thuộc.


4
Điều này không hoạt động với GNU sed, vì vậy sẽ thất bại trên hầu hết các hệ thống. GNU sedyêu cầu bạn không đặt khoảng trống giữa -i''.
slhck

1
Các câu trả lời được chấp nhận làm một công việc tốt hơn để giải thích nó. nhưng +1 để sử dụng cú pháp đúng.
orodbhen

9

Nếu bạn đang sử dụng Git thì bạn có thể làm điều này:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lchỉ liệt kê tên tập tin. -zin một byte null sau mỗi kết quả.

Tôi đã kết thúc việc này vì một số tệp trong một dự án không có dòng mới ở cuối tệp và sed đã thêm một dòng mới ngay cả khi nó không có thay đổi nào khác. (Không có nhận xét nào về việc các tập tin có nên có một dòng mới ở cuối hay không.)


1
+1 lớn cho giải pháp này. Các find ... -print0 | xargs -0 sed ...giải pháp còn lại không chỉ mất nhiều thời gian hơn mà còn thêm các dòng mới vào các tệp chưa có, điều này gây phiền toái khi làm việc bên trong repo git. git greplà ánh sáng nhanh bằng cách so sánh.
Josh Kupershmidt

3

Đây là hàm zsh / perl của tôi, tôi sử dụng cho việc này:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

Và tôi sẽ thực hiện nó bằng cách sử dụng

$ change foo bar **/*.java

(ví dụ)


2

Thử:

sed -i 's/foo/bar/g' $(find . -type f)

Đã thử nghiệm trên Ubuntu 12.04.

Lệnh này sẽ KHÔNG hoạt động nếu tên thư mục con và / hoặc tên tệp chứa khoảng trắng, nhưng nếu bạn không sử dụng lệnh này thì nó sẽ không hoạt động.

Nói chung, việc sử dụng khoảng trắng trong tên thư mục và tên tệp là một thực tế xấu.

http://linuxcommand.org/lc3_lts0020.php

Nhìn vào "Sự thật quan trọng về tên tệp"


Hãy thử nó khi bạn có (các) tệp có dấu cách trong tên của chúng. (Có một quy tắc ngón tay cái nói rằng: "Nếu điều gì đó có vẻ quá tốt là sự thật, thì có lẽ là như vậy." Nếu bạn đã "phát hiện" một giải pháp nhỏ gọn hơn bất cứ ai khác đã đăng trong 3 năm rưỡi, bạn nên tự hỏi tại sao đó có thể là.)
Scott

1

Sử dụng tập lệnh Shell này

Bây giờ tôi sử dụng tập lệnh shell này, kết hợp những điều tôi học được từ các câu trả lời khác và từ việc tìm kiếm trên web. Tôi đặt nó trong một tập tin được gọi changetrong một thư mục trên $PATHvà đã làm chmod +x change.

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

Trường hợp sử dụng của tôi là tôi muốn thay thế foo:/Drive_Letterbằng foo:/bar/baz/xyz Trong trường hợp của tôi, tôi đã có thể làm điều đó với mã sau đây. Tôi đã ở cùng một vị trí thư mục nơi có nhiều tập tin.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

Hy vọng rằng đã giúp.


0

Lệnh sau hoạt động tốt trên Ubuntu và CentOS; tuy nhiên, trong OS XI liên tục gặp lỗi:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": mã lệnh không hợp lệ.

Khi tôi thử chuyển thông số qua xargs, nó hoạt động tốt mà không có lỗi:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

Thực tế là bạn đã thay đổi -iđến -i ''có lẽ là thích hợp hơn so với thực tế là bạn đã thay đổi -execđến -print0 | xargs -0. BTW, bạn có thể không cần -e.
Scott

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Ở trên hoạt động như một bùa mê, nhưng với các thư mục được liên kết, tôi sẽ thêm -Lcờ vào nó. Phiên bản cuối cùng trông như:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
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.