Ejemplos

Ejemplos

Me gustaría tener un script bash que me permita llamar a una función para ver si hay algunos datos contenidos en un archivo y, si no falla, el script principal, algo así como el siguiente, que está simplificado para mantener esto en punto.

Esto no funciona (no sale del script principal cuando falla el subshell).

¿Cómo puedo escribir la require_linefunción para poder decir más de 20 de ellas en un archivo como este?

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

¿Y no requerir un if alrededor de cada uno?

#!/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"
}

Respuesta1

Salir del subshell no cerrará el script principal como lo experimentó.

Pienso en 3 (y media) soluciones:

  1. Úselo set -epara que cualquier comando (o subshell) fallido ("no probado") salga inmediatamente del script principal (esto puede ser excesivo o causar otros problemas),
  2. envía una señal desde la función y la capta con untrap
  3. usar || exit $?después de cada uno VALUEn=$(...)así VALUE=$(require_line "myKey") || exit $?,
  4. combine (3.) con un bucle (no tan elegante) usando eval.

Ese tercero no "requiere exactamente un if alrededor de cada uno" y aún así sería una sintaxis bastante compacta en mi humilde opinión.


Por cierto, esta línea

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

...en realidad es inútil si sale del script completo justo después de esto porque la oración se almacenará en la $VALUEnvariable que no se mostrará.

Sugiero imprimir para que stderrme guste esto:

echo "my error" 1>&2

Ejemplos

Ejemplo de solución 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

Pero si elimino set -edesde el principio, obtengo:

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

Ejemplo de solución 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

Ejemplo de solución 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

Ejemplo de solución 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

Respuesta2

Reestructuremos require_lineun poco tu guión para ayudarte.

Primero, podemos deshacernos de lo inútil cat | grep. En segundo lugar, podemos utilizar grepel comportamiento intrínseco de para indicar el éxito o el fracaso de la búsqueda de KEY, así como imprimir la clave, si la encontramos, 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
}

Esto aprovecha el comportamiento integrado de grep. Si se encuentra la clave, grepimprime la línea coincidente y devuelve éxito. De lo contrario, se toma la elsesucursal y se imprime un mensaje indicando que no se encontró la clave. Además, en el caso de que grepfalle, el mensaje de error se imprime para stderrque no se confunda con una coincidencia válida encontrada en $FILE.

Puede modificar aún más require_linepara aceptar un nombre de archivo como $2 parámetro cambiando la línea para leer local FILE="$2"y luego pasando el nombre de archivo deseado cada vez que invoque require_line.

Ahora, con eso en su lugar....

¿Realmente necesita almacenar cada VALUEn para KEYn, o simplemente necesita asegurarse de que estén todos presentes?

Ahora que tiene una require_linefunción que devuelve claramente un valor de éxito o fracaso, puede simplemente realizar ANDtodas las pruebas juntas. Tan pronto como cualquiera de ellos falle, la prueba general fallará.

Suponiendo que necesita los valores de coincidencia reales, la forma más larga de hacerlo sería:

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

Esto resultará tedioso si tiene que verificar numerosas claves. Usar una matriz puede ser mejor:

#!/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

Ejecutando ese código con algunos datos de muestra:

$ 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, si realmente solo necesita verificar que todas las claves estén presentes sin necesidad de saber cuáles son los valores coincidentes, el código orientado a matrices anterior podría simplificarse para simplemente realizar un bucle hasta que se encuentren todas las claves o cancelar en la primera clave. que se encuentra que falta.

información relacionada