Bash скрипт: `exit 0` не может выйти

Итак, у меня есть этот скрипт Bash:

#!/bin/bash

PID=`ps -u ...`
if [ "$PID" = "" ]; then
    echo $(date) Server off: not backing up
    exit
else
    echo "say Server backup in 10 seconds..." >> fifo
    sleep 10

    STARTTIME="$(date +%s)"

    echo nosave >> fifo
    echo savenow >> fifo
    tail -n 3 -f server.log | while read line
    do
        if echo $line | grep -q 'save complete'; then
            echo $(date) Backing up...
            OF="./backups/backup $(date +%Y-%m-%d\ %H:%M:%S).tar.gz"
            tar -czhf "$OF" data

            echo autosave >> fifo
            echo "$(date) Backup complete, resuming..."
            echo "done"
            exit 0
            echo "done2"
        fi

        TIMEDIFF="$(($(date +%s)-STARTTIME))"
        if ((TIMEDIFF > 70)); then
            echo "Save took too long, canceling backup."
            exit 1
        fi
    done
fi

По сути, сервер принимает данные из fifo и выводит их на server.log. Fifo используется для отправки команд остановки / запуска на сервер для автосохранения. В конце, как только он получает от сервера сообщение о том, что сервер завершил сохранение, он хранит каталог данных и снова запускает сохранение.

Это на exit 0 линия, что у меня проблемы. Все работает нормально, но я получаю этот вывод:

srv:scripts $ ./backup.sh
Sun Nov 24 22:42:09 EST 2013 Backing up...
Sun Nov 24 22:42:10 EST 2013 Backup complete, resuming...
done

Но там висит. Обратите внимание на то, как "готово" отражается, но "выполнено2" не удается. Что-то заставляет его висеть на exit 0,

ДОПОЛНЕНИЕ: просто чтобы избежать путаницы для людей, которые смотрят на это в будущем, оно висит на линии выхода и никогда не возвращается в командную строку. Не уверен, был ли я достаточно ясен в моем первоначальном описании.

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

3 ответа

Решение

Вот небольшой автономный пример, демонстрирующий то же поведение:

echo foo > file
tail -f file | while read; do exit; done

Проблема в том, что, поскольку каждая часть конвейера работает в подоболочке, exit только выходит из while read цикл, а не весь сценарий.

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

Чтобы исправить это, вы можете заменить

tail -n 3 -f server.log | while read line
    do
       ...
    done

с

while read line
do
   ...
done  <  <(tail -n 3 -f server.log)

Путем перенаправления из процесса подстановки поток не должен ждать tail закончить, как это было бы в конвейере, и он не будет работать в подоболочке, так что exit будет фактически выходить из всего сценария.

Но там висит. Обратите внимание на то, как "готово" отражается, но "выполнено2" не удается.

done2 не будет печататься вообще с exit 0 уже закончил ваш скрипт return code 0,

Я не знаю подробностей о bash-подоболочках внутри циклов, но обычно правильный выход из цикла - использовать команду "break". В некоторых случаях этого недостаточно (вам действительно нужно выйти из программы), но рефакторинг этой программы может быть самым простым (самым безопасным, наиболее переносимым) способом решения этой проблемы. Это также может улучшить удобочитаемость, потому что люди не ожидают, что программы завершатся в середине цикла.

Другие вопросы по тегам