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
()

Все комментарии темы не воспринял, так как детектировал попытку срача 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 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.