Thay thế một chuỗi con cho một chuỗi khác trong tập lệnh shell


823

Tôi có "Tôi yêu Suzi và Marry" và tôi muốn đổi "Suzi" thành "Sara".

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
# do something...

Kết quả phải như thế này:

firstString="I love Sara and Marry"

18
Tôi sẽ bắt đầu bằng cách để Suzi xuống một cách nhẹ nhàng. :)
mã số

Không phải bạn, là tôi! đó đáng lẽ phải là chuỗi để nói chuyện với Suzi
GoodSp33d

Câu trả lời:


1360

Để thay thế lần xuất hiện đầu tiên của mẫu bằng một chuỗi đã cho, hãy sử dụng :${parameter/pattern/string}

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"    
# prints 'I love Sara and Marry'

Để thay thế tất cả các lần xuất hiện, sử dụng :${parameter//pattern/string}

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(Điều này được ghi chép lại trong các hướng dẫn sử dụng Bash Reference , §3.5.3 "Shell Parameter Expansion" .)

Lưu ý rằng tính năng này không được chỉ định bởi POSIX - đó là phần mở rộng Bash - vì vậy không phải tất cả các shell Unix đều thực hiện nó. Để biết tài liệu POSIX có liên quan, hãy xem Thông số kỹ thuật cơ sở tiêu chuẩn kỹ thuật của nhóm mở, Số 7 , khối lượng Shell & Tiện ích , §2.6.2 "Mở rộng tham số" .


3
@ruakh làm thế nào để tôi viết tuyên bố này với một hoặc điều kiện. Chỉ cần tôi muốn thay thế Suzi hoặc Marry bằng chuỗi mới.
Priyatham51

3
@ Priyatham51: Không có tính năng tích hợp nào cho việc đó. Chỉ cần thay thế một, sau đó khác.
ruakh

4
@Bu: Không, bởi vì \ntrong bối cảnh đó sẽ đại diện cho chính nó, không phải là một dòng mới. Tôi không có Bash tiện dụng ngay bây giờ để kiểm tra, nhưng bạn sẽ có thể viết một cái gì đó như , $STRING="${STRING/$'\n'/<br />}". (Mặc dù bạn có thể muốn STRING//- thay thế tất cả - thay vì chỉ STRING/.)
ruakh

25
Để rõ ràng, vì điều này làm tôi bối rối một chút, phần đầu tiên phải là một tài liệu tham khảo biến. Bạn không thể làm được echo ${my string foo/foo/bar}. Bạn cầninput="my string foo"; echo ${input/foo/bar}
Henrik N

2
@mgutt: Câu trả lời này liên kết đến các tài liệu liên quan. Tài liệu này không hoàn hảo - ví dụ, thay vì đề cập //trực tiếp, nó đề cập đến những gì xảy ra "Nếu mẫu bắt đầu bằng ' /'" (điều này thậm chí không chính xác, vì phần còn lại của câu đó cho rằng phần bổ sung /không thực sự là một phần của mô hình) - nhưng tôi không biết nguồn nào tốt hơn.
ruakh

208

Điều này có thể được thực hiện hoàn toàn với thao tác chuỗi bash:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

Điều đó sẽ chỉ thay thế sự xuất hiện đầu tiên; để thay thế tất cả, nhân đôi dấu gạch chéo đầu tiên:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"

9
Điểm nào có câu trả lời trùng lặp câu trả lời được chấp nhận, thay vì chỉnh sửa câu trả lời được chấp nhận bằng tùy chọn thay thế toàn cầu? Và thêm regexps đó được hỗ trợ, trong khi tại nó. giải thích những gì với "Thay thế xấu". Bạn có đủ điểm, @Kevin :)
Dan Dascalescu

6
Có vẻ như cả hai đã trả lời trong cùng một phút: O
Jamie Hutber

76

Đối với Dash tất cả các bài viết trước không hoạt động

shGiải pháp tương thích POSIX là:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")

1
Tôi đã nhận được điều này: $ echo $ (echo $ FirstString | sed 's / Suzi / $ secondString / g') Tôi yêu $ secondString và Marry
Qstnr_La

2
@Qstnr_La sử dụng dấu ngoặc kép để thay thế biến:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc

1
Cộng 1 để hiển thị cách xuất ra một biến. Cảm ơn!
Đầu bếp Pharaoh

2
Tôi đã sửa các trích dẫn đơn và cũng thêm các trích dẫn còn thiếu xung quanh echođối số. Nó hoạt động một cách khôn ngoan mà không cần trích dẫn với các chuỗi đơn giản, nhưng dễ dàng phá vỡ bất kỳ chuỗi đầu vào không cần thiết nào (khoảng cách không đều, siêu ký tự shell, v.v.).
tripleee

Trong sh (AWS Codebuild / Ubuntu sh) tôi thấy rằng tôi cần một dấu gạch chéo ở cuối chứ không phải gấp đôi. Tôi sẽ chỉnh sửa nhận xét vì các bình luận ở trên cũng hiển thị một dấu gạch chéo.
Tim

67

thử cái này:

 sed "s/Suzi/$secondString/g" <<<"$firstString"

19
Bạn thực sự không cần Sed cho việc này; Bash hỗ trợ loại thay thế tự nhiên này.
ruakh

12
Tôi đoán điều này được gắn thẻ "bash" nhưng đã đến đây vì cần một cái gì đó đơn giản cho một vỏ khác. Đây là một giải pháp thay thế ngắn gọn tuyệt vời cho những gì wiki.ub Ubuntu.com/ đã làm cho nó trông giống như tôi cần.
14

3
Điều này hoạt động tuyệt vời cho tro / gạch ngang hoặc bất kỳ sh POSIX khác.
Yoshua Wuyts

2
Tôi gặp lỗi sed: -e biểu thức # 1, char 9: tùy chọn không xác định đối với `s
Nam G VU

1
Câu trả lời đầu tiên hoạt động với stdin, tuyệt vời cho chuỗi đường ống.
Dinei

46

Sử dụng bash tốt hơn sed nếu các chuỗi có các ký tự RegExp.

echo ${first_string/Suzi/$second_string}

Nó có thể di động tới Windows và hoạt động với ít nhất là cũ như Bash 3.1.

Để cho thấy bạn không cần phải lo lắng nhiều về việc trốn thoát, hãy biến điều này:

/home/name/foo/bar

Vào đây:

~/foo/bar

Nhưng chỉ khi /home/name là trong đầu. Chúng tôi không cần sed!

Cho rằng bash cho chúng ta các biến ma thuật $PWD$HOME, chúng ta có thể:

echo "${PWD/#$HOME/\~}"

BIÊN TẬP: Cảm ơn Mark Haferkamp trong các bình luận cho ghi chú về trích dẫn / thoát ~. *

Lưu ý cách biến $HOME chứa dấu gạch chéo nhưng điều này không phá vỡ bất cứ điều gì.

Đọc thêm: Hướng dẫn Bash-Scripting nâng cao .
Nếu sử dụng sedlà bắt buộc, hãy chắc chắn để thoát khỏi mọi nhân vật .


Câu trả lời này đã ngăn tôi sử dụng sedvới pwdlệnh để tránh xác định một biến mới mỗi lần tùy chỉnh của tôi $PS1chạy. Bash có cung cấp một cách tổng quát hơn các biến ma thuật để sử dụng đầu ra của lệnh để thay thế chuỗi không? Đối với mã của bạn, tôi phải thoát khỏi ~để ngăn Bash mở rộng nó thành $ HOME. Ngoài ra, #trong lệnh của bạn làm gì?
Đánh dấu Haferkamp

1
@MarkHaferkamp Xem điều này từ liên kết "khuyến nghị đọc thêm". Về "thoát khỏi ~": chú ý cách tôi trích dẫn công cụ. Hãy nhớ luôn luôn trích dẫn công cụ! Và điều này không chỉ hoạt động đối với các biến ma thuật: bất kỳ biến nào cũng có khả năng thay thế, nhận được độ dài chuỗi và hơn thế nữa, trong bash. Chúc mừng bạn đã cố gắng $PS1nhanh: bạn cũng có thể quan tâm $PROMPT_COMMANDnếu bạn cảm thấy thoải mái hơn trong ngôn ngữ lập trình khác và muốn mã hóa lời nhắc được biên dịch.
Camilo Martin

Liên kết "đọc thêm" giải thích "#". Trên Bash 4.3.30, echo "${PWD/#$HOME/~}"không thay thế tôi $HOMEbằng ~. Thay thế ~bằng \~hoặc '~'làm việc. Bất kỳ trong số này làm việc trên Bash 4.2.53 trên một bản phân phối khác. Bạn có thể vui lòng cập nhật bài viết của bạn để trích dẫn hoặc thoát khỏi ~để tương thích tốt hơn? Điều tôi muốn nói với câu hỏi "biến ma thuật" của tôi là: Tôi có thể sử dụng thay thế biến của Bash trên, ví dụ, đầu ra của unamemà không lưu nó làm biến trước không? Đối với cá nhân tôi $PROMPT_COMMAND, nó phức tạp .
Mark Haferkamp

@MarkHaferkamp Whoa, bạn hoàn toàn đúng, xấu của tôi. Sẽ cập nhật câu trả lời ngay bây giờ.
Camilo Martin

1
@MarkHaferkamp Bash và những cạm bẫy khó hiểu của nó ...: P
Camilo Martin

19

Nếu ngày mai bạn quyết định không yêu Marry thì cô ấy cũng có thể được thay thế:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

Phải có 50 cách để rời xa người yêu của bạn.


9

Vì tôi không thể thêm một bình luận. @ruaka Để làm cho ví dụ dễ đọc hơn, hãy viết nó như thế này

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}

Cho đến khi tôi đến với ví dụ của bạn, tôi đã hiểu thứ tự theo cách khác. Điều này đã giúp làm rõ những gì đang xảy ra
raghav710

5

Phương pháp vỏ POSIX thuần túy , không giống như câu trả lời dựa trên cơ sở của Roman Kazanovskyised không cần công cụ bên ngoài, chỉ cần mở rộng tham số riêng của trình bao . Lưu ý rằng tên tệp dài được thu nhỏ để mã phù hợp hơn trên một dòng:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

Đầu ra:

I love Sara and Marry

Làm thế nào nó hoạt động:

  • Loại bỏ mô hình Suffix nhỏ nhất. "${f%$t*}"trả về " I love" nếu hậu tố $t" Suzi*" nằm trong $f" ". I love Suzi and Marry

  • Nhưng nếu t=Zelda, sau đó "${f%$t*}"xóa không có gì và trả về toàn bộ chuỗi " I love Suzi and Marry".

  • Này được sử dụng để kiểm tra nếu $tlà ở $fvới [ "${f%$t*}" != "$f" ]đó sẽ đánh giá để đúng nếu $fchuỗi chứa " Suzi*" và sai nếu không muốn nói.

  • Nếu kiểm tra trả về giá trị true , hãy xây dựng chuỗi mong muốn bằng cách sử dụng Loại bỏ mẫu nhỏ nhất ${f%$t*} " I love" và Loại bỏ mẫu tiền tố nhỏ nhất ${f#*$t} " and Marry", với chuỗi thứ 2$s " Sara" ở giữa.


5

Tôi biết điều này đã cũ nhưng vì không ai đề cập đến việc sử dụng awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'

1
Thủ thuật đó là cách để thực hiện nếu những gì bạn đang cố gắng phân chia không thực sự ở một biến mà là trong một tệp văn bản. Cảm ơn, @Payam!
Felipe SS Schneider

2
echo [string] | sed "s/[original]/[target]/g"
  • "s" có nghĩa là "thay thế"
  • "g" có nghĩa là "toàn cầu, tất cả các lần xuất hiện phù hợp"
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.