Мне просто становится немного удобнее с Bash.
Мне нужна была очень простая функция журнала для записи в файл и вывода на консоль сообщений и ошибок. Я также хотел, чтобы терминал и файл интерпретировали символы новой строки, а не распечатывали их. Вот что я придумал (отлично работает):
#!/bin/bash
log () {
if [[ "$3" == '-e' || "$3" == '--error' ]]; then
>&2 echo -e "ERROR: $1" && printf "ERROR: $1n" >> "$2"
else
echo -e "$1" && printf "$1n" >> "$2"
fi
}
# Example implementation
log_file=snip/init.log
msg="nthis is an error message with leading newline character"
log "$msg" $log_file -e
msg="this is success message with no newline characters"
log "$msg" $log_file
Мне больше не нужна функциональность, но у меня есть вопросы:
- Является ли эффективный и читаемый фрагмент кода, который не сломается при передаче строк со специальными символами?
- Синтаксически, мог бы я сделать это «лучше», например, использовать трубы вместо
&&
.tee
вместо тогоprintf
так далее.? - Как я могу реорганизовать этот код, чтобы разрешить необязательный
-e
флаг, который нужно передать первым?
1 ответ
Здесь довольно много повторений:
echo && print
примерно с теми же аргументами —tee
наверное лучше здесь- пути с ошибками и без ошибок — возможно, используйте одну функцию, чтобы добавить
ERROR:
отметьте и позвоните другому?
Может быть, лучше сначала указать имя файла, а затем любое количество слов сообщения? Это сделало бы интерфейс более похожим на echo
, Например.
Также есть проблема с нашим использованием printf
— Любые %
символы в сообщении об ошибке будут интерпретированы как индикаторы замены. Не то, что мы хотим. Мы могли бы использовать printf %b
вместо этого развернуть escape-последовательности:
# log FILE MESSAGE...
log () {
local file="$1"; shift
printf '%b ' "$@" 'n' | tee -a "$file"
}
# log_err FILE MESSAGE...
log_error () {
local file="$1"; shift
local message="ERROR: $1"; shift
log "$file" "$message" "$@" >&2
}
Если мы действительно хотим иметь единственную функцию с аргументами (например, для добавления дополнительных опций), тогда мы можем захотеть использовать while
/case
подход к синтаксическому анализу опций, который часто используется на программном уровне:
#!/bin/bash
set -eu
# log [-e] [-f FILE] MESSAGE...
log() {
local prefix=""
local stream=1
local files=()
# handle options
while ! ${1+false}
do case "$1" in
-e|--error) prefix="ERROR:"; stream=2 ;;
-f|--file) shift; files+=("${1-}") ;;
--) shift; break ;; # end of arguments
-*) log -e "log: invalid option '$1'"; return 1;;
*) break ;; # start of message
esac
shift
done
if ${1+false}
then log -e "log: no message!"; return 1;
fi
# if we have a prefix, update our argument list
if [ "$prefix" ]
then set -- "$prefix" "$@"
fi
# now perform the action
printf '%b ' "$@" 'n' | tee -a "${files[@]}" >&$stream
}
Здесь есть неочевидный трюк, который может потребовать объяснения: я использовал ${1+false}
расширить до false
когда остался хотя бы один аргумент, или пустой команде, когда аргументов нет (пустая команда считается «истинной» if
и while
).
Я думаю, мне нужно сохранить этот код в одной функции, хотя, поскольку у меня будет еще одна функция журнала для ведения журнала только в файл (без консоли), я вызываю эту функцию
log_silent
. Дело в том, что мне не нужны 4 функции для выполнения этой работы. Я хотел бы сделать это с помощью всего двух функций.— только
Да,
local
иtee -a
и новая строка — все это хорошие улучшения (я должен был попробовать код перед публикацией!). Отредактировано.— Тоби Спейт
Я обновился, чтобы показать, как можно анализировать параметры в одной функции. Должно быть легко добавить параметр «без звука», который устанавливает
stream=/dev/null
.— Тоби Спейт