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 $? отображает корректный код завершения.

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

Скрипт на GitHub

Источник

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

Скачивание файла и обработка ошибок в wget. Обратите внимание!

Преамбула

Хотел, конечно, назвать как-нибудь типа «гадское поведение wget» или «уничтожение файлов wget‘ом», но остановился на этом. Добавлю ссылку в пост о кодах ошибок (копия) на этот пост.

Но на самом деле, это вроде маленькая и многим известная штука, но которой мало кто задумывается. Особенно, если использует wget в своих скриптах. Отдельно отметить это меня сподвиг тот самый скрипт, с которого я начал сегодняшний разговор о wget (копия)

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

Оказывается, wget создает новый файл перед закачкой. Несмотря на то, появятся там какие-то ошибки или нет, и несмотря на то, какие ошибки это будут, хоть это ошибки сервера (например 404, файл не найден), или ошибки сети (файл не удалось докачать), файл все равно будет создан. Хотя по логике, например, ошибку сервера (пусть 404) можно было бы сначала и проверить. Но wget этого не делает, если не случилась ошибка с кодом 2 (ошибка параметров командной строки или конфигурационных файлов).

На самом деле, такой подход довольно правильный, ну и правда, какая разница, почему оно не скачалось (или не сохранилось на диск в случае I/O error). И для анализа ошибок проблем меньше.

Наибольшую проблему это представляет собой тогда, когда пользователь указывает в параметрах wget ключ -O <путь к файлу>, тогда wget придется его полюбому перезаписать.

Так-то у wget есть «защита от дурака», если файл, например, file уже существует, то при следующей закачке файл file (под тем же именем) будет сохранен, как file.1, и если что, старый файл останется в целости и сохранности. Но в скриптах ключ -O удобно использовать. Не надо следить за этими номерами файлов, и вообще в скрипте, wget без ключей, один сплошной геморрой. Так что ключи используют. Но как же правильно это сделать? Да выбирать место сохранения скачанного и проверять коды ошибок wget!

Решение

Если нужно скачать файл, а уж если скачался, заменить, не скачался, оставить старый

1. Скачиваем файл во временный:
wget -O "/tmp/test.tmp" "http://example.org/test.dat"
2. Проверяем коды ошибок (здесь самый простой вариант)

if [ "$?" -eq 0 ]; then
	#Пункт 3
fi


3. Копируем скачанный файл в целевой: cp /tmp/test.tmp /home/pi/test.dat

Естественно, все имена файлов и пути меняем на свои.

Надо скачать файл. Не скачался — вернуть сообщение об ошибке

1. Скачиваем файл:
wget -O "~.data/test.dat" "http://example.org/test.dat"

2. Проверяем коды ошибок (здесь самый простой вариант). Если ошибка — завершаем работу:

if [ "$?" -ne 0 ]; then
	rm "~.data/test.dat"
	echo "Data download error!"
	exit
fi

Посмотреть размер каталога в Linux. В консоли, но интерактивно и наглядно (ncdu).

И наглядно увидеть, где засрано 🙂

Для отображения размеров каталогов (с подкаталогами), есть стнадартная утилита du, но, если честно, она не очень удобная. Гораздо удобнее ее аналог, написанный с помощью ncursesncdu

Пользоваться очень просто. Заходим в каталог, в котором хотим узнать размеры подкаталогов, и просто вызываем утилиту:

ncdu

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


Сканирование

В подкаталоги входить можно по ENTER, возврат назад по стрелке влево, клавише h или < (обычно запятая с шифтом) или по нажатию ENTER на .., как в mc. Краткая справка по ? (не забывайте нажать SHIFT), выход по q.


Отображение каталогов и их размеров

В Slackware устанавливается штатным образом через sbopkg, как в других линуксах не знаю, в Убунте и Дебиане есть в репозиториях.

Linux wget, коды возврата/завершения. Коды ошибок wget.

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

Кто-то, неизвестно почему, утверждал, что в man их нет. Есть. Но пусть уж будут и тут, и мне от склероза, и мало ли кому другому, кто не любит на буржуйском читать.

Коды завершения wget

0 — OK (ошибок нет)
1 — Иная / общая ошибка (generic error code)
2 — Ошибка в параметрах командной строки или файлах конфигурации (.wgetrc или .netrc)
3 — Ошибка файлового ввода/вывода (I/O error)
4 — Ошибка сети (например, при обрыве связи)
5 — Ошибка SSL
6 — Ошибка идентификации (неправильное имя пользователя или пароль)
7 — Ошибка протокола
8 — Ошибка сервера (например, нужный файл на сервере не найден, ошибка 404)

За исключением 0 и 1, коды выхода с меньшими номерами имеют приоритет над кодами с большими номерами, когда встречаются многочисленные типы ошибок.

Linux wget, тайм-аут и повторы. «Виснет» wget при разрыве связи.

Преамбула

Написали тут с вопросом, мол виснет у нас программа, почему никто не знает, кто ее писал уволился давно, поможи. Ну от чего же не помочь. Программа оказалась башевским скриптом, точнее, набором скриптов. Задача небольшая, выгрузить файл с удаленного сервера, переформатировать и положить на другой сервер, где его подхватывает бухгалтерия. Выгрузка делалась банальным wget.

Проблема

Оказалось, скрипт вис, если пропадало соединение с сетью.
Выгрузка производилась простой командой:

wget -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Решение

Оказывается, у wget не проставлен тайм-аут ожидания, а по умолчанию, если его не проставить, wget будет ждать аж 900 секунд (15 минут). Надо, соответственно, его напрямую указать в параметре ключа -T <секунды> (--timeout=<секунды>),
Например, можно поставить вменяемые полминуты (30 с.):

wget --timeout=30 -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Т.е. вис-то, собственно wget, хотя конечно же, не вис, а терпеливо ждал. Но тут и не бухгалтеру, а даже не сильно опытному пользователю ничего не стоит принять такое поведение за то, что wget виснет при разрыве соединения.

Заодно можно указать wget‘у количество попыток для скачивания файла: -t <число> (--tries=<число>), тем более, что указание параметра -O (имя выходного файла) сбрасывает этот параметр в 1.

wget --timeout=30 --tries=3 -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Полезные ссылки

Шпаргалка по Wget. Копия в PDF