Загадка!
“Если достаточно долго лазить по карманам своих курток, можно найти практически любую сумму денег” (народная мудрость). Вот и с задачками примерно такая же ситуация – вспомнил весьма любопытную.
Приятно, знаете ли, иногда посмотреть как люди мучаются над задачками, которые когда-то серьезно озаботили тебя самого :)
Итак, есть следующий код:
#!/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


Есть несколько предположений:
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.
Вот ещё идея:
3) Вывод uname может состоять не из одного слова. Тогда [ `uname` = "FreeBSD" ] может привести к ошибке:
[ `echo a b` = "a b" ] && echo FOO || echo BARbash: [: too many arguments
BAR
[ "`echo a b`" = "a b" ] && echo FOO || echo BARFOO
Jetxee, все что ты перечислил может быть и имеет смысл в каких-то ситуациях, но это явно не чудовищные проблемы :)
По поводу `uname` – справедливое замечание, но это гипотетическая ситуация, ни на одной из систем я не видел, чтобы вывод uname состоял более, чем из одного слова.
И опять-таки, ничего чудовищного в такой ошибке нет, имхо.
Помимо FreeBSD имеется еще и OpenBSD, и NetBSD, где stat работает не так, как в Linux, а как во FreeBSD. А возможно, что перекомпилированное ядро FreeBSD может идентифицироваться uname не как FreeBSD?
Если не брать в голову отсутствие кавычек вокруг $1, а также неполный способ определения размера файла (под солярой нет stat, например), то мне кажется основная проблема в возврате значения из функции. На самом деле таким образом можно вернуть только целое, причем очень ограниченное, так как по сути возвращается код завершения функции (если не изменяет память – максимум – 256). таким образом на больших файлах получим неверное значение.
Решением может служить использование для возврата глобальное переменной, либо через echo.
Михаил, совершенно верно, см. обновленный пост.
Vital, все это имеет место, но согласитесь, были проблемы по-серьезнее :)
Точно. Похоже, вообще return в bash и не предназначен для возврата значений. info bash говорит про возврат _статуса_ из return. Это не C.
Понятно. Да, return я проглядел.