Сценарий Bash завершается преждевременно при вызове другого сценария внутри него.

Вопрос или проблема

Я пытаюсь запустить скрипт, который вызывает другой скрипт, но, похоже, он не возвращается к исходному скрипту, который его вызвал.

Исходный скрипт “backup-plex.sh” вызывает “Linux_Plex_Backup.sh” (передавая аргумент для пути назначения).

После завершения работы скрипта “Linux_Plex_Backup.sh” он должен вернуться к исходному скрипту “backup-plex.sh” и продолжить с командой rsync, но этого не происходит.

Я вызываю исходный скрипт с помощью:

~/.backupScripts/./backup-plex.sh 

Ниже приведен исходный скрипт, который вызывает скрипт “Linux_Plex_Backup.sh”:

#!/bin/bash

# Определить исходный путь
source=/media/plex1

# Определить пути назначения
destination=/media/backPlex1ON
dest_plex_library=PlexLibraryBackup

echo "********************* Выполнение резервного копирования медиатеки Plex *********************"

    # вызов скрипта резервного копирования медиатеки Plex
    sudo -s ~/.backupScripts/PlexBackup/Linux_Plex_Backup.sh $destination/$dest_plex_library

echo "********************* Выполнение резервного копирования Plex 1 с помощью rsync *********************"
# исключения для команды rsync
rsync_excludes="--exclude '*.ini' \
        --exclude '*.DS_Store' \
        --exclude '*._*' \
        --exclude '**/\$RECYCLE.BIN' \
        --exclude '**/\lost+found' \
        --exclude '*.AppleDouble' \
        --exclude '*.localized' \
        --exclude '*.Trash-1000' \
        --exclude '**/\PlexLibraryBackup'"

# параметры команды rsync
    rsync_cmd="/usr/local/bin/rsync"
    rsync_options="-a --progress -l --delete $rsync_excludes"

    eval $rsync_cmd $rsync_options $source $destination

Ниже приведен скрипт “Linux_Plex_Backup.sh”, вызываемый исходным скриптом…

#!/usr/bin/env bash
# shellcheck disable=SC2317,SC2181
#--------------------------------------------------------------------------
# Резервное копирование базы данных Plex Linux в tgz-файл в папке резервного копирования.
# v1.1.6  04-Nov-2024  007revad
#
#   Должен выполняться пользователем из группы sudo, sudoers или wheel, или как root
#
# Для запуска скрипта:
# sudo -i /share/scripts/backup_linux_plex_to_tar.sh
#   Измените /share/scripts/ на путь, где находится этот скрипт
#
# Чтобы выполнить тестовый запуск только на папке профилей Plex, выполните:
# sudo -i /share/scripts/backup_linux_plex_to_tar.sh test
#   Измените /share/scripts/ на путь, где находится этот скрипт
#
# Github: https://github.com/007revad/Linux_Plex_Backup
# Скрипт проверен на https://www.shellcheck.net/
#--------------------------------------------------------------------------

scriptver="v1.1.6"
script=Linux_Plex_Backup
if [ -z $1 ] ; then
        echo "Отсутствует аргумент: определите путь назначения"
        exit
fi

# Чтение переменных из backup_linux_plex.config
Backup_Directory="$1" # JL: директория для резервного копирования передается в качестве параметра ($1) при вызове скрипта Linux_Plex_Backup
Name=""
LogAll=""
KeepQty=""
if [[ -f $(dirname -- "$0";)/backup_linux_plex.config ]];then
    # shellcheck disable=SC1090,SC1091
    while read -r var; do
        if [[ $var =~ ^[a-zA-Z0-9_]+=.* ]]; then export "$var"; fi
    done < "$(dirname -- "$0";)"/backup_linux_plex.config
else
    echo "Файл backup_linux_plex.config отсутствует!"
    exit 1
fi

# Проверка существования директории для резервного копирования
if [[ ! -d $Backup_Directory ]]; then
    echo "Директория для резервного копирования не найдена:"
    echo "$Backup_Directory"
    echo "Проверьте настройки в backup_linux_plex.config"
    exit 1
fi

#--------------------------------------------------------------------------
# Установить переменные даты и времени

# Переменная для таймера для регистрации времени, затраченного на резервное копирование PMS
start="${SECONDS}"

# Получить время и дату начала
Started=$( date )

# Получить сегодняшнюю дату для имени файла
Now=$( date '+%Y%m%d')
# Получить сегодняшнюю дату и время для имени файла в случае, если файл существует
NowLong=$( date '+%Y%m%d-%H%M')

#--------------------------------------------------------------------------
# Установить имя NAS (используется в именах файлов резервного копирования и логов)

case "${Name,,}" in
    distro)
        # Получить дистрибутив Linux
        Nas="$(uname -a | awk '{print $2}')"
        ;;
    hostname|"")
        # Получить имя хоста
        Nas=$( hostname )
        ;;
    *)
        # Установить Nas на псевдоним
        Nas="$Name"
        ;;
esac

#--------------------------------------------------------------------------
# Установить временные имена файлов логов (версию Plex получаем позже)

# Установить имя файла резервного копирования
Backup_Name="${Nas}"_"${Now}"_Plex_"${Version}"_Backup

# Если файл уже существует, включить время в имя
BackupPN="$Backup_Directory/$Backup_Name"
if [[ -f $BackupPN.tgz ]] || [[ -f $BackupPN.log ]] || [[ -f "$BackupPN"_ERROR.log ]]; then
    Backup_Name="${Nas}"_"${NowLong}"_Plex_"${Version}"_Backup
fi

# Установить имя файла лога
Log_File="${Backup_Directory}"/"${Backup_Name}".log

# Установить имя файла ошибок
Err_Log_File="${Backup_Directory}"/"${Backup_Name}"_ERROR.log

#--------------------------------------------------------------------------
# Создать временный файл лога ошибок

# Создать временную директорию для временного файла лога ошибок
Tmp_Dir=$(mktemp -d -t plex_to_tar-XXXXXX)

# Создать временный файл лога ошибок
Tmp_Err_Log_File=$(mktemp "${Tmp_Dir}"/errorlog-XXXXXX)

#--------------------------------------------------------------------------
# Создать ловушку и функцию очистки

# Функция очистки временных логов
# shellcheck disable=SC2329
cleanup(){ 
    arg1=$?
    # Переместить tmp_error_log в лог ошибок, если tmp_error_log не пуст
    if [[ -s $Tmp_Err_Log_File ]] && [[ -d $Backup_Directory ]]; then
        mv "${Tmp_Err_Log_File}" "${Err_Log_File}"
        if [[ $? -gt "0" ]]; then
            echo "ВНИМАНИЕ Не удалось переместить ${Tmp_Err_Log_File} в ${Err_Log_File}"\
                |& tee -a "${Err_Log_File}"
        fi
    fi
    # Удалить нашу временную директорию
    if [[ -d $Tmp_Dir ]]; then
        rm -rf "${Tmp_Dir}"
        if [[ $? -gt "0" ]]; then
            echo "ВНИМАНИЕ Не удалось удалить ${Tmp_Dir}" |& tee -a "${Err_Log_File}"
        fi
    fi

    if [[ $Version ]]; then Version="${Version} "; fi

    # Регистрация и уведомление о успешности или ошибках
    if [[ -f $Err_Log_File ]]; then
        # Регистрация и уведомление о наличии ошибок в резервном копировании
        if [[ ! -f $Log_File ]]; then
            # Добавить имя скрипта в начало файла лога
            basename -- "$0" |& tee -a "${Log_File}"
        fi
        echo -e "\n\e[41mВНИМАНИЕ\e[0m В резервном копировании Plex возникли ошибки! Смотрите лог ошибок:"
        echo -e "\nВНИМАНИЕ В резервном копировании Plex возникли ошибки! Смотрите лог ошибок:" >> "${Log_File}"
        echo -e "$(basename -- "${Err_Log_File}")\n" |& tee -a "${Log_File}"
    else
        # Регистрация и уведомление о успешном резервном копировании
        echo -e "\nРезервное копирование Plex выполнено успешно" |& tee -a "${Log_File}"
    fi
    exit "${arg1}"
}

trap cleanup EXIT

#--------------------------------------------------------------------------
# Проверка, что скрипт выполняется от имени root

if [[ $( whoami ) != "root" ]]; then
    if [[ -d $Backup_Directory ]]; then
        echo "ОШИБКА: Этот скрипт должен выполняться от имени root!" |& tee -a "${Tmp_Err_Log_File}"
        echo "ОШИБКА: $( whoami ) не является root. Прерывание." |& tee -a "${Tmp_Err_Log_File}"
    else
        # Невозможно записать ошибку в файл лога, так как $Backup_Directory не существует
        echo -e "\nОШИБКА: Этот скрипт должен выполняться от имени root!"
        echo -e "ОШИБКА: $( whoami ) не является root. Прерывание.\n"
    fi
    # Прерывание скрипта, так как он не выполняется от имени root
    exit 255
fi

#--------------------------------------------------------------------------
# Расположение папки "Plex Media Server"

# ADM   /volume1/Plex/Library/Plex Media Server
# DSM6  /volume1/Plex/Library/Application Support/Plex Media Server
# DSM7  /volume1/PlexMediaServer/AppData/Plex Media Server
# Linux /var/lib/plexmediaserver/Library/Application Support/Plex Media Server

# Установить расположение данных Plex Media Server
Plex_Data_Path="/var/lib/plexmediaserver/Library/Application Support"

#--------------------------------------------------------------------------
# Проверка существования пути данных Plex Media Server

if [[ ! -d $Plex_Data_Path ]]; then
    echo "Недействительный путь данных Plex Media Server! Прерывание." |& tee -a "${Tmp_Err_Log_File}"
    echo "${Plex_Data_Path}" |& tee -a "${Tmp_Err_Log_File}"
    # Прерывание скрипта, так как путь данных Plex недействителен
    exit 255
fi

#--------------------------------------------------------------------------
# Получение версии Plex Media Server

Version="$(/usr/lib/plexmediaserver/Plex\ Media\ Server --version)"
# Возвращает v1.29.2.6364-6d72b0cf6
# Версия Plex без v или шестнадцатеричной строки
Version=$(printf %s "${Version:1}"| cut -d "-" -f1)
# Возвращает 1.29.2.6364

#--------------------------------------------------------------------------
# Переопределение имен логов для включения версии Plex

# Имя файла резервного копирования
Backup_Name="${Nas}"_"${Now}"_Plex_"${Version}"_Backup

# Если файл уже существует, включить время в имя
BackupPN="$Backup_Directory/$Backup_Name"
if [[ -f $BackupPN.tgz ]] || [[ -f $BackupPN.log ]] || [[ -f "$BackupPN"_ERROR.log ]]; then
    Backup_Name="${Nas}"_"${NowLong}"_Plex_"${Version}"_Backup
fi

# Имя файла лога
Log_File="${Backup_Directory}"/"${Backup_Name}".log

# Имя файла ошибок
Err_Log_File="${Backup_Directory}"/"${Backup_Name}"_ERROR.log

#--------------------------------------------------------------------------
# Начало логирования

echo -e "$script $scriptver\n" |& tee -a "${Log_File}"

# Логирование дистрибутива, версии и имени хоста Linux
Distro="$(uname -a | awk '{print $2}')"
DistroVersion="$(uname -a | awk '{print $3}' | cut -d"-" -f1)"
echo "${Distro}" "${DistroVersion}" |& tee -a "${Log_File}"
echo "Имя хоста: $( hostname )" |& tee -a "${Log_File}"

# Логирование версии Plex
echo Версия Plex: "${Version}" |& tee -a "${Log_File}"

#--------------------------------------------------------------------------
# Проверка существования директории для резервного копирования

if [[ ! -d $Backup_Directory ]]; then
    echo "ОШИБКА: Директория для резервного копирования не найдена! Прерывание резервного копирования." |& tee -a "${Log_File}" "${Tmp_Err_Log_File}"
    # Прерывание скрипта, так как директория для резервного копирования не найдена
    exit 255
fi

#--------------------------------------------------------------------------
# Остановка Plex Media Server

echo "Остановка Plex..." |& tee -a "${Log_File}"

Result=$(systemctl stop plexmediaserver)
code="$?"
# Дать сокетам немного времени, чтобы закрыться
sleep 5

if [[ $code == "0" ]]; then
    echo "Plex Media Server остановлен." |& tee -a "$Log_File"
else
    echo "$Result" |& tee -a "$Log_File"
    exit $code
fi

# Аккуратное завершение любых оставшихся процессов Plex (плагины, служба тюнера и EAE и т.д.)
###pgrep [Pp]lex | xargs kill -15 &>/dev/null
# Дать сокетам немного времени, чтобы закрыться
###sleep 5

# Убить любые оставшиеся процессы, которые DSM не очистил (плагины и EAE)
Pids="$(ps -ef | grep -i 'plex plug-in' | grep -v grep | awk '{print $2}')"
[ "$Pids" != "" ] && kill -9 "$Pids"

Pids="$(ps -ef | grep -i 'plex eae service' | grep -v grep | awk '{print $2}')"
[ "$Pids" != "" ] && kill -9 "$Pids"

Pids="$(ps -ef | grep -i 'plex tuner service' | grep -v grep | awk '{print $2}')"
[ "$Pids" != "" ] && kill -9 "$Pids"

# Дать сокетам немного времени, чтобы закрыться
sleep 2

#--------------------------------------------------------------------------
# Проверка остановки всех процессов Plex

echo Проверка состояния процессов Plex... |& tee -a "${Log_File}"
Response=$(pgrep -l plex)
# Проверка, найден ли plexmediaserver в $Response
if [[ -n $Response ]]; then
    # Принудительное завершение любых оставшихся процессов Plex (плагины, служба тюнера и EAE и т.д.)
    pgrep [Pp]lex | xargs kill -9 &>/dev/null
    sleep 5

    # Проверка, найден ли plexmediaserver все еще в $Response
    Response=$(pgrep -l plex)
    if [[ -n $Response ]]; then
        echo "ОШИБКА: Некоторые процессы Plex все еще работают! Прерывание резервного копирования."\
            |& tee -a "${Log_File}" "${Tmp_Err_Log_File}"
        echo "${Response}" |& tee -a "${Log_File}" "${Tmp_Err_Log_File}"
        # Запуск Plex для уверенности, что он не остался частично запущенным
        /usr/lib/plexmediaserver/Resources/start.sh
        # Прерывание скрипта, так как Plex полностью не завершился
        exit 255
    else
        echo "Все процессы Plex остановлены." |& tee -a "${Log_File}"
    fi
else
    echo "Все процессы Plex остановлены." |& tee -a "${Log_File}"
fi

#--------------------------------------------------------------------------
# Резервное копирование Plex Media Server

echo "=================================================" |& tee -a "${Log_File}"
echo "Резервное копирование файлов данных Plex Media Server..." |& tee -a "${Log_File}"

Exclude_File="$( dirname -- "$0"; )/plex_backup_exclude.txt"

# Проверка аргументов теста или ошибки
if [[ -n $1 ]] && [[ ${1,,} == "error" ]]; then
    # Вызов ошибки для тестирования логов ошибок
    Test="Plex Media Server/Logs/ERROR/"
    echo "Запуск небольшого теста резервного копирования папки журналов" |& tee -a "${Log_File}"
elif [[ -n $1 ]] && [[ ${1,,} == "test" ]]; then
    # Тестирование на небольшой папке журналов
    Test="Plex Media Server/Logs/"
    echo "Запуск небольшого теста резервного копирования папки журналов" |& tee -a "${Log_File}"
fi

# Проверка наличия файла исключения
# Должен следовать за "Проверка аргументов теста или ошибки"
if [[ -f $Exclude_File ]]; then
    # Сброс аргументов
    while [[ $1 ]]; do shift; done
    # Установка аргументов -X excludefile для tar
    set -- "$@" "-X"
    set -- "$@" "${Exclude_File}"
else
    echo "ИНФОРМАЦИЯ: Файл исключения отсутствует." |& tee -a "${Log_File}"
fi

# Использовать короткие имена переменных, чтобы команда tar не была слишком длинной
BD="${Backup_Directory}"
BN="${Backup_Name}"
PDP="${Plex_Data_Path}"
LF="${Log_File}"
TELF="${Tmp_Err_Log_File}"
PMS="Plex Media Server"

# Выполнить команду tar для резервного копирования
if [[ -n $Test ]]; then
    # Запуск теста резервного копирования или теста ошибки
    if [[ ${LogAll,,} == "yes" ]]; then
        echo "Запись всех архивированных файлов" |& tee -a "${Log_File}"
        tar -cvpzf "${BD}"/"${BN}".tgz -C "${PDP}" "${Test}" > >(tee -a "${LF}") 2> >(tee -a "${LF}" "${TELF}" >&2)
    else
        # Не регистрировать все резервные копии.
        echo "Только регистрация ошибок" |& tee -a "${Log_File}"
        tar -cvpzf "${BD}"/"${BN}".tgz -C "${PDP}" "${Test}" 2> >(tee -a "${LF}" "${TELF}" >&2)
    fi
else
    # Резервное копирование в tgz с версией PMS и датой в имени файла, отправка всего вывода в оболочку и лог, плюс ошибки в error.log
    # Использование -C для смены каталога на "/share/Plex/Library/Application Support" для того, чтобы не резервировать абсолю пути
    # и избежать ошибки "tar: Removing leading /"
    if [[ ${LogAll,,} == "yes" ]]; then
        echo "Запись всех архивированных файлов" |& tee -a "${Log_File}"
        tar -cvpzf "${BD}"/"${BN}".tgz "$@" -C "${PDP}" "$PMS/" > >(tee -a "${LF}") 2> >(tee -a "${LF}" "${TELF}" >&2)
    else
        # Не регистрировать все резервные копии.
        echo "Только регистрация ошибок" |& tee -a "${Log_File}"
        tar -cvpzf "${BD}"/"${BN}".tgz "$@" -C "${PDP}" "$PMS/" 2> >(tee -a "${LF}" "${TELF}" >&2)
    fi
fi

echo "Завершено резервное копирование файлов данных Plex Media Server." |& tee -a "${Log_File}"
echo "=================================================" |& tee -a "${Log_File}"

#--------------------------------------------------------------------------
# Запуск Plex Media Server

echo "Запуск Plex..." |& tee -a "${Log_File}"
#/usr/lib/plexmediaserver/Resources/start.sh
systemctl start plexmediaserver

#--------------------------------------------------------------------------
# Удаление старых резервных копий

if [[ $KeepQty -gt "0" ]]; then
    readarray -t array < <(ls "$Backup_Directory" |\
        grep -E "${Nas}"'_[0-9]{8,}(-[0-9]{4,})?_Plex_.*\.tgz' | head -n -"$KeepQty")

    if [[ "${#array[@]}" -gt "0" ]]; then
        echo -e "\nУдаление старых резервных копий" |& tee -a "${Log_File}"
        for file in "${array[@]}"; do
            if [[ -f "$Backup_Directory/$file" ]]; then
                echo "Удаление $file" |& tee -a "${Log_File}"
                rm "$Backup_Directory/$file"
            fi
            if [[ -f "$Backup_Directory/${file%.tgz}.log" ]]; then
                echo "Удаление ${file%.tgz}.log" |& tee -a "${Log_File}"
                rm "$Backup_Directory/${file%.tgz}.log"
            fi
            if [[ -f "$Backup_Directory/${file%.tgz}_ERROR.log" ]]; then
                echo "Удаление ${file%.tgz}_ERROR.log" |& tee -a "${Log_File}"
                rm "$Backup_Directory/${file%.tgz}_ERROR.log"
            fi
        done
    fi
fi

#--------------------------------------------------------------------------
# Добавление времени выполнения к stdout и файлу логов

# Время и дата окончания
Finished=$( date )

# Переменная времени Bash для регистрации времени, затраченного на резервное копирование Plex
end="${SECONDS}"

# Время выполнения в секундах
Runtime=$(( end - start ))

# Добавление времени и даты начала и окончания, а также времени выполнения
echo -e "\nРезервное копирование начато: " "${Started}" |& tee -a "${Log_File}"
echo "Резервное копирование завершено:" "${Finished}" |& tee -a "${Log_File}"
# Добавление дней, часов, минут и секунд из $Runtime
printf "Продолжительность резервного копирования: " |& tee -a "${Log_File}"
printf '%dd:%02dh:%02dm:%02ds\n' \
$((Runtime/86400)) $((Runtime%86400/3600)) $((Runtime%3600/60))\
$((Runtime%60)) |& tee -a "${Log_File}"

#--------------------------------------------------------------------------
# Вызов функции очистки

exit 0

Исправления, которые я внес и почему, прокомментированы в коде ниже. Попробуйте, если проблема сохраняется, пожалуйста, ответьте, и я постараюсь адаптировать исправление.

#!/bin/bash

# Определить исходный путь
source=/media/plex1

# Определить пути назначения
destination=/media/backPlex1ON  # Исправлена опечатка: изменено '==' на '='
dest_plex_library=PlexLibraryBackup

echo "********************* Выполнение резервного копирования медиатеки Plex *********************"

# Вызов скрипта резервного копирования медиатеки Plex с sudo
# Команда sudo -s не является необходимой и может быть причиной проблемы. Вместо этого используйте sudo непосредственно для запуска скрипта.
sudo ~/.backupScripts/PlexBackup/Linux_Plex_Backup.sh $destination/$dest_plex_library

echo "********************* Выполнение резервного копирования Plex 1 с помощью rsync *********************"

# исключения для команды rsync
# Используйте двойные кавычки для переменной rsync_excludes и убедитесь, что команда eval правильно отформатирована.
rsync_excludes="--exclude '*.ini' \
        --exclude '*.DS_Store' \
        --exclude '*._*' \
        --exclude '**/\$RECYCLE.BIN' \
        --exclude '**/lost+found' \
        --exclude '*.AppleDouble' \
        --exclude '*.localized' \
        --exclude '*.Trash-1000' \
        --exclude '**/PlexLibraryBackup'"

# Определение команды и параметров rsync
rsync_cmd="/usr/local/bin/rsync"
rsync_options="-a --progress -l --delete $rsync_excludes"

# Выполнение команды rsync с помощью eval для включения всех параметров и путей
eval $rsync_cmd $rsync_options $source $destination

Надеюсь, это поможет!

Ответ или решение

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

Проблема

Вы запускаете основной скрипт backup-plex.sh, который вызывает другой скрипт Linux_Plex_Backup.sh для выполнения резервного копирования. Однако, после завершения работы второго скрипта, оригинальный скрипт не продолжает выполнение команды rsync, как ожидается.

Причины и решения

  1. Использование sudo -s:

    В вашем основном скрипте используется команда sudo -s, чтобы переключиться на пользователя root и запустить скрипт Linux_Plex_Backup.sh. Эта команда может вызывать проблемы из-за дополнительной инстанции оболочки, которая может завершить выполнение всего процесса. Вместо этого используйте только sudo:

    sudo ~/.backupScripts/PlexBackup/Linux_Plex_Backup.sh $destination/$dest_plex_library
  2. Проблемы с переменными:

    В вашем скрипте есть ошибка синтаксиса – двойное равно (==), которое используется для присваивания. Замените на одно равно:

    destination=/media/backPlex1ON
  3. Проверка завершения второго скрипта:

    Убедитесь, что Linux_Plex_Backup.sh завершает выполнение корректно и возвращает код завершения 0. Если этот скрипт завершает работу с ошибкой (код отличен от 0), основной скрипт не сможет продолжить. Проверьте наличие команды exit в Linux_Plex_Backup.sh, которая используется для выхода с указанием кода ошибки.

  4. Логирование и отладка:

    Включите логирование и отладку внутри обоих скриптов. Это можно сделать добавлением флага -x в начале скрипта, чтобы видеть ход выполнения команд:

    #!/bin/bash -x

    Логирование может помочь выявить конкретную точку, на которой выполнение скрипта останавливается.

  5. Отсутствие конфигурационного файла:

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

  6. Команды rsync и eval:

    Убедитесь, что команды rsync правильно сформированы и передаются. Используйте правильное экранирование и кавычки вокруг включенных путей или опций, чтобы избежать ошибок.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...