Soru Bash'de bir dizenin bir alt dizgi içerip içermediğini nasıl kontrol edilir


Bash’de bir dizim var:

string="My string"

Başka bir dizgi içeriyorsa nasıl test edebilirim?

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

Nerede ?? benim bilinmeyen operatörüm. Echo kullanıyorum ve grep?

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

Bu biraz hantal görünüyor.


1749
2017-10-23 12:37


Menşei


Merhaba, eğer boş ipler yanlışsa, neden bu kadar sakalsın? Önerilen çözümlere rağmen benim için çalışan tek yol buydu. - ericson.cepeda
Kullanabilirsiniz expr buraya komut - cli__
Posix mermileri için bir tane: stackoverflow.com/questions/2829613/... - sehe


Cevaplar:


Kullanabilirsiniz Marcus'un cevabı (* joker karakterler) çift ​​parantez kullanıyorsanız, bir vaka ifadesinin dışında:

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

İğne dizisindeki boşlukların çift tırnak işareti arasına yerleştirilmesi gerektiğini unutmayın. * joker karakterler dışında olmalıdır.


2504
2017-10-23 12:55



Ayrıca, karşılaştırmayı, testte sadece! = İle değiştirerek tersine çevirebileceğinizi de unutmayın. Cevap için teşekkürler! - Quinn Taylor
@Jonik: Eğer shebang eksik ya da var gibi #!/bin/sh. Deneyin #!/bin/bash yerine. - Dennis Williamson
Parantezler ve içerikler arasında boşluk bırakın. - Paul Price
Değişkenleri [[]] içinde alıntı yapmanız gerekmez. Bu sadece iyi çalışacaktır: [[$ string == $ iğne ]] && echo bulundu - Orwellophile
@Orwellophile Dikkatli! İlk bağımsız değişkendeki çift tırnak işaretini yalnızca [[. Görmek ideone.com/ZSy1gZ - nyuszika7h


Regex yaklaşımını tercih ederseniz:

string='My string';

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

388
2017-10-23 20:10



Bir bas komutunda bir egrep regex değiştirmek zorunda kaldı, bu mükemmel çalıştı! - blast_hardcheese
Bir alana ihtiyacınız varsa, ters eğik çizgi ile kaçış: [[ $string =~ My\ s ]] - Matt Tardiff
=~ operatör zaten bir eşleşme için tüm dizgiyi arar; .*Buranın dışı. Ayrıca, alıntılar genellikle ters eğik çizgi için tercih edilir: [[ $string =~ "My s" ]] - bukzor
Bukzor Quotes Bash 3.2 + 'dan çalışmayı durdurdu: tiswww.case.edu/php/chet/bash/FAQ  E14). Bir değişkeni (tırnak işaretleri kullanarak) atamak, sonra karşılaştırmak en iyisidir. Bunun gibi: re="My s"; if [[ $string =~ $re ]] - seanf
Eğer test edin DEĞİL bir dize içerir: if [[ ! "abc" =~ "d" ]] doğru. - KrisWebDev


If ifadesini kullanmayla ilgili emin değilim, ancak bir durum ifadesi ile benzer bir etki yaratabilirsiniz:

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

240
2017-10-23 12:47



Bu, muhtemelen en iyi çözümdür çünkü posix kabuklarına taşınabilir. (a.k.a. basbas yoktur) - technosaurus
@technosaurus Ben sadece bash etiketi olan bir soruya "bashizm" eleştirmek için oldukça garip buluyorum :) - P.P.
@ P.P. Daha sınırlı bir konuda daha evrensel bir çözümün tercihi olarak bir eleştiri değil. Yıllar sonra, insanların (benim gibi) bu cevabı aramak için duracağını ve orijinal sorudan daha geniş bir kapsamda yararlı olanı bulmaktan memnun olabileceğini göz önünde bulundurun. Açık Kaynak dünyasında dedikleri gibi: "seçim iyidir!" - Carl Smotricz
@technosaurus, FWIW [[ $string == *foo* ]] ayrıca bazı POSIX uyumlu sh sürümlerinde çalışır (ör. /usr/xpg4/bin/shSolaris 10) ve ksh (> = 88) - maxschlepzig
@maxschlepzig ... Eğer uzay, satırsonu, sekme veya kullanıcı tanımlı ayırıcı gibi $ IFS'de karakterler yoksa ... case deyimi, garip karışık alıntı olmadan veya IFS'yi itip atmak zorunda kalmadan çalışır - technosaurus


Uyumlu cevap

Bash'a özgü özellikleri kullanarak çok fazla cevap olduğu için, fakir özellikli kabukların altında çalışan bir yol var. :

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

Pratikte bu şunları verebilir:

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

Bu test edildi , ,  ve  (busybox) ve sonuç her zaman:

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

Tek bir fonksiyon içine

@EeroAaltonen tarafından istendiği gibi, aynı kabuk altında test edilen aynı demo sürümü:

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
}

Sonra:

$ 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'.

Uyarı: alıntılardan ve / veya çift tırnaklardan kaçınmanız veya çift tıklatmanız gerekir:

$ 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'.

Basit fonksiyon

Bu test edildi ,  ve tabi ki :

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

Hepsi bu kadar millet!

Öyleyse şimdi:

$ 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

... Gönderilen dize, @Sjlver ile işaret edildiği gibi boş olabilirse, işlev şöyle olur:

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

veya tarafından önerildiği gibi Adrian Günter'in yorumukaçınmak -o Switche:

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

Boş dizeleri ile:

$ 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



Bu daha iyi olurdu, eğer bunu bir işleve sokmanın bir yolunu bulabilirseniz. - Eero Aaltonen
@EeroAaltonen (Yeni eklenen) işlevimi nasıl buluyorsunuz? - F. Hauri
Biliyorum! bul. -name "*" | xargs grep "myfunc" 2> / dev / null - eggmatters
Bu harika çünkü çok uyumlu. Ancak bir hata: Samanlık dizgisi boşsa, çalışmaz. Doğru sürüm olurdu string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }  Son bir düşünce: boş dize boş dizeyi içeriyor mu? Yukarıdaki şeyler evet (nedeniyle -o -z "$1" Bölüm). - Sjlver
+1. Çok iyi! Benim için sipariş dizgisini değiştirdimContain () {[-z "$ {1 ## * $ 2 *}"] && [-z "$ 2" -o -n "$ 1"]; }; "Nerede ara" "Ne bul". Meşgul kutusunda çalışın. Kabul edilen yanıt, meşgul kutusunda çalışmaz. - user3439968


Kabuk betiğinin bir dilden daha az ve bir dizi komuttan ibaret olduğunu hatırlamalısınız. İçgüdüsel olarak, bu "dil" in bir if Birlikte [ ya da [[. Her ikisi de sadece başarı veya başarısızlık gösteren bir çıkış durumu döndüren komutlardır (tıpkı diğer her komut gibi). Bu yüzden kullanayım grepve değil [ Komut.

Sadece yap:

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

Şimdi düşündüğün if onu takip eden komutun çıkış durumunu test etmek gibi (yarı kolon ile tamamlandı). Neden sınamakta olduğunuz dizenin kaynağını yeniden gözden geçirmiyorsunuz?

## 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
#...

-q Bu seçenek yalnızca grep kodunu istediğimiz için grep'in hiçbir şey üretmemesini sağlar. <<< Kabuğun bir sonraki kelimeyi genişletmesini ve komutun girdisi olarak kullanmasını, tek satırlı << Burada belge (Bunun standart mı yoksa bashizm mi olduğundan emin değilim).


110
2017-10-27 14:57



arandılar Burada dizeleri (3.6.7) Bashizm olduğuna inanıyorum - alex.pilon
biri de kullanabilir Süreç İkamesi  if grep -q foo <(echo somefoothing); then - larsr
Bunun maliyeti çok pahalıdır: grep -q foo <<<"$mystring" 1 çatal it ve bashism ve echo $mystring | grep -q foo 2 çatal uçlar (biri boru için ve ikincisini çalıştırmak için) /path/to/grep) - F. Hauri
"Maliyet" ve "tepegöz" hakkındaki düşüncelerimiz, doğduğumuz on yıl içinde çarpıtıldı. Ben uzun zaman önce 'yerleşik test' v. 'Exec grep' savaşlarında savaştım, ama bu tür savaşlar bugün sadece pedantic masa oyunları. 4 milyar silikon transistörünüz sadece orada yalvarıyorlar. bir şey Yapılması (akü akıntısının büyük olasılıkla akması). Çadırdaki uzun direk, uzun bir atışla doğruluktur. Not; Elbette yorumunuzu kabul ediyorum :) - qneill
@qneill, Verimlilik fikrimiz alakasız bir performans yanlısı postmodernist anlatı tarafından çarpıtıldı. Sadece güç harcayan CPU'larda, eşzamanlılığı andıran hiçbir şeye gerek kalmadan, gerçeği göz ardı ederek sadece bir rahatsızlık vermekteydi. Günümüzün modern ofisi eşzamanlılık gerektiriyor, bu yüzden 99'a internet ölçeği deniyor. Telefon büyüklüğünde genel bilgi işlem bir istisna değil, bir kuraldır. Böylece uygulama verimliliğinin kutsal Ruby meraklılarına şap gerektirmemesi gerekiyor. - TechZilla


Kabul edilen cevap en iyisidir, ancak bunu yapmanın birden fazla yolu olduğundan, işte başka bir çözüm:

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

${var/search/replace} olduğu $var ilk örneği ile search ile ikame edilmiş replaceeğer bulunursa (değişmez $var). Değiştirmeye çalışırsanız foo hiçbir şey yok ve dize değişti, o zaman belli ki foo bulundu.


73
2017-10-23 14:35



Yukarıdaki ephemient çözümü:> `[[$ string]! =" $ {string / foo /} "]; sonra yankılanır "O var!" fi 'BusyBox'ın kabuğunu kullanırken kullanışlıdır kül. Kabul edilen çözüm BusyBox ile çalışmaz, çünkü bazı bazi düzenli ifadeler uygulanmadı. - TPoschel
Bu, problemi çözmenin bükülmüş bir yoludur, ama bilmek güzel bir tekniktir. - Sébastien
Farkın eşitsizliği. Oldukça garip düşünce! onu seviyorum - nitinr708


Öyleyse, bu soruya birçok faydalı çözüm var - ancak en az kaynak en hızlı / hangisi?

Bu çerçeveyi kullanarak tekrarlanan testler:

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

Her seferinde TEST'in değiştirilmesi:

[[ $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, F. Houri'nin cevabındaydı)

Ve kıkırdaklar için:

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

Bu nedenle, basit ikame seçeneği, uzatılmış bir testte mi yoksa bir davada mı tahmin edilir? Dava taşınabilir.

100000 greps için borulama tahminen acı verici! İhtiyacı olmayan harici yardımcı programların kullanımıyla ilgili eski kural doğrudur.


31
2017-08-27 19:42



Temiz bir kıyaslama. Beni kullanmaya ikna etti [[ $b == *$a* ]]. - Mad Physicist
Bunu doğru okuyorsam, case en küçük toplam zaman tüketimi ile kazanır. Sonra yıldız işaretini kaçırıyorsunuz $b in *$a gerçi. Biraz daha hızlı sonuç aldım [[ $b == *$a* ]] daha çok case Hata düzeltildi, ancak elbette diğer faktörlere de bağlı olabilir. - tripleee
ideone.com/5roEVtdenememi bazı ek hatalar ile düzeltilmiş ve farklı bir senaryo için test ediyor (dizenin aslında daha uzun dizede bulunmadığı). Sonuçlar büyük ölçüde benzerdir; [[ $b == *$a* ]] hızlı ve case neredeyse hızlı (ve hoş POSIX uyumlu). - tripleee


Bu ayrıca çalışır:

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

Ve negatif test:

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

Bu tarzın biraz daha klasik olduğunu düşünüyorum - Bash kabuğunun özelliklerine daha az bağımlı.

-- argüman saf POSIX paranoya, gibi giriş dizeleri karşı korumalı olarak kullanılan seçenekler gibi --abc veya -a.

Not: Sıkışık bir döngüde bu kod çok Bir (veya iki) ayrı süreç oluşturulacak ve borularla bağlanacağı için, iç Bash kabuk özelliklerini kullanmaktan daha yavaştır.


22
2017-12-01 15:41



OP açıkça bash ile etiketlenmiştir. - gniourf_gniourf
... ama OP versiyon bash'ın; örneğin, eski bash (sıklıkla solaris gibi) bu yeni bash özelliklerini içermeyebilir. (Ben bu tam problemle karşılaştım (solaris w / bash 2.0'daki bash model eşleştirmesi) - michael
echo ulaşılamaz, kullanıyor olmalısın printf '%s' "$haystack yerine. - nyuszika7h
Hayır, sadece kaçının echo Tamamen bir şeyden öte, ama kaçışsız bir metin -. Sizin için işe yarayabilir, ancak taşınabilir değildir. Hatta bile echo olup olmadığına bağlı olarak farklı davranacaktır xpg_echo seçenek belirlendi. P.S. .: Önceki yorumumda çift alıntıyı kapatmayı unuttum. - nyuszika7h
@kevinarpe emin değilim -- içinde listelenmiyor POSIX özellikleri printfama kullanmalısın printf '%s' "$anything" yine de sorunlardan kaçınmak için $anything içerir % karakter. - nyuszika7h


Buna ne dersin:

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

11
2018-02-09 06:15



= ~ regexp uyumu için, dolayısıyla OP'nin amacı için çok güçlüdür. - Georgi K
Şimdiye kadar en iyi ve en basit çözüm. - ivanleoncz


Bu yığın taşması yanıtı uzay ve çizgi karakterleri yakalayan tek kişiydi:

# For null cmd arguments checking   
to_check=' -t'
space_n_dash_chars=' -'
[[ $to_check == *"$space_n_dash_chars"* ]] && echo found

9
2017-08-26 10:13



Gerek yok export Bu izole örnekte. Bir değişkenin #rom tkis komutunu başlattığı komutlara görünür olması gerekiyorsa, dışa aktarmanız gerekir. - tripleee
Gereksiz kaldırıldı exports. - Teemu Leisti