Загадка!

October 19th, 2007 Leave a comment Go to comments

“Если достаточно долго лазить по карманам своих курток, можно найти практически любую сумму денег” (народная мудрость). Вот и с задачками примерно такая же ситуация – вспомнил весьма любопытную.
Приятно, знаете ли, иногда посмотреть как люди мучаются над задачками, которые когда-то серьезно озаботили тебя самого :)

Итак, есть следующий код:

#!/bin/sh

get_size() {
if [ "`uname`" = "FreeBSD" ]; then
r=`stat -f %z $1`
else
r=`stat -c %s $1`
fi
return $r
}

if [ -z $1 ]; then
echo "usage: print_size ";
else
get_size $1;
echo $?
fi

Вот результат работы скрипта:
asveshnikov@fe08a023fb1cc27a:~/tmp$ ls -la
total 112
drwxr-xr-x 2 asveshnikov asveshnikov 4096 2007-10-19 15:38 .
drwxr-xr-x 85 asveshnikov asveshnikov 8192 2007-10-19 13:15 ..
-rw-r--r-- 1 asveshnikov asveshnikov 39781 2007-08-29 15:22 DSC02473.jpg
-rw-r--r-- 1 asveshnikov asveshnikov 49553 2007-08-31 17:41 DSC02481.jpg
-rwxr-xr-x 1 asveshnikov asveshnikov 231 2007-10-19 15:27 print_size.sh
asveshnikov@fe08a023fb1cc27a:~/tmp$ ./print_size.sh print_size.sh
231
asveshnikov@fe08a023fb1cc27a:~/tmp$ ./print_size.sh DSC02473.jpg
39781

Как видите, абсолютно рабочий код, проверялся в Linux и FreeBSD.
При этом он имеет просто ужасную.. нет, чудовищную проблему переносимости, которую при некоторых обстоятельствах крайне сложно обнаружить в скриптах несколько большего размера – какую?

p.s. угадавший первым будет умничкой :)

UPD:

Уже двое человек указали на то, что `uname` желательно взять еще и в кавычки. Вобщем, предлагаю не мелочиться, здесь проблема если и есть, то явно не “чудовищная”. Чтобы больше никого не смущать этим, подправил код.

Решено!

Михаил достаточно подробно описал проблему:

Если не брать в голову отсутствие кавычек вокруг $1, а также неполный способ определения размера файла (под солярой нет stat, например), то мне кажется основная проблема в возврате значения из функции. На самом деле таким образом можно вернуть только целое, причем очень ограниченное, так как по сути возвращается код завершения функции (если не изменяет память – максимум – 256). таким образом на больших файлах получим неверное значение.

Решением может служить использование для возврата глобальное переменной, либо через echo.

С моей стороны осталось лишь пояснить, почему проблема является “чудовищной” – она запросто может привести к порче данных, так как никакие сообщения об ошибках выводиться не будут. Ну и выглядит внешне этот код вполне надежным, так что отлавливать такой баг может быть задачей не из легких.

Собственно, основные проблемы возникают при портировании скрипта с sh на bash:

asveshnikov@fe08a023fb1cc27a:~/tmp$ ls -al DSC02473.jpg
-rw-r--r-- 1 asveshnikov asveshnikov 39781 2007-08-29 15:22 DSC02473.jpg
asveshnikov@fe08a023fb1cc27a:~/tmp$ bash print_size.sh DSC02473.jpg
101

  1. October 19th, 2007 at 16:40 | #1

    Есть несколько предположений:

    0) вызов [ условие ] работает везде, где работает test?

    1) вывод команды uname, может быть что угодно… честно говоря, не знаю, чем отличается stat на FreeBSD от stat на Linux, но подозреваю, что, например, в MacOS X stat больше похож не тот, который в BSD.. Но там, кажется, uname что-то вроде “Darwin xxx” выдаёт, а значит, вызов stat пойдёт как в Linux…

    2) Сам вызов stat. В мане к stat из GNU coreutils сказано, что stat может быть замещён встроенной функциональностью оболочки. Честно говоря, опять таки, я не знаю, что из его функциональности из POSIX, а что — расширения… Т.е. в какой степени на stat можно рассчитывать при уходе от GNU.

  2. October 19th, 2007 at 16:44 | #2

    Вот ещё идея:

    3) Вывод uname может состоять не из одного слова. Тогда [ `uname` = "FreeBSD" ] может привести к ошибке:

    [ `echo a b` = "a b" ] && echo FOO || echo BAR
    bash: [: too many arguments
    BAR

    [ "`echo a b`" = "a b" ] && echo FOO || echo BAR
    FOO

  3. Alexey Sveshnikov
    October 19th, 2007 at 16:46 | #3

    Jetxee, все что ты перечислил может быть и имеет смысл в каких-то ситуациях, но это явно не чудовищные проблемы :)

  4. Alexey Sveshnikov
    October 19th, 2007 at 16:53 | #4

    По поводу `uname` – справедливое замечание, но это гипотетическая ситуация, ни на одной из систем я не видел, чтобы вывод uname состоял более, чем из одного слова.

    И опять-таки, ничего чудовищного в такой ошибке нет, имхо.

  5. October 19th, 2007 at 19:13 | #5

    Помимо FreeBSD имеется еще и OpenBSD, и NetBSD, где stat работает не так, как в Linux, а как во FreeBSD. А возможно, что перекомпилированное ядро FreeBSD может идентифицироваться uname не как FreeBSD?

  6. Михаил
    October 19th, 2007 at 21:48 | #6

    Если не брать в голову отсутствие кавычек вокруг $1, а также неполный способ определения размера файла (под солярой нет stat, например), то мне кажется основная проблема в возврате значения из функции. На самом деле таким образом можно вернуть только целое, причем очень ограниченное, так как по сути возвращается код завершения функции (если не изменяет память – максимум – 256). таким образом на больших файлах получим неверное значение.

    Решением может служить использование для возврата глобальное переменной, либо через echo.

  7. Alexey Sveshnikov
    October 19th, 2007 at 22:54 | #7

    Михаил, совершенно верно, см. обновленный пост.

    Vital, все это имеет место, но согласитесь, были проблемы по-серьезнее :)

  8. October 19th, 2007 at 23:04 | #8

    Точно. Похоже, вообще return в bash и не предназначен для возврата значений. info bash говорит про возврат _статуса_ из return. Это не C.

  9. October 31st, 2007 at 19:13 | #9

    Понятно. Да, return я проглядел.

  1. No trackbacks yet.