Shell: Verificar se uma string contém um caractere especificado [duplicado]

Dec 22 2020
#!/bin/sh

TMP=$(cd Folder && ls) for name in $TMP; do

  if [[ "${name}" != *"a"* -a ${name} == *"b"* ]] ;then
   echo $name
  fi

done

Eu estava tentando gerar os nomes que não contêm 'a', mas sim 'b'. Este é o erro que recebi:

./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found

O que estou fazendo de errado aqui ?

Respostas

5 Kusalananda Dec 22 2020 at 02:19

Seu principal problema é que você está usando uma correspondência de padrão [[ ... ]]em um script executado /bin/shque não oferece suporte a esses tipos de testes. Veja também a questão intitulada Shell script lança um erro não encontrado quando executado a partir de um arquivo sh. Mas, se inserido manualmente, os comandos funcionam

O outro problema é que você está combinando a saída de lsem uma única string antes de dividi-la em palavras em espaços, tabulações e novas linhas, e aplicando o nome do arquivo às palavras geradas. Em seguida, você faz um loop sobre o resultado disso.

Em vez de:

#!/bin/sh

cd Folder || exit 1

for name in *b*; do
    [ -e "$name" ] || [ -L "$name" ] || continue
    case $name in (*a*) ;; (*) printf '%s\n' "$name"; esac
done

Isso percorre todos os arquivos no Folderdiretório que contêm a letra be, em seguida, imprime aqueles que não contêm um a. O teste para o apersonagem é feito usando case ... esac. O *a*padrão corresponde a um acaractere no nome do arquivo, se houver; nesse caso, nada acontece. As printfinstruções estão no "caso padrão" que é executado se não houver nenhum ana string.

Os testes com -ee -Lexistem para garantir que capturamos o caso extremo em que o padrão de loop não corresponde a nada. Nesse caso, o padrão permanecerá não expandido, portanto, para ter certeza de que podemos distinguir entre um padrão não expandido e um arquivo existente real, testamos -e. O -Lteste é capturar o caso mais extremo de um link simbólico que tem o mesmo nome do padrão de loop, mas que não aponta para nenhum lugar válido. O bashcódigo abaixo não precisa disso porque usa a nullglobopção shell (indisponível em /bin/sh).

Executar o loop sobre a expansão do *b*padrão em vez de quaisquer lssaídas tem o benefício de que também é capaz de lidar com nomes de arquivos contendo espaços, tabulações, novas linhas e caracteres globais de nome de arquivo (os nomes de arquivo nunca são colocados em uma única string que tentamos iterar).

No bashshell, você pode fazer o mesmo com um [[ ... ]]teste de correspondência de padrões, como tentou fazer você mesmo:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

for name in *b*; do
    [[ $name != *a* ]] && printf '%s\n' "$name"
done

Veja também:

  • Por que meu script de shell bloqueia com espaços em branco ou outros caracteres especiais?
  • Quando é necessário fazer aspas duplas?
  • Por que * não * analisar `ls` (e o que fazer em vez disso)?
  • Por que printf é melhor que echo?
6 StéphaneChazelas Dec 22 2020 at 02:35

Para imprimir os nomes de arquivos Folderque contenham be não a, em zshe seu ~( exceto / e não ) operador glob:

#! /bin/zsh -
set -o extendedglob
print -rC1 -- Folder/(*b*~*a*)(N:t)

(remova o N(para nullglob, se preferir que um erro seja relatado onde não há arquivo correspondente).

In ksh93(o shell que introduziu esse [[...]]operador (que não faz parte da shsintaxe padrão ) e seus operadores &( e ) e !(...)( não ):

#! /bin/ksh93 -
(
  CDPATH= cd Folder &&
    set -- ~(N)@(*b*&!(*a*)) &&
    (($# > 0)) && printf '%s\n' "$@"
)

Com o bashshell (que gosta zshtambém copiado do ksh [[...]]operador), usando extglobpara apoiar ksh88 operadores glob e nullglobpara obter o efeito de de zsh Nqualificador glob ou ksh93do ~(N)operador glob e alguns negação duplo com |( ou ) para alcançar e :

#! /bin/bash -
(
  shopt -s extglob nullglob
  cd ./Folder &&
    set -- !(!(*b*)|*a*) &&
    (($# > 0)) && printf '%s\n' "$@"
)

Na shsintaxe padrão :

#! /bin/sh -
(
  CDPATH= cd Folder || exit
  set -- [*]b[*] *b*
  [ "$1/$2" = '[*]b[*]/*b*' ] && exit # detect the case where the pattern
                                      # expands to itself because there's
                                      # no match as there's no nullglob
                                      # equivalent in sh
  shift
  for i do
    case $i in (*a*) ;; (*) set -- "$@" "$i" esac shift done [ "$#" -gt 0 ] && printf '%s\n' "$@"
)

Observe que as soluções usadas cdnão funcionarão se você tiver acesso de leitura, Foldermas não acesso de pesquisa.

De maneira mais geral, para responder à questão geral de como verificar se uma string contém outra em sh, o melhor é com a caseconstrução, que é como você faz a correspondência de padrões em sh. Você pode até usar uma função auxiliar:

contains()
  case "$1" in
    (*"$2"*) true;;
    (*) false;;
  esac

Para usar como se:

if contains foobar o; then
  echo foobar contains o
fi
if ! contains foobar z; then
  echo foobar does not contain o
fi
if
  contains "$file" b &&
    ! contains "$file" a then printf '%s\n' "$file contains b and not a "
fi