Como determinar se uma string é uma substring de outra no bash?

49

Eu quero ver se uma string está dentro de uma parte de outra string.
por exemplo:

'ab' in 'abc' -> true
'ab' in 'bcd' -> false

Como posso fazer isso na condição de um script bash?

Lucio
fonte

Respostas:

27

Você pode usar o formulário ${VAR/subs}onde VARcontém a cadeia maior e subsa substring que você está tentando encontrar:

my_string=abc
substring=ab
if [ "${my_string/$substring}" = "$my_string" ] ; then
  echo "${substring} is not in ${my_string}"
else
  echo "${substring} was found in ${my_string}"
fi

Isso funciona porque ${VAR/subs}é igual a, $VARmas com a primeira ocorrência da string subsremovida, principalmente se $VARnão contiver a palavra, subsela não será modificada.

Edwin
fonte
Eu acho que você deve mudar a sequência das echodeclarações. Porque eu receboab is not in abc
Lucio
Você está certo! : P
edwin
Mmm .. Não, o script está errado. Assim que eu recebo ab was found in abc, mas se eu usar, substring=zeu ganhoz was found in abc
Lucio
1
Agora eu entendo ab is not in abc. Mas z was found in abc. Isso é engraçado: D
Lucio
1
Duh! Os ecos estavam no começo disso! XD
Edwin
47

[[ "bcd" =~ "ab" ]]
[[ "abc" =~ "ab" ]]

os colchetes são para o teste e, como são colchetes duplos, podem ser feitos alguns testes extras =~.

Então você pode usar este formulário algo como

var1="ab"
var2="bcd"
if [[ "$var2" =~ "$var1" ]]; then
    echo "pass"
else
    echo "fail"
fi

Edit: corrigido "= ~", havia invertido.

recatado
fonte
1
Eu recebo failcom estes parâmetros:var2="abcd"
Lucio
3
@ Lucio O correto é [[ $string =~ $substring ]]. Eu atualizei a resposta.
Eric Carvalho
12

Usando padrões de nome de arquivo bash (também conhecido como padrões "glob")

substr=ab
[[ abc == *"$substr"* ]] && echo yes || echo no    # yes
[[ bcd == *"$substr"* ]] && echo yes || echo no    # no
Glenn Jackman
fonte
if [["$ JAVA_OPTS"! = "-XX: + UseCompressedOops" ]]; exporte JAVA_OPTS = "$ JAVA_OPTS -XX: + UseCompressedOops"; fi
Mike Slinn
10

As duas abordagens a seguir funcionarão em qualquer ambiente compatível com POSIX, não apenas no bash:

substr=ab
for s in abc bcd; do
    if case ${s} in *"${substr}"*) true;; *) false;; esac; then
        printf %s\\n "'${s}' contains '${substr}'"
    else
        printf %s\\n "'${s}' does not contain '${substr}'"
    fi
done
substr=ab
for s in abc bcd; do
    if printf %s\\n "${s}" | grep -qF "${substr}"; then
        printf %s\\n "'${s}' contains '${substr}'"
    else
        printf %s\\n "'${s}' does not contain '${substr}'"
    fi
done

Ambos os resultados acima:

'abc' contains 'ab'
'bcd' does not contain 'ab'

O primeiro tem a vantagem de não gerar um grepprocesso separado .

Observe que eu uso em printf %s\\n "${foo}"vez de, echo "${foo}"porque echopode ser modificado ${foo}se contiver barras invertidas.

Richard Hansen
fonte
A primeira versão funciona perfeitamente para encontrar substring do nome do monitor na lista de xrandrnomes de monitores armazenados na variável. 1 e seja bem-vindo ao clube de rep de 1K :) #
10118 WinEunuuchs2Unix
6

declaração de caso shell

Esta é a solução mais portátil, funcionará mesmo em conchas Bourne antigas e concha Korn

#!/bin/bash
case "abcd" in
    *$1*) echo "It's a substring" ;;
    *) echo "Not a substring" ;;
esac

Exemplo de execução:

$ ./case_substr.sh "ab"                                                                                           
It's a substring
$ ./case_substr.sh "whatever"                                                                                     
Not a substring

Observe que você não precisa usá- echolo especificamente exit 1e exit 0pode significar sucesso ou fracasso.

O que poderíamos fazer também é criar uma função (que pode ser usada em scripts grandes, se necessário) com valores de retorno específicos (0 na partida, 1 na partida):

$ ./substring_function.sh                                  
ab is substring

$ cat substring_function.sh                                
#!/bin/sh

is_substring(){
    case "$2" in
        *$1*) return 0;;
        *) return 1;;
    esac
}

main(){
   if is_substring "ab" "abcdefg"
   then
       echo "ab is substring"
   fi
}

main $@

grep

$ grep -q 'ab' <<< "abcd" && echo "it's a substring" || echo "not a substring"                                    
it's a substring

Essa abordagem específica é útil com instruções if-else em bash. Também na maior parte portátil

AWK

$ awk '$0~/ab/{print "it is a substring"}' <<< "abcd"                                                             
it is a substring

Pitão

$ python -c 'import sys;sys.stdout.write("it is a substring") if "ab" in sys.stdin.read() else exit(1)' <<< "abcd"
it is a substring

Rubi

$ ruby -e ' puts "is substring" if  ARGV[1].include? ARGV[0]'  "ab" "abcdef"                                             
is substring
Sergiy Kolodyazhnyy
fonte
+1 por ultrapassar todos os outros. Notei aqui e em outros sites de troca de pilhas nenhuma resposta retorna o deslocamento da substring dentro da string. Qual é a missão desta noite :)
WinEunuuchs2Unix
@ WinEunuuchs2Unix Vai fazer isso no bash?
Sergiy Kolodyazhnyy
Sim e não. Estou fazendo um projeto Frankenstein em que o python get recebe todos os metadados das mensagens do gmail.com e o bash analisa e apresenta uma lista da GUI com drill down. Encontrei a resposta aqui: stackoverflow.com/questions/5031764/…
WinEunuuchs2Unix
@ WinEunuuchs2Unix OK. Soa interessante. Pessoalmente, prefiro analisar tudo em Python. Possui muito mais recursos para processamento de texto do que o bash sozinho.
Sergiy Kolodyazhnyy
Conheço suas preferências há cerca de dois anos e as respeito. Mas estou apenas aprendendo Python e conseguindo que o yad trabalhe nele parece complicado para mim. Sem mencionar todo o processamento do array com o qual já estou confortável no bash. Mas pelo menos eu escrevi meu primeiro script python para sugar tudo do gmail.com do google para o arquivo simples do Linux, certo? :)
WinEunuuchs2Unix
5

Cuidado com o [[e ":

[[ $a == z* ]]   # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

[ $a == z* ]     # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).

Então, como o @glenn_jackman disse, mas lembre-se de que, se você envolver o segundo termo inteiro entre aspas duplas, ele mudará o teste para a correspondência literal .

Fonte: http://tldp.org/LDP/abs/html/comparison-ops.html

Campa
fonte
4

Semelhante à resposta de edwin, mas com portabilidade aprimorada para posix & ksh e um toque menos barulhento que o de Richard:

substring=ab

string=abc
if [ "$string" != "${string%$substring*}" ]; then
    echo "$substring IS in $string"
else
    echo "$substring is NOT in $string"
fi

string=bcd
if [ "$string" != "${string%$substring*}" ]; then
    echo "$string contains $substring"
else
    echo "$string does NOT contain $substring"
fi

Resultado:

abc contains ab
bcd does NOT contain ab
Laubster
fonte