Мне бы хотелось иметь 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 (с половиной) решениях:
- используйте
set -e
так, чтобы любая («непроверенная») невыполняемая команда (или подоболочка) немедленно завершала основной скрипт (это может оказаться излишним или вызвать другие проблемы), - отправить сигнал из функции и поймать его с помощью
trap
- используйте
|| exit $?
после каждогоVALUEn=$(...)
подобногоVALUE=$(require_line "myKey") || exit $?
, - объединить (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."
""
Наконец, если вам действительно нужно только проверить наличие всех ключей, не зная соответствующих значений, то приведенный выше код, ориентированный на массив, можно упростить, просто выполняя цикл до тех пор, пока не будут найдены все ключи, или прервать выполнение на первом отсутствующем ключе.