LINUX.ORG.RU
ФорумAdmin

Странное поведение BASH при запуске через cron

 


0

1

Имеются 2 скрипта: страт/стоп виртуальной машины и скрипт бэкапа.

В скрипте бэкапа имеется конструкция вида:

logging "Stop service $ИмяВМ" && logging "VirtualBox service script returned: `service "$ИмяВМ" stop`" && \
(этот блок описывает копирование файлов и может завершиться как true или false) || logging "debug: перепенные и их значения"
Функция logging самописна и не делает ничего сложнее команды echo в файл и консоль (именно в этом порядке). Приведённая выше конструкция эквивалентна следующей:
true && true && (true или false) || logging debug

Скрипт бэкапа запускается и корректно работает от sudo и под root с любыми допустимыми для него параметрами (включая all), но начинает «чудить» при запуске кроном.

Запуск кроном:

Скрипт получает параметр all, что интерпритируется как «бэкап в порядке описанной очереди» (в очереди более 3-х ВМ).

При бэкапе первой же машины даёт осечку блок копирования файлов (не важно почему) и скрипт сразу переходит к бэкапу второй машины - именно тут начинается что-то странное. До третьей машины бэкап не доходит.

Лог бэкапа первой машины:

2015-01-15 06:15:03 AM Stop service AP
2015-01-15 06:15:14 AM VirtualBox service script returned: VirtualBox guest AP stoping...
2015-01-15 06:15:14 AM Start FastCopy (bigsync)...
2015-01-15 06:15:16 AM debug: $1=AP, folder=/home/vbox/AP, config_file=AP.vbox, hdd_file=AP.vdi

Лог бэкапа второй машины:

2015-01-15 06:15:16 AM Stop service LA
2015-01-15 06:15:21 AM VirtualBox service script returned: VirtualBox guest LA stoping...
Странность в том, что логгирование обрывается после выполнения второго вызова функции logging, ведь прописано "(все предыдущие команды) || logging Debug:...". Такое поведение у скрипта регульярно при запуске кроном. Порядок ВМ в списке «all» значения не имеет. Права доступа к файлам и каталогам в порядке. В чём ошибка-то? Почему скрипт не работает только по крону?

★★

Последнее исправление: cetjs2 (всего исправлений: 3)
Ответ на: комментарий от sdio

Встречный вопрос:

Почему конструкция:

logging "VirtualBox service script returned: `service "$ИмяВМ" stop`"

Возвращает:

VirtualBox service script returned: VirtualBox guest LA stoping...
Не означает-ли это, что service была запущена? Плюс к этому - указанная ВМ действительно останавливается.

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 2)
Ответ на: комментарий от sdio

И как это что-то может повлиять на работу конструкции:

echo "1" && echo "2" && (cp;cp;cp...) || echo "Debug:..." 
таким образом, что после второго «echo» выполнение прерывается и debug не выводится? Я думал что такое просто невозможно. Может, cron стартует bash с какими-то специфическими параметрами помимо $PATH?

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 2)
Ответ на: комментарий от sdio

Значит "прерывается", а возвращаемый статус == 0

Не возможно. Команды внутри блока поставлены в конвейер «&&», так что блок не способен прерваться с кодом возврата 0.

Даже если блок копирования завершается провалом с кодом возврата == 0, ниже ещё несколько раз вызывается logging. Так что выполнение скрипта обрывается полностью, как буд-то его кто-то убивает или происходящий сбой настолько разрушителен, что обваливает и родительский шелл.

zzdnx ★★
() автор топика

Скорее всего дело в переменных окружения cron'a. Я бы наверно советовал бекап делать через Bareos.

fjfalcon ★★★
()
Ответ на: комментарий от fjfalcon

Я бэкаплю виртуальные машины (некоторые по 30GB). Бакула в последний раз, когда я её видел, не давала возможности, например, влезть в архив и забрать нужный файл без танцев с бубнами. Это уже исправлено, или всё так же нет интерфейчас к резервным копиям кроме «восстанови и забирай»?

zzdnx ★★
() автор топика
Ответ на: комментарий от sdio

Я могу привести сюда хоть весь код скрипта. Просто не считал нужным делать это без реальной необходимости.

Только подсветка синтаксиса уехала... Отключил.

#!/bin/bash

# This script backup VirtualBox guest(s)

#######################
###    VARIABLES    ###
#######################

sudo_user=rem				# User for manage virtual machines
VBD=/home/vbox				# VirtualBox directory
share=/mnt/archive2			# Mount share for backup
target=/mnt/archive2/vbox		# Target directory for backup

#######################
###    FUNCTIONS    ###
#######################

function getvar () { 
  case "$1" in
# Set group of virtual machines:
    all | ALL ) array=(AP LA PB RD KS) ;;

# Set virtual machine:
#    LAMP )
#          folder="$VBD/$1" ;
#          config_file="$1.vbox" ;
#          hdd_file="$1.vdi" ;;

# Automatically get parameters for virtual machine: 
    * )   su $sudo_user -c "VBoxManage showvminfo \"$1\" 2>>/dev/null >>/dev/null" && \
            folder="`su $sudo_user -c "VBoxManage showvminfo \"$1\"      2>>/dev/null" | grep 'Log'    | sed 's/.* \//\//g;s/\/Logs$//g'`" && \
            config_file="`su $sudo_user -c "VBoxManage showvminfo \"$1\" 2>>/dev/null" | grep ' file:' | sed 's/.*\/\(.*\).*/\1/g'`" && \
            hdd_file="`su $sudo_user -c "VBoxManage showvminfo \"$1\"    2>>/dev/null" | grep "(UUID"  | sed 's/.*\/\(.*\) (.*/\1/g'`" || \
            vm_not_exist=1 && if [[ ! -z "$vm_not_exist" ]] ; then echo -e "Not found VM with name $1." ; fi ;;
  esac
}

function backup () { 
# echo "Backup Virtual Machine $1..."
  if [ "$folder" != "/" ] ; then
    if [ ! -d "$folder/backup" ] ; then
      mkdir "$folder/backup" && chmod -R 777 "$folder/backup"
    fi ; 
  fi
  if [ ! -d "$folder" ] ;      then   echo -e "Error: Target 'folder' is not exist. Exit.\n"       && exit 0 ; else cd "$folder" ; fi
  if [ ! -f "$config_file" ] ; then   echo -e "Error: Target 'config_file' is not exist. Exit.\n"  && exit 0 ; fi
  if [ ! -f "$hdd_file" ] ;    then   echo -e "Error: Target 'hdd_file' is not exist. Exit.\n"     && exit 0 ; fi

  logging2file "Stop service $1" && logging "VirtualBox service script returned: `service "$1" stop`" && \
  ( 
    cp "$config_file" backup/"$config_file" 2>>/dev/null
    logging "Start FastCopy (bigsync)..."                 && bigsync -b 1 -s ./"$hdd_file" -d ./backup/"$hdd_file" 2>>/dev/null && \
    logging2file "Start service $1"                       && logging "VirtualBox service script returned: `service "$1" start`" && \
    logging "Make tgz archive from $folder/backup/..."    && cd "$folder"/backup/ && tar -czpf "$target"/"$1"_`date  +%F`.tgz ./ && \
    logging "tgz archive DONE!" && cd ..
  ) || logging2file "debug: \$1=$1, folder=$folder, config_file=$config_file, hdd_file=$hdd_file"
  unset folder config_file hdd_file
}

function logging2file () {
  if [ ! -z "$folder" ] ; then 
    echo "`date "+%F %r "`$@" >>"$folder/backup/backup.log"
  else
    echo "ERROR logging2file"
  fi 
}

function logging2console () { 
  echo "$@"
}

function logging () { 
  logging2file "$@"
  logging2console "$@"
}


#######################
###      CODE       ###
#######################

if [ -z "$1" ] ; then 
  echo -e "Usage:
  # $0 [all | VM1 VM2 … VMn]"
  exit 0
fi

logging2console "Mount share $share ..."
if [ ! -z $share ] ; then mount $share ; fi

while (( "${#}" >= "1" )) ; do
  getvar ${@} >>/dev/null # getvar have to be here for getting virtual machines list
  if [ -z "$array" ] ; then
    getvar "$1" ; if [ -z "$vm_not_exist" ] ; then backup "$1" ; else unset vm_not_exist ; fi
  else 
    for i in ${array[@]} ; do
      getvar "$i" ; if [ -z "$vm_not_exist" ] ; then backup "$i" ; else unset vm_not_exist ; fi
    done
    unset array
  fi
  shift 1 
done

logging2console "Unmount share $share ..."
if [ ! -z $share ] ; then umount $share ; fi

exit 0

О bigsync - это утилита ускоряет копирование больших файлов (читает куски по мегабайту и выполняет запись только когда куски отличаются). Распространяется в виде исходников.

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 2)
Ответ на: комментарий от zzdnx

Сделай одолжение. В консоли echo $PATH и этот PATH добавь в начало скрипта. Проверь его работу из крона, если не поможет будем разбираться детально.

sdio ★★★★★
()
Ответ на: комментарий от zzdnx

О bigsync - это утилита ускоряет копирование больших файлов (читает куски по мегабайту и выполняет запись только когда куски отличаются). Распространяется в виде исходников.

rsync так работает.

sdio ★★★★★
()
Ответ на: комментарий от sdio

Вообще не знал такого... Спасибо за наводку!

zzdnx ★★
() автор топика
Ответ на: комментарий от sdio

PATH

Загодовок кронтаба:

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

Мой скрипт указывает новый интерпритатор - bash.

В учётке юзера $PATH немного отличается порядком обхода, однако список каталогов тот же:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Сейчас скрипт работает под кроном, выполняется, пока без ошибок. Такое уже было - сбой при боевом запуске, паника и чтение логов, а при тестах работает нормально. Видимо, придётся ждать нового падения в боевом режиме. В функцию backup на каждый чих всталил logging с нумероваными чекпоинтами - буду уточнять место сбоя... Пока что благодарю за помощь и отключаюсь для проведения тестов.

zzdnx ★★
() автор топика
Ответ на: PATH от zzdnx

ещё можно попробовать убрать везде &&. может, где-то при успешном выполнении код возврата не 0, например, если какие-то ворнинги

teod0r ★★★★★
()
Ответ на: комментарий от teod0r

Список соманд в блоке копирования: CD, TAR, BIGSYNC, ECHO, DATE.

ECHO (logging) не может НЕ ВЫПОЛНИТЬ стандартный вывод в консоль - код возврата всегда 0.

CD при невозможности зайти куда-то брыкается обшибкой и в консоль и в код возврата.

TAR если не может чего-то ВСЕГДА об этом уведомляет. С целью избежать проблем особое внимание было уделено правам доступа.

DATE работает даже в RAM-диске.

Под подозрением остаётся BIGSYNC, потому как это сторонняя и «древняя» утилита. Проста как пробка, потому и собирается до сих пор с пол пинка, но я не знаю языка в достаточной степени, чтобы проверить исходник... Методом тестирования я убедился что она возвращает правильные коды перед тем как вставлять её в свой скрипт.

А ещё я специально тестировал именно такую конструкцию (весь блок копированя). Сначала по отдельным частям, а потом в конечном виде. Целью тестов было определение блока как ЕДИНОЙ функции, любой сбой в которой завершает работу блока и передаёт управление дальше с кодом возврата == «false».

zzdnx ★★
() автор топика
Ответ на: комментарий от teod0r

tar

Забавно. Эта информация полезна, но не сейчас.

В моём случае скрипт сбоит по-разному дважды. Первый раз на утилите BIGSYNC, второй раз ещё раньше. До tar`а выполнение не доходит в обоих случаях.

Места прекращения работы помечены *. Количество * == порядковый номер проблемы:

logging && logging** && (cp && bigsync* && tar) || logging

zzdnx ★★
() автор топика

В скрипте бэкапа имеется конструкция вида:

перепиши её в виде нормальных if/else/fi.

Зачем ты так извращаешься?

emulek
()
Ответ на: комментарий от teod0r

tar иногда выводит что-то типо: «дата файла из будущего», при копировании системы в tmpfs

да, и выдаёт $?==2

(не критичная ОШИБКА).

emulek
()
Ответ на: комментарий от emulek

Зачем ты так извращаешься?

Мне так проще воспринимать написаное.

Мне нужно не разделить выполнение по условию на две ветки, а детектировть наличие ошибки в цепочке из нескольких обязательных действий. Конструкция «a && b && ... && z || echo Ошибка!» делает именно это.

zzdnx ★★
() автор топика
Ответ на: Зачем ты так извращаешься? от zzdnx

Мне нужно не разделить выполнение по условию на две ветки, а детектировть наличие ошибки в цепочке из нескольких обязательных действий. Конструкция «a && b && ... && z || echo Ошибка!» делает именно это.

она не совсем это делает. Точнее это совсем НЕ конвейер. Конвейер это когда несколько работяг _одновременно_ выполняют одну и ту же _свою_ операцию. А вот && это _последовательное_ выполнение.

У вас вообще жопа:

a && b && (c && d && e) || f
Скобки в баш НЕ то, что в C/C++. Это субшелл. Т.е. у вас

1. запускается a

2. если a успешно, запускается b

3. если b успешно, запускается суб-процесс, в котором запускается c && d && e

4. если суб-процесс не успешен, запускается f

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

function foo()
{
  command_a || return
  command_b || return
  …
  command_z
}

foo
err=$?

if [ $err != 0 ]; then
  echo "error #$err"
  exit $err
fi

в этом коде foo() возвращает код ошибки, если она была.

emulek
()
Последнее исправление: emulek (всего исправлений: 1)
Ответ на: комментарий от emulek

Ты можешь из комбинации подобной следующей соорудить «неправильно» работающую, в смысле работающую не так как ожидается?


echo "1" && false && echo "3" && (echo "4" && false || echo "Error 6") || echo "Error 7"

sdio ★★★★★
()
Последнее исправление: sdio (всего исправлений: 1)
Ответ на: комментарий от sdio

где-то на UFO была моя тема, в которой я возмущался «неправильной» работой bash именно в такой ситуации. Подумав я конечно понял свою ошибку(мне там помогли осознать), и с тех пор не пользуюсь такими «понятными» конструкциями.

emulek
()
Ответ на: комментарий от emulek

Если-бы ТС писал как я рекомендовал, проблема бы очевидна.

Что может сломаться в такой команде?

echo 1 && echo 2 && (cp a b && bigsync -param bla && tar bla-bla ) || echo "ERROR DETECTED!"
Фактически написано: true && true && (...) || echo «error detected». ЛЮБАЯ ошибка в субшелле вызывает появление сообщения «error detected». И снова напоминаю, что сбои возникают 2 раза: в первый раз в процессе субшелла и второй раз ДО субшелла и именно вторая ошибка не понятна. Изменение формы записи на функции ничего не даст. Будет вызов функции внутри другой функции и не более того.

zzdnx ★★
() автор топика
Ответ на: комментарий от sdio

Я тоже не пользуюсь, т.к. поиск ошибок затруднен

Да, в такой конструкйии поиск действительно может быть затруднён, однако меня интересует не ошибка, а сам факт её наличия. Есть ошибка или нет - не важно, так как нужно идти дальше и выполнять бэкап следующей машины. Единственное, что нужно сделать при ошибке - записать в лог что она была.

zzdnx ★★
() автор топика

Пиши, не жалей строк



$(command1)
err=$?
if [ $err -ne 0 ]; then
   logging "error in command1; Err=$err"
   exit $err # or continue ...
fi

$(command2)
err=$?
if [ $err -ne 0 ]; then
   logging "error in command2; Err=$err"
   exit $err # or continue ...
fi

sdio ★★★★★
()
Последнее исправление: sdio (всего исправлений: 1)
Ответ на: комментарий от sdio

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

zzdnx ★★
() автор топика
Ответ на: комментарий от zzdnx

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

sdio ★★★★★
()
Ответ на: комментарий от maloi

Можно пример ситуации, когда запущенная в UTF-терминале команда echo не может напечатать на экране текст в UTF-кодировке? За исключением случая, в котором команда не может стартовать - подразумевается что команда есть, с правами всё в порядке.

Вариант с перенаправлением вывода в заблокированный файл так же не рассматривается, потому как в функции logging после вывода в файл (что может быть неудачным) стоит команда echo, печатающая текст на экране. Мне просто интересно - я никогда не встречал echo в терминале с ненулевым кодом возврата.

zzdnx ★★
() автор топика
Ответ на: комментарий от zzdnx

У тебя крон, какой к черту терминал? А стандартный вывод может быть перенаправлен извне или вообще закрыт.

maloi ★★★★★
()
Последнее исправление: maloi (всего исправлений: 1)
Ответ на: комментарий от maloi

перенаправлен извне или вообще закрыт

Не встречал такого, потому и не обдумывал этот вариант. Каюсь.

zzdnx ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.