No Bash, como posso verificar se uma sequência começa com algum valor?

745

Gostaria de verificar se uma string começa com "node", por exemplo, "node001". Algo como

if [ $HOST == user* ]
  then
  echo yes
fi

Como posso fazer isso corretamente?


Preciso ainda combinar expressões para verificar se o HOST é "user1" ou começa com "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

Como posso fazer isso corretamente?

Tim
fonte
7
Não fique muito tentado a combinar expressões. Pode parecer mais feio ter duas condicionais separadas, embora você possa fornecer melhores mensagens de erro e facilitar a depuração do seu script. Também evitaria os recursos do bash. O interruptor é o caminho a percorrer.
quer

Respostas:

1073

Este snippet no Advanced Bash Scripting Guide diz:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

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

Então você quase acertou; você precisava de colchetes duplos , não de colchetes simples.


Com relação à sua segunda pergunta, você pode escrevê-lo desta maneira:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

Qual eco

yes1
yes2

ifÉ difícil se acostumar com a sintaxe do Bash (IMO).

Mark Rushakoff
fonte
12
Para regex, você quer dizer [[$ a = ~ ^ z. *]]?
JStrahl
3
Então, existe uma diferença funcional entre [[ $a == z* ]]e [[ $a == "z*" ]]? Em outras palavras: eles funcionam de maneira diferente? E o que você quer dizer especificamente quando diz "$ a é igual a z *"?
Niels Bom
5
Você não precisa do separador de instruções ";" se você colocar "then" em sua própria linha
Yaza 14/10
6
Apenas quanto à completude: Para verificar se uma string termina com ...:[[ $a == *com ]]
Lepe
4
O ABS é uma escolha infeliz de referências - são muito as festividades do W3Schools, cheias de conteúdo desatualizado e exemplos de más práticas; o canal #breen do freenode tenta desencorajar seu uso pelo menos desde 2008 . Alguma chance de se mudar para o BashFAQ # 31 ? (Eu também teria sugerido o wiki do Bash-Hackers, mas ele está desativado há um tempo).
Charles Duffy
207

Se você estiver usando uma versão recente do Bash (v3 +), sugiro o operador de comparação de expressões regulares do Bash =~, por exemplo,

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

Para corresponder a this or thatuma regex, use |, por exemplo,

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

Nota - esta é uma sintaxe de expressão regular 'adequada'.

  • user*significa usee zero ou mais ocorrências de r, portanto, usee userrrrcorresponderão.
  • user.*médias usere zero ou mais ocorrências de qualquer caractere, portanto user1, userXcorresponderão.
  • ^user.*significa corresponder ao padrão user.*no início de $ HOST.

Se você não estiver familiarizado com a sintaxe da expressão regular, tente consultar este recurso .

brabster
fonte
Obrigado, Brabster! Eu adicionei ao post original uma nova pergunta sobre como combinar expressões em if cluase.
Tim
2
É uma pena que a resposta aceita não diga nada sobre a sintaxe da expressão regular.
CarlosRos
20
Para sua informação, o =~operador Bash apenas faz a correspondência de expressões regulares quando o lado direito não está cotado. Se você citar o lado direito "Qualquer parte do padrão pode ser citada para forçá-lo a corresponder como uma sequência". (1.) sempre coloque as expressões regulares à direita entre aspas e (2.) se você armazenar sua expressão regular em uma variável, certifique-se de NÃO citar o lado direito ao fazer a expansão de parâmetros.
Trevor Boyd Smith
145

Eu sempre tento manter o POSIX em shvez de usar extensões Bash, pois um dos principais pontos de script é a portabilidade (além de conectar programas, não substituí-los).

Em sh, existe uma maneira fácil de verificar uma condição "é prefixo".

case $HOST in node*)
    # Your code here
esac

Dada a idade, o arcano e o crocante sh (e o Bash não é a cura: é mais complicado, menos consistente e menos portátil), eu gostaria de destacar um aspecto funcional muito agradável: embora alguns elementos de sintaxe casesejam incorporados , as construções resultantes não são diferentes de qualquer outro trabalho. Eles podem ser compostos da mesma maneira:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

Ou ainda mais curto

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

Ou ainda mais curto (apenas para apresentar !como um elemento de linguagem - mas esse é um estilo ruim agora)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

Se você gosta de ser explícito, crie seu próprio elemento de linguagem:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

Isso não é realmente muito bom?

if beginswith node "$HOST"; then
    # Your code here
fi

E como shsão basicamente apenas trabalhos e listas de strings (e processos internamente, dos quais os trabalhos são compostos), agora podemos fazer alguma programação funcional leve:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

Isso é elegante. Não que eu advogasse o uso shde algo sério - ele quebra muito rapidamente os requisitos do mundo real (sem lambdas, portanto, devemos usar strings. Mas aninhar chamadas de função com strings não é possível, tubos não são possíveis, etc.)

Jo So
fonte
12
+1 Este portátil não é apenas legível, também é legível, idiomático e elegante (para shell script). Também se estende naturalmente a vários padrões; case $HOST in user01 | node* ) ...
Tripleee
Existe um nome para esse tipo de formatação de código? if case $HOST in node*) true;; *) false;; esac; then Eu já vi isso aqui e ali, aos meus olhos parece meio amassado.
Niels Bom
@ NielsBom Eu não sei exatamente o que você quer dizer com formatação, mas o que eu quero dizer é que o código do shell é muito composível . Os casecomandos Becaues são comandos, eles podem entrar if ... then.
Jo
Eu nem vejo por que é compositável, não entendo o suficiente shell script para isso :-) Minha pergunta é sobre como esse código usa parênteses não correspondentes e ponto-e-vírgula duplo. Ele não se parece nada com o script de shell que eu já vi antes, mas eu posso estar acostumado a ver o script bash mais do que o sh, então é isso.
Niels Bom
Nota: Deveria ser beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }diferente se $1tiver um literal *ou ?pode dar uma resposta errada.
LLFourn 5/02
80

Você pode selecionar apenas a parte da sequência que deseja verificar:

if [ "${HOST:0:4}" = user ]

Para sua pergunta de acompanhamento, você pode usar um OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
Martin Clayton
fonte
8
Você deve usar aspas duplas${HOST:0:4}
Jo So
@ Jo Então: Qual é o motivo?
Peter Mortensen
@PeterMortensen, tryHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Eu prefiro os outros métodos já publicados, mas algumas pessoas gostam de usar:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

Editar:

Adicionei seus suplentes à declaração de caso acima

Na sua versão editada, você tem muitos colchetes. Deve ficar assim:

if [[ $HOST == user1 || $HOST == node* ]];
Pausado até novo aviso.
fonte
Obrigado, Dennis! Eu adicionei ao post original uma nova pergunta sobre como combinar expressões em if cluase.
Tim
11
"algumas pessoas gostam de ...": este é mais portátil entre versões e conchas.
Carlosayam 17/10/11
Com instruções de caso, você pode deixar as aspas à volta da variável, pois não ocorre a divisão de palavras. Sei que é inútil e inconsistente, mas gosto de deixar de lado as aspas para torná-lo localmente mais visualmente atraente.
Jo #
E no meu caso, eu tive que deixar de lado as aspas antes): "/ *") não funcionou, / *) funcionou. (Eu estou procurando cordas começando com /, isto é, caminhos absolutos)
Josias Yoder
36

Embora eu ache a maioria das respostas aqui bastante corretas, muitas delas contêm bashismos desnecessários. A expansão do parâmetro POSIX oferece tudo o que você precisa:

[ "${host#user}" != "${host}" ]

e

[ "${host#node}" != "${host}" ]

${var#expr}retira o menor correspondente prefixo expra partir ${var}e que retorna. Portanto, se ${host}é que não começar com user( node), ${host#user}( ${host#node}) é o mesmo que ${host}.

exprpermite fnmatch()curingas, portanto, ${host#node??}e amigos também funcionam.

dhke
fonte
2
Eu diria que o basismo [[ $host == user* ]]pode ser necessário, já que é muito mais legível do que [ "${host#user}" != "${host}" ]. Como tal, desde que você controle o ambiente em que o script é executado (segmente as versões mais recentes bash), o primeiro é preferível.
X-yuri #
2
@ x-yuri Francamente, eu simplesmente colocaria isso em uma has_prefix()função e nunca mais a veria .
dhke
30

Como #tem um significado no Bash, cheguei à seguinte solução.

Além disso, eu gosto mais de empacotar strings com "" para superar espaços, etc.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
ozma
fonte
Era exatamente disso que eu precisava. Obrigado!
Ionică Bizău
obrigado, eu também queria saber como combinar uma string começando com blah:, parece que esta é a resposta!
Anentropic
12

Adicionando um pouco mais de detalhes de sintaxe à resposta de classificação mais alta de Mark Rushakoff.

A expressão

$HOST == node*

Também pode ser escrito como

$HOST == "node"*

O efeito é o mesmo. Apenas verifique se o curinga está fora do texto citado. Se o curinga estiver dentro das aspas, ele será interpretado literalmente (ou seja, não como curinga).

Sr Cabeça de Batata
fonte
8

@OP, para ambas as perguntas, você pode usar case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

Segunda questão

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

Or Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
ghostdog74
fonte
Observe que ;&só está disponível no Bash> = 4.
Pausado até novo aviso.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

não funciona, porque todos [, [[e testreconhecer a mesma gramática não recursiva. Consulte a seção EXPRESSÕES CONDICIONAIS na sua página de manual do Bash.

Como um aparte, o SUSv3 diz

O comando condicional derivado do KornShell (colchete duplo [[]] ) foi removido da descrição da linguagem de comando do shell em uma proposta anterior. Surgiram objeções de que o problema real é o uso indevido do comando test ( [ ), e colocá-lo no shell é a maneira errada de corrigir o problema. Em vez disso, a documentação adequada e uma nova palavra reservada do shell ( ! ) São suficientes.

Testes que requerem várias operações de teste podem ser realizados no nível do shell usando invocações individuais do comando test e da lógica do shell, em vez de usar o sinalizador de teste com tendência a erro -o .

Você precisaria escrever dessa maneira, mas o teste não suporta:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

test usa = para igualdade de strings e, mais importante, não suporta correspondência de padrões.

case/ esactem um bom suporte para correspondência de padrões:

case $HOST in
user1|node*) echo yes ;;
esac

Ele tem o benefício adicional de não depender do Bash, e a sintaxe é portátil. Na especificação Unix única , a linguagem de comandos do shell :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
apenas alguém
fonte
1
[e testsão Bash builtins, bem como programas externos. Tente type -a [.
Pausado até novo aviso.
Muito obrigado por explicar os problemas com o "composto ou", @ só alguém - estava procurando exatamente por algo assim! Felicidades! Nota PS (não relacionado com OP): if [ -z $aa -or -z $bb ]; ... dá " bash: [: -ou: operador binário esperado "; no entanto if [ -z "$aa" -o -z "$bb" ] ; ...passa.
Sdaau
2

grep

Esquecendo o desempenho, este é o POSIX e parece melhor do que as casesoluções:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

Explicação:

Ciro Santilli adicionou uma nova foto
fonte
2

Ajustei a resposta de @ markrushakoff para torná-la uma função que pode ser chamada:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

Use-o assim:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

Aqui está uma versão mais complexa que fornece um valor padrão especificado:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

Use-o assim:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false
Mike Slinn
fonte
-5

Outra coisa que você pode fazer é catdescobrir o que está ecoando e canalizarinline cut -c 1-1

Richard Tang
fonte