Linux shell, pipes (конвейеры) и код завершения.

Преамбула

При работе с конвейерами (пайпами, «трубами», pipe), часто возникает вопрос, как отловить ошибку, произошедшую внутри конвейера.

Суть проблемы

Для примера возьмем код, где ход загрузки файла отображается dialog’овым прогрессбаром (копия):

#!/bin/bash

FADDR="http://tolik-punkoff.com/static/test.mp3"

wget --progress=dot -O "./test.mp3" "$FADDR" 2>&1 |\
stdbuf -o0 awk '/[.] +[0-9][0-9]?[0-9]?%/ { print substr($0,63,3) }' | \
dialog --gauge "Download file from $FADDR" 10 100

Как видите, конвейер (т.е. передача вывода от одной команды другой через |), тут налицо.

Если после данной команды просто дописать echo $?, то будет видно, что результат будет 0.

И когда все в порядке:

И когда все не в порядке (в данном случае, сделан обрыв связи):

На самом деле, это происходит потому, что в переменной $? оказывается код завершения последней команды в pipe.

Решение

В современных версиях bash перед использованием контейнера, необходимо добавить команду:

set -o pipefail

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

Примечание: Следует иметь в виду “безобидные” команды, которые могут вернуть не ноль.
В больших скриптах со сложными конструкциями и длинными конвеерами можно упустить этот момент из виду, что может привести к некорректным результатам.

Проверка

После добавления вышеуказанной команды в скрипт делаем тест с обрывом связи:

Или указываем в переменной $FADDR некорректный адрес:

Теперь echo $? отображает корректный код завершения.

Модификация скрипта

С учетом вышеизложенного, а так же с учетом знания о кодах завершения wget (копия) и о том, что wget портит файлы в случае ошибки загрузки (копия) можно красиво модифицировать скрипт, чтоб он сообщал пользователю о результате после загрузки.

#!/bin/bash

FADDR="http://tolik-punkoff.com/static/test.mp3"
FPATH="./test.mp3"

messagebox() #1 - title $2 - message
{
    dialog --title "$1" --msgbox "$2" 10 41
}


set -o pipefail

wget --progress=dot --tries=10 --timeout=30 -O "$FPATH"  "$FADDR" 2>&1 |\
stdbuf -o0 awk '/[.] +[0-9][0-9]?[0-9]?%/ { print substr($0,63,3) }' | \
dialog --gauge "Download file from $FADDR" 10 100

RETVAL=$?

case "$RETVAL" in
    0)
	 messagebox "Complete!" "File downloaded!"
	;;
    1)
	messagebox "Error" "Generic error ($RETVAL)"
	;;
    2)
	messagebox "Error" "Command line or config error ($RETVAL)"
	;;
    3)
	messagebox "Error" "I/O error ($RETVAL)"
	;;
    4)
	messagebox "Error" "Network failure ($RETVAL)"
	;;
    5)
	messagebox "Error" "SSL verification failure ($RETVAL)"
	;;
    6)
	messagebox "Error" "Username/password authentication failure ($RETVAL)"
	;;
    7)
	messagebox "Error" "Protocol error ($RETVAL)"
	;;
    8)
	messagebox "Error" "Server error ($RETVAL)"
	;;
    *)
	messagebox "Error" "Unknow error ($RETVAL)"
	;;
esac

#remove wrong file if exist
if [ "$RETVAL" -ne 0 ];then
    if [ -e "$FPATH" ];then
	rm "$FPATH"
    fi
fi

Результат

Все ОК:

Обрыв связи:

Неверный адрес:


Скрипт на GitHub

Источник

Linux pipes tips & tricks Копия в PDF

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

Ваш адрес email не будет опубликован.