Pytanie Jak sprawdzić, czy ciąg zawiera podciąg w Bashu


Mam ciąg w Bash:

string="My string"

Jak mogę przetestować, czy zawiera inny ciąg?

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

Gdzie ?? jest moim nieznanym operatorem. Czy używam echa i grep?

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

Wygląda trochę niezgrabnie.


1749
2017-10-23 12:37


pochodzenie


Cześć, jeśli puste ciągi są fałszywe, dlaczego uważasz je za niezdarne? To był jedyny sposób, który zadziałał dla mnie, pomimo proponowanych rozwiązań. - ericson.cepeda
Możesz użyć expr polecenie tutaj - cli__
Oto jedna z powłok POSIX: stackoverflow.com/questions/2829613/... - sehe


Odpowiedzi:


Możesz użyć Odpowiedź Marcusa (* symbole wieloznaczne) poza przypadkiem, jeśli używasz podwójnych nawiasów:

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

Zwróć uwagę, że spacje w łańcuchu igieł muszą być umieszczone między podwójnymi cudzysłowami a znakami * symbole wieloznaczne powinny znajdować się na zewnątrz.


2504
2017-10-23 12:55



Zwróć uwagę, że możesz odwrócić porównanie, przechodząc do! = W teście. Dziękuję za odpowiedź! - Quinn Taylor
@Jonik: Być może brakuje ci shebang lub go masz #!/bin/sh. Próbować #!/bin/bash zamiast. - Dennis Williamson
Pozostaw odstęp między nawiasami i zawartością. - Paul Price
Nie musisz cytować zmiennych w [[]]. To zadziała równie dobrze: [[$ string == $ igła ]] && echo znalezione - Orwellophile
@Orwellophile Ostrożny! Możesz pominąć podwójne cudzysłowy w pierwszym argumencie [[. Widzieć ideone.com/ZSy1gZ - nyuszika7h


Jeśli wolisz podejście regex:

string='My string';

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

388
2017-10-23 20:10



Musiał zastąpić regex egrep w skrypcie basha, działało to doskonale! - blast_hardcheese
Jeśli potrzebujesz spacji, uciec z odwrotnym ukośnikiem: [[ $string =~ My\ s ]] - Matt Tardiff
The =~ operator już przeszukuje cały ciąg na mecz; .*tutaj są nieistotne. Poza tym cytaty są zwykle lepsze od ukośników odwrotnych: [[ $string =~ "My s" ]] - bukzor
@bukzor Cytaty przestały działać tutaj z Bash 3.2+: tiswww.case.edu/php/chet/bash/FAQ  E14). Prawdopodobnie najlepiej jest przypisać do zmiennej (za pomocą cudzysłowu), a następnie porównać. Lubię to: re="My s"; if [[ $string =~ $re ]] - seanf
Sprawdź, czy to działa NIE zawierają ciąg: if [[ ! "abc" =~ "d" ]] jest prawdziwy. - KrisWebDev


Nie jestem pewien, czy użyć instrukcji if, ale można uzyskać podobny efekt w przypadku instrukcji case:

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

240
2017-10-23 12:47



Jest to prawdopodobnie najlepsze rozwiązanie, ponieważ jest przenośne dla powłok POSIX. (a.k.a. brak bashisms) - technosaurus
@technosaurus Uważam za raczej dziwne krytykowanie "bashism" w pytaniu, które ma tylko tag bash :) - P.P.
@ P.P. To nie tyle krytyka, co preferencja bardziej uniwersalnego rozwiązania niż bardziej ograniczona. Proszę wziąć pod uwagę, że wiele lat później ludzie (tacy jak ja) zatrzymają się, aby znaleźć tę odpowiedź i mogą z przyjemnością znaleźć taką, która przydaje się w szerszym zakresie niż oryginalne pytanie. Jak mówią w świecie Open Source: "wybór jest dobry!" - Carl Smotricz
@technosaurus, FWIW [[ $string == *foo* ]] działa również w niektórych wersjach SH zgodnych z POSIX (np. /usr/xpg4/bin/sh w systemie Solaris 10) i ksh (> = 88) - maxschlepzig
@maxschlepzig ... nie, jeśli są znaki w $ IFS, takie jak spacja, znak nowej linii, tabulator lub separator zdefiniowany przez użytkownika ... case case działa jednak bez dziwnej, mieszanej oferty lub konieczności pushowania i wyskakiwania IFS - technosaurus


Zgodna odpowiedź

Ponieważ istnieje już wiele odpowiedzi przy użyciu funkcji specyficznych dla Bash, istnieje sposób pracy pod gorszymi opisywanymi pociskami, takimi jak :

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

W praktyce może to dać:

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

Zostało to przetestowane w ramach , ,  i  (busybox), a wynik jest zawsze:

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

Do jednej funkcji

Na pytanie @EeroAaltonena jest tutaj wersja tego samego demo, przetestowana pod tymi samymi powłokami:

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
}

Następnie:

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

Ogłoszenie: musisz uciec lub podwójnie zawrzeć cytaty i / lub podwójne cudzysłowy:

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

Prosta funkcja

Zostało to przetestowane w ramach ,  i oczywiście :

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

To wszyscy ludzie!

Więc teraz:

$ 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

... Lub jeśli przesłany ciąg może być pusty, jak wskazano przez @Sjlver, funkcja stanie się:

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

lub zgodnie z sugestią użytkownika Komentarz Adrian Günter, unikając -o switche:

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

Z pustymi ciągami:

$ 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



Byłoby jeszcze lepiej, gdybyś mógł znaleźć jakiś sposób, aby umieścić to w funkcji. - Eero Aaltonen
@EeroAaltonen Jak znaleźć moją (dodaną) funkcję? - F. Hauri
Wiem! odnaleźć . -name "*" | xargs grep "myfunc" 2> / dev / null - eggmatters
To jest cudowne, ponieważ jest tak kompatybilne. Jednak jeden błąd: nie działa, jeśli łańcuch stogu siana jest pusty. Właściwa wersja byłaby string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }Ostatnia myśl: czy pusty ciąg zawiera pusty ciąg znaków? Wersja powyżej rzeczy tak (z powodu -o -z "$1" część). - Sjlver
+1. Bardzo dobrze! Dla mnie zmieniłem kolejność: stringContain () {[-z "$ {1 ## * $ 2 *}"] && [-z "$ 2" -o -n "$ 1"]; }; "Wyszukaj gdzie" "Znajdź co". Pracuj w busybox. Zaakceptowana powyższa odpowiedź nie działa w zajętym polu. - user3439968


Powinieneś pamiętać, że skrypty powłoki są mniejszym językiem niż zbiorem poleceń. Instynktownie myślisz, że ten "język" wymaga od ciebie podążania if z a [ lub [[. Oba są po prostu poleceniami, które zwracają status wyjścia wskazujący na sukces lub porażkę (tak jak każde inne polecenie). Z tego powodu użyłbym grep, a nie [ dowództwo.

Po prostu zrób:

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

Teraz, o czym myślisz if jako testowanie statusu wyjścia polecenia następującego po nim (uzupełnienie średnikiem). Dlaczego nie zastanowić się nad źródłem testowanego łańcucha?

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

The -q opcja sprawia, że ​​grep nie wypisuje niczego, ponieważ chcemy tylko kodu powrotu. <<< sprawia, że ​​powłoka rozszerza następne słowo i używa go jako wejścia do polecenia, jednolinijkowej wersji << tutaj dokument (nie jestem pewien, czy jest to standard czy bashizm).


110
2017-10-27 14:57



nazywają się tutaj ciągi znaków (3.6.7) Wierzę, że to jest bashism - alex.pilon
można również użyć Zastąpienie procesu  if grep -q foo <(echo somefoothing); then - larsr
Koszt tego jest bardzo kosztowny: robienie grep -q foo <<<"$mystring" implie 1 fork i is bashism i echo $mystring | grep -q foo implie 2 widelce (jeden dla rury i drugi dla biegu /path/to/grep) - F. Hauri
Nasze idee "kosztów" i "kosztów ogólnych" są wypaczone przez dekadę, w której się rodzimy. Walczyłem w wojnie "builtin test 'v.' Exec grep 'dawno temu, ale takie wojny są dzisiaj tylko pedantycznymi grami planszowymi. Twoje 4 miliardy tranzystorów krzemowych właśnie tam siedzą i błagają coś zrobić (najprawdopodobniej wyciek prądu z baterii). Długi słup w namiocie jest poprawny przez długie ujęcie. P.S. Zgadzam się oczywiście z twoim komentarzem :) - qneill
@qneill, Nasze pomysły na efektywność są nieistotne, wypaczone przez postmodernistyczną narrację apologety. Tylko w przypadku marnowania mocy procesorów, bez potrzeby niczego przypominającego współbieżność, ignorowanie prawdy było zwykłą niedogodnością. Dzisiejsze nowoczesne biuro wymaga współbieżności, że około 99 będzie się nazywać skalą internetową. Komputerowe przetwarzanie ogólne to norma, a nie wyjątek. Dlatego zauważając, że wydajność wdrożenia nie powinna wymagać od świętych pasjonatów Rubinów. - TechZilla


Przyjęta odpowiedź jest najlepsza, ale ponieważ istnieje więcej niż jeden sposób, aby to zrobić, oto inne rozwiązanie:

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

${var/search/replace} jest $var z pierwszym wystąpieniem search zastąpione przez replace, jeśli zostanie znaleziony (nie zmienia się $var). Jeśli spróbujesz wymienić foo przez nic, a sznur się zmienił, to oczywiście foo został znaleziony.


73
2017-10-23 14:35



Rozwiązanie ephemient powyżej:> `if [" $ string "! =" $ {string / foo /} "]; następnie echo "It's there!" fi` jest przydatne podczas korzystania z powłoki BusyBox popiół. Przyjęte rozwiązanie nie działa z BusyBox, ponieważ niektóre wyrażenia regularne basha nie są zaimplementowane. - TPoschel
Jest to pokrętny sposób rozwiązania problemu, ale jest to miła technika. - Sébastien
nierówność różnicy. Dość dziwna myśl! kocham to - nitinr708


Istnieje wiele przydatnych rozwiązań tego pytania - ale który jest najszybszy / korzysta z najmniejszego zasobu?

Powtarzane testy z użyciem tej ramki:

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

Zastępując TEST za każdym razem:

[[ $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 był w odpowiedzi F. Houri)

I na chichoty:

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

Tak więc prosta opcja podstawiania wygrywa, czy w rozszerzonym teście, czy w przypadku. Skrzynia jest przenośna.

Rozgałęzienie do 100000 greps jest prawdopodobnie bolesne! Stara zasada dotycząca korzystania z zewnętrznych narzędzi bez potrzeby jest prawdziwa.


31
2017-08-27 19:42



Zgrabny benchmark. Przekonany, żebym użył [[ $b == *$a* ]]. - Mad Physicist
Jeśli czytam to poprawnie, case wygrywa z najmniejszym całkowitym zużyciem czasu. Brakuje Ci gwiazdki $b in *$a chociaż. Dostaję nieco szybsze wyniki [[ $b == *$a* ]] niż dla case z poprawionym błędem, ale oczywiście może też zależeć od innych czynników. - tripleee
ideone.com/5roEVt ma swój eksperyment z naprawionymi dodatkowymi błędami i testami dla innego scenariusza (gdzie łańcuch w rzeczywistości nie występuje w dłuższym łańcuchu). Wyniki są w dużej mierze podobne; [[ $b == *$a* ]] jest szybki i case jest prawie tak szybki (i przyjemnie kompatybilny z POSIX). - tripleee