
Итак, у меня есть скрипт резервного копирования, который выглядит так:
tar -cf "${BACKUP_TAR}" "${LATEST_SUCCESSFUL_BACKUP}" 2>&1 | tee -a "${LOG_FILE}"
local PACKING_EXITCODE=${PIPESTATUS[0]}
if [ ${PACKING_EXITCODE} -eq 0 ]; then
logging 'Packing successful'
else
logging "ERROR: Packing failed! ERROR: ${PACKING_EXITCODE}. Disk space?"
df -h 2>&1 | tee -a "${LOG_FILE}"
logging "Check the log file: ${LOG_FILE}"
set_lockfile 'destroy'
backup_remove_package
exit 1
fi
logging
это функция для правильного входа в мой файл журнала.
logging () {
local now="$(date)"
local logfile=$2
local logfile=${logfile:-$LOG_FILE}
cat <<< "${now} $@" | tee -a "${logfile}"
}
set_lockfile "destroy"` — это функция, которая удаляет мой файл блокировки.
set_lockfile () {
local lockfile_action=$1
local lockfile=$2
local lockfile=${lockfile:-$LOCK_FILE}
if [ "${lockfile_action}" == "create" ]; then
#...
elif [ "${lockfile_action}" == "destroy" ]; then
destroy_lockfile $lockfile
else
logging 'ERROR: Wrong argument for locking file: use create or destroy'
exit 1
fi
}
destroy_lockfile () {
local lockfile=$1
if [ ! -f ${lockfile} ]; then
logging "WARNING: Lockfile ${lockfile} not found!"
else
logging "Removing lockfile ${lockfile}"
rm -f "${lockfile}"
fi
}
backup_remove_package
— это функция удаления всех созданных временных файлов.
У меня произошла ошибка упаковки из-за переполнения диска, как вы можете догадаться, это ожидаемое поведение для df -h
.
Интересная вещь — это журнал резервного копирования. В нем говорится:
tar: /tmp/backup/20180827T223001.tar: Wrote only 4096 of 10240 bytes
tar: Error is not recoverable: exiting now
Filesystem Size Used Avail Use% Mounted on
/dev/xvda1 788G 788G 0 100% /
devtmpfs 3.9G 60K 3.9G 1% /dev
tmpfs 3.9G 0 3.9G 0% /dev/shm
Это значит, tar
что произошел сбой, затем он прошел через if
условие, каким-то образом пропустил logging "ERROR: ..."
, выполнил df -h
и умер, пропустив остальное.
Каким-то образом это выглядит так, будто он пропускает любую функцию, но выполняет команды.
Резервное копирование вызывается из cron.d
файла. Я НЕ установил set -e
, поэтому выход при ошибке не выполняется.
Есть идеи, почему это происходит?
решение1
Ваш скрипт, похоже, работает так, как и ожидалось. Вывод df
явно дошел до $LOG_FILE
и exit 1
приводит к завершению работы скрипта.
Мы не знаем, что logging
делает ваша команда, но, насколько я знаю, она не предназначена для записи в $LOG_FILE
. Если бы это было так, было бы немного глупо писатьПроверьте файл журнала: ${LOG_FILE}там.
Редактировать
Теперь, когда вы опубликовали logging
функцию, я вижу, что она использует строку here-string ( <<<
).
В bash
here-strings и here-documents реализованы с использованием временных файлов (в $TMPDIR
или /tmp
если $TMPDIR
не определено). Если бы это была файловая система, которая была заполнена, это объяснило бы, почему logging
ничего не выводится.
$ sudo mount -o size=1 -t tmpfs empty /mnt/1
$ yes > /mnt/1/fill-up
yes: standard output: No space left on device
$ TMPDIR=/mnt/1 bash -c 'cat <<< test'
bash: cannot create temp file for here-document: No space left on device
Вместо:
local now="$(date)"
cat <<< "${now} $@" | tee -a "${logfile}"
Просто используйте:
printf '%(%FT%T%z)T %s\n' -1 "$*"
printf '%(%FT%T%z)T %s\n' -1 "$*" >> "$logfile"
Или:
local msg
printf -v msg '%(%FT%T%z)T %s' -1 "$*"
printf '%s\n' "$msg"
printf '%s\n' "$msg" >> "$logfile"
(предполагается, $IFS
что не установлено или начинается с пробела)
Это сохраняет временный файл, но также позволяет избежать разветвления любого процесса или выполнения любой внешней команды (что также может привести к сбою при некоторых патологических состояниях) (и дает вам более удобный формат даты, который вы можете свободно адаптировать).
В целом, система с заполненными файловыми системами /tmp и /var является неполноценной системой, в которой можно ожидать, что многое не будет работать должным образом.
Здесь вам повезло, что у вас вообще есть логи. Дисковое пространство для файлов выделяется блоками (обычно 4К на ext4), поэтому, вероятно, вы получили какой-то вывод в `$LOG_FILE (поскольку последний блок был выделен до того, как файловая система заполнилась).
Скрипты, запущенные cron, также имеют свои stdout и stderr во временном файле (затем cron пытается отправить электронное письмо с их содержимым, если они не пустые). Таким образом, любая из команд может иметь свой write(1, ...)
или write(2, ...)
также завершаться ошибкой (с ошибкой ENOSPC), что может привести к их неправильному поведению или преждевременному завершению, если они посчитают это фатальной ошибкой.
решение2
Существует высокая вероятность того, что проблема в том, что
PACKING_EXITCODE=${PIPESTATUS[0]}
это не действительный шелл-код, а нечто конкретное bash
.
Cron вызывает команды, /bin/sh
которые отличаются от bash
.
Вы могли бы начать свой сценарий с
#!/bin/bash
и сделать скрипт исполняемым, chmod +x scriptname
чтобы убедиться, что bash
определенный код выполняется оболочкой по умолчанию bash
, а не оболочкой по умолчанию.