Exemplos

Exemplos

Eu gostaria de ter um script bash que me permita chamar uma função para ver se alguns dados estão contidos em um arquivo e, se não estiver, falha no script principal, como o seguinte, que é simplificado para manter isso no ponto.

Isso não funciona (não sai do script principal quando o subshell falha).

Como posso escrever a require_linefunção para que eu pudesse dizer mais de 20 delas em um arquivo como este

VALUE1=$(require_line "myKey1")
VALUE2=$(require_line "myKey2")
...

e não exigir um if em torno de cada um?

#!/bin/bash
set -eo pipefail

VALUE=$(require_line "myKey")

require_line(){
  local KEY=$1
  local DATA=$(cat /tmp/myfile)
  local INFO=$(echo "$DATA" | grep "$KEY")

  if [ ! -z "$INFO" ]
  then
    echo "Key not found in $DATA, key: $KEY"
    exit 1;
  fi
  echo "$INFO"
}

Responder1

Sair do subshell não sairá do script principal como você o experimentou.

Penso em 3 (e meia) soluções:

  1. use set -epara que qualquer comando (ou subshell) com falha ("não testado") saia imediatamente do script principal (isso pode ser um exagero ou causar outros problemas),
  2. enviar um sinal da função e capturá-lo com umtrap
  3. use || exit $?depois de cada um VALUEn=$(...)assim VALUE=$(require_line "myKey") || exit $?,
  4. combine (3.) com um loop (não tão elegante) usando eval.

Esse terceiro não "requer exatamente um if em torno de cada um" e ainda seria uma sintaxe IMHO bastante compacta.


Por falar nisso, está linha

echo "Key not found in $DATA, key: $KEY"

...é realmente inútil se você sair de todo o script logo depois disso, porque a frase será armazenada na $VALUEnvariável que não será exibida.

Sugiro imprimir stderrassim:

echo "my error" 1>&2

Exemplos

Exemplo para solução 1

#!/bin/sh
set -e

myfunc(){
        echo $1
        if [ "$1" != "OK" ] ; then exit 1 ; fi
}

VALUE1=$(myfunc "OK") 
echo $VALUE1
VALUE2=$(myfunc "NO WAY") 
echo $VALUE2

echo "main script did not exit"
$ ./test.sh
OK
zsh: exit 1     ./test.sh

Mas se eu remover set -edesde o início, recebo:

$ ./test.sh
OK
NO WAY
main script did not exit

Exemplo para solução 2

#!/bin/sh

trap "exit $?" USR1

myfunc(){
        echo $1
        if [ "$1" != "OK" ] ; then kill -USR1 $$ ; fi 
}

VALUE1=$(myfunc "OK") 
echo $VALUE1
VALUE2=$(myfunc "NO WAY")
echo $VALUE2

echo "main script did not exit"
$ ./test.sh
OK

Exemplo para solução 3

#!/bin/sh

myfunc(){
        echo $1
        if [ "$1" != "OK" ] ; then exit 1 ; fi
}

VALUE1=$(myfunc "OK") || exit $?
echo $VALUE1
VALUE2=$(myfunc "NO WAY") || exit $?
echo $VALUE2

echo "main script did not exit"
$ ./test.sh
OK
zsh: exit 1     ./test.sh

Exemplo para solução 4

#!/bin/sh

myfunc(){
        echo $1
        if [ "$1" != "OK" ] ; then exit 1 ; fi
}

I=1
for key in "OK" "OK" "NO WAY": ; do
        eval "VALUE$I=\$(myfunc \"$key\")" || exit $?
        eval "echo \$VALUE$I"
        I=$(($I+1))
done
echo "main script did not exit"
$ ./test.sh
OK
OK
zsh: exit 1     ./test.sh

Responder2

Vamos reestruturar require_lineum pouco o seu roteiro para te ajudar.

Primeiro, podemos nos livrar do inútil cat | grep. Segundo, podemos usar grepo comportamento intrínseco de para indicar sucesso ou fracasso na busca por KEY, bem como imprimir a chave, se encontrada, para stdout.

require_line(){
  local KEY="$1"
  local FILE="/tmp/myfile"

  if grep "$KEY" "$FILE"
  then
    return 0
  else
    printf 'Key not found in:\n\n"%s"\n\nKey: "%s"\n' "$(cat "$FILE")" "$KEY" >&2
    return 1
  fi
}

Isso aproveita o comportamento integrado do grep. Se a chave for encontrada, grepimprime a linha correspondente e retorna sucesso. Caso contrário, a elseramificação será tomada e uma mensagem será impressa indicando que a chave não foi encontrada. Além disso, no caso de grepfalha, a mensagem de erro é impressa para stderrque a mensagem de erro não seja confundida com uma correspondência válida encontrada em $FILE.

Você pode modificar ainda mais require_linepara aceitar um nome de arquivo como $2 parâmetro, alterando a linha para read local FILE="$2"e passando o nome de arquivo desejado sempre que invocar require_line.

Agora, com isso no lugar....

Você realmente precisa armazenar cada VALUEn para KEYn ou apenas precisa garantir que todos estejam presentes?

Agora que você tem uma require_linefunção que retorna de forma limpa um valor de sucesso ou falha, você pode simplesmente ANDreunir todos os seus testes. Assim que qualquer um deles falhar, o teste geral falhará.

Supondo que você precise dos valores reais de correspondência, a maneira demorada de fazer isso seria:

if value1=$(require_line "key1") &&
   value2=$(require_line "key2") &&
   value3=$(require_line "key3")
then
   printf "%s\n" "$value1" "$value2" "$value3"
else
   printf "One or more keys failed.\n" >&2
fi

Isso será entediante se você tiver várias chaves para verificar. Usar uma matriz pode ser melhor:

#!/usr/bin/env bash

require_line(){
  local KEY="$1"
  local FILE="/tmp/myfile" # or perhaps "$2"

  if grep "$KEY" "$FILE"
  then
    return 0
  else
    printf 'Key not found in:\n\n"%s"\n\nKey: "%s"\n' "$(cat "$FILE")" "$KEY" >&2
    return 1
  fi
}

declare keys=("this" "is" "a" "test" "\." "keyN")
N=${#keys[@]}

declare values=()

j=0
while [ $j -lt $N ] && values[$j]="$(require_line "${keys[j]}")"
do
  j=$(($j+1))
done

if [ $j -lt $N ]
then
  printf 'error: found only %d keys out of %d:\n' $j $N
  printf '  "%s"\n' "${values[@]}"
fi

Executando esse código com alguns dados de exemplo:

$ cat /tmp/myfile
this is a test.
$ ./test.sh 
Key not found in:

"this is a test."

Key: "keyN"
error: found only 5 keys out of 6:
  "this is a test."
  "this is a test."
  "this is a test."
  "this is a test."
  "this is a test."
  ""

Por último, se você realmente precisa apenas verificar se todas as chaves estão presentes sem precisar saber quais são os valores de correspondência, o código orientado a array acima pode ser simplificado para simplesmente fazer um loop até que todas as chaves sejam encontradas ou para abortar na primeira chave que está faltando.

informação relacionada