Примеры

Примеры

Мне бы хотелось иметь bash-скрипт, который позволял бы мне вызывать функцию, чтобы проверить, содержатся ли какие-либо данные в файле, и если нет, завершать основной скрипт, что-то вроде следующего, который упрощен для сохранения сути.

Это не работает (не происходит выход из основного скрипта при сбое вложенной оболочки).

Как мне написать require_lineфункцию, чтобы я мог иметь, скажем, более 20 из них в файле, например так?

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

и не требовать «если» вокруг каждого из них?

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

решение1

Выход из подоболочки не приведет к выходу из основного сценария, как это было раньше.

Я думаю о 3 (с половиной) решениях:

  1. используйте set -eтак, чтобы любая («непроверенная») невыполняемая команда (или подоболочка) немедленно завершала основной скрипт (это может оказаться излишним или вызвать другие проблемы),
  2. отправить сигнал из функции и поймать его с помощьюtrap
  3. используйте || exit $?после каждого VALUEn=$(...)подобного VALUE=$(require_line "myKey") || exit $?,
  4. объединить (3.) с (не таким элегантным) циклом, используя eval.

Третий вариант не совсем «требует if вокруг каждого» и все равно будет иметь довольно компактный синтаксис, ИМХО.


Кстати, эта строка

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

...на самом деле бесполезно, если вы сразу после этого выйдете из всего скрипта, поскольку предложение будет сохранено в $VALUEnпеременной, которая не будет отображена.

Предлагаю распечатать вот stderrтак:

echo "my error" 1>&2

Примеры

Пример для решения 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

Но если я уберу set -eс самого начала, то получу:

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

Пример для решения 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

Пример для решения 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

Пример для решения 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

решение2

Давайте require_lineнемного реструктурируем ваш сценарий, чтобы вам помочь.

Во-первых, мы можем избавиться от бесполезного cat | grep. Во-вторых, мы можем использовать grepвнутреннее поведение , чтобы указать на успешность или неудачу поиска KEY, а также вывести ключ, если он найден, в 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
}

Это использует встроенное поведение grep. Если ключ найден, grepвыводит соответствующую строку и возвращает успех. В противном случае ветвь elseберется, и выводится сообщение о том, что ключ не найден. Кроме того, в случае grepнеудачи выводится сообщение об ошибке, stderrчтобы сообщение об ошибке не было ошибочно принято за действительное совпадение, найденное в $FILE.

Вы можете дополнительно модифицировать его, require_lineчтобы принимать имя файла в качестве $2 параметра, изменив строку на read local FILE="$2"и затем передавая желаемое имя файла каждый раз при вызове require_line.

Теперь, когда все это на месте...

Действительно ли вам нужно хранить каждое VALUEn для KEYn или вам просто нужно убедиться, что они все присутствуют?

Теперь, когда у вас есть require_lineфункция, которая чисто возвращает значение успеха или неудачи, вы можете просто провести ANDвсе ваши тесты вместе. Как только любой из них провалится, провалится и весь тест.

Если предположить, что вам нужны фактические значения соответствия, то самый длинный способ сделать это будет выглядеть так:

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

Это будет утомительно, если вам нужно проверить множество ключей. Использование массива может быть лучше:

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

Запустим этот код с некоторыми образцами данных:

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

Наконец, если вам действительно нужно только проверить наличие всех ключей, не зная соответствующих значений, то приведенный выше код, ориентированный на массив, можно упростить, просто выполняя цикл до тех пор, пока не будут найдены все ключи, или прервать выполнение на первом отсутствующем ключе.

Связанный контент