Tìm và thay thế bên trong tệp văn bản từ lệnh Bash


561

Cách đơn giản nhất để thực hiện tìm và thay thế cho một chuỗi đầu vào đã cho, nói abcvà thay thế bằng một chuỗi khác, nói XYZtrong tệp là /tmp/file.txtgì?

Tôi đang viết một ứng dụng và sử dụng IronPython để thực thi các lệnh thông qua SSH - nhưng tôi không biết rõ về Unix và không biết phải tìm gì.

Tôi đã nghe nói rằng Bash, ngoài việc là một giao diện dòng lệnh, có thể là một ngôn ngữ kịch bản rất mạnh mẽ. Vì vậy, nếu điều này là đúng, tôi cho rằng bạn có thể thực hiện các hành động như thế này.

Tôi có thể làm điều đó với bash không, và kịch bản (một dòng) đơn giản nhất để đạt được mục tiêu của tôi là gì?

Câu trả lời:


930

Cách dễ nhất là sử dụng sed (hoặc perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Điều này sẽ gọi sed để thực hiện chỉnh sửa tại chỗ do -itùy chọn. Điều này có thể được gọi từ bash.

Nếu bạn thực sự thực sự muốn sử dụng chỉ bash, thì những điều sau đây có thể hoạt động:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Vòng lặp này trên mỗi dòng, thực hiện thay thế và ghi vào một tệp tạm thời (không muốn ghi đè đầu vào). Di chuyển ở cuối chỉ di chuyển tạm thời đến tên ban đầu.


3
Ngoại trừ việc gọi mv là khá nhiều 'non Bash' như sử dụng sed. Tôi gần như đã nói giống như tiếng vang, nhưng đó là một vỏ dựng sẵn.
mỏng

5
Tuy nhiên, đối số -i cho sed không tồn tại đối với Solaris (và tôi sẽ nghĩ rằng một số triển khai khác), vì vậy hãy ghi nhớ điều đó. Chỉ cần dành vài phút để tìm ra điều đó ...
Panky

2
Lưu ý đến bản thân: về biểu thức chính quy của sed: s/..../..../ - Substitute/g - Global
tổng kiểm tra

89
Lưu ý đối với người dùng Mac invalid command code Cgặp lỗi ... Đối với các thay thế tại chỗ, BSD sedyêu cầu một phần mở rộng tệp sau -icờ vì nó lưu tệp sao lưu với phần mở rộng đã cho. Ví dụ: sed -i '.bak' 's/find/replace/' /file.txt Bạn có thể bỏ qua bản sao lưu bằng cách sử dụng một chuỗi trống như vậy:sed -i '' 's/find/replace/' /file.txt
Austin

8
Mẹo: Nếu bạn muốn sử dụng phản hồi không nhạy cảm trong trường hợps/abc/XYZ/gi
Boris D. Teoharov

166

Thao tác tệp thường không được thực hiện bởi Bash, nhưng bởi các chương trình được gọi bởi Bash, ví dụ:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

Các -ilá cờ nói với nó để làm một sự thay thế tại chỗ.

Xem man perlrunđể biết thêm chi tiết, bao gồm cách sao lưu tệp gốc.


37
Người theo chủ nghĩa thuần túy trong tôi nói rằng bạn không thể chắc chắn Perl sẽ có mặt trên hệ thống. Nhưng đó là điều rất hiếm khi xảy ra ngày nay. Có lẽ tôi đang thể hiện tuổi của mình.
mỏng

3
Bạn có thể hiển thị một ví dụ phức tạp hơn. Một cái gì đó như thay thế "chdir / blah" bằng "chdir / blah2". Tôi đã thử perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, nhưng tôi liên tục gặp lỗi với Không có khoảng cách giữa mẫu và từ theo sau không được chấp nhận ở dòng -e 1. Chưa từng có (trong regex; được đánh dấu bởi <- TẠI ĐÂY trong m / (chdir) () (<- TẠI ĐÂY ?: \\ / at -e dòng 1.
CMCDragonkai

@CMCDragonkai Kiểm tra câu trả lời này: stackoverflow.com/a/12061491/2730528
Alfonso Santiago

69

Tôi đã rất ngạc nhiên khi tôi vấp phải điều này ...

Có một replacelệnh mà "mysql-server"gói với gói, vì vậy nếu bạn đã cài đặt nó hãy thử nó:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Xem man replaceđể biết thêm về điều này.


12
Có hai điều có thể ở đây: a) replacelà một công cụ độc lập hữu ích và mọi người nên phát hành nó một cách riêng biệt và phụ thuộc vào nó b) replaceyêu cầu một số MySQL o_O Dù bằng cách nào, cài đặt máy chủ mysql để thay thế sẽ là điều sai lầm. :)
Philip Whitehouse

Chỉ hoạt động cho mac? trong Ubuntu của tôi, tôi không có lệnh đó
paul

1
Đó là bởi vì bạn chưa mysql-servercài đặt gói. Như được chỉ ra bởi @rayro, replacelà một phần của nó.
Phius

2
"Cảnh báo: thay thế không được chấp nhận và sẽ bị xóa trong phiên bản tương lai."
Steven Vachon

1
Hãy cẩn thận để không chạy lệnh REPLACE trên Windows! Trên Windows, lệnh REPLACE là để sao chép nhanh các tệp. Không liên quan đến cuộc thảo luận này.
Maor

53

Đây là một bài viết cũ nhưng đối với bất kỳ ai muốn sử dụng các biến như @centurian cho biết các trích dẫn duy nhất có nghĩa là không có gì sẽ được mở rộng.

Một cách đơn giản để nhận các biến trong là thực hiện nối chuỗi vì điều này được thực hiện bởi juxtap vị trí trong bash, sau đây sẽ hoạt động:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt

39

Bash, giống như các shell khác, chỉ là một công cụ để phối hợp các lệnh khác. Thông thường, bạn sẽ cố gắng sử dụng các lệnh UNIX tiêu chuẩn, nhưng tất nhiên bạn có thể sử dụng Bash để gọi bất cứ thứ gì, bao gồm các chương trình được biên dịch của riêng bạn, các tập lệnh shell khác, tập lệnh Python và Perl, v.v.

Trong trường hợp này, có một vài cách để làm điều đó.

Nếu bạn muốn đọc một tệp và ghi nó vào một tệp khác, thực hiện tìm kiếm / thay thế khi bạn đi, hãy sử dụng sed:

sed 's/abc/XYZ/g' <infile >outfile

Nếu bạn muốn chỉnh sửa tệp tại chỗ (như thể mở tệp trong trình chỉnh sửa, chỉnh sửa tệp, sau đó lưu tệp), hãy cung cấp hướng dẫn cho trình chỉnh sửa dòng 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex giống như vi mà không có chế độ toàn màn hình. Bạn có thể cung cấp cho nó các lệnh tương tự như tại dấu nhắc ':' của vi.


33

Tôi đã tìm thấy chủ đề này trong số những người khác và tôi đồng ý rằng nó chứa các câu trả lời đầy đủ nhất vì vậy tôi cũng đang thêm câu hỏi của mình:

  1. sededrất hữu ích ... bằng tay. Nhìn vào mã này từ @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Khi hạn chế của tôi là sử dụng nó trong tập lệnh shell, không có biến nào có thể được sử dụng bên trong thay cho "abc" hoặc "XYZ". Các BashFAQ dường như đồng ý với những gì tôi hiểu ít nhất. Vì vậy, tôi không thể sử dụng:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    Nhưng những gì chúng ta có thể làm gì? Như, @Johnny nói sử dụng một while read..., nhưng thật không may, đó không phải là kết thúc của câu chuyện. Sau đây làm việc tốt với tôi:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. Như người ta có thể thấy nếu nullglobđược thiết lập sau đó, nó cư xử lạ lùng khi có một chuỗi có chứa một *như trong:

    <VirtualHost *:80>
     ServerName www.example.com

    trở thành

    <VirtualHost ServerName www.example.com

    không có khung góc kết thúc và Apache2 thậm chí không thể tải.

  4. Kiểu phân tích cú pháp này phải chậm hơn tìm kiếm một lần và thay thế, nhưng, như bạn đã thấy, có bốn biến cho bốn mẫu tìm kiếm khác nhau hoạt động trong một chu kỳ phân tích cú pháp.

Giải pháp phù hợp nhất tôi có thể nghĩ ra với các giả định đã cho của vấn đề.


12
Trong (2) của bạn - bạn có thể làm sed -e "s/$x/$y/", và nó sẽ hoạt động. Không phải là dấu ngoặc kép. Nó có thể gây nhầm lẫn nghiêm trọng nếu các chuỗi trong chính các biến chứa các ký tự có ý nghĩa đặc biệt. Ví dụ: nếu x = "/" hoặc x = "\". Khi bạn gặp phải những vấn đề này, điều đó có nghĩa là bạn nên ngừng cố gắng sử dụng trình bao cho công việc này.
mỏng

Hi slim, tôi thấy rằng bạn cũng chống lại việc sử dụng Perl. Giải pháp của bạn là gì? Bởi vì trên thực tế tôi muốn thay đổi một cách linh hoạt một đường dẫn trong một tệp, điều đó có nghĩa là tôi có rất nhiều / trong chuỗi!
Mahdi

20

Bạn có thể sử dụng sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Sử dụng -icho "bỏ qua trường hợp" nếu bạn không chắc chắn văn bản để tìm là abchay ABChoặc AbCvv

Bạn có thể sử dụng findsednếu bạn không biết tên tệp của mình:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Tìm và thay thế trong tất cả các tệp Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;

12

Bạn cũng có thể sử dụng edlệnh để thực hiện tìm kiếm trong tệp và thay thế:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Xem thêm trong " Chỉnh sửa tệp qua tập lệnh vớied ".


3
Giải pháp này độc lập với sự không tương thích của GNU / FreeBSD (Mac OSX) (không giống như sed -i <pattern> <filename>). Rất đẹp!
Peterino

11

Nếu tệp bạn đang làm việc không quá lớn và việc tạm thời lưu trữ nó trong một biến không có vấn đề gì, thì bạn có thể sử dụng thay thế chuỗi Bash trên toàn bộ tệp cùng một lúc - không cần phải đi qua từng dòng:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Toàn bộ nội dung tệp sẽ được coi là một chuỗi dài, bao gồm cả ngắt dòng.

XYZ có thể là một biến $replacement, ví dụ , và một ưu điểm của việc không sử dụng sed ở đây là bạn không cần phải lo lắng rằng chuỗi tìm kiếm hoặc thay thế có thể chứa ký tự phân cách mẫu sed (thường, nhưng không nhất thiết, /). Một bất lợi là không thể sử dụng các biểu thức thông thường hoặc bất kỳ hoạt động phức tạp hơn của sed.


Bất kỳ lời khuyên cho việc sử dụng này với các ký tự tab? Vì một số lý do, tập lệnh của tôi không tìm thấy bất cứ thứ gì với các tab sau khi thay đổi từ sed với nhiều lối thoát sang phương thức này.
Brian Hannay

2
Nếu bạn muốn đặt một tab trong chuỗi bạn đang thay thế, bạn có thể làm như vậy với cú pháp "trích dẫn đơn như búp bê" của Bash, do đó, một tab được biểu thị bằng $ '\ t' và bạn có thể thực hiện $ echo 'tab' $ '\ t''parparated'> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff


5

Hãy thử lệnh shell sau:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'

4
Đây là một câu trả lời tuyệt vời cho "làm thế nào để tôi vô tình làm tất cả các tệp trong tất cả các thư mục con", nhưng đó dường như không phải là những gì được hỏi ở đây.
tripleee

Cú pháp này sẽ không hoạt động cho phiên bản BSD sed, sử dụng sed -i''thay thế.
kenorb

5

Để chỉnh sửa văn bản trong tệp không tương tác, bạn cần trình soạn thảo văn bản tại chỗ như vim.

Dưới đây là ví dụ đơn giản về cách sử dụng nó từ dòng lệnh:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Điều này tương đương với câu trả lời @slim của biên tập viên về cơ bản là điều tương tự.

Dưới đây là một vài exví dụ thực tế.

Thay thế văn bản foovới bartrong file:

ex -s +%s/foo/bar/ge -cwq file.txt

Xóa các khoảng trắng ở cuối cho nhiều tệp:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Xử lý sự cố (khi thiết bị đầu cuối bị kẹt):

  • Thêm -V1param để hiển thị thông điệp dài dòng.
  • Buộc bỏ bằng cách : -cwq!.

Xem thêm:


Muốn làm thay thế tương tác. Do đó đã thử "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Nhưng thiết bị đầu cuối bị kẹt bây giờ. Làm thế nào chúng ta sẽ thực hiện thay thế một cách tương tác mà không có vỏ bash hành xử kỳ lạ.
Vineeshvs

Để gỡ lỗi, thêm -V1, buộc bỏ, sử dụng wq!.
kenorb

2

Bạn cũng có thể sử dụng python trong tập lệnh bash. Tôi đã không có nhiều thành công với một số câu trả lời hàng đầu ở đây và thấy điều này hoạt động mà không cần vòng lặp:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()

1

Bạn có thể sử dụng lệnh rpl. Ví dụ bạn muốn thay đổi tên miền trong toàn bộ dự án php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Đây không phải là nguyên nhân rõ ràng, nhưng nó rất nhanh chóng và hữu ích. :)

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.