LINUX.ORG.RU

Обновление bash скрипта

 , , ,


0

3

В процессе обновления утилиты udept уже кое-чего наделал и оно вроде-бы даже местами заработало… но натолкнулся на такой {интересный/забавный/бородатый} кусок bash кода который выполняет сравнение версий и возвращает даже отрицательные (!!!) результаты из функции при помощи return.

Насколько я понял когда-то давно в бородатых версиях bash такой фокус возможно прокатывал… а теперь нужно менять return-ы на глобальную переменную. Если я понял не правильно то как в 4м bash-е вернуть отрицательное значение из функции?

Собственно само оно со строки 1226:

# Compare two numbers as fractional parts of a decimal fraction.
function comm_float() {
        local f1="$1" f2="$2" i d1 d2
        for ((i=0;i<${#f1}||i<${#f2};++i)); do
                d1=${f1:$i:1}; [[ "$d1" ]] || d1=0
                d2=${f2:$i:1}; [[ "$d2" ]] || d2=0
                ((d1<d2)) && return -7; ((d1>d2)) && return 7
        done
        return 0
}

# Compare version specifiers s1, s2. Returns <0, 0, >0 if s1 is resp lower, 
# equal, higher than s2. Note that <0 actually means >127 (-1 -> 255, etc)
# 0: equal; 1: differ in erev; 2: differ in status number; 3: differ in status;
# 4: differ in letter; 5: differ in mmm length; 6: differ in mmm
#
# This needs to be kept equivalent to portage_versions.vercmp().
#
# NOTE: Portage considers e.g. 7.0 and 7.0.0 to be equivalent. Yes, I know, 
# this sucks. HATE HATE HATE
#
# status code number: alpha->0, beta->1, pre->2, rc->3, (none)->4, p->5
function comm_ver() {
        local s1_mlsr="$1" s2_mlsr="$2"
        [[ "$s1_mlsr" == "$s2_mlsr" ]] && return 0
        if [[ "$s1_mlsr" == */* || "$s2_mlsr" == */* ]]; then
                echo "Oops: comm_ver called with category/package: $s1_mlsr $s2_mlsr" | format_error >&2
                backtrace
                return 0
        fi
        s1_mls="${s1_mlsr/%-r+([[:digit:]])}"; s1_r="${s1_mlsr#${s1_mls}}"
        s1_r="${s1_r/#-r*(0)}"
        s1_ml="${s1_mls%%*(_@(alpha|beta|pre|rc|p)*([[:digit:]]))}"
        s1_ss="${s1_mls#${s1_ml}}"
        s1_m="${s1_ml/%[[:lower:]]}"; s1_l="${s1_ml#${s1_m}}"; s1_m=( ${s1_m//./ } )
        s2_mls="${s2_mlsr/%-r+([[:digit:]])}"; s2_r="${s2_mlsr#${s2_mls}}"
        s2_r="${s2_r/#-r*(0)}"
        s2_ml="${s2_mls%%*(_@(alpha|beta|pre|rc|p)*([[:digit:]]))}"
        s2_ss="${s2_mls#${s2_ml}}"
        s2_m="${s2_ml/%[[:lower:]]}"; s2_l="${s2_ml#${s2_m}}"; s2_m=( ${s2_m//./ } )
        local i
        for ((i=0; i<${#s1_m[@]} || i<${#s2_m[@]}; ++i)); do
                s1mi="${s1_m[$i]}"; s2mi="${s2_m[$i]}"
                [[ "$s1mi" || "$s2mi" == '0' ]] || return -5
                [[ "$s2mi" || "$s1mi" == '0' ]] || return 5
                if [[ "$s1mi" == 0* || "$s2mi" == 0* ]]; then
                        comm_float "$s1mi" "$s2mi"; ret=$?; ((ret)) && return $ret
                else
                        ((s1mi < s2mi)) && return -6
                        ((s1mi > s2mi)) && return 6
                fi
        done
        ((${#s1_m[@]}!=${#s2_m[@]})) && [[ "${s1_l}${s1_s}${s1_r}" == "${s2_l}${s2_s}${s2_r}" ]] && return 0
        [[ "$s1_l" < "$s2_l" ]] && return -4
        [[ "$s1_l" > "$s2_l" ]] && return 4
        s1_ss=( ${s1_ss//_/ } ); s2_ss=( ${s2_ss//_/ } )
        for ((i=0; i<${#s1_ss[@]} || i<${#s2_ss[@]}; ++i)); do
                s1si="${s1_ss[$i]}"; s2si="${s2_ss[$i]}"
                s1sci="${s1si/%*([[:digit:]])}"; s2sci="${s2si/%*([[:digit:]])}"
                s1_scn="$((6-${#s1sci}))"; [[ $s1_scn -ge 4 ]] && s1_scn=$((10-$s1_scn))
                s2_scn="$((6-${#s2sci}))"; [[ $s2_scn -ge 4 ]] && s2_scn=$((10-$s2_scn))
                [[ "$s1_scn" < "$s2_scn" ]] && return -3
                [[ "$s1_scn" > "$s2_scn" ]] && return 3
                s1_sn="${s1si##${s1sci}*(0)}"
                s2_sn="${s2si##${s2sci}*(0)}"
                [[ "$s1_sn" -lt "$s2_sn" ]] && return -2
                [[ "$s1_sn" -gt "$s2_sn" ]] && return 2
        done
        [[ "${s1_r}" -lt "${s2_r}" ]] && return -1
        [[ "${s1_r}" -gt "${s2_r}" ]] && return 1
        echo "Error! comm_ver failed $1 $2" >&2
        exit 128        # should not reach here
}

Я правильно понимаю что, теперь вместо return <some_value> надо делать как то так (retval= <some_value>; exit 0) где, к примеру, retval и будет той самой глобальной переменной?

Я конечно пытался преобразовать код в нечто скажем так… кхм в общем меня начисто сбила такая милая функция:

function vercmp() {
	comm_ver "$1" "$3"
	case "$2" in
		"=" ) (( $? == 0 ));;
		"~" ) (( $? == 255 || $? == 0 || $? == 1 ));;
		"<=" ) (( $? == 0 || $? >= 128 ));;
		"<" ) (( $? >= 128 ));;
		">=" ) (( $? < 128 ));;
		">" ) (( $? > 0 && $? < 128 ));;
		* ) format_error <<<"Unrecognised version comparator: $2" >&2
	esac
}

В исходных данных две вполне конкретные версии файлов к примеру «3.3.9» и «3.4.1»… Так вот что вы скажете в данном случае про знак «~»?

★★★★★

скрипт - наглядная иллюстрация выражения «адов пиздец»

anonymous
()

теперь вместо return <some_value> надо делать как то так (retval= <some_value>; exit 0)

Если вы спрашиваете, как делать по-хорошему, то нет. Функция в Баше используется в том же контексте, что внешний исполняемый файл. Иначе говоря, если можно взять функцию foo() и выделить ее в файл /usr/bin/foo, ничего не меняя в остальном скрипте — это хорошо. Поэтому стоит делать так:

foo() {
<...>
echo "$result"
}

А в главном коде писать, соответственно:

MYFOO="$(foo)"

P. S. А exit — это, кстати, выход из всего скрипта.

Zmicier ★★★★★
()

2 anonymous более 3,5к строк написанных под древний bash… закономерно что оно такое. И вообще счастье что там так много всего все еще просто чудесно работает.

2 Zmicier

Если вы спрашиваете, как делать по-хорошему, то нет.

Я спрашиваю как это минимальными усилиями заставить работать в свежих bash-ах.

А exit — это, кстати, выход из всего скрипта.

Ага тут похоже туплю…

init_6 ★★★★★
() автор топика
Ответ на: комментарий от init_6

А в данном случае правильный и минимально затратный метод совпадают. return везде заменяете на echo, а конструкции типа

comm_float "$s1mi" "$s2mi"; ret=$?;
на
ret="$(comm_float "$s1mi" "$s2mi")";

Zmicier ★★★★★
()
Ответ на: комментарий от init_6

Зачем кстати используют bash, почему не какой-нибудь другой ЯП повыше? Или что б везде работало?

anonymous
()
Ответ на: комментарий от anonymous

Зачем кстати используют bash, почему не какой-нибудь другой ЯП повыше? Или что б везде работало?

anonymous а к чему там «какой-нибудь другой ЯП повыше» если bash справляется и его, в данном случае, хватает на все? Задачи там тупейшие то о чем речь сейчас сравнение версий файлов. Там еще есть и парсинг файликов и прочие мелочи. По сути тут ничего сложнее bash и не нужно.

init_6 ★★★★★
() автор топика
Ответ на: комментарий от anonymous

что этот скрипт делает

Compare two numbers as fractional parts of a decimal fraction.

Compare version specifiers s1, s2. Returns <0, 0, >0 if s1 is resp lower, equal, higher than s2.

и вообще udept

udept is a collection of Portage scripts, maintenance tools and analysis tools, written in bash and powered by the dep engine.

Zmicier ★★★★★
()
Ответ на: комментарий от anonymous

init_6, а что этот скрипт (и вообще udept) делает?

Ну блин… ссылка на толксы в заголовке темы!

А короче говоря во многих старых мануалах оно упоминается как инструмент для нахождения избыточных записей в world(которые всё-равно нужны другим записям в world или входят в system) dep -p -w. Всю историю этой штуки можно проследить по багзилле ссылки в первом же сообщении…

init_6 ★★★★★
() автор топика
Ответ на: комментарий от madcore

madcore во первых тут в 3,5к строк и без perl-а вполне себе весело. А потом лично мне вкуснее bash.

+ Если ты не заметил то я не пишу с нуля а пока-что просто пытаюсь заставить работать скрипт чужой разработки под новый bash.

init_6 ★★★★★
() автор топика
Последнее исправление: init_6 (всего исправлений: 1)
Ответ на: комментарий от Zmicier

return везде заменяете на echo

Черт, непонятно написал.

return -6 надо, разумеется, заменить на echo "-6"; return. Лучше это выделить в некую функцию:

result() {
echo "$1"
return
}

Zmicier ★★★★★
()
Ответ на: комментарий от Zmicier

по-хорошему

Охохо, а если функция может три раза досрочно завершиться, как ты потом найдёшь, в каком месте она вышла?

Deleted
()
Ответ на: комментарий от Zmicier

В функции переданные параметры проходят тщательную проверку, от результатов которой будет зависеть, выполнится над ними тело функции или сэкономится время. Или функция вызывает внутри себя другие функции, в которых тоже может произойти останов. А если ещё при этом функции надо вывести что-то в консоль?

Я не против использования $(funcname) где-нибудь в несколькострочниках и там, где функция даолжна работать абсолютно линейно и предсказуемо. Но для таких вот скриптов как в оп-посте, лучше пользоваться return, а echo оставить для stdout, так хоть сообщение об ошибке перед выходом можно оставить. Кроме того, return позволяет возвращать коды возврата из вложенных функций

#!/bin/bash

foo() {
    [ "$@" ] || return 3
    bar $@ || return $?
}

bar() {
    ls $@ || return 4
}

foo $@ || exit $?
В этом примере мы можем получить минимум 5 разных вариантов кода возврата и его возможной причины.

Deleted
()
Ответ на: комментарий от Deleted

а echo оставить для stdout, так хоть сообщение об ошибке перед выходом можно оставить

Для сообщений об ошибке существует stderr! А для обработки ошибок — код возврата, как вы совершенно верно написали в примере. Использовать его для возврата результирующего значения — извращение, для этого есть stdout.

Zmicier ★★★★★
()
Ответ на: комментарий от Zmicier

Для сообщений об ошибке существует stderr!

Да, но покуда разница между выводом ошибок в stdout и stderr ни на что не влияет, stdout проще пользоваться.

Использовать его для возврата результирующего значения — извращение, для этого есть stdout.

Я потому и решил встрять в разговор. А ещё мне кажется, ты поделил на ноль свой предыдущий пост.

Deleted
()

Гентушник — это такой подвид homo.

anonymous
()
Ответ на: комментарий от init_6

Такому танцору и баш мешает.

anonymous
()

Все комментарии темы не воспринял, так как детектировал попытку срача bash vs perl.

По теме, bash никогда не возвращал отрицательные значения, просто превращал ″signed char″ в ″unsigned char″. ″return -1″ фактически возвращал 255. Раньше ″-1″ работал, теперь, вроде как, нужно писать ″return — -1″.

mky ★★★★★
()
Ответ на: комментарий от init_6

блин… сколько мороки!

Да, мороки править все return'ы будет много. Странный комментарий перед функцией comm_ver(), сначала поясняют, что -1 == 255, а потом :

# This needs to be kept equivalent to portage_versions.vercmp().

такое ощущение, что тот кто писал (переписывал) знал, что возвращать отрицательные значения неправильно, но, типа так нужно.

mky ★★★★★
()
Ответ на: комментарий от init_6

ломается от сетов

Наверное, он (udept) о них ничего не знает. Не уверен, но возможно, что будет лучше переписывать его заново.

mky ★★★★★
()
Ответ на: комментарий от mky

Наверное, он (udept) о них ничего не знает.

Так и есть. Оно окончательно умерло и было выпилено из официального дерева портежей в 2009м. А сеты появились совсем недавно. Так что да оно о них просто ничего не знает.

Не уверен, но возможно, что будет лучше переписывать его заново.

Меня не вдохновляет писать 3,5к строк на bash заново. Проще доработать готовое. К тому же там пока-что и исправлять то особо было нечего ну вот эта ерунда с return и /etc/make.conf --> /etc/portage/make.conf и очевидные вещи с /etc/portage/package* которые раньше были файлами а теперь стали хоть файлами хоть нет :)

init_6 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.