Como você sabe se uma string contém outra string no POSIX sh?

136

Eu quero escrever um script de shell Unix que faça várias lógicas se houver uma string dentro de outra string. Por exemplo, se eu estiver em uma determinada pasta, ramifique. Alguém poderia me dizer como fazer isso? Se possível, eu gostaria de tornar isso não específico do shell (ou seja, não apenas do bash), mas se não houver outra maneira de fazer isso.

#!/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
fonte
2
Sei que isso é antigo, mas aqui estão algumas coisas a serem observadas para futuros visitantes: (1) Geralmente, é uma boa prática reservar nomes de variáveis ​​SNAKE_CASE para variáveis ​​internas do ambiente e do shell. (2) A configuração CURRENT_DIRé redundante; você pode apenas usar $PWD.
nyuszika7h

Respostas:

162

Aqui está mais uma solução. Ele usa a expansão de parâmetro de substring POSIX , para que ele funcione no Bash, Dash, KornShell (ksh), shell Z (zsh), etc.

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

Uma versão funcionalizada com alguns exemplos:

# 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
fonte
3
Isso não funciona para mim se a substring contém barras invertidas. Como de costume, substring="$( printf '%q' "$2" )"salva o dia.
Egor Tensin
isso também combina com substrinsg errado. string = "aplha beta betaone" substring = "beta". combina tanto betaone quanto dbeta, o que acho errado.
rajeev
1
Que tal usar curingas ? [[ $haystack == *"My needle"* ]]
Pablo #
4
O que há de errado é que colchetes duplos não são POSIX, que foi a premissa da pergunta, @Pablo.
Rob Kennedy
1
Isso não funciona com caracteres especiais como []. Veja minha resposta stackoverflow.com/a/54490453/712666 .
Alex Skrypnyk
85

Shell POSIX puro:

#!/bin/sh
CURRENT_DIR=`pwd`

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

Os shells estendidos, como o ksh ou o bash, têm mecanismos de correspondência sofisticados, mas o estilo antigo caseé surpreendentemente poderoso.

Norman Ramsey
fonte
40

Infelizmente, não conheço uma maneira de fazer isso no sh. No entanto, usando o bash (a partir da versão 3.0.0, que provavelmente é o que você possui), você pode usar o operador = ~ assim:

#!/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

Como um bônus adicional (e / ou um aviso, se suas strings tiverem caracteres engraçados), = ~ aceita expressões regulares como o operando certo se você deixar de fora as aspas.

John Hyland
fonte
3
Não cite a regex ou ela não funcionará em geral. Por exemplo, tente [[ test =~ "test.*" ]]vs. [[ test =~ test.* ]].
L0b0 11/01/12
1
Bem, funcionará bem se você estiver testando uma substring, como na pergunta original, mas não tratará o operando certo como um regex. Vou atualizar minha resposta para deixar isso mais claro.
John Hyland
3
Este é o Bash, não o POSIX sh, conforme a pergunta.
Reid
28
#!/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
fonte
2
Altere grep -q "$2"para grep -q "$2" > /dev/nullpara evitar saída indesejada.
Victor Sergienko
15

Aqui está um link para várias soluções do seu problema.

Este é o meu favorito, pois faz o sentido legível mais humano:

O método curinga da estrela

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0
ma77c
fonte
Em shrecebi "operando desconhecido" com isso. Funciona com o Bash.
precisa saber é o seguinte
13
[[não é POSIX
Ian #
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
fonte
6

expressões regulares do Bash . Ou há 'expr':

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

-> saída: a sequência 1 contém a sequência 2


fonte
2

Veja a página de manual do programa 'test'. Se você está apenas testando a existência de um diretório, normalmente faria algo assim:

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

Se você está realmente tentando corresponder a uma string, também pode usar regras de expansão bash e curingas:

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

Se você precisar fazer algo mais complexo, precisará usar outro programa como o expr.

jdeseno
fonte
1
Parece haver uma aspa espúria ( ") em seu segundo exemplo.
Alexis Wilke
2

Em casos especiais em que você deseja descobrir se uma palavra está contida em um texto longo, é possível percorrer o texto longo com um loop.

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

Isso é pura casca de Bourne.

Kemin Zhou
fonte
1

Se você quiser um método apenas do ksh que seja tão rápido quanto "test", poderá fazer algo como:

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

Ele funciona excluindo a agulha no palheiro e comparando o comprimento da corda dos antigos e novos.

JoeOfTex
fonte
1
Não seria possível retornar o resultado de test? Como em return [ ${#haystack} -eq ${#1} ]?
Alexis Wilke
Sim, está correto. Vou deixar assim, pois é mais fácil entender para leigos. Se você usar seu código, use o método @AlexisWilke.
21719 JoeOfTex