Skąd wiadomo, czy łańcuch zawiera inny łańcuch w POSIX sh?

140

Chcę napisać skrypt powłoki systemu Unix, który będzie wykonywał różne logiki, jeśli w innym ciągu znajduje się ciąg. Na przykład, jeśli jestem w określonym folderze, odejdź. Czy ktoś mógłby mi powiedzieć, jak to osiągnąć? Jeśli to możliwe, chciałbym, aby to nie było specyficzne dla powłoki (tj. Nie tylko dla basha), ale jeśli nie ma innego sposobu, mogę to zrobić.

#!/usr/bin/env sh

if [ "$PWD" contains "String1" ]
then
    echo "String1 present"
elif [ "$PWD" contains "String2" ]
then
    echo "String2 present"
else
    echo "Else"
fi
Matt
źródło
2
Zdaję sobie sprawę, że to jest stare, ale oto kilka rzeczy, na które należy zwrócić uwagę przyszłym odwiedzającym: (1) Zwykle dobrą praktyką jest zarezerwowanie nazw zmiennych SNAKE_CASE dla zmiennych środowiskowych i wewnętrznych powłoki. (2) Ustawienie CURRENT_DIRjest zbędne; możesz po prostu użyć $PWD.
nyuszika7h

Odpowiedzi:

171

Oto jeszcze jedno rozwiązanie. Wykorzystuje rozwinięcie parametrów podciągów POSIX , więc działa w Bash, Dash, KornShell (ksh), Z shell (zsh) itp.

test "${string#*$word}" != "$string" && echo "$word found in $string"

Sfunkcjonalizowana wersja z kilkoma przykładami:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    if test "${string#*$substring}" != "$string"
    then
        return 0    # $substring is in $string
    else
        return 1    # $substring is not in $string
    fi
}

contains "abcd" "e" || echo "abcd does not contain e"
contains "abcd" "ab" && echo "abcd contains ab"
contains "abcd" "bc" && echo "abcd contains bc"
contains "abcd" "cd" && echo "abcd contains cd"
contains "abcd" "abcd" && echo "abcd contains abcd"
contains "" "" && echo "empty string contains empty string"
contains "a" "" && echo "a contains empty string"
contains "" "a" || echo "empty string does not contain a"
contains "abcd efgh" "cd ef" && echo "abcd efgh contains cd ef"
contains "abcd efgh" " " && echo "abcd efgh contains a space"
fjarlq
źródło
3
To nie działa dla mnie, jeśli podciąg zawiera odwrotne ukośniki. Jak zwykle substring="$( printf '%q' "$2" )"ratuje dzień.
Egor Tensin
to również pasuje do niewłaściwego substrinsg. string = "aplha beta betaone" substring = "beta". pasuje zarówno do betaone, jak i do dbety, co moim zdaniem jest złe.
rajeev
1
A co z używaniem symboli wieloznacznych ? [[ $haystack == *"My needle"* ]]
Pablo Bianchi
6
Nieprawidłowe jest to, że podwójne nawiasy nie są zgodne z POSIX, co było założeniem pytania @Pablo.
Rob Kennedy
2
Nie działa to w przypadku znaków specjalnych, takich jak []. Zobacz moją odpowiedź stackoverflow.com/a/54490453/712666 .
Alex Skrypnyk
88

Czysta powłoka POSIX:

#!/bin/sh
CURRENT_DIR=`pwd`

case "$CURRENT_DIR" in
  *String1*) echo "String1 present" ;;
  *String2*) echo "String2 present" ;;
  *)         echo "else" ;;
esac

Rozszerzone powłoki, takie jak ksh lub bash, mają fantazyjne mechanizmy dopasowywania, ale stary styl casejest zaskakująco potężny.

Norman Ramsey
źródło
38

Niestety, nie wiem, jak to zrobić w sh. Jednak używając basha (zaczynając od wersji 3.0.0, którą prawdopodobnie masz), możesz użyć operatora = ~ w następujący sposób:

#!/bin/bash
CURRENT_DIR=`pwd`

if [[ "$CURRENT_DIR" =~ "String1" ]]
then
 echo "String1 present"
elif [[ "$CURRENT_DIR" =~ "String2" ]]
then
 echo "String2 present"
else
 echo "Else"
fi

Jako dodatkowy bonus (i / lub ostrzeżenie, jeśli twoje łańcuchy mają jakieś śmieszne znaki), = ~ akceptuje wyrażenia regularne jako właściwy operand, jeśli pominiesz cudzysłowy.

John Hyland
źródło
3
Nie zacytować regex, czy będzie to nie działa w ogóle. Na przykład, spróbuj [[ test =~ "test.*" ]]Vs. [[ test =~ test.* ]].
l0b0
1
Cóż, będzie działać dobrze, jeśli testujesz podciąg, tak jak w pierwotnym pytaniu, ale nie potraktuje właściwego operandu jako wyrażenia regularnego. Zaktualizuję odpowiedź, aby była bardziej zrozumiała.
John Hyland,
3
To jest Bash, a nie POSIX sh, jak zadaje pytanie.
Reid,
29
#!/usr/bin/env sh

# Searches a subset string in a string:
# 1st arg:reference string
# 2nd arg:subset string to be matched

if echo "$1" | grep -q "$2"
then
    echo "$2 is in $1"
else 
    echo "$2 is not in $1"
fi
a.saurabh
źródło
2
Zmień grep -q "$2"na, grep -q "$2" > /dev/nullaby uniknąć niepożądanego wyjścia.
Victor Sergienko
Zmień grep -q "$2"na, grep -q "$2" 2>/dev/nullaby uniknąć niepożądanego wyjścia.
ulidtko
14
case $(pwd) in
  *path) echo "ends with path";;
  path*) echo "starts with path";;
  *path*) echo "contains path";;
  *) echo "this is the default";;
esac
helpermethod
źródło
12

Oto łącze do różnych rozwiązań Twojego problemu.

To jest mój ulubiony, ponieważ ma najbardziej czytelny dla człowieka sens:

Metoda Star Wildcard

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0
ma77c
źródło
Na shdostałam „nieznany argument” z tym. Działa jednak z Bash.
halfer
15
[[nie jest POSIX
Ian
4

Istnieją wyrażenia regularne Bash . Lub jest „wyrażenie”:

 if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
bmargulies
źródło
2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

-> wyjście: ciąg 1 zawiera ciąg 2


źródło
2

Jeśli potrzebujesz metody opartej tylko na ksh , która jest tak szybka jak „test”, możesz zrobić coś takiego:

contains() # haystack needle
{
    haystack=${1/$2/}
    if [ ${#haystack} -ne ${#1} ] ; then
        return 1
    fi
    return 0
}

Działa poprzez usunięcie igły w stogu siana, a następnie porównanie długości sznurka starego i nowego stogu.

JoeOfTex
źródło
1
Czy nie byłoby możliwe zwrócenie wyniku test? Jak w return [ ${#haystack} -eq ${#1} ]?
Alexis Wilke
Tak to jest poprawne. Zostawię to w ten sposób, ponieważ jest to łatwiejsze do zrozumienia dla laików. Jeśli używasz w swoim kodzie, użyj metody @AlexisWilke.
JoeOfTex
2

Zobacz stronę podręcznika programu „test”. Jeśli po prostu sprawdzasz istnienie katalogu, normalnie zrobiłbyś coś takiego:

if test -d "String1"; then
  echo "String1 present"
end

Jeśli faktycznie próbujesz dopasować ciąg, możesz również użyć reguł rozwinięcia bash i symboli wieloznacznych:

if test -d "String*"; then
  echo "A directory starting with 'String' is present"
end

Jeśli chcesz zrobić coś bardziej złożonego, musisz użyć innego programu, takiego jak expr.

jdeseno
źródło
1
Wydaje się, że w drugim przykładzie znajduje się fałszywy podwójny cudzysłów („).
Alexis Wilke,
2

W szczególnych przypadkach, gdy chcesz sprawdzić, czy słowo jest zawarte w długim tekście, możesz iterować po długim tekście za pomocą pętli.

found=F
query_word=this
long_string="many many words in this text"
for w in $long_string; do
    if [ "$w" = "$query_word" ]; then
          found=T
          break
    fi
done

To jest czysta skorupa Bourne'a.

Kemin Zhou
źródło