k001
k001
:...
k001 [userpic]
про баш и про даш

В баше можно сделать
. /some/file || exit 1

При этом мы получим, что хотим -- или заинлайним файл /some/file, или выйдем с ошибкой:
a.sh: line 1: /some/file: No such file or directory

Просто и понятно.

А в даше (который в Debian часто стоит шелом по умолчанию) такой фокус не проходит. Самый простой вариант добиться того, что мы делаем выше -- написать
cat /some/file >/dev/null || exit 1
. /some/file || exit 1


(Второй exit 1 тут нужен для совместимости с предыдущим вариантом, если последняя команда в /some/file вернула ненулевой код). Чем плох такой вариант (кроме того, что много писанины и имя файла два раза упоминается)? А тем, что сообщение об ошибке будет менее осмысленное:
cat: /some/file: No such file or directory

Казалось бы, причём тут cat?

Вариантом, работающим в обеих шеллах, будет что-то вроде
if test -f /some/file; then
echo "/some/file: No such file or directory"
exit 1
fi
. /some/file


Вроде всё хорошо (кроме того, что кода ещё больше, имя файла упоминается уже три раза)? Ан нет! Мы проверили, что файл есть, но не проверили, можно ли его прочитать. Ну, добавим ещё один if... Но даже и это не спасёт нас, потому что системное сообщение об ошибке strerror(ENOENT) мы заменили на своё произвольное, что сводит на нет усилия локализаторов.

Итого -- лучше всего сделать через cat, а юзер пускай догадывается, причём тут cat.

Если у кого есть более кошерный вариант -- расскажите.

Tags: , , ,
Comments

cat /some/file > /dev/null 2>&1 || echo "File not found" && exit 1

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

Это не работает, потому что exit произойдёт в любом случае.

Ну, значит, так надо:

cat /some/file > /dev/null 2>&1 || { echo "File not found or unreadable" && exit 1; }

с перлом попутал, в шелле, оказывается, && и || имеют равный приоритет.

> echo "File not found or unreadable" && exit 1;

(1) А тут-то зачем &&? Проверить, что echo вернул 0? Ну да это неважно :)
(2) Сообщение об ошибке не локализовано. bash и cat в случае русской локали будут ругаться на русском.

Ну и ещё, в порядке придирок, cat является внешней командой, может так случиться, что она не запустится (к примеру, превышен ulimit на количество процессов (-u)) — в этом случае мы получим неправильную ошибку.

В то же время test и source (.) являются встроенными, поэтому такой фигни не случится.

чего-чего по умолчанию?

alexkuklin@kuklinnb:~$ dash
bash: dash: команда не найдена

alexkuklin@kuklinnb:~$ apt-cache policy dash
dash:
Установлен: (отсутствует)
Кандидат: 0.5.4-12
Таблица версий:
0.5.5.1-3 0
500 http://mirror.yandex.ru sid/main Packages
0.5.5.1-2.3 0
500 http://mirror.yandex.ru testing/main Packages
0.5.4-12 0
990 http://mirror.yandex.ru lenny/main Packages

Да, должен был написать не «часто», а «в некоторых версиях»…

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

MYFILE="/some/file"
if test -r "$MYFILE"; then
echo "$MYFILE: No such file or directory"
exit 1
fi
. "$MYFILE"

(1) если файл есть (но у нас нет прав его читать), получим неверное сообщение об ошибке

(2) сообщение об ошибке не локализовано (а вот cat /some/file будет по-русски ругаться, если русская локаль).

Я бы попробовал посмотреть в сторону двойного чтения файла, один из которых — в сабшелле.

( . $FILENAME ) || exit 1
. $FILENAME


Дальше при желании можно поглядеть на $? и т.п.

Если заинклюженный файл что-то печатает в stdout, вероятно, нужно при первом вызове делать > /dev/null (при этом stderr нужно оставлять для сообщения об ошибке). А вот если он печатает что-то в stderr -- тогда плохо. Остаётся надеяться, что в файл, подключаемый через точку, обычно запихивают только инициализацию environment'а, а не сложную логику.

строго говоря, это не dash-специфика, и даже не ash-специфика. в ksh93 она тоже присутствует, например. (в отличие от bash и zsh.) к сожалению, у меня нет под рукой какой-нибудь дремучей реализации pre-posix sh, но мне очень кажется, что это изначальное поведение.

т. е. ash-deriviates, ровно как и ksh93 ведут себя следующим образом: если мы выполняем в интерактивном шелле «.» без дополнительных операндов, мы получаем правильный return code, как только добавляем сравнение return code — видим, что оно не работает.

делаем следующий ход — пишем простой скрипт:
. ~/invalid-file
echo $?
echo finish

и видим — ksh93 и dash после первой строчки выходят их скрипта. с return code 1. как ты добился того, что у тебя он продолжается — это ты думай сам.

побочный вывод — субшелл в такой ситуации должен работать. и правда — «( . ~/invalid-file ) || echo ouch» работает. собственно, судя по всему, это и есть ответ для тебя.

s,^#!/bin/sh,#!/bin/bash,

;)

Ну да. Только боюсь, заклюют. «В то время, как вся прогрессивная общественность борется с фашизмом башизмами, некоторые позволяют себе чёрт знает что».

если тебе под линукс — забей, наличие /bin/bash подразумевать можно.

если под линукс и фрю, скажем, — да, имеет смысл писать под bash и ash.

ну а если под что-то более широкое — один только солярисный /bin/sh может совсем отбить охоту запихивать сложную логику в шелл.

вообще-то pipefail это именно башизм и есть

1. в скрипте писать "#!/bin/bash"
2. не писать на shell

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

Насколько я понимаю, классическим решением является что-то вроде:

test -x /some/file.sh && /some/file.sh || echo "No such file!"

(1) в вашем варианте, если файл будет, но на нём не будет бита x, мы получим некорректное сообщение об ошибке "No such file" (вместо, скажем, "Cannot execute")

(2) в исходной задаче мы не запускаем файл, а сорсим, поэтому проверять надо на -f и на -r, а не на -x

(3) в вашем варианте сообщение об ошибке не локализовано

(4) вот пример, когда ваша конструкция работает неправильно:
test -x /bin/false && /bin/false || echo "No such file"

Я бы сделал функцию

include()
{
[ -r "$1" ] && . "$1" || exit 1
}

include /foo/bar
include /baz/quux

Re: Я бы сделал функцию

Она не пишет сообщение об ошибке, то есть почему мы вывалились с ненулевым кодом.

С другой стороны...

Почитал POSIX про операцию "dot" — там написано, что если имя файла не содержит слэш, то файл ищется в PATH. Другими словами, в общем случае все предложенные варианты, включая твой, не верны для этого случая.

Также странно почему на эту команду не влияет `set +e`.

Re: С другой стороны...

> если имя файла не содержит слэш, то файл ищется в PATH

и только самый первый вариант «source file || exit 1» будет верным…

С другой стороны, в моих конкретных скриптах я этой функциональностью не пользуюсь — всегда задан абсолютный путь. Более того, я не встречал скриптов, где этим бы пользовались.

А чем плох классический путь?

if [ -r /some/file ] ; then
 . /some/file
else
 exit 1
fi


Если надо проверить, что файл обычный - дописать в test -a -f /some/file

Тем, что тут не пишется причина, по которой мы вывалились с ненулевым кодом возврата.

Спасибо, теперь мысль понятна. Поначалу не дошло, что вы хотите локализованное сообщение подручными средствами.

да нет, локализация — это только одна из проблем, не единственная.

Помимо всего прочего, у всех вариантов с проверкой заранее есть потенциальный race condition — файл может исчезнуть после проверки, но до использования :]

It works for me

$ dash -c '. /some/file || exit 1'; echo $?
.: 1: Can't open /some/file
2
Даже "||exit 1" не обязательно, если все равно нужно прервать выполнение, а вот если прерывать не надо, то придется писать дополнительное условие, да.

Re: It works for me

Это, конечно, отлично на первый взгляд. Только вот у source (он же .), в отличие от cat >/dev/null, есть всякие побочные эффекты. Мне же надо только вывалиться с ошибкой, если файла нет или он не читается. А тут ведь файл будет парситься, если там что-то не шельное — будут ошибки, если что-то шельное — оно выполнится или поменяет нам энвайромент…

проверять на dash и падать с воплем «под этой хуйнёй работайте сами!!1111»