Beispiele

Beispiele

Ich hätte gerne ein Bash-Skript, mit dem ich eine Funktion aufrufen kann, um zu prüfen, ob eine Datei Daten enthält. Wenn dies nicht der Fall ist, schlägt das Hauptskript fehl, ungefähr wie das folgende, das der Übersichtlichkeit halber vereinfacht wurde.

Dies funktioniert nicht (das Hauptskript wird nicht beendet, wenn die Untershell fehlschlägt).

Wie kann ich die Funktion so schreiben require_line, dass ich sagen wir mal über 20 davon in einer Datei haben kann, wie hier

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

und nicht bei jedem ein „wenn“ erforderlich ist?

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

Antwort1

Das Beenden der Subshell beendet nicht, wie Sie es erlebt haben, das Hauptskript.

Mir fallen 3 (und eine halbe) Lösungen ein:

  1. so verwenden set -e, dass jeder ("ungetestete") fehlgeschlagene Befehl (oder jede Subshell) das Hauptskript sofort beendet (das kann übertrieben sein oder andere Probleme verursachen),
  2. Senden Sie ein Signal von der Funktion und fangen Sie es mit einemtrap
  3. verwenden Sie || exit $?nach jedem VALUEn=$(...)wie folgt VALUE=$(require_line "myKey") || exit $?,
  4. kombiniere (3.) mit einer (nicht ganz so eleganten) Schleife unter Verwendung von eval.

Die dritte erfordert nicht unbedingt „ein if um jedes herum“ und wäre meiner Meinung nach immer noch eine ziemlich kompakte Syntax.


Übrigens, diese Linie

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

...ist eigentlich nutzlos, wenn Sie das ganze Skript direkt danach beenden, da der Satz in der $VALUEnVariable gespeichert wird, die nicht angezeigt wird.

Ich schlage vor, stderrso auszudrucken:

echo "my error" 1>&2

Beispiele

Beispiel für Lösung 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

Wenn ich es jedoch set -evom Anfang entferne, erhalte ich:

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

Beispiel für Lösung 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

Beispiel für Lösung 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

Beispiel für Lösung 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

Antwort2

require_lineUm Ihnen zu helfen, strukturieren wir Ihr Skript ein wenig um.

Erstens können wir das nutzlose loswerden cat | grep. Zweitens können wir grepdas intrinsische Verhalten von verwenden, um den Erfolg oder Misserfolg der Suche nach anzuzeigen KEYund den Schlüssel, falls gefunden, an auszugeben 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
}

Dies nutzt das integrierte Verhalten von grep. Wenn der Schlüssel gefunden wird, grepwird die passende Zeile gedruckt und Erfolg zurückgegeben. Andernfalls elsewird der Zweig genommen und eine Meldung ausgegeben, die angibt, dass der Schlüssel nicht gefunden wurde. Außerdem wird im Fall von grepfehlgeschlagen die Fehlermeldung an ausgegeben, stderrdamit die Fehlermeldung nicht mit einer gültigen Übereinstimmung verwechselt wird, die in gefunden wurde $FILE.

Sie können die Funktion außerdem so ändern, require_linedass als Parameter ein Dateiname akzeptiert wird, $2 indem Sie die Zeile zum Lesen ändern local FILE="$2"und dann bei jedem Aufruf den gewünschten Dateinamen übergeben require_line.

Nun, da dies erledigt ist …

Müssen Sie wirklich jeden VALUEn für KEYn speichern oder müssen Sie nur sicherstellen, dass sie alle vorhanden sind?

Da Sie nun eine Funktion haben require_line, die einen Erfolgs- oder Fehlerwert sauber zurückgibt, können Sie ANDalle Ihre Tests einfach zusammenfassen. Sobald einer davon fehlschlägt, schlägt der gesamte Test fehl.

Vorausgesetzt, Sie benötigen die tatsächlichen Übereinstimmungswerte, wäre die umständliche Vorgehensweise:

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

Das wird mühsam, wenn Sie viele Schlüssel überprüfen müssen. Die Verwendung eines Arrays kann besser sein:

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

Ausführen dieses Codes mit einigen Beispieldaten:

$ 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."
  ""

Wenn Sie schließlich wirklich nur überprüfen müssen, ob alle Schlüssel vorhanden sind, ohne die Übereinstimmungswerte kennen zu müssen, könnte der arrayorientierte Code oben so vereinfacht werden, dass er einfach eine Schleife ausführt, bis alle Schlüssel gefunden sind, oder beim ersten fehlenden Schlüssel abbricht.

verwandte Informationen