Câu hỏi Cách kiểm tra xem chuỗi có chứa chuỗi con trong Bash không


Tôi có một chuỗi trong Bash:

string="My string"

Làm thế nào tôi có thể kiểm tra nếu nó có chứa một chuỗi?

if [ $string ?? 'foo' ]; then
  echo "It's there!"
fi

Ở đâu ?? là toán tử không xác định của tôi. Tôi có sử dụng tiếng vọng và grep?

if echo "$string" | grep 'foo'; then
  echo "It's there!"
fi

Điều đó có vẻ hơi vụng về.


1749
2017-10-23 12:37


gốc


Xin chào, nếu các chuỗi rỗng là sai, tại sao bạn lại coi nó là vụng về? Đó là cách duy nhất làm việc cho tôi, bất chấp các giải pháp được đề xuất. - ericson.cepeda
Bạn có thể dùng expr lệnh ở đây - cli__
Đây là một cho vỏ posix: stackoverflow.com/questions/2829613/… - sehe


Các câu trả lời:


Bạn có thể dùng Câu trả lời của Marcus (* ký tự đại diện) bên ngoài một tuyên bố trường hợp, quá, nếu bạn sử dụng dấu ngoặc kép:

string='My long string'
if [[ $string = *"My long"* ]]; then
  echo "It's there!"
fi

Lưu ý rằng các khoảng trống trong chuỗi kim cần được đặt giữa dấu ngoặc kép và * ký tự đại diện nên ở bên ngoài.


2504
2017-10-23 12:55



Cũng lưu ý rằng bạn có thể đảo ngược so sánh bằng cách chỉ chuyển sang! = Trong thử nghiệm. Cảm ơn câu trả lời! - Quinn Taylor
@ Jonik: Bạn có thể bị thiếu shebang hoặc có nó #!/bin/sh. Thử #!/bin/bash thay thế. - Dennis Williamson
Để khoảng cách giữa các dấu ngoặc và nội dung. - Paul Price
Bạn không cần phải trích dẫn các biến bên trong [[]]. Điều này cũng sẽ hoạt động: [[$ string == $ kim ]] && echo tìm thấy - Orwellophile
@Orwellophile Cẩn thận! Bạn chỉ có thể bỏ qua các dấu ngoặc kép trong đối số đầu tiên [[. Xem ideone.com/ZSy1gZ - nyuszika7h


Nếu bạn thích phương pháp regex:

string='My string';

if [[ $string =~ .*My.* ]]
then
   echo "It's there!"
fi

388
2017-10-23 20:10



Đã phải thay thế một regex egrep trong một kịch bản bash, điều này làm việc hoàn hảo! - blast_hardcheese
Nếu bạn cần một không gian, hãy thoát nó bằng dấu gạch chéo ngược: [[ $string =~ My\ s ]] - Matt Tardiff
Các =~ toán tử đã tìm kiếm toàn bộ chuỗi cho một trận đấu; các .*ở đây là không liên quan. Ngoài ra, dấu ngoặc kép thường thích hợp hơn cho các dấu gạch chéo ngược: [[ $string =~ "My s" ]] - bukzor
@bukzor Quotes ngừng hoạt động ở đây như Bash 3.2+: tiswww.case.edu/php/chet/bash/FAQ  E14). Nó có lẽ là tốt nhất để gán cho một biến (sử dụng dấu ngoặc kép), sau đó so sánh. Như thế này: re="My s"; if [[ $string =~ $re ]] - seanf
Kiểm tra xem nó có KHÔNG PHẢI chứa một chuỗi: if [[ ! "abc" =~ "d" ]] là đúng. - KrisWebDev


Tôi không chắc chắn về cách sử dụng một tuyên bố nếu, nhưng bạn có thể có được một hiệu ứng tương tự với một tuyên bố trường hợp:

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac

240
2017-10-23 12:47



Đây có lẽ là giải pháp tốt nhất vì nó có thể di chuyển đến vỏ posix. (a.k.a. không bashisms) - technosaurus
@technosaurus Tôi tìm thấy nó khá kỳ quặc để chỉ trích "bashism" trong một câu hỏi mà chỉ có bash tag :) - P.P.
@ P.P. Nó không phải là quá nhiều một lời chỉ trích như là ưu tiên của một giải pháp phổ quát hơn trên một giới hạn hơn. Xin lưu ý rằng, nhiều năm sau, mọi người (như tôi) sẽ dừng lại để tìm câu trả lời này và có thể vui mừng khi tìm ra câu trả lời hữu ích trong phạm vi rộng hơn câu hỏi gốc. Như họ nói trong thế giới nguồn mở: "lựa chọn là tốt!" - Carl Smotricz
@technosaurus, FWIW [[ $string == *foo* ]] cũng hoạt động trong một số phiên bản sh tương thích POSIX (ví dụ: /usr/xpg4/bin/sh trên Solaris 10) và ksh (> = 88) - maxschlepzig
@maxschlepzig ... không nếu có các ký tự trong $ IFS chẳng hạn như dấu cách, dòng mới, tab hoặc dấu phân cách người dùng ... trường hợp hoạt động mặc dù không có báo giá hỗn hợp lạ hoặc phải đẩy và bật IFS - technosaurus


Câu trả lời tương thích

Vì đã có rất nhiều câu trả lời bằng cách sử dụng các tính năng đặc trưng của Bash, có một cách làm việc trong các shell kém hơn, như :

[ -z "${string##*$reqsubstr*}" ]

Trong thực tế, điều này có thể cho:

string='echo "My string"'
for reqsubstr in 'o "M' 'alt' 'str';do
  if [ -z "${string##*$reqsubstr*}" ] ;then
      echo "String '$string' contain substring: '$reqsubstr'."
    else
      echo "String '$string' don't contain substring: '$reqsubstr'."
    fi
  done

Điều này đã được kiểm tra theo , ,  và  (busybox) và kết quả luôn là:

String 'echo "My string"' contain substring: 'o "M'.
String 'echo "My string"' don't contain substring: 'alt'.
String 'echo "My string"' contain substring: 'str'.

Vào một chức năng

Như được hỏi bởi @EeroAaltonen đây là một phiên bản của cùng một bản demo, được thử nghiệm dưới cùng một hệ vỏ:

myfunc() {
    reqsubstr="$1"
    shift
    string="$@"
    if [ -z "${string##*$reqsubstr*}" ] ;then
        echo "String '$string' contain substring: '$reqsubstr'.";
      else
        echo "String '$string' don't contain substring: '$reqsubstr'." 
    fi
}

Sau đó:

$ myfunc 'o "M' 'echo "My String"'
String 'echo "My String"' contain substring 'o "M'.

$ myfunc 'alt' 'echo "My String"'
String 'echo "My String"' don't contain substring 'alt'.

Để ý: bạn phải thoát hoặc gấp đôi dấu ngoặc kép và / hoặc dấu ngoặc kép:

$ myfunc 'o "M' echo "My String"
String 'echo My String' don't contain substring: 'o "M'.

$ myfunc 'o "M' echo \"My String\"
String 'echo "My String"' contain substring: 'o "M'.

Chức năng đơn giản

Điều này đã được kiểm tra theo ,  và dĩ nhiên :

stringContain() { [ -z "${2##*$1*}" ]; }

Đó là tất cả mọi người!

Ngay sau đó:

$ if stringContain 'o "M3' 'echo "My String"';then echo yes;else echo no;fi
no
$ if stringContain 'o "M' 'echo "My String"';then echo yes;else echo no;fi
yes

... Hoặc nếu chuỗi được gửi có thể trống, như được chỉ bởi @Sjlver, hàm sẽ trở thành:

stringContain() { [ -z "${2##*$1*}" ] && [ -z "$1" -o -n "$2" ]; }

hoặc theo đề xuất của Nhận xét của Adrian Günter, tránh -o switche:

stringContain() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] ;} ; }

Với chuỗi rỗng:

$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o "M' ''; then echo yes; else echo no; fi
no

137
2017-12-08 23:06



Điều này sẽ tốt hơn, nếu bạn có thể tìm ra cách nào đó để đưa nó vào một hàm. - Eero Aaltonen
@EeroAaltonen Làm thế nào để bạn tìm thấy hàm (mới được thêm) của tôi? - F. Hauri
Tôi biết! tìm thấy . tên "*" | xargs grep "myfunc" 2> / dev / null - eggmatters
Điều này thật tuyệt vời vì nó rất tương thích. Một lỗi, mặc dù: Nó không hoạt động nếu chuỗi haystack rỗng. Phiên bản chính xác sẽ là string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }  Suy nghĩ cuối cùng: chuỗi rỗng có chứa chuỗi rỗng không? Phiên bản ở trên mọi thứ có (vì -o -z "$1" phần). - Sjlver
+1. Rất tốt! Đối với tôi, tôi đã thay đổi thứ tự stringContain () {[-z "$ {1 ## * $ 2 *}"] && [-z "$ 2" -o -n "$ 1"]; }; "Tìm kiếm ở đâu" "Tìm nội dung". Làm việc trong busybox. Câu trả lời được chấp nhận ở trên không hoạt động trong busybox. - user3439968


Bạn nên nhớ rằng shell scripting là ít của một ngôn ngữ và nhiều hơn nữa của một bộ sưu tập các lệnh. Theo bản năng, bạn nghĩ rằng "ngôn ngữ" này yêu cầu bạn tuân theo if với một [ hoặc một [[. Cả hai chỉ là những lệnh trả về trạng thái thoát cho thấy thành công hay thất bại (giống như mọi lệnh khác). Vì lý do đó tôi sẽ sử dụng grepchứ không phải [ chỉ huy.

Cứ làm đi:

if grep -q foo <<<"$string"; then
    echo "It's there"
fi

Bây giờ bạn đang nghĩ đến if như kiểm tra trạng thái thoát của lệnh theo sau nó (hoàn thành với dấu chấm phẩy). Tại sao không xem xét lại nguồn gốc của chuỗi bạn đang thử nghiệm?

## Instead of this
filetype="$(file -b "$1")"
if grep -q "tar archive" <<<"$filetype"; then
#...

## Simply do this
if file -b "$1" | grep -q "tar archive"; then
#...

Các -q tùy chọn làm cho grep không xuất ra bất cứ thứ gì, vì chúng ta chỉ muốn mã trả về. <<< làm cho trình bao mở rộng từ tiếp theo và sử dụng nó làm đầu vào cho lệnh, một phiên bản một dòng của << ở đây tài liệu (Tôi không chắc liệu đây là tiêu chuẩn hay bashism).


110
2017-10-27 14:57



chúng được gọi là các chuỗi ở đây (3.6.7) Tôi tin rằng đó là chủ nghĩa bashism - alex.pilon
người ta cũng có thể sử dụng Thay thế quy trình  if grep -q foo <(echo somefoothing); then - larsr
Chi phí này rất đắt: làm grep -q foo <<<"$mystring" implie 1 ngã ba và là bashism và echo $mystring | grep -q foo implie 2 dĩa (một cho ống và thứ hai để chạy /path/to/grep) - F. Hauri
Ý tưởng của chúng tôi về "chi phí" và "phí" bị sai lệch bởi thập kỷ mà chúng tôi được sinh ra. Tôi đã chiến đấu trong cuộc thử nghiệm 'xây dựng thử nghiệm' v. 'Exec grep' cách đây rất lâu, nhưng những cuộc chiến tranh như vậy chỉ là trò chơi hội đồng quản trị tầm thường ngày nay. 4 tỷ bóng bán dẫn silicon của bạn chỉ ngồi đó xin ăn một cái gì đó để làm (rò rỉ pin hiện tại rất có thể). Cái cột dài trong lều là chính xác bằng một cú đánh dài. P.S. Tôi đồng ý tất nhiên với bình luận của bạn :) - qneill
@qneill, Các ý tưởng về hiệu quả của chúng tôi không liên quan bị sai lệch bởi một câu chuyện về hậu trường hiện đại, hậu biện. Chỉ trên điện lãng phí CPU, mà không cần bất cứ điều gì giống như đồng thời, đã bỏ qua sự thật một sự bất tiện chỉ. Văn phòng hiện đại ngày nay đòi hỏi sự tương tranh, rằng khoảng 99 sẽ được gọi là quy mô internet. Điện toán có kích thước điện thoại nói chung là một tiêu chuẩn, không phải là một ngoại lệ. Vì vậy, lưu ý hiệu quả thực hiện không nên yêu cầu những người đam mê Ruby thiêng liêng screed. - TechZilla


Câu trả lời được chấp nhận là tốt nhất, nhưng vì có nhiều cách để thực hiện, đây là một giải pháp khác:

if [ "$string" != "${string/foo/}" ]; then
    echo "It's there!"
fi

${var/search/replace} Là $var với phiên bản đầu tiên của search thay thế bởi replace, nếu nó được tìm thấy (nó không thay đổi $var). Nếu bạn cố gắng thay thế foo bởi không có gì, và chuỗi đã thay đổi, sau đó rõ ràng foo được tìm thấy.


73
2017-10-23 14:35



giải pháp ephemient ở trên:> `if [" $ string "! =" $ {string / foo /} "]; sau đó lặp lại "It's there!" fi` rất hữu ích khi sử dụng shell của BusyBox tro. Giải pháp được chấp nhận không hoạt động với BusyBox vì một số cụm từ thông dụng của bash không được triển khai. - TPoschel
Đây là một cách giải quyết vấn đề, nhưng đó là một kỹ thuật tốt để biết. - Sébastien
sự bất bình đẳng của sự khác biệt. Ý nghĩ kỳ lạ! tôi thích nó - nitinr708


Vì vậy, có rất nhiều giải pháp hữu ích cho câu hỏi - nhưng đó là nhanh nhất / sử dụng tài nguyên ít nhất?

Lặp lại các bài kiểm tra bằng cách sử dụng khung này:

/usr/bin/time bash -c 'a=two;b=onetwothree; x=100000; while [ $x -gt 0 ]; do TEST ; x=$(($x-1)); done'

Thay thế TEST mỗi lần:

[[ $b =~ $a ]]           2.92user 0.06system 0:02.99elapsed 99%CPU

[ "${b/$a//}" = "$b" ]   3.16user 0.07system 0:03.25elapsed 99%CPU

[[ $b == *$a* ]]         1.85user 0.04system 0:01.90elapsed 99%CPU

case $b in *$a):;;esac   1.80user 0.02system 0:01.83elapsed 99%CPU

doContain $a $b          4.27user 0.11system 0:04.41elapsed 99%CPU

(doContain nằm trong câu trả lời của F. Houri)

Và để cười khúc khích:

echo $b|grep -q $a       12.68user 30.86system 3:42.40elapsed 19%CPU !ouch!

Vì vậy, tùy chọn substituion tùy chọn đơn giản thắng kiện cho dù trong một thử nghiệm mở rộng hoặc một trường hợp. Trường hợp này là di động.

Đường ống ra 100000 greps có thể được dự đoán là đau đớn! Quy tắc cũ về việc sử dụng các tiện ích bên ngoài mà không cần phải đúng.


31
2017-08-27 19:42



Chuẩn bị gọn gàng. Thuyết phục tôi sử dụng [[ $b == *$a* ]]. - Mad Physicist
Nếu tôi đọc chính xác, case thắng với tổng thời gian tiêu thụ nhỏ nhất. Bạn đang bỏ dấu hoa thị sau $b in *$a Tuy nhiên. Tôi nhận được kết quả nhanh hơn một chút cho [[ $b == *$a* ]] hơn cho case với lỗi được sửa chữa, nhưng tất nhiên cũng có thể phụ thuộc vào các yếu tố khác. - tripleee
ideone.com/5roEVt đã thử nghiệm của tôi với một số lỗi bổ sung cố định và thử nghiệm cho một kịch bản khác nhau (nơi chuỗi thực sự không có trong chuỗi dài hơn). Kết quả là phần lớn tương tự; [[ $b == *$a* ]] nhanh chóng và case gần như nhanh chóng (và tương thích POSIX dễ dàng). - tripleee


Điều này cũng hoạt động:

if printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  printf "Found needle in haystack"
fi

Và thử nghiệm âm tính là:

if ! printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  echo "Did not find needle in haystack"
fi

Tôi cho rằng kiểu dáng này cổ điển hơn một chút - ít phụ thuộc vào các tính năng của vỏ Bash.

Các -- đối số là thuần túy POSIX hoang tưởng, được sử dụng để bảo vệ chống lại các chuỗi đầu vào tương tự như các tùy chọn, chẳng hạn như --abc hoặc là -a.

Lưu ý: Trong một vòng lặp chặt chẽ, mã này sẽ là nhiều chậm hơn so với sử dụng các tính năng vỏ Bash bên trong, vì một (hoặc hai) quy trình riêng biệt sẽ được tạo và kết nối thông qua các đường ống.


22
2017-12-01 15:41



OP được gắn thẻ rõ ràng với bash. - gniourf_gniourf
... nhưng OP không nói phiên bản của bash; ví dụ: bash cũ (như solaris thường có) có thể không bao gồm các tính năng bash mới hơn này. (Tôi đã chạy vào vấn đề chính xác này (kết hợp mẫu bash không được triển khai) trên solaris w / bash 2.0) - michael
echo không thể chuyển đổi, bạn nên sử dụng printf '%s' "$haystack thay thế. - nyuszika7h
Không, chỉ cần tránh echo hoàn toàn cho bất cứ điều gì nhưng văn bản theo nghĩa đen mà không có lối thoát không bắt đầu bằng -. Nó có thể làm việc cho bạn, nhưng nó không phải di động. Ngay cả bash's echo sẽ hoạt động khác nhau tùy thuộc vào việc xpg_echo tùy chọn được đặt. P .: Tôi quên đóng ngoặc kép trong bình luận trước đó của mình. - nyuszika7h
@ kevinarpe Tôi không chắc chắn, -- không được liệt kê trong Thông số POSIX cho printf, nhưng bạn nên sử dụng printf '%s' "$anything" dù sao, để tránh các vấn đề nếu $anything chứa một % tính cách. - nyuszika7h


Còn cái này thì sao:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi

11
2018-02-09 06:15



= ~ là để đối sánh regexp, do đó quá mạnh cho mục đích của OP. - Georgi K
Giải pháp tốt nhất và đơn giản nhất, cho đến nay. - ivanleoncz