Xóa một tiền tố / hậu tố cố định khỏi một chuỗi trong Bash


485

Trong bashkịch bản của tôi, tôi có một chuỗi và tiền tố / hậu tố của nó. Tôi cần xóa tiền tố / hậu tố khỏi chuỗi gốc.

Ví dụ: giả sử tôi có các giá trị sau:

string="hello-world"
prefix="hell"
suffix="ld"

Làm thế nào để tôi có được kết quả sau đây?

result="o-wor"


14
Hãy cảnh giác khi liên kết với cái gọi là Hướng dẫn viết kịch bản Bash nâng cao; Nó chứa một hỗn hợp của lời khuyên tốt và khủng khiếp.
tripleee

Câu trả lời:


719
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

40
Cũng ## và %% Có, mà loại bỏ càng nhiều càng tốt nếu $ tiền tố hoặc hậu tố $ chứa ký tự đại diện.
pts

28
Có cách nào để kết hợp hai trong một dòng không? Tôi đã cố gắng ${${string#prefix}%suffix}nhưng nó không hoạt động.
static_rtti

28
@static_rtti Không, thật không may, bạn không thể lồng thay thế tham số như thế này. Tôi biết, đó là một sự xấu hổ.
Adrian Frühwirth

87
@ AdrianFrühwirth: toàn bộ ngôn ngữ là một sự xấu hổ, nhưng nó rất hữu ích :)
static_rtti

8
Nvm, "thay bash" trong Google tìm thấy những gì tôi muốn.
Tyler

89

Sử dụng sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Trong lệnh sed, ^ký tự khớp với văn bản bắt đầu bằng $prefixvà dấu cuối $khớp với văn bản kết thúc bằng $suffix.

Adrian Frühwirth đưa ra một số điểm tốt trong các bình luận dưới đây, nhưng sedvới mục đích này có thể rất hữu ích. Thực tế là nội dung của tiền tố $ và hậu tố $ được diễn giải bởi sed có thể là tốt HOẶC xấu - miễn là bạn chú ý, bạn sẽ ổn thôi. Vẻ đẹp là, bạn có thể làm một cái gì đó như thế này:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

đó có thể là những gì bạn muốn, và vừa huyền ảo vừa mạnh mẽ hơn thay thế bash. Nếu bạn nhớ rằng quyền kiểm soát lớn đến trách nhiệm lớn lao (như Spiderman nói), bạn cần sử dụng tốt.

Giới thiệu nhanh về sed có thể được tìm thấy tại http://evc-cit.info/cit052/sed_tutorial.html

Một lưu ý về vỏ và sử dụng các chuỗi:

Đối với ví dụ cụ thể được đưa ra, những điều sau đây cũng sẽ hoạt động:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... nhưng chỉ vì:

  1. echo không quan tâm có bao nhiêu chuỗi trong danh sách đối số của nó và
  2. Không có khoảng trống trong $ tiền tố và hậu tố $

Đó là thực tế chung là tốt để trích dẫn một chuỗi trên dòng lệnh bởi vì ngay cả nếu nó chứa dấu cách nó sẽ được trình bày cho các lệnh như một đối số duy nhất. Chúng tôi trích dẫn $ tiền tố và hậu tố $ với cùng lý do: mỗi chỉnh sửa lệnh để sed sẽ được thông qua như là một chuỗi. Chúng tôi sử dụng dấu ngoặc kép vì chúng cho phép nội suy thay đổi; có chúng tôi sử dụng dấu nháy đơn lệnh sed đã có thể nhận một chữ $prefix$suffixđó chắc chắn không phải là những gì chúng tôi muốn.

Cũng lưu ý rằng, tôi sử dụng dấu ngoặc đơn khi đặt các biến prefixsuffix. Chúng tôi chắc chắn không muốn bất cứ điều gì trong chuỗi để được giải thích, vì vậy chúng tôi trích dẫn đơn họ vì vậy không suy diễn ra. Một lần nữa, nó có thể không cần thiết trong ví dụ này nhưng đó là một thói quen rất tốt để tham gia.


8
Thật không may, đây là lời khuyên xấu vì nhiều lý do: 1) không thể viện chứng, $stringđược áp dụng tách từ và globbing. 2) $prefix$suffixcó thể chứa các biểu thức sedsẽ diễn giải, ví dụ: biểu thức chính quy hoặc ký tự được sử dụng làm dấu phân cách sẽ phá vỡ toàn bộ lệnh. 3) Gọi sedhai lần là không cần thiết ( -e 's///' -e '///'thay vào đó bạn có thể ) và đường ống cũng có thể tránh được. Ví dụ, xem xét string='./ *'và / hoặc prefix='./'xem nó phá vỡ khủng khiếp do 1)2).
Adrian Frühwirth

Lưu ý thú vị: sed có thể lấy hầu hết mọi thứ như một dấu phân cách. Trong trường hợp của tôi, vì tôi đã phân tích các thư mục tiền tố ra khỏi các đường dẫn, nên tôi không thể sử dụng /, vì vậy tôi đã sử dụng sed "s#^$prefix##, thay vào đó. (Tính dễ vỡ: tên tệp không thể chứa #. Vì tôi kiểm soát các tệp, chúng tôi an toàn ở đó.)
Olie

@Olie Filenames có thể chứa bất kỳ ký tự nào ngoại trừ ký tự gạch chéo và null, trừ khi bạn kiểm soát, bạn không thể giả sử tên tệp không chứa các ký tự nhất định.
Adrian Frühwirth

Vâng, không biết tôi đã nghĩ gì ở đó. Có lẽ iOS? Không biết. Tên tệp chắc chắn có thể chứa "#". Không biết tại sao tôi nói vậy. :)
Olie

@Olie: Khi tôi hiểu nhận xét ban đầu của bạn, bạn đã nói rằng giới hạn của sự lựa chọn của bạn #là sử dụng dấu phân cách của sed có nghĩa là bạn không thể xử lý các tệp có chứa ký tự đó.
P Daddy

17

Bạn có biết độ dài của tiền tố và hậu tố của bạn? Trong trường hợp của bạn:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

Hay nói chung hơn:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

Nhưng giải pháp từ Adrian Frühwirth thật tuyệt! Tôi không biết về điều đó!


14

Tôi sử dụng grep để xóa tiền tố khỏi đường dẫn (không được xử lý tốt bởi sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K loại bỏ khỏi trận đấu tất cả các nhân vật trước nó.


grep -Plà một phần mở rộng không chuẩn. Thêm sức mạnh cho bạn nếu nó được hỗ trợ trên nền tảng của bạn, nhưng đây là lời khuyên không rõ ràng nếu mã của bạn cần có tính di động hợp lý.
tripleee

@tripleee Thật vậy. Nhưng tôi nghĩ rằng một hệ thống với GNU Bash được cài đặt cũng có một grep hỗ trợ PCRE.
Vladimir Petrakovich

1
Không, hệ điều hành MacOS ví dụ có Bash out of the box nhưng không GNU grep. Các phiên bản trước đó thực sự có -Ptùy chọn từ BSD grepnhưng họ đã loại bỏ nó.
tripleee

9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Ghi chú:

# $ tiền tố: thêm # đảm bảo rằng chuỗi con "hell" bị xóa chỉ khi nó được tìm thấy ở đầu. % $ Hậu tố: thêm% đảm bảo rằng substring "ld" được lấy ra chỉ khi nó được tìm thấy cuối cùng.

Không có những thứ này, các chuỗi con "địa ngục" và "ld" sẽ bị loại bỏ ở mọi nơi, thậm chí nó được tìm thấy ở giữa.


Cảm ơn đã chú ý! qq: trong ví dụ mã của bạn, bạn cũng có một dấu gạch chéo chuyển tiếp /ngay sau chuỗi, đó là để làm gì?
DiegoSalazar

1
/ tách chuỗi hiện tại và chuỗi phụ. chuỗi con ở đây là hậu tố trong câu hỏi được đăng.
Vijay Vat


6

Giải pháp nhỏ và phổ quát:

expr "$string" : "$prefix\(.*\)$suffix"

1
Nếu bạn đang sử dụng Bash, có lẽ bạn không nên sử dụng expr. Đó là một loại tiện ích bồn rửa nhà bếp tiện lợi trở lại vào thời của vỏ Bourne ban đầu, nhưng giờ đã vượt qua thời kỳ tốt nhất trước đó.
tripleee

5

Sử dụng câu trả lời @Adrian Frühwirth:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

sử dụng nó như thế này

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

0

Tôi sẽ sử dụng các nhóm chụp trong regex:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)đảm bảo rằng nội dung của ${suffix}sẽ được loại trừ khỏi nhóm chụp. Về mặt ví dụ, đó là chuỗi tương đương [^A-Z]*. Nếu không, bạn sẽ nhận được:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
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.