Shell: Verificar se uma string contém um caractere especificado [duplicado]
#!/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
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?
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